From f569cffa8e95ba80f34f621afedcda13b7c3968d Mon Sep 17 00:00:00 2001 From: jean-pierre charras Date: Thu, 2 Mar 2023 19:38:19 +0100 Subject: [PATCH] Step exporter fixes and enhancements: - fix duplicate code and a few bugs (some are due to changes in code over the years) - ADDED: option to export tracks and vias on external layers Exporting tracks is *very* time consuming, and need a bit of optimization. --- common/jobs/job_export_pcb_step.h | 7 +- kicad/cli/command_export_pcb_step.cpp | 12 +- pcbnew/dialogs/dialog_export_step.cpp | 20 ++- pcbnew/dialogs/dialog_export_step_base.cpp | 19 +- pcbnew/dialogs/dialog_export_step_base.fbp | 87 ++++++++- pcbnew/dialogs/dialog_export_step_base.h | 5 +- pcbnew/exporters/step/exporter_step.cpp | 73 +++++++- pcbnew/exporters/step/exporter_step.h | 25 ++- pcbnew/exporters/step/step_pcb_model.cpp | 198 ++++++++++++++------- pcbnew/exporters/step/step_pcb_model.h | 65 +++++-- pcbnew/pcbnew_jobs_handler.cpp | 3 +- 11 files changed, 390 insertions(+), 124 deletions(-) diff --git a/common/jobs/job_export_pcb_step.h b/common/jobs/job_export_pcb_step.h index 8ce7bd9037..b70c83e05f 100644 --- a/common/jobs/job_export_pcb_step.h +++ b/common/jobs/job_export_pcb_step.h @@ -39,7 +39,9 @@ public: m_outputFile(), m_xOrigin( 0.0 ), m_yOrigin( 0.0 ), - m_minDistance( 0.01 ) // 0.01 mm is a good value to connect 2 items of the board outlines + // 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 { } @@ -53,7 +55,8 @@ public: wxString m_outputFile; double m_xOrigin; double m_yOrigin; - double m_minDistance; + double m_BoardOutlinesChainingEpsilon; + bool m_exportTracks; }; #endif \ No newline at end of file diff --git a/kicad/cli/command_export_pcb_step.cpp b/kicad/cli/command_export_pcb_step.cpp index ae53d9d8d4..4e3111009e 100644 --- a/kicad/cli/command_export_pcb_step.cpp +++ b/kicad/cli/command_export_pcb_step.cpp @@ -37,6 +37,7 @@ #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 REGEX_QUANTITY "([\\s]*[+-]?[\\d]*[.]?[\\d]*)" #define REGEX_DELIMITER "(?:[\\s]*x)" @@ -74,6 +75,11 @@ CLI::EXPORT_PCB_STEP_COMMAND::EXPORT_PCB_STEP_COMMAND() : COMMAND( "step" ) .implicit_value( true ) .default_value( false ); + m_argParser.add_argument( ARG_EXPORT_TRACKS ) + .help( UTF8STDSTR( _( "Export tracks (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" ) ) ); @@ -101,6 +107,7 @@ int CLI::EXPORT_PCB_STEP_COMMAND::doPerform( KIWAY& aKiway ) step->m_filename = FROM_UTF8( m_argParser.get( ARG_INPUT ).c_str() ); step->m_outputFile = FROM_UTF8( m_argParser.get( ARG_OUTPUT ).c_str() ); step->m_boardOnly = m_argParser.get( ARG_BOARD_ONLY ); + step->m_exportTracks = m_argParser.get( ARG_EXPORT_TRACKS ); wxString userOrigin = FROM_UTF8( m_argParser.get( ARG_USER_ORIGIN ).c_str() ); @@ -142,6 +149,7 @@ int CLI::EXPORT_PCB_STEP_COMMAND::doPerform( KIWAY& aKiway ) } wxString minDistance = FROM_UTF8( m_argParser.get( ARG_MIN_DISTANCE ).c_str() ); + if( !minDistance.IsEmpty() ) { std::regex re_pattern( REGEX_QUANTITY REGEX_UNIT, @@ -149,7 +157,7 @@ int CLI::EXPORT_PCB_STEP_COMMAND::doPerform( KIWAY& aKiway ) std::smatch sm; std::string str( minDistance.ToUTF8() ); std::regex_search( str, sm, re_pattern ); - step->m_minDistance = atof( sm.str( 1 ).c_str() ); + step->m_BoardOutlinesChainingEpsilon = atof( sm.str( 1 ).c_str() ); std::string tunit( sm[2] ); @@ -157,7 +165,7 @@ int CLI::EXPORT_PCB_STEP_COMMAND::doPerform( KIWAY& aKiway ) { if( !tunit.compare( "in" ) || !tunit.compare( "inch" ) ) { - step->m_minDistance *= 25.4; + step->m_BoardOutlinesChainingEpsilon *= 25.4; } else if( tunit.compare( "mm" ) ) { diff --git a/pcbnew/dialogs/dialog_export_step.cpp b/pcbnew/dialogs/dialog_export_step.cpp index f999de2e60..892fd62672 100644 --- a/pcbnew/dialogs/dialog_export_step.cpp +++ b/pcbnew/dialogs/dialog_export_step.cpp @@ -107,13 +107,15 @@ private: double m_userOriginY; // remember last User Origin Y value int m_originUnits; // remember last units for User Origin bool m_noVirtual; // remember last preference for No Virtual Component + static bool m_exportTracks; // 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 }; -int DIALOG_EXPORT_STEP::m_toleranceLastChoice = -1; // Use default - +int DIALOG_EXPORT_STEP::m_toleranceLastChoice = -1; // Use default +bool DIALOG_EXPORT_STEP::m_exportTracks = false; DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString& aBoardPath ) : DIALOG_EXPORT_STEP_BASE( aParent ) @@ -172,6 +174,7 @@ DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString& m_userOriginY = cfg->m_ExportStep.origin_y; m_noVirtual = cfg->m_ExportStep.no_virtual; + m_cbExportTracks->SetValue( m_exportTracks ); m_cbRemoveVirtual->SetValue( m_noVirtual ); m_cbSubstModels->SetValue( cfg->m_ExportStep.replace_models ); m_cbOverwriteFile->SetValue( cfg->m_ExportStep.overwrite_file ); @@ -224,7 +227,7 @@ DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString& } if( m_toleranceLastChoice >= 0 ) - m_tolerance->SetSelection( m_toleranceLastChoice ); + m_choiceTolerance->SetSelection( m_toleranceLastChoice ); // Now all widgets have the size fixed, call FinishDialogSettings finishDialogSettings(); @@ -252,7 +255,8 @@ DIALOG_EXPORT_STEP::~DIALOG_EXPORT_STEP() cfg->m_ExportStep.no_virtual = m_cbRemoveVirtual->GetValue(); - m_toleranceLastChoice = m_tolerance->GetSelection(); + m_toleranceLastChoice = m_choiceTolerance->GetSelection(); + m_exportTracks = m_cbExportTracks->GetValue(); } @@ -318,9 +322,10 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent ) m_parent->SetLastPath( LAST_PATH_STEP, m_filePickerSTEP->GetPath() ); double tolerance; // default value in mm - m_toleranceLastChoice = m_tolerance->GetSelection(); + m_toleranceLastChoice = m_choiceTolerance->GetSelection(); + m_exportTracks = m_cbExportTracks->GetValue(); - switch( m_tolerance->GetSelection() ) + switch( m_choiceTolerance->GetSelection() ) { case 0: tolerance = 0.001; break; default: @@ -394,6 +399,9 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent ) if( GetSubstOption() ) cmdK2S.Append( wxT( " --subst-models" ) ); + if( m_exportTracks ) + cmdK2S.Append( wxT( " --export-tracks" ) ); + // 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 = '\''; diff --git a/pcbnew/dialogs/dialog_export_step_base.cpp b/pcbnew/dialogs/dialog_export_step_base.cpp index 1ed6c51fbc..bfede9a5c7 100644 --- a/pcbnew/dialogs/dialog_export_step_base.cpp +++ b/pcbnew/dialogs/dialog_export_step_base.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b) +// C++ code generated with wxFormBuilder (version 3.10.1-282-g1fa54006) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -120,17 +120,22 @@ DIALOG_EXPORT_STEP_BASE::DIALOG_EXPORT_STEP_BASE( wxWindow* parent, wxWindowID i m_cbOverwriteFile = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Overwrite old file"), wxDefaultPosition, wxDefaultSize, 0 ); sbOtherOptions->Add( m_cbOverwriteFile, 0, wxBOTTOM|wxRIGHT, 5 ); + m_cbExportTracks = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Export tracks (time consuming)"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cbExportTracks->SetToolTip( _("Export tracks and vias on external copper layers.\nWarning: this is *extremely* time consuming.") ); + + sbOtherOptions->Add( m_cbExportTracks, 0, wxTOP|wxBOTTOM|wxRIGHT, 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 ); - wxString m_toleranceChoices[] = { _("Tight (0.001 mm)"), _("Standard (0.01 mm)"), _("Loose (0.1 mm)") }; - int m_toleranceNChoices = sizeof( m_toleranceChoices ) / sizeof( wxString ); - m_tolerance = new wxChoice( sbOtherOptions->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, m_toleranceNChoices, m_toleranceChoices, 0 ); - m_tolerance->SetSelection( 1 ); - m_tolerance->SetToolTip( _("Tolerance sets the distance between two points that are considered joined.") ); + wxString m_choiceToleranceChoices[] = { _("Tight (0.001 mm)"), _("Standard (0.01 mm)"), _("Loose (0.1 mm)") }; + int m_choiceToleranceNChoices = sizeof( m_choiceToleranceChoices ) / sizeof( wxString ); + m_choiceTolerance = new wxChoice( sbOtherOptions->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, m_choiceToleranceNChoices, m_choiceToleranceChoices, 0 ); + m_choiceTolerance->SetSelection( 1 ); + m_choiceTolerance->SetToolTip( _("Tolerance sets the distance between two points that are considered joined when building the board outlines.") ); - sbOtherOptions->Add( m_tolerance, 0, wxALL|wxEXPAND, 5 ); + sbOtherOptions->Add( m_choiceTolerance, 0, wxALL|wxEXPAND, 5 ); bSizer2->Add( sbOtherOptions, 1, wxEXPAND|wxRIGHT|wxLEFT, 10 ); diff --git a/pcbnew/dialogs/dialog_export_step_base.fbp b/pcbnew/dialogs/dialog_export_step_base.fbp index f1086350bb..0c601d3d3c 100644 --- a/pcbnew/dialogs/dialog_export_step_base.fbp +++ b/pcbnew/dialogs/dialog_export_step_base.fbp @@ -36,6 +36,7 @@ wxBOTH 1 + 0 1 impl_virtual @@ -95,6 +96,7 @@ Dock 0 Left + 0 1 1 @@ -156,6 +158,7 @@ Dock 0 Left + 0 1 1 @@ -244,6 +247,7 @@ Dock 0 Left + 0 1 1 @@ -308,6 +312,7 @@ Dock 0 Left + 0 1 1 @@ -372,6 +377,7 @@ Dock 0 Left + 0 1 1 @@ -436,6 +442,7 @@ Dock 0 Left + 0 1 1 @@ -530,6 +537,7 @@ Dock 0 Left + 0 1 1 @@ -592,6 +600,7 @@ Dock 0 Left + 0 1 1 @@ -656,6 +665,7 @@ Dock 0 Left + 0 1 1 @@ -717,6 +727,7 @@ Dock 0 Left + 0 1 1 @@ -782,6 +793,7 @@ Dock 0 Left + 0 1 1 @@ -843,6 +855,7 @@ Dock 0 Left + 0 1 1 @@ -925,6 +938,7 @@ Dock 0 Left + 0 1 1 @@ -989,6 +1003,7 @@ Dock 0 Left + 0 1 1 @@ -1053,6 +1068,7 @@ Dock 0 Left + 0 1 1 @@ -1091,6 +1107,71 @@ + + 5 + wxTOP|wxBOTTOM|wxRIGHT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Export tracks (time consuming) + + 0 + + + 0 + + 1 + m_cbExportTracks + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + Export tracks and vias on external copper layers. Warning: this is *extremely* time consuming. + + wxFILTER_NONE + wxDefaultValidator + + + + + + 5 wxLEFT|wxRIGHT|wxTOP @@ -1116,6 +1197,7 @@ Dock 0 Left + 0 1 1 @@ -1178,6 +1260,7 @@ Dock 0 Left + 0 1 1 @@ -1192,7 +1275,7 @@ 0 1 - m_tolerance + m_choiceTolerance 1 @@ -1206,7 +1289,7 @@ ; ; forward_declare 0 - Tolerance sets the distance between two points that are considered joined. + Tolerance sets the distance between two points that are considered joined when building the board outlines. wxFILTER_NONE wxDefaultValidator diff --git a/pcbnew/dialogs/dialog_export_step_base.h b/pcbnew/dialogs/dialog_export_step_base.h index e4672d2489..db93047e53 100644 --- a/pcbnew/dialogs/dialog_export_step_base.h +++ b/pcbnew/dialogs/dialog_export_step_base.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b) +// C++ code generated with wxFormBuilder (version 3.10.1-282-g1fa54006) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -57,8 +57,9 @@ class DIALOG_EXPORT_STEP_BASE : public DIALOG_SHIM wxCheckBox* m_cbRemoveVirtual; wxCheckBox* m_cbSubstModels; wxCheckBox* m_cbOverwriteFile; + wxCheckBox* m_cbExportTracks; wxStaticText* m_staticTextTolerance; - wxChoice* m_tolerance; + wxChoice* m_choiceTolerance; wxStdDialogButtonSizer* m_sdbSizer; wxButton* m_sdbSizerOK; wxButton* m_sdbSizerCancel; diff --git a/pcbnew/exporters/step/exporter_step.cpp b/pcbnew/exporters/step/exporter_step.cpp index 7da90374c0..7e1354fd20 100644 --- a/pcbnew/exporters/step/exporter_step.cpp +++ b/pcbnew/exporters/step/exporter_step.cpp @@ -27,7 +27,10 @@ #include #include #include +#include +#include #include +#include "step_pcb_model.h" #include #include @@ -48,7 +51,6 @@ #include #endif -#define DEFAULT_BOARD_THICKNESS 1.6 void ReportMessage( const wxString& aMessage ) { @@ -116,8 +118,7 @@ EXPORTER_STEP::EXPORTER_STEP( BOARD* aBoard, const EXPORTER_STEP_PARAMS& aParams m_board( aBoard ), m_pcbModel( nullptr ), m_pcbName(), - m_minDistance( STEPEXPORT_MIN_DISTANCE ), - m_boardThickness( DEFAULT_BOARD_THICKNESS ) + m_boardThickness( DEFAULT_BOARD_THICKNESS_MM ) { m_solderMaskColor = COLOR4D( 0.08, 0.20, 0.14, 0.83 ); @@ -143,6 +144,12 @@ bool EXPORTER_STEP::composePCB( FOOTPRINT* aFootprint, VECTOR2D aOrigin ) { if( m_pcbModel->AddPadHole( pad, aOrigin ) ) hasdata = true; + + if( ExportTracksAndVias() ) + { + if( m_pcbModel->AddPadShape( pad, aOrigin ) ) + hasdata = true; + } } if( ( aFootprint->GetAttributes() & FP_EXCLUDE_FROM_BOM ) && !m_params.m_includeExcludedBom ) @@ -234,6 +241,41 @@ bool EXPORTER_STEP::composePCB( FOOTPRINT* aFootprint, VECTOR2D aOrigin ) } +bool EXPORTER_STEP::composePCB( PCB_TRACK* aTrack, VECTOR2D aOrigin ) +{ + if( aTrack->Type() == PCB_VIA_T ) + { + PAD dummy( nullptr ); + int hole = static_cast( aTrack )->GetDrillValue(); + dummy.SetDrillSize( VECTOR2I( hole, hole ) ); + dummy.SetPosition( aTrack->GetStart() ); + dummy.SetSize( VECTOR2I( aTrack->GetWidth(), aTrack->GetWidth() ) ); + + if( m_pcbModel->AddPadHole( &dummy, aOrigin ) ) + { + if( m_pcbModel->AddPadShape( &dummy, aOrigin ) ) + return false; + } + + return true; + } + + PCB_LAYER_ID pcblayer = aTrack->GetLayer(); + + if( pcblayer != F_Cu && pcblayer != B_Cu ) + return false; + + SHAPE_POLY_SET copper_shapes; + int maxError = m_board->GetDesignSettings().m_MaxError; + + aTrack->TransformShapeToPolygon( copper_shapes, pcblayer, 0, maxError, ERROR_INSIDE ); + + m_pcbModel->AddCopperPolygonShapes( &copper_shapes, pcblayer == F_Cu, aOrigin ); + + return true; +} + + bool EXPORTER_STEP::composePCB() { if( m_pcbModel ) @@ -264,14 +306,27 @@ bool EXPORTER_STEP::composePCB() m_pcbModel->SetPCBThickness( m_boardThickness ); - // Set the min distance betewenn 2 points for OCC to see these 2 points as merged - double minDistmm = std::max( m_params.m_minDistance, STEPEXPORT_MIN_ACCEPTABLE_DISTANCE ); - m_pcbModel->SetMinDistance( minDistmm ); + // Note: m_params.m_BoardOutlinesChainingEpsilon is used only to build the board outlines, + // not to set OCC chaining epsilon (much smaller) + // + // Set the min distance between 2 points for OCC to see these 2 points as merged + // OCC_MAX_DISTANCE_TO_MERGE_POINTS is acceptable for OCC, otherwise there are issues + // to handle the shapes chaining on copper layers, because the Z dist is 0.035 mm and the + // min dist must be much smaller (we use 0.001 mm giving good results) + m_pcbModel->OCCSetMergeMaxDistance( OCC_MAX_DISTANCE_TO_MERGE_POINTS ); m_pcbModel->SetMaxError( m_board->GetDesignSettings().m_MaxError ); - for( FOOTPRINT* i : m_board->Footprints() ) - composePCB( i, origin ); + // For copper layers, only pads and tracks are added, because adding everything on copper + // generate unreasonable file sizes and take a unreasonable calculation time. + for( FOOTPRINT* fp : m_board->Footprints() ) + composePCB( fp, origin ); + + if( ExportTracksAndVias() ) + { + for( PCB_TRACK* track : m_board->Tracks() ) + composePCB( track, origin ); + } ReportMessage( wxT( "Create PCB solid model\n" ) ); @@ -291,7 +346,7 @@ bool EXPORTER_STEP::composePCB() void EXPORTER_STEP::determinePcbThickness() { - m_boardThickness = DEFAULT_BOARD_THICKNESS; + m_boardThickness = DEFAULT_BOARD_THICKNESS_MM; const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); diff --git a/pcbnew/exporters/step/exporter_step.h b/pcbnew/exporters/step/exporter_step.h index 5b5e931830..eb49e83939 100644 --- a/pcbnew/exporters/step/exporter_step.h +++ b/pcbnew/exporters/step/exporter_step.h @@ -3,7 +3,7 @@ * * Copyright (C) 2022 Mark Roszko * Copyright (C) 2016 Cirilo Bernardo - * Copyright (C) 2016-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -30,9 +30,14 @@ #include #include +// Default value to chain 2 shapes when creating the board outlines +// from shapes on Edges.Cut layer +#define BOARD_DEFAULT_CHAINING_EPSILON 0.01 + class PCBMODEL; class BOARD; class FOOTPRINT; +class PCB_TRACK; class FILENAME_RESOLVER; class EXPORTER_STEP_PARAMS @@ -45,8 +50,10 @@ public: m_useDrillOrigin( false ), m_includeExcludedBom( true ), m_substModels( true ), - m_minDistance( STEPEXPORT_MIN_DISTANCE ), - m_boardOnly( false ) {}; + m_BoardOutlinesChainingEpsilon( BOARD_DEFAULT_CHAINING_EPSILON ), + m_boardOnly( false ), + m_exportTracks( false ) + {}; wxString m_outputFile; @@ -57,8 +64,9 @@ public: bool m_useDrillOrigin; bool m_includeExcludedBom; bool m_substModels; - double m_minDistance; + double m_BoardOutlinesChainingEpsilon; bool m_boardOnly; + bool m_exportTracks; }; class EXPORTER_STEP @@ -75,9 +83,13 @@ public: void SetFail() { m_fail = true; } void SetWarn() { m_warn = true; } + /// Return rue to export tracks and vias on top and bottom copper layers + bool ExportTracksAndVias() { return m_params.m_exportTracks; } + private: bool composePCB(); bool composePCB( FOOTPRINT* aFootprint, VECTOR2D aOrigin ); + bool composePCB( PCB_TRACK* aTrack, VECTOR2D aOrigin ); void determinePcbThickness(); EXPORTER_STEP_PARAMS m_params; @@ -93,10 +105,7 @@ private: std::unique_ptr m_pcbModel; wxString m_pcbName; - // minimum distance between points to treat them as separate entities (mm) - double m_minDistance; - - double m_boardThickness; + double m_boardThickness; KIGFX::COLOR4D m_solderMaskColor; }; diff --git a/pcbnew/exporters/step/step_pcb_model.cpp b/pcbnew/exporters/step/step_pcb_model.cpp index b6b41b65df..e6123d96e1 100644 --- a/pcbnew/exporters/step/step_pcb_model.cpp +++ b/pcbnew/exporters/step/step_pcb_model.cpp @@ -3,7 +3,7 @@ * * Copyright (C) 2022 Mark Roszko * Copyright (C) 2016 Cirilo Bernardo - * Copyright (C) 2016-2022 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -93,21 +93,11 @@ static constexpr double USER_PREC = 1e-4; static constexpr double USER_ANGLE_PREC = 1e-6; -// minimum PCB thickness in mm (2 microns assumes a very thin polyimide film) -static constexpr double THICKNESS_MIN = 0.002; - -// default PCB thickness in mm -static constexpr double THICKNESS_DEFAULT = 1.6; - // nominal offset from the board static constexpr double BOARD_OFFSET = 0.05; -// min. length**2 below which 2 points are considered coincident -static constexpr double MIN_LENGTH2 = STEPEXPORT_MIN_DISTANCE * STEPEXPORT_MIN_DISTANCE; - - -// supported file types -enum FormatType +// supported file types for 3D models +enum MODEL3D_FORMAT_TYPE { FMT_NONE, FMT_STEP, @@ -120,7 +110,7 @@ enum FormatType }; -FormatType fileType( const char* aFileName ) +MODEL3D_FORMAT_TYPE fileType( const char* aFileName ) { wxFileName lfile( wxString::FromUTF8Unchecked( aFileName ) ); @@ -194,12 +184,13 @@ STEP_PCB_MODEL::STEP_PCB_MODEL( const wxString& aPcbName ) m_components = 0; m_precision = USER_PREC; m_angleprec = USER_ANGLE_PREC; - m_thickness = THICKNESS_DEFAULT; - m_minDistance2 = MIN_LENGTH2; + m_boardThickness = BOARD_THICKNESS_DEFAULT_MM; + m_copperThickness = COPPER_THICKNESS_DEFAULT_MM; + m_mergeOCCMaxDist = OCC_MAX_DISTANCE_TO_MERGE_POINTS; m_minx = 1.0e10; // absurdly large number; any valid PCB X value will be smaller m_pcbName = aPcbName; - BRepBuilderAPI::Precision( STEPEXPORT_MIN_DISTANCE ); - m_maxError = 5000; // 5 microns + BRepBuilderAPI::Precision( m_mergeOCCMaxDist ); + m_maxError = pcbIUScale.mmToIU( ARC_TO_SEGMENT_MAX_ERROR_MM ); } @@ -208,22 +199,79 @@ STEP_PCB_MODEL::~STEP_PCB_MODEL() m_doc->Close(); } +bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin ) +{ + TopoDS_Shape curr_shape_top; + TopoDS_Shape curr_shape_bottom; + const std::shared_ptr& pad_shape = aPad->GetEffectivePolygon(); + bool success = true; + + if( aPad->IsOnLayer( F_Cu ) ) + { + // Make a shape on top copper layer + success = MakeShape( curr_shape_top, pad_shape.get()->COutline(0), m_copperThickness, m_boardThickness, aOrigin ); + + if( success ) + m_board_outlines.push_back( curr_shape_top ); + } + + if( success && aPad->IsOnLayer( B_Cu ) ) + { + success = MakeShape( curr_shape_bottom, pad_shape.get()->COutline(0), m_copperThickness, -m_copperThickness, aOrigin ); + + if( success ) + m_board_outlines.push_back( curr_shape_bottom ); + } + + if( !success ) // Error + ReportMessage( wxT( "OCC error adding pad/via polygon.\n" ) ); + + return success; +} + + +bool STEP_PCB_MODEL::AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, bool aOnTop, const VECTOR2D& aOrigin ) +{ + bool success = true; + + for( int ii = 0; ii < aPolyShapes->OutlineCount(); ii++ ) + { + TopoDS_Shape copper_shape; + double z_pos = aOnTop ? m_boardThickness : -m_copperThickness; + + if( MakeShape( copper_shape, aPolyShapes->COutline( ii ), m_copperThickness, z_pos, aOrigin ) ) + { + m_board_outlines.push_back( copper_shape ); + } + else + { + ReportMessage( wxString::Format( wxT( "Could not add shape (%d points) to copper layer on %s.\n" ), + aPolyShapes->COutline( ii ).PointCount(), + aOnTop ? wxT( "top" ) : wxT( "bottom" ) ) ); + success = false; + } + } + + return success; +} + bool STEP_PCB_MODEL::AddPadHole( const PAD* aPad, const VECTOR2D& aOrigin ) { - if( NULL == aPad || !aPad->GetDrillSize().x ) + if( aPad == nullptr || !aPad->GetDrillSize().x ) return false; VECTOR2I pos = aPad->GetPosition(); + double holeZsize = m_boardThickness + ( m_copperThickness * 2 ); if( aPad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE ) { TopoDS_Shape s = - BRepPrimAPI_MakeCylinder( pcbIUScale.IUTomm( aPad->GetDrillSize().x ) * 0.5, m_thickness * 2.0 ).Shape(); + BRepPrimAPI_MakeCylinder( pcbIUScale.IUTomm( aPad->GetDrillSize().x ) * 0.5, holeZsize * 2.0 ).Shape(); gp_Trsf shift; shift.SetTranslation( gp_Vec( pcbIUScale.IUTomm( pos.x - aOrigin.x ), - -pcbIUScale.IUTomm( pos.y - aOrigin.y ), - -m_thickness * 0.5 ) ); + -pcbIUScale.IUTomm( pos.y - aOrigin.y ), + -holeZsize * 0.5 ) ); BRepBuilderAPI_Transform hole( s, shift ); m_cutouts.push_back( hole.Shape() ); return true; @@ -231,6 +279,7 @@ bool STEP_PCB_MODEL::AddPadHole( const PAD* aPad, const VECTOR2D& aOrigin ) // slotted hole SHAPE_POLY_SET holeOutlines; + if( !aPad->TransformHoleToPolygon( holeOutlines, 0, m_maxError, ERROR_INSIDE ) ) { return false; @@ -240,7 +289,7 @@ bool STEP_PCB_MODEL::AddPadHole( const PAD* aPad, const VECTOR2D& aOrigin ) if( holeOutlines.OutlineCount() > 0 ) { - if( MakeShape( hole, holeOutlines.COutline( 0 ), m_thickness, aOrigin ) ) + if( MakeShape( hole, holeOutlines.COutline( 0 ), holeZsize*2, -holeZsize * 0.5, aOrigin ) ) { m_cutouts.push_back( hole ); } @@ -312,11 +361,11 @@ bool STEP_PCB_MODEL::AddComponent( const std::string& aFileNameUTF8, const std:: void STEP_PCB_MODEL::SetPCBThickness( double aThickness ) { if( aThickness < 0.0 ) - m_thickness = THICKNESS_DEFAULT; - else if( aThickness < THICKNESS_MIN ) - m_thickness = THICKNESS_MIN; + m_boardThickness = BOARD_THICKNESS_DEFAULT_MM; + else if( aThickness < BOARD_THICKNESS_MIN_MM ) + m_boardThickness = BOARD_THICKNESS_MIN_MM; else - m_thickness = aThickness; + m_boardThickness = aThickness; } @@ -328,14 +377,11 @@ void STEP_PCB_MODEL::SetBoardColor( double r, double g, double b ) } -void STEP_PCB_MODEL::SetMinDistance( double aDistance ) +void STEP_PCB_MODEL::OCCSetMergeMaxDistance( double aDistance ) { // Ensure a minimal value (in mm) - aDistance = std::max( aDistance, STEPEXPORT_MIN_ACCEPTABLE_DISTANCE ); - - // m_minDistance2 keeps a squared distance value - m_minDistance2 = aDistance * aDistance; - BRepBuilderAPI::Precision( aDistance ); + m_mergeOCCMaxDist = aDistance; + BRepBuilderAPI::Precision( m_mergeOCCMaxDist ); } bool STEP_PCB_MODEL::isBoardOutlineValid() @@ -345,7 +391,7 @@ bool STEP_PCB_MODEL::isBoardOutlineValid() bool STEP_PCB_MODEL::MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aChain, - double aThickness, const VECTOR2D& aOrigin ) + double aThickness, double aZposition, const VECTOR2D& aOrigin ) { if( !aShape.IsNull() ) return false; // there is already data in the shape object @@ -354,11 +400,12 @@ bool STEP_PCB_MODEL::MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aC return false; // the loop is not closed BRepBuilderAPI_MakeWire wire; - TopoDS_Edge edge; - bool success = true; + bool success = true; gp_Pnt start = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( 0 ).x - aOrigin.x ), - -pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), 0.0 ); + -pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), aZposition ); + + int items_count = 0; for( int j = 0; j < aChain.PointCount(); j++ ) { @@ -367,38 +414,35 @@ bool STEP_PCB_MODEL::MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aC gp_Pnt end; if( next >= aChain.PointCount() ) - { - end = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( 0 ).x - aOrigin.x ), - -pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), 0.0 ); - } - else - { - end = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( next ).x - aOrigin.x ), - -pcbIUScale.IUTomm( aChain.CPoint( next ).y - aOrigin.y ), 0.0 ); - } + next = 0; - // Do not export very small segments: they can create broken outlines - double seg_len = std::abs( end.X() - start.X()) + std::abs(start.Y() - end.Y() ); - double min_len = 0.0001; // In mm, i.e. 0.1 micron + end = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( next ).x - aOrigin.x ), + -pcbIUScale.IUTomm( aChain.CPoint( next ).y - aOrigin.y ), aZposition ); - if( seg_len < min_len ) + // Do not export too short segments: they create broken shape because OCC thinks + // start point and end point are at the same place + double seg_len = std::hypot( end.X() - start.X(), end.Y() - start.Y() ); + + if( seg_len <= m_mergeOCCMaxDist ) continue; - try { + TopoDS_Edge edge; edge = BRepBuilderAPI_MakeEdge( start, end ); wire.Add( edge ); - if( BRepBuilderAPI_WireDone != wire.Error() ) + if( wire.Error() != BRepBuilderAPI_WireDone ) { ReportMessage( wxT( "failed to add curve\n" ) ); return false; } + + items_count++; } catch( const Standard_Failure& e ) { - ReportMessage( - wxString::Format( wxT( "OCC exception: %s\n" ), e.GetMessageString() ) ); + ReportMessage( wxString::Format( wxT( "MakeShape1: OCC exception: %s\n" ), + e.GetMessageString() ) ); success = false; } @@ -420,7 +464,8 @@ bool STEP_PCB_MODEL::MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aC catch( const Standard_Failure& e ) { ReportMessage( - wxString::Format( wxT( "OCC exception: %s\n" ), e.GetMessageString() ) ); + wxString::Format( wxT( "MakeShape2 (items_count %d): OCC exception: %s\n" ), + items_count, e.GetMessageString() ) ); return false; } @@ -448,9 +493,10 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) m_hasPCB = true; // whether or not operations fail we note that CreatePCB has been invoked - // Support for more than one main outline (more than one board) - std::vector board_outlines; + // 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++ ) { const SHAPE_LINE_CHAIN& outline = aOutline.COutline( cnt ); @@ -460,7 +506,7 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) TopoDS_Shape curr_brd; - if( !MakeShape( curr_brd, outline, m_thickness, aOrigin ) ) + if( !MakeShape( curr_brd, outline, m_boardThickness, 0.0, aOrigin ) ) { // Error ReportMessage( wxString::Format( @@ -468,7 +514,7 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) cnt+1, outline.PointCount() ) ); } else - board_outlines.push_back( curr_brd ); + m_board_outlines.push_back( curr_brd ); // Generate board cutouts in current main outline: if( aOutline.HoleCount( cnt ) > 0 ) @@ -482,7 +528,8 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) const SHAPE_LINE_CHAIN& holeOutline = aOutline.Hole( cnt, ii ); TopoDS_Shape hole; - if( MakeShape( hole, holeOutline, m_thickness, aOrigin ) ) + if( MakeShape( hole, holeOutline, m_boardThickness + (m_copperThickness*4), + -m_copperThickness*2, aOrigin ) ) { m_cutouts.push_back( hole ); } @@ -501,8 +548,15 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) holelist.Append( hole ); // Remove holes for each board (usually there is only one board - for( TopoDS_Shape& board: board_outlines ) + int cnt = 0; + for( TopoDS_Shape& board: m_board_outlines ) { + cnt++; + + if( cnt % 10 == 0 ) + ReportMessage( wxString::Format( wxT( "added %d/%d shapes\n" ), + cnt, (int)m_board_outlines.size() ) ); + BRepAlgoAPI_Cut Cut; TopTools_ListOfShape mainbrd; @@ -521,7 +575,7 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) ReportMessage( wxT( "\nGenerate board full shape.\n" ) ); // Dont expand the component or else coloring it gets hard - for( TopoDS_Shape& board: board_outlines ) + for( TopoDS_Shape& board: m_board_outlines ) { m_pcb_labels.push_back( m_assy->AddComponent( m_assy_label, board, false ) ); @@ -538,13 +592,15 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) // color the PCB Handle( XCAFDoc_ColorTool ) colorTool = XCAFDoc_DocumentTool::ColorTool( m_doc->Main() ); - Quantity_Color color( m_boardColor[0], m_boardColor[1], m_boardColor[2], Quantity_TOC_RGB ); + Quantity_Color board_color( m_boardColor[0], m_boardColor[1], m_boardColor[2], Quantity_TOC_RGB ); + Quantity_Color copper_color( 0.7, 0.61, 0.0, Quantity_TOC_RGB ); int pcbIdx = 1; + int copper_objects_cnt = 0; for( TDF_Label& pcb_label : m_pcb_labels ) { - colorTool->SetColor( pcb_label, color, XCAFDoc_ColorSurf ); + colorTool->SetColor( pcb_label, board_color, XCAFDoc_ColorSurf ); Handle( TDataStd_TreeNode ) node; @@ -568,15 +624,21 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) } } - TopExp_Explorer topex; // color the PCB + TopExp_Explorer topex; topex.Init( m_assy->GetShape( pcb_label ), TopAbs_SOLID ); while( topex.More() ) { - colorTool->SetColor( topex.Current(), color, XCAFDoc_ColorSurf ); + 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++; } #if( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 ) @@ -711,7 +773,7 @@ bool STEP_PCB_MODEL::getModelLabel( const std::string& aFileNameUTF8, VECTOR3D a m_app->NewDocument( "MDTV-XCAF", doc ); wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) ); - FormatType modelFmt = fileType( aFileNameUTF8.c_str() ); + MODEL3D_FORMAT_TYPE modelFmt = fileType( aFileNameUTF8.c_str() ); switch( modelFmt ) { @@ -944,7 +1006,7 @@ bool STEP_PCB_MODEL::getModelLocation( bool aBottom, VECTOR2D aPosition, double } else { - aOffset.z += m_thickness; + aOffset.z += m_boardThickness; lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation ); lPos.Multiply( lRot ); } diff --git a/pcbnew/exporters/step/step_pcb_model.h b/pcbnew/exporters/step/step_pcb_model.h index 0adc85938b..d7db3c1742 100644 --- a/pcbnew/exporters/step/step_pcb_model.h +++ b/pcbnew/exporters/step/step_pcb_model.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2016 Cirilo Bernardo - * Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -42,9 +42,27 @@ #include #include -///< Default minimum distance between points to treat them as separate ones (mm) -static constexpr double STEPEXPORT_MIN_DISTANCE = 0.01; -static constexpr double STEPEXPORT_MIN_ACCEPTABLE_DISTANCE = 0.001; +/** + * Default distance between points to treat them as separate ones (mm) + * 0.001 mm is a reasonable value. A too large value creates issues by + * merging points that should be different. + * Remember we are a 3D space, so a thin line can be broken if 2 points + * are merged (in X, Y, Z coords) when they should not. + * round shapes converted to polygon can also be not good with a to large value + */ +static constexpr double OCC_MAX_DISTANCE_TO_MERGE_POINTS = 0.001; + +// default PCB thickness in mm +static constexpr double BOARD_THICKNESS_DEFAULT_MM = 1.6; + +// minimum PCB thickness in mm (10 microns assumes a very thin polyimide film) +static constexpr double BOARD_THICKNESS_MIN_MM = 0.01; + +// default copper thickness in mm +static constexpr double COPPER_THICKNESS_DEFAULT_MM = 0.035; + +// Max error to approximate an arc by segments (in mm) +static constexpr double ARC_TO_SEGMENT_MAX_ERROR_MM = 0.005; class PAD; @@ -62,6 +80,12 @@ public: // add a pad hole or slot (must be in final position) bool AddPadHole( const PAD* aPad, const VECTOR2D& aOrigin ); + // add a pad/via shape (must be in final position) + 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 ); + // add a component at the given position and orientation bool AddComponent( const std::string& aFileName, const std::string& aRefDes, bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset, @@ -75,8 +99,9 @@ public: // aThickness > THICKNESS_MIN == use aThickness void SetPCBThickness( double aThickness ); - // Set the minimum distance (in mm) to consider 2 points have the same coordinates - void SetMinDistance( double aDistance ); + // 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 ); void SetMaxError( int aMaxError ) { m_maxError = aMaxError; } @@ -84,7 +109,7 @@ public: bool CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ); bool MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& chain, double aThickness, - const VECTOR2D& aOrigin ); + double aZposition, const VECTOR2D& aOrigin ); #ifdef SUPPORTS_IGES // write the assembly model in IGES format @@ -135,20 +160,26 @@ private: Handle( TDocStd_Document ) m_doc; Handle( XCAFDoc_ShapeTool ) m_assy; TDF_Label m_assy_label; - bool m_hasPCB; // set true if CreatePCB() has been invoked - std::vector 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; - double m_precision; // model (length unit) numeric precision - double m_angleprec; // angle numeric precision - double m_boardColor[3];// RGB values - double m_thickness; // PCB thickness, mm + bool m_hasPCB; // set true if CreatePCB() has been invoked + std::vector 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; + double m_precision; // model (length unit) numeric precision + double m_angleprec; // angle numeric precision + double m_boardColor[3]; // RGB values + double m_boardThickness; // PCB thickness, mm + double m_copperThickness; // copper thickness, mm - double m_minx; // leftmost curve point - double m_minDistance2; // minimum squared distance between items (mm) + double m_minx; // leftmost curve point + double m_mergeOCCMaxDist; // minimum distance (mm) below which two + // points are considered coincident by OCC + // Holes in main outlines (more than one board) std::vector m_cutouts; + // Main outlines (more than one board) + std::vector m_board_outlines; + /// Name of the PCB, which will most likely be the file name of the path. wxString m_pcbName; diff --git a/pcbnew/pcbnew_jobs_handler.cpp b/pcbnew/pcbnew_jobs_handler.cpp index be0f806dea..22a799fa2f 100644 --- a/pcbnew/pcbnew_jobs_handler.cpp +++ b/pcbnew/pcbnew_jobs_handler.cpp @@ -96,8 +96,9 @@ int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob ) } EXPORTER_STEP_PARAMS params; + params.m_exportTracks = aStepJob->m_exportTracks; params.m_includeExcludedBom = aStepJob->m_includeExcludedBom; - params.m_minDistance = aStepJob->m_minDistance; + params.m_BoardOutlinesChainingEpsilon = aStepJob->m_BoardOutlinesChainingEpsilon; params.m_overwrite = aStepJob->m_overwrite; params.m_substModels = aStepJob->m_substModels; params.m_origin = VECTOR2D( aStepJob->m_xOrigin, aStepJob->m_yOrigin );