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 The 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 <advanced_config.h>
28#include <board.h>
30#include <footprint.h>
31#include <pcb_textbox.h>
32#include <pcb_table.h>
33#include <pcb_tablecell.h>
34#include <pcb_track.h>
35#include <pcb_shape.h>
36#include <pcb_barcode.h>
37#include <pcb_painter.h>
38#include <pad.h>
39#include <zone.h>
40#include <fp_lib_table.h>
41#include "step_pcb_model.h"
42
43#include <pgm_base.h>
44#include <reporter.h>
45#include <base_units.h>
46#include <filename_resolver.h>
47#include <trace_helpers.h>
48#include <project_pcb.h>
50
51#include <Message.hxx> // OpenCascade messenger
52#include <Message_PrinterOStream.hxx> // OpenCascade output messenger
53#include <Standard_Failure.hxx> // In open cascade
54
55#include <Standard_Version.hxx>
56
57#include <wx/crt.h>
58#include <wx/log.h>
59#include <core/profile.h> // To use GetRunningMicroSecs or another profiling utility
60
61#define OCC_VERSION_MIN 0x070500
62
63#if OCC_VERSION_HEX < OCC_VERSION_MIN
64#include <Message_Messenger.hxx>
65#endif
66
67
68class KICAD_PRINTER : public Message_Printer
69{
70public:
71 KICAD_PRINTER( REPORTER* aReporter ) :
72 m_reporter( aReporter )
73 {}
74
75protected:
76#if OCC_VERSION_HEX < OCC_VERSION_MIN
77 virtual void Send( const TCollection_ExtendedString& theString,
78 const Message_Gravity theGravity,
79 const Standard_Boolean theToPutEol ) const override
80 {
81 Send( TCollection_AsciiString( theString ), theGravity, theToPutEol );
82 }
83
84 virtual void Send( const TCollection_AsciiString& theString,
85 const Message_Gravity theGravity,
86 const Standard_Boolean theToPutEol ) const override
87#else
88 virtual void send( const TCollection_AsciiString& theString,
89 const Message_Gravity theGravity ) const override
90#endif
91 {
92 wxString msg( theString.ToCString() );
93
94#if OCC_VERSION_HEX < OCC_VERSION_MIN
95 if( theToPutEol )
96 msg += wxT( "\n" );
97#else
98 msg += wxT( "\n" );
99#endif
100
101 m_reporter->Report( msg, getSeverity( theGravity ) );
102 }
103
104private:
105 SEVERITY getSeverity( const Message_Gravity theGravity ) const
106 {
107 switch( theGravity )
108 {
109 case Message_Trace: return RPT_SEVERITY_DEBUG;
110 case Message_Info: return RPT_SEVERITY_DEBUG;
111 case Message_Warning: return RPT_SEVERITY_WARNING;
112 case Message_Alarm: return RPT_SEVERITY_WARNING;
113 case Message_Fail: return RPT_SEVERITY_ERROR;
114
115 // There are no other values, but gcc doesn't appear to be able to work that out.
116 default: return RPT_SEVERITY_UNDEFINED;
117 }
118 }
119
120private:
122};
123
124
126 REPORTER* aReporter ) :
127 m_params( aParams ),
128 m_reporter( aReporter ),
129 m_board( aBoard ),
130 m_pcbModel( nullptr )
131{
132 m_copperColor = COLOR4D( 0.7, 0.61, 0.0, 1.0 );
133
134 if( m_params.m_ExportComponents )
135 m_padColor = COLOR4D( 0.50, 0.50, 0.50, 1.0 );
136 else
138
139 // TODO: make configurable
140 m_platingThickness = pcbIUScale.mmToIU( 0.025 );
141
142 // Init m_pcbBaseName to the board short filename (no path, no ext)
143 // m_pcbName is used later to identify items in step file
144 wxFileName fn( aBoard->GetFileName() );
145 m_pcbBaseName = fn.GetName();
146
147 // Remove the autosave prefix
149
150 m_resolver = std::make_unique<FILENAME_RESOLVER>();
151 m_resolver->Set3DConfigDir( wxT( "" ) );
152 // needed to add the project to the search stack
153 m_resolver->SetProject( aBoard->GetProject() );
154 m_resolver->SetProgramBase( &Pgm() );
155}
156
157
161
162
164 SHAPE_POLY_SET* aClipPolygon )
165{
166 bool hasdata = false;
167 std::vector<PAD*> padsMatchingNetFilter;
168
169 // Dump the pad holes into the PCB
170 for( PAD* pad : aFootprint->Pads() )
171 {
172 bool castellated = pad->GetProperty() == PAD_PROP::CASTELLATED;
173 std::shared_ptr<SHAPE_SEGMENT> holeShape = pad->GetEffectiveHoleShape();
174
175 SHAPE_POLY_SET holePoly;
176 holeShape->TransformToPolygon( holePoly, pad->GetMaxError(), ERROR_INSIDE );
177
178 // This helps with fusing
179 holePoly.Deflate( m_platingThickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, pad->GetMaxError() );
180
181 for( PCB_LAYER_ID pcblayer : pad->GetLayerSet() )
182 {
183 if( pad->IsOnLayer( pcblayer ) )
184 m_poly_holes[pcblayer].Append( holePoly );
185 }
186
187 if( pad->HasHole() )
188 {
189 int platingThickness = pad->GetAttribute() == PAD_ATTRIB::PTH ? m_platingThickness : 0;
190
191 if( m_pcbModel->AddHole( *holeShape, platingThickness, F_Cu, B_Cu, false, aOrigin, true, true ) )
192 hasdata = true;
193
195 //if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
196 //{
197 // m_poly_holes[F_SilkS].Append( holePoly );
198 // m_poly_holes[B_SilkS].Append( holePoly );
199 //}
200 }
201
202 if( !m_params.m_NetFilter.IsEmpty() && !pad->GetNetname().Matches( m_params.m_NetFilter ) )
203 continue;
204
205 if( m_params.m_ExportPads )
206 {
207 if( m_pcbModel->AddPadShape( pad, aOrigin, false, castellated ? aClipPolygon : nullptr) )
208 hasdata = true;
209
210 if( m_params.m_ExportSoldermask )
211 {
212 for( PCB_LAYER_ID pcblayer : pad->GetLayerSet() )
213 {
214 if( pcblayer != F_Mask && pcblayer != B_Mask )
215 continue;
216
217 SHAPE_POLY_SET poly;
218 PCB_LAYER_ID cuLayer = ( pcblayer == F_Mask ) ? F_Cu : B_Cu;
219 pad->TransformShapeToPolygon( poly, cuLayer, pad->GetSolderMaskExpansion( cuLayer ),
220 pad->GetMaxError(), ERROR_INSIDE );
221
222 m_poly_shapes[pcblayer][wxEmptyString].Append( poly );
223 }
224 }
225 }
226
227 padsMatchingNetFilter.push_back( pad );
228 }
229
230 // Build 3D shapes of the footprint graphic items:
231 for( PCB_LAYER_ID pcblayer : m_layersToExport )
232 {
233 if( IsCopperLayer( pcblayer ) && !m_params.m_ExportTracksVias )
234 continue;
235
236 SHAPE_POLY_SET buffer;
237
238 aFootprint->TransformFPShapesToPolySet( buffer, pcblayer, 0, aFootprint->GetMaxError(), ERROR_INSIDE,
239 true, /* include text */
240 true, /* include shapes */
241 false /* include private items */ );
242
243 if( !IsCopperLayer( pcblayer ) )
244 {
245 m_poly_shapes[pcblayer][wxEmptyString].Append( buffer );
246 }
247 else
248 {
249 std::map<const SHAPE_POLY_SET::POLYGON*, PAD*> polyPadMap;
250
251 // Only add polygons colliding with any matching pads
252 for( const SHAPE_POLY_SET::POLYGON& poly : buffer.CPolygons() )
253 {
254 for( PAD* pad : padsMatchingNetFilter )
255 {
256 if( !pad->IsOnLayer( pcblayer ) )
257 continue;
258
259 std::shared_ptr<SHAPE_POLY_SET> padPoly = pad->GetEffectivePolygon( pcblayer );
260 SHAPE_POLY_SET gfxPoly( poly );
261
262 if( padPoly->Collide( &gfxPoly ) )
263 {
264 polyPadMap[&poly] = pad;
265 m_poly_shapes[pcblayer][pad->GetNetname()].Append( gfxPoly );
266 break;
267 }
268 }
269 }
270
271 if( m_params.m_NetFilter.empty() )
272 {
273 // Add polygons with no net
274 for( const SHAPE_POLY_SET::POLYGON& poly : buffer.CPolygons() )
275 {
276 auto it = polyPadMap.find( &poly );
277
278 if( it == polyPadMap.end() )
279 m_poly_shapes[pcblayer][wxEmptyString].Append( poly );
280 }
281 }
282 }
283 }
284
285 if( ( !(aFootprint->GetAttributes() & (FP_THROUGH_HOLE|FP_SMD)) ) && !m_params.m_IncludeUnspecified )
286 {
287 return hasdata;
288 }
289
290 if( ( aFootprint->GetAttributes() & FP_DNP ) && !m_params.m_IncludeDNP )
291 {
292 return hasdata;
293 }
294
295 // Prefetch the library for this footprint
296 // In case we need to resolve relative footprint paths
297 wxString libraryName = aFootprint->GetFPID().GetLibNickname();
298 wxString footprintBasePath = wxEmptyString;
299
300 double posX = aFootprint->GetPosition().x - aOrigin.x;
301 double posY = (aFootprint->GetPosition().y) - aOrigin.y;
302
303 if( m_board->GetProject() )
304 {
305 try
306 {
307 // FindRow() can throw an exception
308 const FP_LIB_TABLE_ROW* fpRow =
309 PROJECT_PCB::PcbFootprintLibs( m_board->GetProject() )->FindRow( libraryName, false );
310
311 if( fpRow )
312 footprintBasePath = fpRow->GetFullURI( true );
313 }
314 catch( ... )
315 {
316 // Do nothing if the libraryName is not found in lib table
317 }
318 }
319
320 // Exit early if we don't want to include footprint models
321 if( m_params.m_BoardOnly || !m_params.m_ExportComponents )
322 {
323 return hasdata;
324 }
325
326 bool componentFilter = !m_params.m_ComponentFilter.IsEmpty();
327 std::vector<wxString> componentFilterPatterns;
328
329 if( componentFilter )
330 {
331 wxStringTokenizer tokenizer( m_params.m_ComponentFilter, ", \t\r\n", wxTOKEN_STRTOK );
332
333 while( tokenizer.HasMoreTokens() )
334 componentFilterPatterns.push_back( tokenizer.GetNextToken() );
335
336 bool found = false;
337
338 for( const wxString& pattern : componentFilterPatterns )
339 {
340 if( aFootprint->GetReference().Matches( pattern ) )
341 {
342 found = true;
343 break;
344 }
345 }
346
347 if( !found )
348 return hasdata;
349 }
350
351 VECTOR2D newpos( pcbIUScale.IUTomm( posX ), pcbIUScale.IUTomm( posY ) );
352
353 for( const FP_3DMODEL& fp_model : aFootprint->Models() )
354 {
355 if( !fp_model.m_Show || fp_model.m_Filename.empty() )
356 continue;
357
358 std::vector<wxString> searchedPaths;
359 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
360 embeddedFilesStack.push_back( aFootprint->GetEmbeddedFiles() );
361 embeddedFilesStack.push_back( m_board->GetEmbeddedFiles() );
362
363 wxString mname = m_resolver->ResolvePath( fp_model.m_Filename, footprintBasePath,
364 std::move( embeddedFilesStack ) );
365
366 if( mname.empty() || !wxFileName::FileExists( mname ) )
367 {
368 // the error path will return an empty name sometimes, at least report back the original filename
369 if( mname.empty() )
370 mname = fp_model.m_Filename;
371
372 m_reporter->Report( wxString::Format( _( "Could not add 3D model for %s.\n"
373 "File not found: %s\n" ),
374 aFootprint->GetReference(),
375 mname ),
377 continue;
378 }
379
380 std::string fname( mname.ToUTF8() );
381 std::string refName( aFootprint->GetReference().ToUTF8() );
382
383 try
384 {
385 bool bottomSide = aFootprint->GetLayer() == B_Cu;
386
387 // the rotation is stored in degrees but opencascade wants radians
388 VECTOR3D modelRot = fp_model.m_Rotation;
389 modelRot *= M_PI;
390 modelRot /= 180.0;
391
392 if( m_pcbModel->AddComponent( fname, refName, bottomSide, newpos,
393 aFootprint->GetOrientation().AsRadians(),
394 fp_model.m_Offset, modelRot,
395 fp_model.m_Scale, m_params.m_SubstModels ) )
396 {
397 hasdata = true;
398 }
399 }
400 catch( const Standard_Failure& e )
401 {
402 m_reporter->Report( wxString::Format( _( "Could not add 3D model for %s.\n"
403 "OpenCASCADE error: %s\n" ),
404 aFootprint->GetReference(),
405 e.GetMessageString() ),
407 }
408
409 }
410
411 return hasdata;
412}
413
414
416{
417 bool skipCopper = !m_params.m_ExportTracksVias
418 || ( !m_params.m_NetFilter.IsEmpty()
419 && !aTrack->GetNetname().Matches( m_params.m_NetFilter ) );
420
421 if( m_params.m_ExportSoldermask && aTrack->IsOnLayer( F_Mask ) )
422 {
423 aTrack->TransformShapeToPolygon( m_poly_shapes[F_Mask][wxEmptyString], F_Mask,
424 aTrack->GetSolderMaskExpansion(), aTrack->GetMaxError(),
425 ERROR_INSIDE );
426 }
427
428 if( m_params.m_ExportSoldermask && aTrack->IsOnLayer( B_Mask ) )
429 {
430 aTrack->TransformShapeToPolygon( m_poly_shapes[B_Mask][wxEmptyString], B_Mask,
431 aTrack->GetSolderMaskExpansion(), aTrack->GetMaxError(),
432 ERROR_INSIDE );
433 }
434
435 if( aTrack->Type() == PCB_VIA_T )
436 {
437 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
438
439 std::shared_ptr<SHAPE_SEGMENT> holeShape = via->GetEffectiveHoleShape();
440 SHAPE_POLY_SET holePoly;
441 holeShape->TransformToPolygon( holePoly, via->GetMaxError(), ERROR_INSIDE );
442
443 // This helps with fusing
444 holePoly.Deflate( m_platingThickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, via->GetMaxError() );
445
446 LSET layers( via->GetLayerSet() & m_layersToExport );
447
448 PCB_LAYER_ID top_layer, bot_layer;
449 via->LayerPair( &top_layer, &bot_layer );
450
451 if( !skipCopper )
452 {
453 for( PCB_LAYER_ID pcblayer : layers )
454 {
455 const std::shared_ptr<SHAPE>& shape = via->GetEffectiveShape( pcblayer );
456
457 SHAPE_POLY_SET poly;
458 shape->TransformToPolygon( poly, via->GetMaxError(), ERROR_INSIDE );
459 m_poly_shapes[pcblayer][via->GetNetname()].Append( poly );
460 m_poly_holes[pcblayer].Append( holePoly );
461 }
462
463 m_pcbModel->AddBarrel( *holeShape, top_layer, bot_layer, true, aOrigin, via->GetNetname() );
464 }
465
467 //if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
468 //{
469 // m_poly_holes[F_SilkS].Append( holePoly );
470 // m_poly_holes[B_SilkS].Append( holePoly );
471 //}
472
473 m_pcbModel->AddHole( *holeShape, m_platingThickness, top_layer, bot_layer, true, aOrigin,
474 !m_params.m_FillAllVias, m_params.m_CutViasInBody );
475
476 return true;
477 }
478
479 if( skipCopper )
480 return true;
481
482 PCB_LAYER_ID pcblayer = aTrack->GetLayer();
483
484 if( !m_layersToExport.Contains( pcblayer ) )
485 return false;
486
487 aTrack->TransformShapeToPolygon( m_poly_shapes[pcblayer][aTrack->GetNetname()], pcblayer, 0,
488 aTrack->GetMaxError(), ERROR_INSIDE );
489
490 return true;
491}
492
493
495{
496 for( ZONE* zone : m_board->Zones() )
497 {
498 LSET layers = zone->GetLayerSet();
499
500 if( ( layers & LSET::AllCuMask() ).count() && !m_params.m_NetFilter.IsEmpty()
501 && !zone->GetNetname().Matches( m_params.m_NetFilter ) )
502 {
503 continue;
504 }
505
506 for( PCB_LAYER_ID layer : layers )
507 {
508 SHAPE_POLY_SET fill_shape;
509 zone->TransformSolidAreasShapesToPolygon( layer, fill_shape );
510 fill_shape.Unfracture();
511
512 fill_shape.SimplifyOutlines( ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel );
513
514 m_poly_shapes[layer][zone->GetNetname()].Append( fill_shape );
515 }
516 }
517}
518
519
521{
522 PCB_LAYER_ID pcblayer = aItem->GetLayer();
523 int maxError = aItem->GetMaxError();
524
525 if( !m_layersToExport.Contains( pcblayer ) )
526 return false;
527
528 if( IsCopperLayer( pcblayer ) && !m_params.m_ExportTracksVias )
529 return false;
530
531 if( IsInnerCopperLayer( pcblayer ) && !m_params.m_ExportInnerCopper )
532 return false;
533
534 switch( aItem->Type() )
535 {
536 case PCB_SHAPE_T:
537 {
538 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
539
540 if( IsCopperLayer( pcblayer ) && !m_params.m_NetFilter.IsEmpty()
541 && !graphic->GetNetname().Matches( m_params.m_NetFilter ) )
542 {
543 return true;
544 }
545
546 LINE_STYLE lineStyle = graphic->GetLineStyle();
547
548 if( lineStyle == LINE_STYLE::SOLID )
549 {
550 graphic->TransformShapeToPolySet( m_poly_shapes[pcblayer][graphic->GetNetname()], pcblayer, 0,
551 maxError, ERROR_INSIDE );
552 }
553 else
554 {
555 std::vector<SHAPE*> shapes = graphic->MakeEffectiveShapes( true );
556 const PCB_PLOT_PARAMS& plotParams = m_board->GetPlotOptions();
557 KIGFX::PCB_RENDER_SETTINGS renderSettings;
558
559 renderSettings.SetDashLengthRatio( plotParams.GetDashedLineDashRatio() );
560 renderSettings.SetGapLengthRatio( plotParams.GetDashedLineGapRatio() );
561
562 for( SHAPE* shape : shapes )
563 {
564 STROKE_PARAMS::Stroke( shape, lineStyle, graphic->GetWidth(), &renderSettings,
565 [&]( const VECTOR2I& a, const VECTOR2I& b )
566 {
567 SHAPE_SEGMENT seg( a, b, graphic->GetWidth() );
568 seg.TransformToPolygon( m_poly_shapes[pcblayer][graphic->GetNetname()],
569 maxError, ERROR_INSIDE );
570 } );
571 }
572
573 for( SHAPE* shape : shapes )
574 delete shape;
575 }
576
577 if( graphic->IsHatchedFill() )
578 m_poly_shapes[pcblayer][graphic->GetNetname()].Append( graphic->GetHatching() );
579
580 if( m_params.m_ExportSoldermask && graphic->IsOnLayer( F_Mask ) )
581 {
582 graphic->TransformShapeToPolygon( m_poly_shapes[F_Mask][wxEmptyString], F_Mask,
583 graphic->GetSolderMaskExpansion(), maxError, ERROR_INSIDE );
584 }
585
586 if( m_params.m_ExportSoldermask && graphic->IsOnLayer( B_Mask ) )
587 {
588 graphic->TransformShapeToPolygon( m_poly_shapes[B_Mask][wxEmptyString], B_Mask,
589 graphic->GetSolderMaskExpansion(), maxError, ERROR_INSIDE );
590 }
591
592 break;
593 }
594
595 case PCB_TEXT_T:
596 {
597 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
598
599 text->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
600 break;
601 }
602
603 case PCB_BARCODE_T:
604 {
605 PCB_BARCODE* barcode = static_cast<PCB_BARCODE*>( aItem );
606
607 barcode->TransformShapeToPolySet( m_poly_shapes[pcblayer][wxEmptyString], pcblayer, 0, maxError,
608 ERROR_INSIDE );
609 break;
610 }
611
612 case PCB_TEXTBOX_T:
613 {
614 PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( aItem );
615
616 // border
617 if( textbox->IsBorderEnabled() )
618 {
619 textbox->PCB_SHAPE::TransformShapeToPolygon( m_poly_shapes[pcblayer][wxEmptyString], pcblayer, 0,
620 maxError, ERROR_INSIDE );
621 }
622
623 // text
624 textbox->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
625 break;
626 }
627
628 case PCB_TABLE_T:
629 {
630 PCB_TABLE* table = static_cast<PCB_TABLE*>( aItem );
631
632 for( PCB_TABLECELL* cell : table->GetCells() )
633 {
634 cell->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
635 }
636
637 table->DrawBorders(
638 [&]( const VECTOR2I& ptA, const VECTOR2I& ptB, const STROKE_PARAMS& stroke )
639 {
640 SHAPE_SEGMENT seg( ptA, ptB, stroke.GetWidth() );
641 seg.TransformToPolygon( m_poly_shapes[pcblayer][wxEmptyString], maxError, ERROR_INSIDE );
642 } );
643
644 break;
645 }
646
647 default: wxFAIL_MSG( "buildGraphic3DShape: unhandled item type" );
648 }
649
650 return true;
651}
652
653
655{
656 // Specialize the STEP_PCB_MODEL generator for specific output format
657 // it can have some minor actions for the generator
658 switch( m_params.m_Format )
659 {
661 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STEP );
662 break;
663
665 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STEPZ );
666 break;
667
669 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_BREP );
670 break;
671
673 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_XAO );
674 break;
675
677 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_GLTF );
678 break;
679
681 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PLY );
682 break;
683
685 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STL );
686 break;
687
689 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_U3D );
690 break;
691
693 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PDF );
694 break;
695
696 default:
697 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_UNKNOWN );
698 break;
699 }
700}
701
702
704{
705 if( m_pcbModel )
706 return true;
707
708 SHAPE_POLY_SET pcbOutlines; // stores the board main outlines
709
710 if( !m_board->GetBoardPolygonOutlines( pcbOutlines,
711 /* error handler */ nullptr,
712 /* allows use arcs in outlines */ true ) )
713 {
714 wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) );
715 }
716
717 SHAPE_POLY_SET pcbOutlinesNoArcs = pcbOutlines;
718 pcbOutlinesNoArcs.ClearArcs();
719
720 VECTOR2D origin;
721
722 // Determine the coordinate system reference:
723 // Precedence of reference point is Drill Origin > Grid Origin > User Offset
724 if( m_params.m_UseDrillOrigin )
725 origin = m_board->GetDesignSettings().GetAuxOrigin();
726 else if( m_params.m_UseGridOrigin )
727 origin = m_board->GetDesignSettings().GetGridOrigin();
728 else
729 origin = m_params.m_Origin;
730
731 m_pcbModel = std::make_unique<STEP_PCB_MODEL>( m_pcbBaseName, m_reporter );
732
734
735 m_pcbModel->SetCopperColor( m_copperColor.r, m_copperColor.g, m_copperColor.b );
736 m_pcbModel->SetPadColor( m_padColor.r, m_padColor.g, m_padColor.b );
737
738 m_pcbModel->SetStackup( m_board->GetStackupOrDefault() );
739 m_pcbModel->SetEnabledLayers( m_layersToExport );
740 m_pcbModel->SetFuseShapes( m_params.m_FuseShapes );
741 m_pcbModel->SetNetFilter( m_params.m_NetFilter );
742
743 // Note: m_params.m_BoardOutlinesChainingEpsilon is used only to build the board outlines,
744 // not to set OCC chaining epsilon (much smaller)
745 //
746 // Set the min distance between 2 points for OCC to see these 2 points as merged
747 // OCC_MAX_DISTANCE_TO_MERGE_POINTS is acceptable for OCC, otherwise there are issues
748 // to handle the shapes chaining on copper layers, because the Z dist is 0.035 mm and the
749 // min dist must be much smaller (we use 0.001 mm giving good results)
750 m_pcbModel->OCCSetMergeMaxDistance( OCC_MAX_DISTANCE_TO_MERGE_POINTS );
751
752 // For copper layers, only pads and tracks are added, because adding everything on copper
753 // generate unreasonable file sizes and take a unreasonable calculation time.
754 for( FOOTPRINT* fp : m_board->Footprints() )
755 buildFootprint3DShapes( fp, origin, &pcbOutlinesNoArcs );
756
757 for( PCB_TRACK* track : m_board->Tracks() )
758 buildTrack3DShape( track, origin );
759
760 for( BOARD_ITEM* item : m_board->Drawings() )
761 buildGraphic3DShape( item, origin );
762
763 if( m_params.m_ExportZones )
764 buildZones3DShape( origin );
765
766 for( PCB_LAYER_ID pcblayer : m_layersToExport.Seq() )
767 {
768 SHAPE_POLY_SET holes = m_poly_holes[pcblayer];
769 holes.Simplify();
770
771 if( pcblayer == F_Mask || pcblayer == B_Mask )
772 {
773 // Mask layer is negative
774 SHAPE_POLY_SET mask = pcbOutlinesNoArcs;
775
776 for( auto& [netname, poly] : m_poly_shapes[pcblayer] )
777 {
778 poly.Simplify();
779
780 poly.SimplifyOutlines( pcbIUScale.mmToIU( 0.003 ) );
781 poly.Simplify();
782
783 mask.BooleanSubtract( poly );
784 }
785
786 mask.BooleanSubtract( holes );
787
788 m_pcbModel->AddPolygonShapes( &mask, pcblayer, origin, wxEmptyString );
789 }
790 else
791 {
792 for( auto& [netname, poly] : m_poly_shapes[pcblayer] )
793 {
794 poly.Simplify();
795
796 poly.SimplifyOutlines( pcbIUScale.mmToIU( 0.003 ) );
797 poly.Simplify();
798
799 // Subtract holes
800 poly.BooleanSubtract( holes );
801
802 // Clip to board outline
803 poly.BooleanIntersection( pcbOutlinesNoArcs );
804
805 m_pcbModel->AddPolygonShapes( &poly, pcblayer, origin, netname );
806 }
807 }
808 }
809
810 m_reporter->Report( wxT( "Create PCB solid model.\n" ), RPT_SEVERITY_DEBUG );
811
812 m_reporter->Report( wxString::Format( wxT( "Board outline: found %d initial points.\n" ),
813 pcbOutlines.FullPointCount() ),
815
816 if( !m_pcbModel->CreatePCB( pcbOutlines, origin, m_params.m_ExportBoardBody ) )
817 {
818 m_reporter->Report( _( "Could not create PCB solid model.\n" ), RPT_SEVERITY_ERROR );
819 return false;
820 }
821
822 return true;
823}
824
825
827{
828 // Display the export time, for statistics
829 int64_t stats_startExportTime = GetRunningMicroSecs();
830
831 // setup opencascade message log
832 struct SCOPED_PRINTER
833 {
834 Handle( Message_Printer ) m_handle;
835
836 SCOPED_PRINTER( const Handle( Message_Printer ) & aHandle ) : m_handle( aHandle )
837 {
838 Message::DefaultMessenger()->AddPrinter( m_handle );
839 };
840
841 ~SCOPED_PRINTER() { Message::DefaultMessenger()->RemovePrinter( m_handle ); }
842 };
843
844 Message::DefaultMessenger()->RemovePrinters( STANDARD_TYPE( Message_PrinterOStream ) );
845 SCOPED_PRINTER occtPrinter( new KICAD_PRINTER( m_reporter ) );
846
847 m_reporter->Report( wxT( "Determining PCB data.\n" ), RPT_SEVERITY_DEBUG );
848
849 if( m_params.m_OutputFile.IsEmpty() )
850 {
851 wxFileName fn = m_board->GetFileName();
852 fn.SetName( fn.GetName() );
853 fn.SetExt( m_params.GetDefaultExportExtension() );
854
855 m_params.m_OutputFile = fn.GetFullName();
856 }
857
859
860 if( m_params.m_ExportInnerCopper )
862
863 if( m_params.m_ExportSilkscreen )
864 {
867 }
868
869 if( m_params.m_ExportSoldermask )
870 {
873 }
874
875 m_layersToExport &= m_board->GetEnabledLayers();
876
877 try
878 {
879 m_reporter->Report( wxString::Format( wxT( "Build %s data.\n" ), m_params.GetFormatName() ),
881
882 if( !buildBoard3DShapes() )
883 {
884 m_reporter->Report( _( "\n"
885 "** Error building STEP board model. Export aborted. **\n" ),
887 return false;
888 }
889
890 m_reporter->Report( wxString::Format( wxT( "Writing %s file.\n" ), m_params.GetFormatName() ),
892
893 bool success = true;
895 success = m_pcbModel->WriteSTEP( m_outputFile, m_params.m_OptimizeStep, false );
896 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STEPZ )
897 success = m_pcbModel->WriteSTEP( m_outputFile, m_params.m_OptimizeStep, true );
898 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::BREP )
899 success = m_pcbModel->WriteBREP( m_outputFile );
900 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::XAO )
901 success = m_pcbModel->WriteXAO( m_outputFile );
902 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::GLB )
903 success = m_pcbModel->WriteGLTF( m_outputFile );
904 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PLY )
905 success = m_pcbModel->WritePLY( m_outputFile );
906 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STL )
907 success = m_pcbModel->WriteSTL( m_outputFile );
908 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::U3D )
909 success = m_pcbModel->WriteU3D( m_outputFile );
910 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PDF )
911 success = m_pcbModel->WritePDF( m_outputFile );
912
913 if( !success )
914 {
915 m_reporter->Report( wxString::Format( _( "\n"
916 "** Error writing %s file. **\n" ),
917 m_params.GetFormatName() ),
919 return false;
920 }
921 else
922 {
923 m_reporter->Report( wxString::Format( wxT( "%s file '%s' created.\n" ),
924 m_params.GetFormatName(),
925 m_outputFile ),
927 }
928 }
929 catch( const Standard_Failure& e )
930 {
931 m_reporter->Report( e.GetMessageString(), RPT_SEVERITY_ERROR );
932 m_reporter->Report( wxString::Format( _( "\n"
933 "** Error exporting %s file. Export aborted. **\n" ),
934 m_params.GetFormatName() ),
936 return false;
937 }
938 #ifndef DEBUG
939 catch( ... )
940 {
941 m_reporter->Report( wxString::Format( _( "\n"
942 "** Error exporting %s file. Export aborted. **\n" ),
943 m_params.GetFormatName() ),
945 return false;
946 }
947 #endif
948
949 // Display calculation time in seconds
950 double calculation_time = (double)( GetRunningMicroSecs() - stats_startExportTime) / 1e6;
951 m_reporter->Report( wxString::Format( _( "\n"
952 "Export time %.3f s\n" ),
953 calculation_time ),
955
956 return !m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR );
957}
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:79
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:232
virtual void TransformShapeToPolySet(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc) const
Convert the item shape to a polyset.
Definition board_item.h:425
int GetMaxError() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
const wxString & GetFileName() const
Definition board.h:359
PROJECT * GetProject() const
Definition board.h:554
double AsRadians() const
Definition eda_angle.h:120
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
const SHAPE_POLY_SET & GetHatching() const
Definition eda_shape.h:148
virtual std::vector< SHAPE * > MakeEffectiveShapes(bool aEdgeOnly=false) const
Make a set of SHAPE objects representing the EDA_SHAPE.
Definition eda_shape.h:379
bool IsHatchedFill() const
Definition eda_shape.h:124
LINE_STYLE GetLineStyle() const
void buildZones3DShape(VECTOR2D aOrigin)
REPORTER * m_reporter
bool buildTrack3DShape(PCB_TRACK *aTrack, const VECTOR2D &aOrigin)
bool buildFootprint3DShapes(FOOTPRINT *aFootprint, const VECTOR2D &aOrigin, SHAPE_POLY_SET *aClipPolygon)
std::map< PCB_LAYER_ID, SHAPE_POLY_SET > m_poly_holes
wxString m_outputFile
EXPORTER_STEP_PARAMS m_params
wxString m_pcbBaseName
the name of the project (board short filename (no path, no ext) used to identify items in step file
std::unique_ptr< FILENAME_RESOLVER > m_resolver
bool buildBoard3DShapes()
std::unique_ptr< STEP_PCB_MODEL > m_pcbModel
std::map< PCB_LAYER_ID, std::map< wxString, SHAPE_POLY_SET > > m_poly_shapes
KIGFX::COLOR4D m_copperColor
EXPORTER_STEP(BOARD *aBoard, const EXPORTER_STEP_PARAMS &aParams, REPORTER *aReporter)
bool buildGraphic3DShape(BOARD_ITEM *aItem, const VECTOR2D &aOrigin)
KIGFX::COLOR4D m_padColor
EDA_ANGLE GetOrientation() const
Definition footprint.h:248
std::deque< PAD * > & Pads()
Definition footprint.h:224
int GetAttributes() const
Definition footprint.h:327
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:257
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...
const LIB_ID & GetFPID() const
Definition footprint.h:269
std::vector< FP_3DMODEL > & Models()
Definition footprint.h:241
const wxString & GetReference() const
Definition footprint.h:661
EMBEDDED_FILES * GetEmbeddedFiles() override
Definition footprint.h:1014
VECTOR2I GetPosition() const override
Definition footprint.h:245
Hold a record identifying a library accessed by the appropriate footprint library #PLUGIN object in t...
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...
virtual void Send(const TCollection_ExtendedString &theString, const Message_Gravity theGravity, const Standard_Boolean theToPutEol) const override
REPORTER * m_reporter
SEVERITY getSeverity(const Message_Gravity theGravity) const
KICAD_PRINTER(REPORTER *aReporter)
virtual void Send(const TCollection_AsciiString &theString, const Message_Gravity theGravity, const Standard_Boolean theToPutEol) const override
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:104
PCB specific render settings.
Definition pcb_painter.h:82
void SetGapLengthRatio(double aRatio)
void SetDashLengthRatio(double aRatio)
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...
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & ExternalCuMask()
Return a mask holding the Front and Bottom layers.
Definition lset.cpp:617
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:582
static const LSET & InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition lset.cpp:560
Definition pad.h:54
Parameters and options when plotting/printing a board.
double GetDashedLineGapRatio() const
double GetDashedLineDashRatio() const
int GetWidth() const override
int GetSolderMaskExpansion() const
void TransformShapeToPolySet(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc) const override
Convert the item shape to a polyset.
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.
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
bool IsBorderEnabled() const
Disables the border, this is done by changing the stroke internally.
void TransformTextToPolySet(SHAPE_POLY_SET &aBuffer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc) const
Function TransformTextToPolySet Convert the text to a polygonSet describing the actual character stro...
int GetSolderMaskExpansion() const
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.
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
static FP_LIB_TABLE * PcbFootprintLibs(PROJECT *aProject)
Return the table of footprint libraries without Kiway.
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
Represent a set of closed polygons.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
int FullPointCount() const
Return the number of points in the shape poly set.
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
void Unfracture()
Convert a single outline slitted ("fractured") polygon into a set ouf outlines with holes.
void SimplifyOutlines(int aMaxError=0)
Simplifies the lines in the polyset.
void Deflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError)
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const std::vector< POLYGON > & CPolygons() const
void TransformToPolygon(SHAPE_POLY_SET &aBuffer, int aError, ERROR_LOC aErrorLoc) const override
Fills a SHAPE_POLY_SET with a polygon representation of this shape.
An abstract shape on 2D plane.
Definition shape.h:126
Simple container to manage line stroke parameters.
int GetWidth() const
static void Stroke(const SHAPE *aShape, LINE_STYLE aLineStyle, int aWidth, const KIGFX::RENDER_SETTINGS *aRenderSettings, const std::function< void(const VECTOR2I &a, const VECTOR2I &b)> &aStroker)
Handle a list of polygons defining a copper zone.
Definition zone.h:74
@ ROUND_ALL_CORNERS
All angles are rounded.
#define _(s)
@ FP_SMD
Definition footprint.h:82
@ FP_DNP
Definition footprint.h:87
@ FP_THROUGH_HOLE
Definition footprint.h:81
static const std::string AutoSaveFilePrefix
Handle(KICAD3D_INFO) KICAD3D_INFO
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:676
bool IsInnerCopperLayer(int aLayerId)
Test whether a layer is an inner (In1_Cu to In30_Cu) copper layer.
Definition layer_ids.h:698
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ F_Mask
Definition layer_ids.h:97
@ F_SilkS
Definition layer_ids.h:100
@ B_SilkS
Definition layer_ids.h:101
@ F_Cu
Definition layer_ids.h:64
@ PTH
Plated through hole pad.
Definition padstack.h:82
@ CASTELLATED
a pad with a castellated through hole
Definition padstack.h:105
BARCODE class definition.
PGM_BASE & Pgm()
The global program "get" accessor.
Definition pgm_base.cpp:913
see class PGM_BASE
int64_t GetRunningMicroSecs()
An alternate way to calculate an elapsed time (in microsecondes) to class PROF_COUNTER.
SEVERITY
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_UNDEFINED
@ RPT_SEVERITY_DEBUG
@ RPT_SEVERITY_INFO
@ RPT_SEVERITY_ACTION
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 ...
LINE_STYLE
Dashed line types.
#define M_PI
wxLogTrace helper definitions.
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:88
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:93
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:92
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:101
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:94
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694
VECTOR3< double > VECTOR3D
Definition vector3.h:230
Definition of file extensions used in Kicad.