7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2024-11-25 09:54:59 +00:00
kicad/pcbnew/pcb_io/odbpp/odb_feature.cpp
2024-10-06 18:45:25 -04:00

1004 lines
33 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
* Author: SYSUEric <jzzhuang666@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "odb_feature.h"
#include <sstream>
#include "pcb_shape.h"
#include "odb_defines.h"
#include "pcb_track.h"
#include "pcb_textbox.h"
#include "zone.h"
#include "board.h"
#include "board_design_settings.h"
#include "geometry/eda_angle.h"
#include "odb_eda_data.h"
#include "pcb_io_odbpp.h"
#include <map>
#include "wx/log.h"
#include <callback_gal.h>
void FEATURES_MANAGER::AddFeatureLine( const VECTOR2I& aStart, const VECTOR2I& aEnd,
uint64_t aWidth )
{
AddFeature<ODB_LINE>( ODB::AddXY( aStart ), ODB::AddXY( aEnd ),
AddCircleSymbol( ODB::SymDouble2String( aWidth ) ) );
}
void FEATURES_MANAGER::AddFeatureArc( const VECTOR2I& aStart, const VECTOR2I& aEnd,
const VECTOR2I& aCenter, uint64_t aWidth,
ODB_DIRECTION aDirection )
{
AddFeature<ODB_ARC>( ODB::AddXY( aStart ), ODB::AddXY( aEnd ), ODB::AddXY( aCenter ),
AddCircleSymbol( ODB::SymDouble2String( aWidth ) ), aDirection );
}
void FEATURES_MANAGER::AddPadCircle( const VECTOR2I& aCenter, uint64_t aDiameter,
const EDA_ANGLE& aAngle, bool aMirror,
double aResize /*= 1.0 */ )
{
AddFeature<ODB_PAD>( ODB::AddXY( aCenter ),
AddCircleSymbol( ODB::SymDouble2String( aDiameter ) ), aAngle, aMirror,
aResize );
}
bool FEATURES_MANAGER::AddContour( const SHAPE_POLY_SET& aPolySet, int aOutline /*= 0*/,
FILL_T aFillType /*= FILL_T::FILLED_SHAPE*/ )
{
// todo: args modify aPolySet.Polygon( aOutline ) instead of aPolySet
if( aPolySet.OutlineCount() < ( aOutline + 1 ) )
return false;
AddFeatureSurface( aPolySet.Polygon( aOutline ), aFillType );
return true;
}
void FEATURES_MANAGER::AddShape( const PCB_SHAPE& aShape, PCB_LAYER_ID aLayer )
{
switch( aShape.GetShape() )
{
case SHAPE_T::CIRCLE:
{
int diameter = aShape.GetRadius() * 2;
int width = aShape.GetWidth();
VECTOR2I center = ODB::GetShapePosition( aShape );
wxString innerDim = ODB::SymDouble2String( ( diameter - width / 2 ) );
wxString outerDim = ODB::SymDouble2String( ( width + diameter ) );
if( aShape.GetFillMode() == FILL_T::NO_FILL )
{
AddFeature<ODB_PAD>( ODB::AddXY( center ), AddRoundDonutSymbol( outerDim, innerDim ) );
}
else
{
AddFeature<ODB_PAD>( ODB::AddXY( center ), AddCircleSymbol( outerDim ) );
}
break;
}
case SHAPE_T::RECTANGLE:
{
int stroke_width = aShape.GetWidth();
int width = std::abs( aShape.GetRectangleWidth() ) + stroke_width;
int height = std::abs( aShape.GetRectangleHeight() ) + stroke_width;
wxString rad = ODB::SymDouble2String( ( stroke_width / 2.0 ) );
VECTOR2I center = ODB::GetShapePosition( aShape );
if( aShape.GetFillMode() == FILL_T::NO_FILL )
{
AddFeature<ODB_PAD>( ODB::AddXY( center ),
AddRoundRectDonutSymbol( ODB::SymDouble2String( width ),
ODB::SymDouble2String( height ),
ODB::SymDouble2String( stroke_width ),
rad ) );
}
else
{
AddFeature<ODB_PAD>( ODB::AddXY( center ),
AddRoundRectSymbol( ODB::SymDouble2String( width ),
ODB::SymDouble2String( height ), rad ) );
}
break;
}
case SHAPE_T::POLY:
{
int soldermask_min_thickness = 0;
// TODO: check if soldermask_min_thickness should be Stroke width
if( aLayer != UNDEFINED_LAYER && LSET( { F_Mask, B_Mask } ).Contains( aLayer ) )
soldermask_min_thickness = aShape.GetWidth();
int maxError = m_board->GetDesignSettings().m_MaxError;
SHAPE_POLY_SET poly_set;
if( soldermask_min_thickness == 0 )
{
poly_set = aShape.GetPolyShape().CloneDropTriangulation();
poly_set.Fracture( SHAPE_POLY_SET::PM_FAST );
}
else
{
SHAPE_POLY_SET initialPolys;
// add shapes inflated by aMinThickness/2 in areas
aShape.TransformShapeToPolygon( initialPolys, aLayer, 0, maxError, ERROR_OUTSIDE );
aShape.TransformShapeToPolygon( poly_set, aLayer, soldermask_min_thickness / 2 - 1,
maxError, ERROR_OUTSIDE );
poly_set.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
poly_set.Deflate( soldermask_min_thickness / 2 - 1,
CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
poly_set.BooleanAdd( initialPolys, SHAPE_POLY_SET::PM_FAST );
poly_set.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
}
for( int ii = 0; ii < poly_set.OutlineCount(); ++ii )
{
if( aShape.GetFillMode() != FILL_T::NO_FILL )
{
AddContour( poly_set, ii, FILL_T::FILLED_SHAPE );
}
AddContour( poly_set, ii, FILL_T::NO_FILL );
}
break;
}
case SHAPE_T::ARC:
{
ODB_DIRECTION dir = !aShape.IsClockwiseArc() ? ODB_DIRECTION::CW : ODB_DIRECTION::CCW;
AddFeatureArc( aShape.GetStart(), aShape.GetEnd(), aShape.GetCenter(),
aShape.GetStroke().GetWidth(), dir );
break;
}
case SHAPE_T::BEZIER:
{
const std::vector<VECTOR2I>& points = aShape.GetBezierPoints();
for( size_t i = 0; i < points.size(); i++ )
{
AddFeatureLine( points[i], points[i + 1], aShape.GetStroke().GetWidth() );
}
break;
}
case SHAPE_T::SEGMENT:
{
AddFeatureLine( aShape.GetStart(), aShape.GetEnd(), aShape.GetStroke().GetWidth() );
break;
}
default:
{
wxLogError( wxT( "Unknown shape when adding ODB++ layer feature" ) );
break;
}
}
}
void FEATURES_MANAGER::AddFeatureSurface( const SHAPE_POLY_SET::POLYGON& aPolygon,
FILL_T aFillType /*= FILL_T::FILLED_SHAPE */ )
{
AddFeature<ODB_SURFACE>( aPolygon, aFillType );
}
void FEATURES_MANAGER::AddPadShape( const PAD& aPad, PCB_LAYER_ID aLayer )
{
FOOTPRINT* fp = aPad.GetParentFootprint();
bool mirror = false;
if( aPad.GetOrientation() != ANGLE_0 )
{
if( fp && fp->IsFlipped() )
mirror = true;
}
int maxError = m_board->GetDesignSettings().m_MaxError;
VECTOR2I expansion{ 0, 0 };
if( aLayer != UNDEFINED_LAYER && LSET( { F_Mask, B_Mask } ).Contains( aLayer ) )
expansion.x = expansion.y = aPad.GetSolderMaskExpansion( aLayer );
if( aLayer != UNDEFINED_LAYER && LSET( { F_Paste, B_Paste } ).Contains( aLayer ) )
expansion = aPad.GetSolderPasteMargin( aLayer );
int mask_clearance = expansion.x;
VECTOR2I plotSize = aPad.GetSize( aLayer ) + 2 * expansion;
VECTOR2I center = aPad.ShapePos( aLayer );
wxString width = ODB::SymDouble2String( std::abs( plotSize.x ) );
wxString height = ODB::SymDouble2String( std::abs( plotSize.y ) );
switch( aPad.GetShape( aLayer ) )
{
case PAD_SHAPE::CIRCLE:
{
wxString diam = ODB::SymDouble2String( plotSize.x );
AddFeature<ODB_PAD>( ODB::AddXY( center ), AddCircleSymbol( diam ), aPad.GetOrientation(),
mirror );
break;
}
case PAD_SHAPE::RECTANGLE:
{
if( mask_clearance > 0 )
{
wxString rad = ODB::SymDouble2String( mask_clearance );
AddFeature<ODB_PAD>( ODB::AddXY( center ), AddRoundRectSymbol( width, height, rad ),
aPad.GetOrientation(), mirror );
}
else
{
AddFeature<ODB_PAD>( ODB::AddXY( center ), AddRectSymbol( width, height ),
aPad.GetOrientation(), mirror );
}
break;
}
case PAD_SHAPE::OVAL:
{
AddFeature<ODB_PAD>( ODB::AddXY( center ), AddOvalSymbol( width, height ),
aPad.GetOrientation(), mirror );
break;
}
case PAD_SHAPE::ROUNDRECT:
{
wxString rad = ODB::SymDouble2String( aPad.GetRoundRectCornerRadius( aLayer ) );
AddFeature<ODB_PAD>( ODB::AddXY( center ), AddRoundRectSymbol( width, height, rad ),
aPad.GetOrientation(), mirror );
break;
}
case PAD_SHAPE::CHAMFERED_RECT:
{
int shorterSide = std::min( plotSize.x, plotSize.y );
int chamfer = std::max(
0, KiROUND( aPad.GetChamferRectRatio( aLayer ) * shorterSide ) );
wxString rad = ODB::SymDouble2String( chamfer );
int positions = aPad.GetChamferPositions( aLayer );
AddFeature<ODB_PAD>( ODB::AddXY( center ),
AddChamferRectSymbol( width, height, rad, positions ),
aPad.GetOrientation(), mirror );
break;
}
case PAD_SHAPE::TRAPEZOID:
{
SHAPE_POLY_SET outline;
aPad.TransformShapeToPolygon( outline, aLayer, 0, maxError, ERROR_INSIDE );
// Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate()
// which can create bad shapes if margin.x is < 0
if( mask_clearance )
{
outline.InflateWithLinkedHoles( expansion.x, CORNER_STRATEGY::ROUND_ALL_CORNERS,
maxError, SHAPE_POLY_SET::PM_FAST );
}
for( int ii = 0; ii < outline.OutlineCount(); ++ii )
AddContour( outline, ii );
break;
}
case PAD_SHAPE::CUSTOM:
{
SHAPE_POLY_SET shape;
aPad.MergePrimitivesAsPolygon( aLayer, &shape );
// as for custome shape, odb++ don't rotate the polygon,
// so we rotate the polygon in kicad anticlockwise
shape.Rotate( aPad.GetOrientation() );
shape.Move( center );
if( expansion != VECTOR2I( 0, 0 ) )
{
shape.InflateWithLinkedHoles( std::max( expansion.x, expansion.y ),
CORNER_STRATEGY::ROUND_ALL_CORNERS, maxError,
SHAPE_POLY_SET::PM_FAST );
}
for( int ii = 0; ii < shape.OutlineCount(); ++ii )
AddContour( shape, ii );
break;
}
default: wxLogError( wxT( "Unknown pad type" ) ); break;
}
}
void FEATURES_MANAGER::InitFeatureList( PCB_LAYER_ID aLayer, std::vector<BOARD_ITEM*>& aItems )
{
auto add_track = [&]( PCB_TRACK* track )
{
auto iter = GetODBPlugin()->GetViaTraceSubnetMap().find( track );
if( iter == GetODBPlugin()->GetViaTraceSubnetMap().end() )
{
wxLogError( wxT( "Failed to get subnet trace data" ) );
return;
}
auto subnet = iter->second;
if( track->Type() == PCB_TRACE_T )
{
PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
shape.SetStart( track->GetStart() );
shape.SetEnd( track->GetEnd() );
shape.SetWidth( track->GetWidth() );
AddShape( shape );
subnet->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::COPPER, m_layerName,
m_featuresList.size() - 1 );
}
else if( track->Type() == PCB_ARC_T )
{
PCB_ARC* arc = static_cast<PCB_ARC*>( track );
PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
shape.SetArcGeometry( arc->GetStart(), arc->GetMid(), arc->GetEnd() );
shape.SetWidth( arc->GetWidth() );
AddShape( shape );
subnet->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::COPPER, m_layerName,
m_featuresList.size() - 1 );
}
else
{
// add via
PCB_VIA* via = static_cast<PCB_VIA*>( track );
if( aLayer != PCB_LAYER_ID::UNDEFINED_LAYER )
{
// to draw via copper shape on copper layer
AddVia( via, aLayer );
subnet->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::COPPER, m_layerName,
m_featuresList.size() - 1 );
if( !m_featuresList.empty() )
{
AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::PAD_USAGE::VIA );
AddFeatureAttribute(
*m_featuresList.back(),
ODB_ATTR::GEOMETRY{ "VIA_RoundD"
+ std::to_string( via->GetWidth( aLayer ) ) } );
}
}
else
{
// to draw via drill hole on drill layer
if( m_layerName.Contains( "drill" ) )
{
AddViaDrillHole( via, aLayer );
subnet->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::HOLE, m_layerName,
m_featuresList.size() - 1 );
// TODO: confirm TOOLING_HOLE
// AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::PAD_USAGE::TOOLING_HOLE );
if( !m_featuresList.empty() )
{
AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::DRILL::VIA );
AddFeatureAttribute(
*m_featuresList.back(),
ODB_ATTR::GEOMETRY{ "VIA_RoundD"
+ std::to_string( via->GetWidth( aLayer ) ) } );
}
}
}
}
};
auto add_zone = [&]( ZONE* zone )
{
SHAPE_POLY_SET zone_shape = zone->GetFilledPolysList( aLayer )->CloneDropTriangulation();
for( int ii = 0; ii < zone_shape.OutlineCount(); ++ii )
{
AddContour( zone_shape, ii );
auto iter = GetODBPlugin()->GetPlaneSubnetMap().find( std::make_pair( aLayer, zone ) );
if( iter == GetODBPlugin()->GetPlaneSubnetMap().end() )
{
wxLogError( wxT( "Failed to get subnet plane data" ) );
return;
}
iter->second->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::COPPER, m_layerName,
m_featuresList.size() - 1 );
if( zone->IsTeardropArea() && !m_featuresList.empty() )
AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::TEAR_DROP{ true } );
}
};
auto add_text = [&]( BOARD_ITEM* item )
{
EDA_TEXT* text_item = nullptr;
if( PCB_TEXT* tmp_text = dynamic_cast<PCB_TEXT*>( item ) )
text_item = static_cast<EDA_TEXT*>( tmp_text );
else if( PCB_TEXTBOX* tmp_text = dynamic_cast<PCB_TEXTBOX*>( item ) )
text_item = static_cast<EDA_TEXT*>( tmp_text );
if( !text_item->IsVisible() || text_item->GetShownText( false ).empty() )
return;
auto plot_text = [&]( const VECTOR2I& aPos, const wxString& aTextString,
const TEXT_ATTRIBUTES& aAttributes, KIFONT::FONT* aFont,
const KIFONT::METRICS& aFontMetrics )
{
KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
TEXT_ATTRIBUTES attributes = aAttributes;
int penWidth = attributes.m_StrokeWidth;
if( penWidth == 0 && attributes.m_Bold ) // Use default values if aPenWidth == 0
penWidth =
GetPenSizeForBold( std::min( attributes.m_Size.x, attributes.m_Size.y ) );
if( penWidth < 0 )
penWidth = -penWidth;
attributes.m_StrokeWidth = penWidth;
std::list<VECTOR2I> pts;
auto push_pts = [&]()
{
if( pts.size() < 2 )
return;
// Polylines are only allowed for more than 3 points.
// Otherwise, we have to use a line
if( pts.size() < 3 )
{
PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
shape.SetStart( pts.front() );
shape.SetEnd( pts.back() );
shape.SetWidth( attributes.m_StrokeWidth );
AddShape( shape );
AddFeatureAttribute( *m_featuresList.back(),
ODB_ATTR::STRING{ aTextString.ToStdString() } );
}
else
{
for( auto it = pts.begin(); std::next( it ) != pts.end(); ++it )
{
auto it2 = std::next( it );
PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
shape.SetStart( *it );
shape.SetEnd( *it2 );
shape.SetWidth( attributes.m_StrokeWidth );
AddShape( shape );
if( !m_featuresList.empty() )
AddFeatureAttribute( *m_featuresList.back(),
ODB_ATTR::STRING{ aTextString.ToStdString() } );
}
}
pts.clear();
};
CALLBACK_GAL callback_gal(
empty_opts,
// Stroke callback
[&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
{
if( !pts.empty() )
{
if( aPt1 == pts.back() )
pts.push_back( aPt2 );
else if( aPt2 == pts.front() )
pts.push_front( aPt1 );
else if( aPt1 == pts.front() )
pts.push_front( aPt2 );
else if( aPt2 == pts.back() )
pts.push_back( aPt1 );
else
{
push_pts();
pts.push_back( aPt1 );
pts.push_back( aPt2 );
}
}
else
{
pts.push_back( aPt1 );
pts.push_back( aPt2 );
}
},
// Polygon callback
[&]( const SHAPE_LINE_CHAIN& aPoly )
{
if( aPoly.PointCount() < 3 )
return;
SHAPE_POLY_SET poly_set;
poly_set.AddOutline( aPoly );
for( int ii = 0; ii < poly_set.OutlineCount(); ++ii )
{
AddContour( poly_set, ii, FILL_T::FILLED_SHAPE );
if( !m_featuresList.empty() )
AddFeatureAttribute(
*m_featuresList.back(),
ODB_ATTR::STRING{ aTextString.ToStdString() } );
}
} );
aFont->Draw( &callback_gal, aTextString, aPos, aAttributes, aFontMetrics );
if( !pts.empty() )
push_pts();
};
bool isKnockout = false;
if( item->Type() == PCB_TEXT_T || item->Type() == PCB_FIELD_T )
isKnockout = static_cast<PCB_TEXT*>( item )->IsKnockout();
else if( item->Type() == PCB_TEXTBOX_T )
isKnockout = static_cast<PCB_TEXTBOX*>( item )->IsKnockout();
const KIFONT::METRICS& fontMetrics = item->GetFontMetrics();
KIFONT::FONT* font = text_item->GetFont();
if( !font )
{
wxString defaultFontName; // empty string is the KiCad stroke font
font = KIFONT::FONT::GetFont( defaultFontName, text_item->IsBold(),
text_item->IsItalic() );
}
wxString shownText( text_item->GetShownText( true ) );
if( shownText.IsEmpty() )
return;
VECTOR2I pos = text_item->GetTextPos();
TEXT_ATTRIBUTES attrs = text_item->GetAttributes();
attrs.m_StrokeWidth = text_item->GetEffectiveTextPenWidth();
attrs.m_Angle = text_item->GetDrawRotation();
attrs.m_Multiline = false;
if( isKnockout )
{
PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
SHAPE_POLY_SET finalpolyset;
text->TransformTextToPolySet( finalpolyset, 0, m_board->GetDesignSettings().m_MaxError,
ERROR_INSIDE );
finalpolyset.Fracture( SHAPE_POLY_SET::PM_FAST );
for( int ii = 0; ii < finalpolyset.OutlineCount(); ++ii )
{
AddContour( finalpolyset, ii, FILL_T::FILLED_SHAPE );
if( !m_featuresList.empty() )
AddFeatureAttribute( *m_featuresList.back(),
ODB_ATTR::STRING{ shownText.ToStdString() } );
}
}
else if( text_item->IsMultilineAllowed() )
{
std::vector<VECTOR2I> positions;
wxArrayString strings_list;
wxStringSplit( shownText, strings_list, '\n' );
positions.reserve( strings_list.Count() );
text_item->GetLinePositions( positions, strings_list.Count() );
for( unsigned ii = 0; ii < strings_list.Count(); ii++ )
{
wxString& txt = strings_list.Item( ii );
plot_text( positions[ii], txt, attrs, font, fontMetrics );
}
}
else
{
plot_text( pos, shownText, attrs, font, fontMetrics );
}
};
auto add_shape = [&]( PCB_SHAPE* shape )
{
// FOOTPRINT* fp = shape->GetParentFootprint();
AddShape( *shape, aLayer );
};
auto add_pad = [&]( PAD* pad )
{
auto iter = GetODBPlugin()->GetPadSubnetMap().find( pad );
if( iter == GetODBPlugin()->GetPadSubnetMap().end() )
{
wxLogError( wxT( "Failed to get subnet top data" ) );
return;
}
if( aLayer != PCB_LAYER_ID::UNDEFINED_LAYER )
{
// FOOTPRINT* fp = pad->GetParentFootprint();
AddPadShape( *pad, aLayer );
iter->second->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::COPPER, m_layerName,
m_featuresList.size() - 1 );
if( !m_featuresList.empty() )
AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::PAD_USAGE::TOEPRINT );
if( !pad->HasHole() && !m_featuresList.empty() )
AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::SMD{ true } );
}
else
{
// drill layer round hole or slot hole
if( m_layerName.Contains( "drill" ) )
{
// here we exchange round hole or slot hole into pad to draw in drill layer
PAD dummy( *pad );
dummy.Padstack().SetMode( PADSTACK::MODE::NORMAL );
if( pad->GetDrillSizeX() == pad->GetDrillSizeY() )
dummy.SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); // round hole shape
else
dummy.SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); // slot hole shape
dummy.SetOffset( PADSTACK::ALL_LAYERS,
VECTOR2I( 0, 0 ) ); // use hole position not pad position
dummy.SetSize( PADSTACK::ALL_LAYERS, pad->GetDrillSize() );
AddPadShape( dummy, aLayer );
if( pad->GetAttribute() == PAD_ATTRIB::PTH )
{
// only plated holes link to subnet
iter->second->AddFeatureID( EDA_DATA::FEATURE_ID::TYPE::HOLE, m_layerName,
m_featuresList.size() - 1 );
if( !m_featuresList.empty() )
AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::DRILL::PLATED );
}
else
{
if( !m_featuresList.empty() )
AddFeatureAttribute( *m_featuresList.back(), ODB_ATTR::DRILL::NON_PLATED );
}
}
}
// AddFeatureAttribute( *m_featuresList.back(),
// ODB_ATTR::GEOMETRY{ "PAD_xxxx" } );
};
for( BOARD_ITEM* item : aItems )
{
switch( item->Type() )
{
case PCB_TRACE_T:
case PCB_ARC_T:
case PCB_VIA_T: add_track( static_cast<PCB_TRACK*>( item ) ); break;
case PCB_ZONE_T: add_zone( static_cast<ZONE*>( item ) ); break;
case PCB_PAD_T: add_pad( static_cast<PAD*>( item ) ); break;
case PCB_SHAPE_T: add_shape( static_cast<PCB_SHAPE*>( item ) ); break;
case PCB_TEXT_T:
case PCB_FIELD_T: add_text( item ); break;
case PCB_TEXTBOX_T:
{
PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( item );
add_text( item );
if( textbox->IsBorderEnabled() )
add_shape( textbox );
}
break;
case PCB_DIMENSION_T:
case PCB_TARGET_T:
case PCB_DIM_ALIGNED_T:
case PCB_DIM_LEADER_T:
case PCB_DIM_CENTER_T:
case PCB_DIM_RADIAL_T:
case PCB_DIM_ORTHOGONAL_T:
//TODO: Add support for dimensions
break;
default: break;
}
}
}
void FEATURES_MANAGER::AddVia( const PCB_VIA* aVia, PCB_LAYER_ID aLayer )
{
if( !aVia->FlashLayer( aLayer ) )
return;
PAD dummy( nullptr ); // default pad shape is circle
dummy.SetPadstack( aVia->Padstack() );
dummy.SetPosition( aVia->GetStart() );
AddPadShape( dummy, aLayer );
}
void FEATURES_MANAGER::AddViaDrillHole( const PCB_VIA* aVia, PCB_LAYER_ID aLayer )
{
PAD dummy( nullptr ); // default pad shape is circle
int hole = aVia->GetDrillValue();
dummy.SetPosition( aVia->GetStart() );
dummy.SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( hole, hole ) );
AddPadShape( dummy, aLayer );
}
void FEATURES_MANAGER::GenerateProfileFeatures( std::ostream& ost ) const
{
ost << "UNITS=" << PCB_IO_ODBPP::m_unitsStr << std::endl;
ost << "#\n#Num Features\n#" << std::endl;
ost << "F " << m_featuresList.size() << std::endl;
if( m_featuresList.empty() )
return;
ost << "#\n#Layer features\n#" << std::endl;
for( const auto& feat : m_featuresList )
{
feat->WriteFeatures( ost );
}
}
void FEATURES_MANAGER::GenerateFeatureFile( std::ostream& ost ) const
{
ost << "UNITS=" << PCB_IO_ODBPP::m_unitsStr << std::endl;
ost << "#\n#Num Features\n#" << std::endl;
ost << "F " << m_featuresList.size() << std::endl << std::endl;
if( m_featuresList.empty() )
return;
ost << "#\n#Feature symbol names\n#" << std::endl;
for( const auto& [n, name] : m_allSymMap )
{
ost << "$" << n << " " << name << std::endl;
}
WriteAttributes( ost );
ost << "#\n#Layer features\n#" << std::endl;
for( const auto& feat : m_featuresList )
{
feat->WriteFeatures( ost );
}
}
void ODB_FEATURE::WriteFeatures( std::ostream& ost )
{
switch( GetFeatureType() )
{
case FEATURE_TYPE::LINE: ost << "L "; break;
case FEATURE_TYPE::ARC: ost << "A "; break;
case FEATURE_TYPE::PAD: ost << "P "; break;
case FEATURE_TYPE::SURFACE: ost << "S "; break;
default: return;
}
WriteRecordContent( ost );
ost << std::endl;
}
void ODB_LINE::WriteRecordContent( std::ostream& ost )
{
ost << m_start.first << " " << m_start.second << " " << m_end.first << " " << m_end.second
<< " " << m_symIndex << " P 0";
WriteAttributes( ost );
}
void ODB_ARC::WriteRecordContent( std::ostream& ost )
{
ost << m_start.first << " " << m_start.second << " " << m_end.first << " " << m_end.second
<< " " << m_center.first << " " << m_center.second << " " << m_symIndex << " P 0 "
<< ( m_direction == ODB_DIRECTION::CW ? "Y" : "N" );
WriteAttributes( ost );
}
void ODB_PAD::WriteRecordContent( std::ostream& ost )
{
ost << m_center.first << " " << m_center.second << " ";
// TODO: support resize symbol
// ost << "-1" << " " << m_symIndex << " "
// << m_resize << " P 0 ";
ost << m_symIndex << " P 0 ";
if( m_mirror )
ost << "9 " << ODB::Double2String( m_angle.Normalize().AsDegrees() );
else
ost << "8 " << ODB::Double2String( ( ANGLE_360 - m_angle ).Normalize().AsDegrees() );
WriteAttributes( ost );
}
ODB_SURFACE::ODB_SURFACE( uint32_t aIndex, const SHAPE_POLY_SET::POLYGON& aPolygon,
FILL_T aFillType /*= FILL_T::FILLED_SHAPE*/ ) : ODB_FEATURE( aIndex )
{
if( !aPolygon.empty() && aPolygon[0].PointCount() >= 3 )
{
m_surfaces = std::make_unique<ODB_SURFACE_DATA>( aPolygon );
if( aFillType != FILL_T::NO_FILL )
{
m_surfaces->AddPolygonHoles( aPolygon );
}
}
else
{
delete this;
}
}
void ODB_SURFACE::WriteRecordContent( std::ostream& ost )
{
ost << "P 0";
WriteAttributes( ost );
ost << std::endl;
m_surfaces->WriteData( ost );
ost << "SE";
}
ODB_SURFACE_DATA::ODB_SURFACE_DATA( const SHAPE_POLY_SET::POLYGON& aPolygon )
{
const std::vector<VECTOR2I>& pts = aPolygon[0].CPoints();
if( !pts.empty() )
{
if( m_polygons.empty() )
{
m_polygons.resize( 1 );
}
m_polygons.at( 0 ).reserve( pts.size() );
m_polygons.at( 0 ).emplace_back( pts.back() );
for( size_t jj = 0; jj < pts.size(); ++jj )
{
m_polygons.at( 0 ).emplace_back( pts.at( jj ) );
}
}
}
void ODB_SURFACE_DATA::AddPolygonHoles( const SHAPE_POLY_SET::POLYGON& aPolygon )
{
for( size_t ii = 1; ii < aPolygon.size(); ++ii )
{
wxCHECK2( aPolygon[ii].PointCount() >= 3, continue );
const std::vector<VECTOR2I>& hole = aPolygon[ii].CPoints();
if( hole.empty() )
continue;
if( m_polygons.size() <= ii )
{
m_polygons.resize( ii + 1 );
m_polygons[ii].reserve( hole.size() );
}
m_polygons.at( ii ).emplace_back( hole.back() );
for( size_t jj = 0; jj < hole.size(); ++jj )
{
m_polygons.at( ii ).emplace_back( hole[jj] );
}
}
}
void ODB_SURFACE_DATA::WriteData( std::ostream& ost ) const
{
ODB::CHECK_ONCE is_island;
for( const auto& contour : m_polygons )
{
if( contour.empty() )
continue;
ost << "OB " << ODB::AddXY( contour.back().m_end ).first << " "
<< ODB::AddXY( contour.back().m_end ).second << " ";
if( is_island() )
ost << "I";
else
ost << "H";
ost << std::endl;
for( const auto& line : contour )
{
if( SURFACE_LINE::LINE_TYPE::SEGMENT == line.m_type )
ost << "OS " << ODB::AddXY( line.m_end ).first << " "
<< ODB::AddXY( line.m_end ).second << std::endl;
else
ost << "OC " << ODB::AddXY( line.m_end ).first << " "
<< ODB::AddXY( line.m_end ).second << " " << ODB::AddXY( line.m_center ).first
<< " " << ODB::AddXY( line.m_center ).second << " "
<< ( line.m_direction == ODB_DIRECTION::CW ? "Y" : "N" ) << std::endl;
}
ost << "OE" << std::endl;
}
}