Add zone export to step export

Mileage may vary on performance, decent enough for my board.
Warning, freecad chokes on boards with zones, but commerical tools are fine

Fixes https://gitlab.com/kicad/code/kicad/-/issues/15234
This commit is contained in:
Marek Roszko 2023-08-20 22:10:43 -04:00
parent a9c4534a38
commit b52b05ebbb
11 changed files with 205 additions and 94 deletions

View File

@ -43,6 +43,7 @@ public:
// max dist to chain 2 items (lines or curves) to build the board outlines
m_BoardOutlinesChainingEpsilon( 0.01 ), // 0.01 mm is a good value
m_exportTracks( false ), // Extremely time consuming if true
m_exportZones( false ), // Extremely time consuming if true
m_format( JOB_EXPORT_PCB_3D::FORMAT::UNKNOWN )
{
}
@ -67,6 +68,7 @@ public:
double m_yOrigin;
double m_BoardOutlinesChainingEpsilon;
bool m_exportTracks;
bool m_exportZones;
JOB_EXPORT_PCB_3D::FORMAT m_format;
};

View File

@ -38,7 +38,8 @@
#define ARG_MIN_DISTANCE "--min-distance"
#define ARG_USER_ORIGIN "--user-origin"
#define ARG_BOARD_ONLY "--board-only"
#define ARG_EXPORT_TRACKS "--export-tracks"
#define ARG_INCLUDE_TRACKS "--include-tracks"
#define ARG_INCLUDE_ZONES "--include-zones"
#define ARG_FORMAT "--format"
#define REGEX_QUANTITY "([\\s]*[+-]?[\\d]*[.]?[\\d]*)"
@ -92,11 +93,16 @@ CLI::PCB_EXPORT_3D_COMMAND::PCB_EXPORT_3D_COMMAND( const std::string& aName,
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_EXPORT_TRACKS )
m_argParser.add_argument( ARG_INCLUDE_TRACKS )
.help( UTF8STDSTR( _( "Export tracks (extremely time consuming)" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_INCLUDE_ZONES )
.help( UTF8STDSTR( _( "Export zones (extremely time consuming)" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_MIN_DISTANCE )
.default_value( std::string( "0.01mm" ) )
.help( UTF8STDSTR( _( "Minimum distance between points to treat them as separate ones" ) ) );
@ -125,7 +131,8 @@ int CLI::PCB_EXPORT_3D_COMMAND::doPerform( KIWAY& aKiway )
step->m_filename = FROM_UTF8( m_argParser.get<std::string>( ARG_INPUT ).c_str() );
step->m_outputFile = FROM_UTF8( m_argParser.get<std::string>( ARG_OUTPUT ).c_str() );
step->m_boardOnly = m_argParser.get<bool>( ARG_BOARD_ONLY );
step->m_exportTracks = m_argParser.get<bool>( ARG_EXPORT_TRACKS );
step->m_exportTracks = m_argParser.get<bool>( ARG_INCLUDE_TRACKS );
step->m_exportZones = m_argParser.get<bool>( ARG_INCLUDE_ZONES );
step->m_format = m_format;
if( step->m_format == JOB_EXPORT_PCB_3D::FORMAT::UNKNOWN )

View File

@ -115,6 +115,8 @@ private:
bool m_noDNP; // remember last preference for No DNP Component
static bool m_exportTracks; // remember last preference to export tracks
// (stored only for the session)
static bool m_exportZones; // remember last preference to export tracks
// (stored only for the session)
wxString m_boardPath; // path to the exported board file
static int m_toleranceLastChoice; // Store m_tolerance option during a session
};
@ -122,6 +124,7 @@ private:
int DIALOG_EXPORT_STEP::m_toleranceLastChoice = -1; // Use default
bool DIALOG_EXPORT_STEP::m_exportTracks = false;
bool DIALOG_EXPORT_STEP::m_exportZones = false;
DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString& aBoardPath ) :
DIALOG_EXPORT_STEP_BASE( aParent )
@ -187,6 +190,7 @@ DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString&
m_noDNP = cfg->m_ExportStep.no_dnp;
m_cbExportTracks->SetValue( m_exportTracks );
m_cbExportZones->SetValue( m_exportZones );
m_cbRemoveUnspecified->SetValue( m_noUnspecified );
m_cbRemoveDNP->SetValue( m_noDNP );
m_cbSubstModels->SetValue( cfg->m_ExportStep.replace_models );
@ -424,7 +428,10 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent )
cmdK2S.Append( wxT( " --subst-models" ) );
if( m_exportTracks )
cmdK2S.Append( wxT( " --export-tracks" ) );
cmdK2S.Append( wxT( " --include-tracks" ) );
if( m_exportZones )
cmdK2S.Append( wxT( " --include-zones" ) );
// Note: for some reason, using \" to insert a quote in a format string, under MacOS
// wxString::Format does not work. So use a %c format in string

View File

@ -130,6 +130,11 @@ DIALOG_EXPORT_STEP_BASE::DIALOG_EXPORT_STEP_BASE( wxWindow* parent, wxWindowID i
sbOtherOptions->Add( m_cbExportTracks, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 );
m_cbExportZones = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Export zones (time consuming)"), wxDefaultPosition, wxDefaultSize, 0 );
m_cbExportZones->SetToolTip( _("Export tracks and vias on external copper layers.\nWarning: this is *extremely* time consuming.") );
sbOtherOptions->Add( m_cbExportZones, 0, wxBOTTOM|wxRIGHT|wxTOP, 5 );
m_staticTextTolerance = new wxStaticText( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Board outline chaining tolerance:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextTolerance->Wrap( -1 );
sbOtherOptions->Add( m_staticTextTolerance, 0, wxLEFT|wxRIGHT|wxTOP, 5 );

View File

@ -1219,6 +1219,70 @@
<property name="window_style"></property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxBOTTOM|wxRIGHT|wxTOP</property>
<property name="proportion">0</property>
<object class="wxCheckBox" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="checked">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Export zones (time consuming)</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_cbExportZones</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip">Export tracks and vias on external copper layers.&#x0A;Warning: this is *extremely* time consuming.</property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxLEFT|wxRIGHT|wxTOP</property>

View File

@ -59,6 +59,7 @@ class DIALOG_EXPORT_STEP_BASE : public DIALOG_SHIM
wxCheckBox* m_cbSubstModels;
wxCheckBox* m_cbOverwriteFile;
wxCheckBox* m_cbExportTracks;
wxCheckBox* m_cbExportZones;
wxStaticText* m_staticTextTolerance;
wxChoice* m_choiceTolerance;
wxStdDialogButtonSizer* m_sdbSizer;

View File

@ -30,6 +30,7 @@
#include <pcb_track.h>
#include <pcb_shape.h>
#include <pad.h>
#include <zone.h>
#include <fp_lib_table.h>
#include "step_pcb_model.h"
@ -322,6 +323,25 @@ bool EXPORTER_STEP::buildTrack3DShape( PCB_TRACK* aTrack, VECTOR2D aOrigin )
}
void EXPORTER_STEP::buildZones3DShape( VECTOR2D aOrigin )
{
for( ZONE* zone : m_board->Zones() )
{
for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
{
if( layer == F_Cu || layer == B_Cu )
{
SHAPE_POLY_SET copper_shape;
zone->TransformSolidAreasShapesToPolygon( layer, copper_shape );
copper_shape.Fracture( SHAPE_POLY_SET::PM_FAST );
m_pcbModel->AddCopperPolygonShapes( &copper_shape, layer == F_Cu, aOrigin, false );
}
}
}
}
bool EXPORTER_STEP::buildGraphic3DShape( BOARD_ITEM* aItem, VECTOR2D aOrigin )
{
PCB_SHAPE* graphic = dynamic_cast<PCB_SHAPE*>( aItem );
@ -410,8 +430,13 @@ bool EXPORTER_STEP::buildBoard3DShapes()
m_top_copper_shapes.Fracture( SHAPE_POLY_SET::PM_FAST );
m_bottom_copper_shapes.Fracture( SHAPE_POLY_SET::PM_FAST );
m_pcbModel->AddCopperPolygonShapes( &m_top_copper_shapes, true, origin );
m_pcbModel->AddCopperPolygonShapes( &m_bottom_copper_shapes, false, origin );
m_pcbModel->AddCopperPolygonShapes( &m_top_copper_shapes, true, origin, true );
m_pcbModel->AddCopperPolygonShapes( &m_bottom_copper_shapes, false, origin, true );
if( m_params.m_exportZones )
{
buildZones3DShape( origin );
}
ReportMessage( wxT( "Create PCB solid model\n" ) );

View File

@ -55,6 +55,7 @@ public:
m_BoardOutlinesChainingEpsilon( BOARD_DEFAULT_CHAINING_EPSILON ),
m_boardOnly( false ),
m_exportTracks( false ),
m_exportZones( false ),
m_format( FORMAT::STEP )
{};
@ -77,6 +78,7 @@ public:
double m_BoardOutlinesChainingEpsilon;
bool m_boardOnly;
bool m_exportTracks;
bool m_exportZones;
FORMAT m_format;
wxString GetDefaultExportExtension();
@ -104,6 +106,7 @@ private:
bool buildBoard3DShapes();
bool buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOrigin );
bool buildTrack3DShape( PCB_TRACK* aTrack, VECTOR2D aOrigin );
void buildZones3DShape( VECTOR2D aOrigin );
bool buildGraphic3DShape( BOARD_ITEM* aItem, VECTOR2D aOrigin );
void calculatePcbThickness();

View File

@ -228,14 +228,14 @@ bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin )
-pcbIUScale.IUTomm( pos.y - aOrigin.y ),
Zpos ) );
BRepBuilderAPI_Transform round_shape( curr_shape, shift );
m_board_outlines.push_back( round_shape.Shape() );
m_board_copper_pads.push_back( round_shape.Shape() );
}
else
{
success = MakeShape( curr_shape, pad_shape.get()->COutline(0), m_copperThickness, Zpos, aOrigin );
if( success )
m_board_outlines.push_back( curr_shape );
m_board_copper_pads.push_back( curr_shape );
}
}
@ -250,7 +250,8 @@ bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin )
}
bool STEP_PCB_MODEL::AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, bool aOnTop, const VECTOR2D& aOrigin )
bool STEP_PCB_MODEL::AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, bool aOnTop,
const VECTOR2D& aOrigin, bool aTrack )
{
bool success = true;
@ -261,7 +262,10 @@ bool STEP_PCB_MODEL::AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes,
if( MakeShape( copper_shape, aPolyShapes->COutline( ii ), m_copperThickness, z_pos, aOrigin ) )
{
m_board_outlines.push_back( copper_shape );
if( aTrack )
m_board_copper_tracks.push_back( copper_shape );
else
m_board_copper_zones.push_back( copper_shape );
}
else
{
@ -576,10 +580,9 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin )
return true;
}
Handle( XCAFDoc_ColorTool ) colorTool = XCAFDoc_DocumentTool::ColorTool( m_doc->Main() );
m_hasPCB = true; // whether or not operations fail we note that CreatePCB has been invoked
// Number of items having the copper color
int copper_item_count = m_board_outlines.size();
// Support for more than one main outline (more than one board)
for( int cnt = 0; cnt < aOutline.OutlineCount(); cnt++ )
@ -632,41 +635,83 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin )
for( TopoDS_Shape& hole : m_cutouts )
holelist.Append( hole );
auto subtractShapes = [&]( const wxString& aWhat, std::vector<TopoDS_Shape>& aShapesList,
TopTools_ListOfShape& aSubtrahend )
{
// Remove holes for each item (board body or bodies, one can have more than one board)
// and copper items (copper_item_count items)
int cnt = 0;
for( TopoDS_Shape& board: m_board_outlines )
for( TopoDS_Shape& shape : aShapesList )
{
cnt++;
if( cnt % 10 == 0 )
ReportMessage( wxString::Format( wxT( "added %d/%d shapes\n" ),
cnt, (int)m_board_outlines.size() ) );
ReportMessage( wxString::Format( _( "Cutting %d/%d %s\n" ), cnt,
(int) aShapesList.size(), aWhat ) );
TopTools_ListOfShape mainbrd;
mainbrd.Append( board );
mainbrd.Append( shape );
BRepAlgoAPI_Cut Cut;
Cut.SetArguments( mainbrd );
Cut.SetTools( holelist );
Cut.SetTools( aSubtrahend );
Cut.Build();
board = Cut.Shape();
shape = Cut.Shape();
}
};
subtractShapes( _( "pads" ), m_board_copper_pads, holelist );
subtractShapes( _( "shapes" ), m_board_outlines, holelist );
subtractShapes( _( "tracks" ), m_board_copper_tracks, holelist );
subtractShapes( _( "zones" ), m_board_copper_zones, holelist );
}
// push the board to the data structure
ReportMessage( wxT( "\nGenerate board full shape.\n" ) );
// Dont expand the component or else coloring it gets hard
for( TopoDS_Shape& board: m_board_outlines )
auto pushToAssembly = [&]( std::vector<TopoDS_Shape>& aShapesList, Quantity_Color aColor, const wxString& aShapeName )
{
m_pcb_labels.push_back( m_assy->AddComponent( m_assy_label, board, false ) );
int i = 1;
for( TopoDS_Shape& shape : aShapesList )
{
Handle( TDataStd_TreeNode ) node;
// Dont expand the component or else coloring it gets hard
TDF_Label lbl =
m_assy->AddComponent( m_assy_label, shape, false );
m_pcb_labels.push_back( lbl );
if( m_pcb_labels.back().IsNull() )
return false;
break;
lbl.FindAttribute( XCAFDoc::ShapeRefGUID(), node );
TDF_Label shpLbl = node->Father()->Label();
if( !shpLbl.IsNull() )
{
colorTool->SetColor( shpLbl, aColor, XCAFDoc_ColorSurf );
wxString shapeName;
if( aShapesList.size() > 1 )
{
shapeName = wxString::Format(
wxT( "%s_%s_%d" ), m_pcbName, aShapeName, i );
}
else
{
shapeName = wxString::Format(
wxT( "%s_%s" ), m_pcbName, aShapeName );
}
TCollection_ExtendedString partname(
shapeName.ToUTF8().data() );
TDataStd_Name::Set( shpLbl, partname );
}
i++;
}
};
// AddComponent adds a label that has a reference (not a parent/child relation) to the real
// label. We need to extract that real label to name it for the STEP output cleanly
@ -676,68 +721,15 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin )
// to "Component" or "Assembly".
// Init colors for the board body and the copper items (if any)
Handle( XCAFDoc_ColorTool ) colorTool = XCAFDoc_DocumentTool::ColorTool( m_doc->Main() );
Quantity_Color board_color( m_boardColor[0], m_boardColor[1], m_boardColor[2],
Quantity_TOC_RGB );
Quantity_Color copper_color( m_copperColor[0], m_copperColor[1], m_copperColor[2],
Quantity_TOC_RGB );
int pcbIdx = 1;
int copper_objects_cnt = 0;
for( TDF_Label& pcb_label : m_pcb_labels )
{
Handle( TDataStd_TreeNode ) node;
if( pcb_label.FindAttribute( XCAFDoc::ShapeRefGUID(), node ) )
{
// Gives a name to each board object
TDF_Label label = node->Father()->Label();
if( !label.IsNull() )
{
wxString pcbName;
// Note, we include the pcb/project name as a prefix
// because several STEP importing CAD software like SolidWorks
// will deduplicate anything imported by it's STEP name
if( copper_objects_cnt < copper_item_count )
{
pcbName = wxString::Format( wxT( "%s_Copper_Item%d" ),
m_pcbName, copper_objects_cnt+1 );
}
else
{
if( m_pcb_labels.size() == 1 )
pcbName = wxString::Format( wxT( "%s_PCB" ), m_pcbName );
else
pcbName = wxString::Format( wxT( "%s_PCB%d" ), m_pcbName, pcbIdx++ );
}
std::string pcbNameStdString( pcbName.ToUTF8() );
TCollection_ExtendedString partname( pcbNameStdString.c_str() );
TDataStd_Name::Set( label, partname );
}
}
// color the PCB
TopExp_Explorer topex;
topex.Init( m_assy->GetShape( pcb_label ), TopAbs_SOLID );
while( topex.More() )
{
// First objects are copper objects, last(s) is the board body
if( copper_objects_cnt < copper_item_count )
colorTool->SetColor( topex.Current(), copper_color, XCAFDoc_ColorSurf );
else
colorTool->SetColor( topex.Current(), board_color, XCAFDoc_ColorSurf );
topex.Next();
}
copper_objects_cnt++;
}
pushToAssembly( m_board_copper_tracks, copper_color, "track" );
pushToAssembly( m_board_copper_zones, copper_color, "zone" );
pushToAssembly( m_board_copper_pads, copper_color, "pad" );
pushToAssembly( m_board_outlines, board_color, "PCB" );
#if( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 )
m_assy->UpdateAssemblies();

View File

@ -87,7 +87,8 @@ public:
bool AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin );
// add a set of polygons (must be in final position) on top or bottom of the board as copper
bool AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, bool aOnTop, const VECTOR2D& aOrigin );
bool AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, bool aOnTop,
const VECTOR2D& aOrigin, bool aTrack );
// add a component at the given position and orientation
bool AddComponent( const std::string& aFileName, const std::string& aRefDes, bool aBottom,
@ -221,6 +222,9 @@ private:
// Main outlines (more than one board)
std::vector<TopoDS_Shape> m_board_outlines;
std::vector<TopoDS_Shape> m_board_copper_zones;
std::vector<TopoDS_Shape> m_board_copper_tracks;
std::vector<TopoDS_Shape> m_board_copper_pads;
/// Name of the PCB, which will most likely be the file name of the path.
wxString m_pcbName;

View File

@ -98,6 +98,7 @@ int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
EXPORTER_STEP_PARAMS params;
params.m_exportTracks = aStepJob->m_exportTracks;
params.m_exportZones = aStepJob->m_exportZones;
params.m_includeUnspecified = aStepJob->m_includeUnspecified;
params.m_includeDNP = aStepJob->m_includeDNP;
params.m_BoardOutlinesChainingEpsilon = aStepJob->m_BoardOutlinesChainingEpsilon;