ADDED: Fuse Shapes option for STEP/BREP/GLTF export.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/17777
This commit is contained in:
Alex Shvartzkop 2024-04-19 03:42:41 +03:00
parent f8434b1c07
commit cd2925d3d7
12 changed files with 198 additions and 83 deletions

View File

@ -40,6 +40,7 @@ JOB_EXPORT_PCB_3D::JOB_EXPORT_PCB_3D( bool aIsCli ) :
m_BoardOutlinesChainingEpsilon( 0.01 ), // 0.01 mm is a good value
m_exportTracks( false ), // Time consuming if true
m_exportZones( false ), // Time consuming if true
m_fuseShapes( false ), // Time consuming if true
m_format( JOB_EXPORT_PCB_3D::FORMAT::UNKNOWN ),
m_vrmlUnits( JOB_EXPORT_PCB_3D::VRML_UNITS::METERS ),
m_vrmlModelDir( wxEmptyString ),

View File

@ -63,6 +63,7 @@ public:
double m_BoardOutlinesChainingEpsilon;
bool m_exportTracks;
bool m_exportZones;
bool m_fuseShapes;
JOB_EXPORT_PCB_3D::FORMAT m_format;
VRML_UNITS m_vrmlUnits;

View File

@ -39,6 +39,7 @@
#define ARG_BOARD_ONLY "--board-only"
#define ARG_INCLUDE_TRACKS "--include-tracks"
#define ARG_INCLUDE_ZONES "--include-zones"
#define ARG_FUSE_SHAPES "--fuse-shapes"
#define ARG_NO_OPTIMIZE_STEP "--no-optimize-step"
#define ARG_FORMAT "--format"
#define ARG_VRML_UNITS "--units"
@ -111,6 +112,10 @@ CLI::PCB_EXPORT_3D_COMMAND::PCB_EXPORT_3D_COMMAND( const std::string& aNa
.help( UTF8STDSTR( _( "Export zones" ) ) )
.flag();
m_argParser.add_argument( ARG_FUSE_SHAPES )
.help( UTF8STDSTR( _( "Fuse overlapping geometry together" ) ) )
.flag();
m_argParser.add_argument( ARG_MIN_DISTANCE )
.default_value( std::string( "0.01mm" ) )
.help( UTF8STDSTR(
@ -163,6 +168,7 @@ int CLI::PCB_EXPORT_3D_COMMAND::doPerform( KIWAY& aKiway )
step->m_substModels = m_argParser.get<bool>( ARG_SUBST_MODELS );
step->m_exportTracks = m_argParser.get<bool>( ARG_INCLUDE_TRACKS );
step->m_exportZones = m_argParser.get<bool>( ARG_INCLUDE_ZONES );
step->m_fuseShapes = m_argParser.get<bool>( ARG_FUSE_SHAPES );
step->m_boardOnly = m_argParser.get<bool>( ARG_BOARD_ONLY );
}

View File

@ -119,6 +119,7 @@ private:
static bool m_optimizeStep; // remember last preference for Optimize STEP file (stored only for the session)
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)
static bool m_fuseShapes; // 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
};
@ -128,6 +129,7 @@ int DIALOG_EXPORT_STEP::m_toleranceLastChoice = -1; // Use default
bool DIALOG_EXPORT_STEP::m_optimizeStep = true;
bool DIALOG_EXPORT_STEP::m_exportTracks = false;
bool DIALOG_EXPORT_STEP::m_exportZones = false;
bool DIALOG_EXPORT_STEP::m_fuseShapes = false;
DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString& aBoardPath ) :
DIALOG_EXPORT_STEP_BASE( aParent )
@ -180,6 +182,7 @@ DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString&
m_cbOptimizeStep->SetValue( m_optimizeStep );
m_cbExportTracks->SetValue( m_exportTracks );
m_cbExportZones->SetValue( m_exportZones );
m_cbFuseShapes->SetValue( m_fuseShapes );
m_cbRemoveUnspecified->SetValue( m_noUnspecified );
m_cbRemoveDNP->SetValue( m_noDNP );
m_cbSubstModels->SetValue( cfg->m_ExportStep.replace_models );
@ -275,6 +278,7 @@ DIALOG_EXPORT_STEP::~DIALOG_EXPORT_STEP()
m_optimizeStep = m_cbOptimizeStep->GetValue();
m_exportTracks = m_cbExportTracks->GetValue();
m_exportZones = m_cbExportZones->GetValue();
m_fuseShapes = m_cbFuseShapes->GetValue();
}
@ -383,6 +387,7 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent )
m_optimizeStep = m_cbOptimizeStep->GetValue();
m_exportTracks = m_cbExportTracks->GetValue();
m_exportZones = m_cbExportZones->GetValue();
m_fuseShapes = m_cbFuseShapes->GetValue();
switch( m_choiceTolerance->GetSelection() )
{
@ -476,6 +481,9 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent )
if( m_exportZones )
cmdK2S.Append( wxT( " --include-zones" ) );
if( m_fuseShapes )
cmdK2S.Append( wxT( " --fuse-shapes" ) );
// 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
int quote = '\'';

View File

@ -142,12 +142,6 @@ DIALOG_EXPORT_STEP_BASE::DIALOG_EXPORT_STEP_BASE( wxWindow* parent, wxWindowID i
sbOtherOptions->Add( m_cbOptimizeStep, 0, wxBOTTOM|wxRIGHT, 5 );
m_cbFuseShapes_hidden = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Fuse shapes (time consuming)"), wxDefaultPosition, wxDefaultSize, 0 );
m_cbFuseShapes_hidden->Hide();
m_cbFuseShapes_hidden->SetToolTip( _("Combine intersecting geometry into one shape.") );
sbOtherOptions->Add( m_cbFuseShapes_hidden, 0, wxBOTTOM|wxRIGHT, 5 );
m_cbExportCompound_hidden = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Export as Compound shape"), wxDefaultPosition, wxDefaultSize, 0 );
m_cbExportCompound_hidden->Hide();
m_cbExportCompound_hidden->SetToolTip( _("Merges all shapes into a single Compound shape. Useful for external software that does de-duplication based on shape names.") );
@ -167,6 +161,11 @@ DIALOG_EXPORT_STEP_BASE::DIALOG_EXPORT_STEP_BASE( wxWindow* parent, wxWindowID i
sbOtherOptions->Add( m_cbExportZones, 0, wxBOTTOM|wxRIGHT, 5 );
m_cbFuseShapes = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Fuse shapes (time consuming)"), wxDefaultPosition, wxDefaultSize, 0 );
m_cbFuseShapes->SetToolTip( _("Combine intersecting geometry into one shape.") );
sbOtherOptions->Add( m_cbFuseShapes, 0, wxBOTTOM|wxRIGHT, 5 );
m_cbExportSilkscreen_hidden = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Export silkscreen"), wxDefaultPosition, wxDefaultSize, 0 );
m_cbExportSilkscreen_hidden->Hide();
m_cbExportSilkscreen_hidden->SetToolTip( _("Export silkscreen graphics.") );

View File

@ -62,11 +62,11 @@
<property name="name">bSizerSTEPFile</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">protected</property>
<object class="sizeritem" expanded="false">
<object class="sizeritem" expanded="true">
<property name="border">10</property>
<property name="flag">wxEXPAND|wxALL</property>
<property name="proportion">0</property>
<object class="wxBoxSizer" expanded="false">
<object class="wxBoxSizer" expanded="true">
<property name="minimum_size"></property>
<property name="name">bSizerTop</property>
<property name="orient">wxHORIZONTAL</property>
@ -275,11 +275,11 @@
</object>
</object>
</object>
<object class="sizeritem" expanded="false">
<object class="sizeritem" expanded="true">
<property name="border">5</property>
<property name="flag">wxBOTTOM|wxEXPAND|wxTOP</property>
<property name="proportion">1</property>
<object class="wxBoxSizer" expanded="false">
<object class="wxBoxSizer" expanded="true">
<property name="minimum_size"></property>
<property name="name">bSizer2</property>
<property name="orient">wxHORIZONTAL</property>
@ -974,11 +974,11 @@
</object>
</object>
</object>
<object class="sizeritem" expanded="false">
<object class="sizeritem" expanded="true">
<property name="border">10</property>
<property name="flag">wxEXPAND|wxRIGHT|wxLEFT</property>
<property name="proportion">1</property>
<object class="wxStaticBoxSizer" expanded="false">
<object class="wxStaticBoxSizer" expanded="true">
<property name="id">wxID_ANY</property>
<property name="label">Other Options</property>
<property name="minimum_size"></property>
@ -1370,71 +1370,6 @@
<property name="window_style"></property>
</object>
</object>
<object class="sizeritem" expanded="true">
<property name="border">5</property>
<property name="flag">wxBOTTOM|wxRIGHT</property>
<property name="proportion">0</property>
<object class="wxCheckBox" expanded="true">
<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="drag_accept_files">0</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">1</property>
<property name="id">wxID_ANY</property>
<property name="label">Fuse shapes (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_cbFuseShapes_hidden</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"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip">Combine intersecting geometry into one shape.</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="true">
<property name="border">5</property>
<property name="flag">wxBOTTOM|wxRIGHT</property>
@ -1689,6 +1624,71 @@
<property name="window_style"></property>
</object>
</object>
<object class="sizeritem" expanded="true">
<property name="border">5</property>
<property name="flag">wxBOTTOM|wxRIGHT</property>
<property name="proportion">0</property>
<object class="wxCheckBox" expanded="true">
<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="drag_accept_files">0</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">Fuse shapes (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_cbFuseShapes</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"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip">Combine intersecting geometry into one shape.</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="true">
<property name="border">5</property>
<property name="flag">wxBOTTOM|wxRIGHT</property>

View File

@ -66,11 +66,11 @@ class DIALOG_EXPORT_STEP_BASE : public DIALOG_SHIM
wxCheckBox* m_cbOverwriteFile;
wxStaticLine* m_staticline11_hidden;
wxCheckBox* m_cbOptimizeStep;
wxCheckBox* m_cbFuseShapes_hidden;
wxCheckBox* m_cbExportCompound_hidden;
wxStaticLine* m_staticline1;
wxCheckBox* m_cbExportTracks;
wxCheckBox* m_cbExportZones;
wxCheckBox* m_cbFuseShapes;
wxCheckBox* m_cbExportSilkscreen_hidden;
wxCheckBox* m_cbExportSoldermask_hidden;
wxCheckBox* m_cbExportSolderpaste_hidden;

View File

@ -402,6 +402,7 @@ bool EXPORTER_STEP::buildBoard3DShapes()
m_pcbModel->SetCopperColor( m_copperColor.r, m_copperColor.g, m_copperColor.b );
m_pcbModel->SetPCBThickness( m_boardThickness );
m_pcbModel->SetFuseShapes( m_params.m_fuseShapes );
// Note: m_params.m_BoardOutlinesChainingEpsilon is used only to build the board outlines,
// not to set OCC chaining epsilon (much smaller)

View File

@ -56,6 +56,7 @@ public:
m_boardOnly( false ),
m_exportTracks( false ),
m_exportZones( false ),
m_fuseShapes( false ),
m_optimizeStep( true ),
m_format( FORMAT::STEP )
{};
@ -81,6 +82,7 @@ public:
bool m_boardOnly;
bool m_exportTracks;
bool m_exportZones;
bool m_fuseShapes;
bool m_optimizeStep;
FORMAT m_format;

View File

@ -209,6 +209,7 @@ STEP_PCB_MODEL::STEP_PCB_MODEL( const wxString& aPcbName )
m_minx = 1.0e10; // absurdly large number; any valid PCB X value will be smaller
m_pcbName = aPcbName;
m_maxError = pcbIUScale.mmToIU( ARC_TO_SEGMENT_MAX_ERROR_MM );
m_fuseShapes = false;
}
@ -329,9 +330,9 @@ bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin )
_( "** ShapeUpgrade_UnifySameDomain produced a null shape **\n" ) );
m_board_copper_pads.emplace_back( fusedShape );
}
}
else
{
}
else
{
for( TopoDS_Shape& sh : topodsShapes )
m_board_copper_pads.emplace_back( sh );
}
@ -544,6 +545,12 @@ void STEP_PCB_MODEL::SetPCBThickness( double aThickness )
}
void STEP_PCB_MODEL::SetFuseShapes( bool aValue )
{
m_fuseShapes = aValue;
}
void STEP_PCB_MODEL::SetBoardColor( double r, double g, double b )
{
m_boardColor[0] = r;
@ -1110,8 +1117,6 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin )
BRepAlgoAPI_Cut cut;
// This helps cutting circular holes in zones where a hole is already cut in Clipper
cut.SetFuzzyValue( 0.0005 );
cut.SetRunParallel( true );
cut.SetToFillHistory( false );
@ -1218,9 +1223,95 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin )
Quantity_Color copper_color( m_copperColor[0], m_copperColor[1], m_copperColor[2],
Quantity_TOC_RGB );
if( m_fuseShapes )
{
ReportMessage( wxT( "Fusing shapes\n" ) );
auto iterateCopperItems = [this]( std::function<void( TopoDS_Shape& )> aFn )
{
for( TopoDS_Shape& shape : m_board_copper_tracks )
aFn( shape );
for( TopoDS_Shape& shape : m_board_copper_zones )
aFn( shape );
for( TopoDS_Shape& shape : m_board_copper_pads )
aFn( shape );
};
BRepAlgoAPI_Fuse mkFuse;
TopTools_ListOfShape shapeArguments, shapeTools;
iterateCopperItems(
[&]( TopoDS_Shape& sh )
{
if( sh.IsNull() )
return;
if( shapeArguments.IsEmpty() )
shapeArguments.Append( sh );
else
shapeTools.Append( sh );
} );
mkFuse.SetRunParallel( true );
mkFuse.SetToFillHistory( false );
mkFuse.SetArguments( shapeArguments );
mkFuse.SetTools( shapeTools );
mkFuse.Build();
if( mkFuse.HasErrors() || mkFuse.HasWarnings() )
{
ReportMessage( _( "** Got problems while fusing shapes **\n" ) );
if( mkFuse.HasErrors() )
{
ReportMessage( _( "Errors:\n" ) );
mkFuse.DumpErrors( std::cout );
}
if( mkFuse.HasWarnings() )
{
ReportMessage( _( "Warnings:\n" ) );
mkFuse.DumpWarnings( std::cout );
}
std::cout << "\n";
}
if( mkFuse.IsDone() )
{
ReportMessage( wxT( "Removing extra faces\n" ) );
TopoDS_Shape fusedShape = mkFuse.Shape();
ShapeUpgrade_UnifySameDomain unify( fusedShape, false, true, false );
unify.History() = nullptr;
unify.Build();
TopoDS_Shape unifiedShapes = unify.Shape();
if( !unifiedShapes.IsNull() )
{
m_board_copper_fused.emplace_back( unifiedShapes );
}
else
{
ReportMessage( _( "** ShapeUpgrade_UnifySameDomain produced a null shape **\n" ) );
m_board_copper_fused.emplace_back( fusedShape );
}
m_board_copper_tracks.clear();
m_board_copper_zones.clear();
m_board_copper_pads.clear();
}
}
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_copper_fused, copper_color, "copper" );
pushToAssembly( m_board_outlines, board_color, "PCB" );
#if( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 )

View File

@ -110,6 +110,9 @@ public:
// aThickness > THICKNESS_MIN == use aThickness
void SetPCBThickness( double aThickness );
// enable fusing the geometry
void SetFuseShapes( bool aValue );
// Set the max distance (in mm) to consider 2 points have the same coordinates
// and can be merged
void OCCSetMergeMaxDistance( double aDistance = OCC_MAX_DISTANCE_TO_MERGE_POINTS );
@ -229,6 +232,7 @@ private:
Handle( XCAFDoc_ShapeTool ) m_assy;
TDF_Label m_assy_label;
bool m_hasPCB; // set true if CreatePCB() has been invoked
bool m_fuseShapes; // fuse geometry together
std::vector<TDF_Label> m_pcb_labels; // labels for the PCB model (one by main outline)
MODEL_MAP m_models; // map of file names to model labels
int m_components; // number of successfully loaded components;
@ -252,6 +256,7 @@ private:
std::vector<TopoDS_Shape> m_board_copper_zones;
std::vector<TopoDS_Shape> m_board_copper_tracks;
std::vector<TopoDS_Shape> m_board_copper_pads;
std::vector<TopoDS_Shape> m_board_copper_fused;
/// Name of the PCB, which will most likely be the file name of the path.
wxString m_pcbName;

View File

@ -192,6 +192,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_fuseShapes = aStepJob->m_fuseShapes;
params.m_includeUnspecified = aStepJob->m_includeUnspecified;
params.m_includeDNP = aStepJob->m_includeDNP;
params.m_BoardOutlinesChainingEpsilon = aStepJob->m_BoardOutlinesChainingEpsilon;