KiCad PCB EDA Suite
Loading...
Searching...
No Matches
exporter_step.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2022 Mark Roszko <[email protected]>
5 * Copyright (C) 2016 Cirilo Bernardo <[email protected]>
6 * Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26#include "exporter_step.h"
27#include <board.h>
29#include <footprint.h>
30#include <pcb_track.h>
31#include <pcb_shape.h>
32#include <pad.h>
33#include <fp_lib_table.h>
34#include "step_pcb_model.h"
35
36#include <pgm_base.h>
37#include <base_units.h>
38#include <filename_resolver.h>
39#include <trace_helpers.h>
40
41#include <Message.hxx> // OpenCascade messenger
42#include <Message_PrinterOStream.hxx> // OpenCascade output messenger
43#include <Standard_Failure.hxx> // In open cascade
44
45#include <Standard_Version.hxx>
46
47#include <wx/crt.h>
48#include <wx/log.h>
49
50#define OCC_VERSION_MIN 0x070500
51
52#if OCC_VERSION_HEX < OCC_VERSION_MIN
53#include <Message_Messenger.hxx>
54#endif
55
56
57void ReportMessage( const wxString& aMessage )
58{
59 wxPrintf( aMessage );
60 fflush( stdout ); // Force immediate printing (needed on mingw)
61}
62
63class KiCadPrinter : public Message_Printer
64{
65public:
66 KiCadPrinter( EXPORTER_STEP* aConverter ) : m_converter( aConverter ) {}
67
68protected:
69#if OCC_VERSION_HEX < OCC_VERSION_MIN
70 virtual void Send( const TCollection_ExtendedString& theString,
71 const Message_Gravity theGravity,
72 const Standard_Boolean theToPutEol ) const override
73 {
74 Send( TCollection_AsciiString( theString ), theGravity, theToPutEol );
75 }
76
77 virtual void Send( const TCollection_AsciiString& theString,
78 const Message_Gravity theGravity,
79 const Standard_Boolean theToPutEol ) const override
80#else
81 virtual void send( const TCollection_AsciiString& theString,
82 const Message_Gravity theGravity ) const override
83#endif
84 {
85 if( theGravity >= Message_Warning
86 || ( wxLog::IsAllowedTraceMask( traceKiCad2Step ) && theGravity == Message_Info ) )
87 {
88 ReportMessage( theString.ToCString() );
89
90#if OCC_VERSION_HEX < OCC_VERSION_MIN
91 if( theToPutEol )
92 ReportMessage( wxT( "\n" ) );
93#else
94 ReportMessage( wxT( "\n" ) );
95#endif
96 }
97
98 if( theGravity == Message_Warning )
100
101 if( theGravity >= Message_Alarm )
103
104 if( theGravity == Message_Fail )
106 }
107
108private:
110};
111
112
114 m_params( aParams ),
115 m_error( false ),
116 m_fail( false ),
117 m_warn( false ),
118 m_hasDrillOrigin( false ),
119 m_hasGridOrigin( false ),
120 m_board( aBoard ),
121 m_pcbModel( nullptr ),
122 m_boardThickness( DEFAULT_BOARD_THICKNESS_MM )
123{
124 m_solderMaskColor = COLOR4D( 0.08, 0.20, 0.14, 0.83 );
125 m_copperColor = COLOR4D( 0.7, 0.61, 0.0, 1.0 );
126
127 // Init m_pcbBaseName to the board short filename (no path, no ext)
128 // m_pcbName is used later to identify items in step file
129 wxFileName fn( aBoard->GetFileName() );
130 m_pcbBaseName = fn.GetName();
131
132 m_resolver = std::make_unique<FILENAME_RESOLVER>();
133 m_resolver->Set3DConfigDir( wxT( "" ) );
134 // needed to add the project to the search stack
135 m_resolver->SetProject( aBoard->GetProject() );
136 m_resolver->SetProgramBase( &Pgm() );
137}
138
139
141{
142}
143
144
146{
147 bool hasdata = false;
148
149 // Dump the pad holes into the PCB
150 for( PAD* pad : aFootprint->Pads() )
151 {
152 if( m_pcbModel->AddPadHole( pad, aOrigin ) )
153 hasdata = true;
154
155 if( ExportTracksAndVias() )
156 {
157 if( m_pcbModel->AddPadShape( pad, aOrigin ) )
158 hasdata = true;
159 }
160 }
161
162 // Build 3D shapes of the footprint graphic items on external layers:
163 if( ExportTracksAndVias() )
164 {
165 int maxError = m_board->GetDesignSettings().m_MaxError;
167 false, /* include text */
168 true, /* include shapes */
169 false /* include private items */ );
171 false, /* include text */
172 true, /* include shapes */
173 false /* include private items */ );
174 }
175
176 if( ( !(aFootprint->GetAttributes() & (FP_THROUGH_HOLE|FP_SMD)) ) && !m_params.m_includeUnspecified )
177 {
178 return hasdata;
179 }
180
181 if( ( aFootprint->GetAttributes() & FP_DNP ) && !m_params.m_includeDNP )
182 {
183 return hasdata;
184 }
185
186 // Prefetch the library for this footprint
187 // In case we need to resolve relative footprint paths
188 wxString libraryName = aFootprint->GetFPID().GetLibNickname();
189 wxString footprintBasePath = wxEmptyString;
190
191 double posX = aFootprint->GetPosition().x - aOrigin.x;
192 double posY = (aFootprint->GetPosition().y) - aOrigin.y;
193
194 if( m_board->GetProject() )
195 {
196 try
197 {
198 // FindRow() can throw an exception
199 const FP_LIB_TABLE_ROW* fpRow =
200 m_board->GetProject()->PcbFootprintLibs()->FindRow( libraryName, false );
201
202 if( fpRow )
203 footprintBasePath = fpRow->GetFullURI( true );
204 }
205 catch( ... )
206 {
207 // Do nothing if the libraryName is not found in lib table
208 }
209 }
210
211 // Exit early if we don't want to include footprint models
213 {
214 return hasdata;
215 }
216
217 VECTOR2D newpos( pcbIUScale.IUTomm( posX ), pcbIUScale.IUTomm( posY ) );
218
219 for( const FP_3DMODEL& fp_model : aFootprint->Models() )
220 {
221 if( !fp_model.m_Show || fp_model.m_Filename.empty() )
222
223 continue;
224
225 std::vector<wxString> searchedPaths;
226 wxString mname = m_resolver->ResolvePath( fp_model.m_Filename, footprintBasePath );
227
228
229 if( !wxFileName::FileExists( mname ) )
230 {
231 ReportMessage( wxString::Format( wxT( "Could not add 3D model to %s.\n"
232 "File not found: %s\n" ),
233 aFootprint->GetReference(), mname ) );
234 continue;
235 }
236
237 std::string fname( mname.ToUTF8() );
238 std::string refName( aFootprint->GetReference().ToUTF8() );
239 try
240 {
241 bool bottomSide = aFootprint->GetLayer() == B_Cu;
242
243 // the rotation is stored in degrees but opencascade wants radians
244 VECTOR3D modelRot = fp_model.m_Rotation;
245 modelRot *= M_PI;
246 modelRot /= 180.0;
247
248 if( m_pcbModel->AddComponent( fname, refName, bottomSide,
249 newpos,
250 aFootprint->GetOrientation().AsRadians(),
251 fp_model.m_Offset, modelRot,
252 fp_model.m_Scale, m_params.m_substModels ) )
253 {
254 hasdata = true;
255 }
256 }
257 catch( const Standard_Failure& e )
258 {
259 ReportMessage( wxString::Format( wxT( "Could not add 3D model to %s.\n"
260 "OpenCASCADE error: %s\n" ),
261 aFootprint->GetReference(), e.GetMessageString() ) );
262 }
263
264 }
265
266 return hasdata;
267}
268
269
271{
272 if( aTrack->Type() == PCB_VIA_T )
273 {
274 PAD dummy( nullptr );
275 int hole = static_cast<PCB_VIA*>( aTrack )->GetDrillValue();
276 dummy.SetDrillSize( VECTOR2I( hole, hole ) );
277 dummy.SetPosition( aTrack->GetStart() );
278 dummy.SetSize( VECTOR2I( aTrack->GetWidth(), aTrack->GetWidth() ) );
279
280 if( m_pcbModel->AddPadHole( &dummy, aOrigin ) )
281 {
282 if( m_pcbModel->AddPadShape( &dummy, aOrigin ) )
283 return false;
284 }
285
286 return true;
287 }
288
289 PCB_LAYER_ID pcblayer = aTrack->GetLayer();
290
291 if( pcblayer != F_Cu && pcblayer != B_Cu )
292 return false;
293
294 int maxError = m_board->GetDesignSettings().m_MaxError;
295
296 if( pcblayer == F_Cu )
297 aTrack->TransformShapeToPolygon( m_top_copper_shapes, pcblayer, 0, maxError, ERROR_INSIDE );
298 else
299 aTrack->TransformShapeToPolygon( m_bottom_copper_shapes, pcblayer, 0, maxError, ERROR_INSIDE );
300
301 return true;
302}
303
304
306{
307 PCB_SHAPE* graphic = dynamic_cast<PCB_SHAPE*>( aItem );
308
309 if( ! graphic )
310 return false;
311
312 PCB_LAYER_ID pcblayer = graphic->GetLayer();
313
314 if( pcblayer != F_Cu && pcblayer != B_Cu )
315 return false;
316
317 SHAPE_POLY_SET copper_shapes;
318 int maxError = m_board->GetDesignSettings().m_MaxError;
319
320
321 if( pcblayer == F_Cu )
322 graphic->TransformShapeToPolygon( m_top_copper_shapes, pcblayer, 0,
323 maxError, ERROR_INSIDE );
324 else
325 graphic->TransformShapeToPolygon( m_bottom_copper_shapes, pcblayer, 0,
326 maxError, ERROR_INSIDE );
327
328 return true;
329}
330
331
333{
334 if( m_pcbModel )
335 return true;
336
337 SHAPE_POLY_SET pcbOutlines; // stores the board main outlines
338
339 if( !m_board->GetBoardPolygonOutlines( pcbOutlines,
340 /* error handler */ nullptr,
341 /* allows use arcs in outlines */ true ) )
342 {
343 wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) );
344 }
345
346 VECTOR2D origin;
347
348 // Determine the coordinate system reference:
349 // Precedence of reference point is Drill Origin > Grid Origin > User Offset
352 else if( m_params.m_useGridOrigin )
354 else
355 origin = m_params.m_origin;
356
357 m_pcbModel = std::make_unique<STEP_PCB_MODEL>( m_pcbBaseName );
358
359 // TODO: Handle when top & bottom soldermask colours are different...
362
363 m_pcbModel->SetPCBThickness( m_boardThickness );
364
365 // Note: m_params.m_BoardOutlinesChainingEpsilon is used only to build the board outlines,
366 // not to set OCC chaining epsilon (much smaller)
367 //
368 // Set the min distance between 2 points for OCC to see these 2 points as merged
369 // OCC_MAX_DISTANCE_TO_MERGE_POINTS is acceptable for OCC, otherwise there are issues
370 // to handle the shapes chaining on copper layers, because the Z dist is 0.035 mm and the
371 // min dist must be much smaller (we use 0.001 mm giving good results)
372 m_pcbModel->OCCSetMergeMaxDistance( OCC_MAX_DISTANCE_TO_MERGE_POINTS );
373
375
376 // For copper layers, only pads and tracks are added, because adding everything on copper
377 // generate unreasonable file sizes and take a unreasonable calculation time.
378 for( FOOTPRINT* fp : m_board->Footprints() )
379 buildFootprint3DShapes( fp, origin );
380
381 if( ExportTracksAndVias() )
382 {
383 for( PCB_TRACK* track : m_board->Tracks() )
384 buildTrack3DShape( track, origin );
385
386 for( BOARD_ITEM* item : m_board->Drawings() )
387 buildGraphic3DShape( item, origin );
388 }
389
392
393 m_pcbModel->AddCopperPolygonShapes( &m_top_copper_shapes, true, origin );
394 m_pcbModel->AddCopperPolygonShapes( &m_bottom_copper_shapes, false, origin );
395
396 ReportMessage( wxT( "Create PCB solid model\n" ) );
397
398 wxString msg;
399 msg.Printf( wxT( "Board outline: find %d initial points\n" ), pcbOutlines.FullPointCount() );
400 ReportMessage( msg );
401
402 if( !m_pcbModel->CreatePCB( pcbOutlines, origin ) )
403 {
404 ReportMessage( wxT( "could not create PCB solid model\n" ) );
405 return false;
406 }
407
408 return true;
409}
410
411
413{
415
417
418 if( bds.GetStackupDescriptor().GetCount() )
419 {
420 int thickness = 0;
421
422 for( BOARD_STACKUP_ITEM* item : bds.GetStackupDescriptor().GetList() )
423 {
424 switch( item->GetType() )
425 {
427 // Dielectric can have sub-layers. Layer 0 is the main layer
428 // Not frequent, but possible
429 for( int idx = 0; idx < item->GetSublayersCount(); idx++ )
430 thickness += item->GetThickness( idx );
431
432 break;
433
435 if( item->IsEnabled() )
436 thickness += item->GetThickness();
437
438 break;
439
440 default:
441 break;
442 }
443 }
444
445 if( thickness > 0 )
446 m_boardThickness = pcbIUScale.IUTomm( thickness );
447 }
448}
449
450
452{
453 // setup opencascade message log
454 Message::DefaultMessenger()->RemovePrinters( STANDARD_TYPE( Message_PrinterOStream ) );
455 Message::DefaultMessenger()->AddPrinter( new KiCadPrinter( this ) );
456
457 ReportMessage( _( "Determining PCB data\n" ) );
459 wxString msg;
460 msg.Printf( _( "Board Thickness from stackup: %.3f mm\n" ), m_boardThickness );
461 ReportMessage( msg );
462
463 try
464 {
465 ReportMessage( _( "Build STEP data\n" ) );
466
467 if( !buildBoard3DShapes() )
468 {
469 ReportMessage( _( "\n** Error building STEP board model. Export aborted. **\n" ) );
470 return false;
471 }
472
473 ReportMessage( _( "Writing STEP file\n" ) );
474
475 if( !m_pcbModel->WriteSTEP( m_outputFile ) )
476 {
477 ReportMessage( _( "\n** Error writing STEP file. **\n" ) );
478 return false;
479 }
480 else
481 {
482 ReportMessage( wxString::Format( _( "\nSTEP file '%s' created.\n" ), m_outputFile ) );
483 }
484 }
485 catch( const Standard_Failure& e )
486 {
487 ReportMessage( e.GetMessageString() );
488 ReportMessage( _( "\n** Error exporting STEP file. Export aborted. **\n" ) );
489 return false;
490 }
491 catch( ... )
492 {
493 ReportMessage( _( "\n** Error exporting STEP file. Export aborted. **\n" ) );
494 return false;
495 }
496
497 if( m_fail || m_error )
498 {
499 if( m_fail )
500 {
501 msg = _( "Unable to create STEP file.\n"
502 "Check that the board has a valid outline and models." );
503 }
504 else if( m_error || m_warn )
505 {
506 msg = _( "STEP file has been created, but there are warnings." );
507 }
508
509 ReportMessage( msg );
510 }
511
512 return true;
513}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:109
#define DEFAULT_BOARD_THICKNESS_MM
@ BS_ITEM_TYPE_COPPER
Definition: board_stackup.h:43
@ BS_ITEM_TYPE_DIELECTRIC
Definition: board_stackup.h:44
Container for design settings for a BOARD object.
const VECTOR2I & GetGridOrigin()
const VECTOR2I & GetAuxOrigin()
BOARD_STACKUP & GetStackupDescriptor()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:71
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition: board_item.h:196
Manage one layer needed to make a physical board.
Definition: board_stackup.h:91
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
int GetCount() const
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:270
bool GetBoardPolygonOutlines(SHAPE_POLY_SET &aOutlines, OUTLINE_ERROR_HANDLER *aErrorHandler=nullptr, bool aAllowUseArcsInPolygons=false)
Extract the board outlines and build a closed polygon from lines, arcs and circle items on edge cut l...
Definition: board.cpp:2027
FOOTPRINTS & Footprints()
Definition: board.h:312
TRACKS & Tracks()
Definition: board.h:309
const wxString & GetFileName() const
Definition: board.h:307
DRAWINGS & Drawings()
Definition: board.h:315
PROJECT * GetProject() const
Definition: board.h:448
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:728
double AsRadians() const
Definition: eda_angle.h:153
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:97
KIGFX::COLOR4D m_solderMaskColor
SHAPE_POLY_SET m_top_copper_shapes
SHAPE_POLY_SET m_bottom_copper_shapes
wxString m_outputFile
Definition: exporter_step.h:83
bool buildGraphic3DShape(BOARD_ITEM *aItem, VECTOR2D aOrigin)
EXPORTER_STEP_PARAMS m_params
Definition: exporter_step.h:99
EXPORTER_STEP(BOARD *aBoard, const EXPORTER_STEP_PARAMS &aParams)
wxString m_pcbBaseName
the name of the project (board short filename (no path, no ext) used to identify items in step file
bool buildFootprint3DShapes(FOOTPRINT *aFootprint, VECTOR2D aOrigin)
std::unique_ptr< FILENAME_RESOLVER > m_resolver
bool ExportTracksAndVias()
Return rue to export tracks and vias on top and bottom copper layers.
Definition: exporter_step.h:90
bool buildBoard3DShapes()
std::unique_ptr< STEP_PCB_MODEL > m_pcbModel
void calculatePcbThickness()
bool buildTrack3DShape(PCB_TRACK *aTrack, VECTOR2D aOrigin)
KIGFX::COLOR4D m_copperColor
double m_boardThickness
EDA_ANGLE GetOrientation() const
Definition: footprint.h:193
int GetAttributes() const
Definition: footprint.h:252
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition: footprint.h:202
PADS & Pads()
Definition: footprint.h:172
void TransformFPShapesToPolySet(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool aIncludeText=true, bool aIncludeShapes=true, bool aIncludePrivateItems=false) const
Generate shapes of graphic items (outlines) on layer aLayer as polygons and adds these polygons to aB...
Definition: footprint.cpp:2703
const LIB_ID & GetFPID() const
Definition: footprint.h:214
std::vector< FP_3DMODEL > & Models()
Definition: footprint.h:186
const wxString & GetReference() const
Definition: footprint.h:521
VECTOR2I GetPosition() const override
Definition: footprint.h:190
Hold a record identifying a library accessed by the appropriate footprint library PLUGIN object in th...
Definition: fp_lib_table.h:41
const FP_LIB_TABLE_ROW * FindRow(const wxString &aNickName, bool aCheckIfEnabled=false)
Return an FP_LIB_TABLE_ROW if aNickName is found in this table or in any chained fall back table frag...
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:103
double r
Red component.
Definition: color4d.h:375
double g
Green component.
Definition: color4d.h:376
double b
Blue component.
Definition: color4d.h:377
EXPORTER_STEP * m_converter
KiCadPrinter(EXPORTER_STEP *aConverter)
virtual void Send(const TCollection_ExtendedString &theString, const Message_Gravity theGravity, const Standard_Boolean theToPutEol) const override
virtual void Send(const TCollection_AsciiString &theString, const Message_Gravity theGravity, const Standard_Boolean theToPutEol) const override
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition: lib_id.h:87
const wxString GetFullURI(bool aSubstituted=false) const
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
Definition: pad.h:59
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const override
Convert the shape to a closed polygon.
Definition: pcb_shape.cpp:379
int GetWidth() const
Definition: pcb_track.h:107
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const override
Convert the track shape to a closed polygon.
Definition: pcb_track.cpp:1214
const VECTOR2I & GetStart() const
Definition: pcb_track.h:113
virtual FP_LIB_TABLE * PcbFootprintLibs(KIWAY &aKiway)
Return the table of footprint libraries.
Definition: project.cpp:324
Represent a set of closed polygons.
void Fracture(POLYGON_MODE aFastMode)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
int FullPointCount() const
Return the number of points in the shape poly set.
#define _(s)
void ReportMessage(const wxString &aMessage)
@ FP_SMD
Definition: footprint.h:70
@ FP_DNP
Definition: footprint.h:77
@ FP_THROUGH_HOLE
Definition: footprint.h:69
@ ERROR_INSIDE
const wxChar *const traceKiCad2Step
Flag to enable KiCad2Step debug tracing.
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:59
@ B_Cu
Definition: layer_ids.h:95
@ F_Cu
Definition: layer_ids.h:64
see class PGM_BASE
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:115
std::vector< FAB_LAYER_COLOR > dummy
static constexpr double OCC_MAX_DISTANCE_TO_MERGE_POINTS
Default distance between points to treat them as separate ones (mm) 0.001 mm or less is a reasonable ...
constexpr double IUTomm(int iu) const
Definition: base_units.h:87
wxLogTrace helper definitions.
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:93
VECTOR2< int > VECTOR2I
Definition: vector2d.h:588