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 m_resolver = std::make_unique<FILENAME_RESOLVER>();
148 m_resolver->Set3DConfigDir( wxT( "" ) );
149 // needed to add the project to the search stack
150 m_resolver->SetProject( aBoard->GetProject() );
151 m_resolver->SetProgramBase( &Pgm() );
152}
153
154
158
159
161 SHAPE_POLY_SET* aClipPolygon )
162{
163 bool hasdata = false;
164 std::vector<PAD*> padsMatchingNetFilter;
165
166 // Dump the pad holes into the PCB
167 for( PAD* pad : aFootprint->Pads() )
168 {
169 bool castellated = pad->GetProperty() == PAD_PROP::CASTELLATED;
170 std::shared_ptr<SHAPE_SEGMENT> holeShape = pad->GetEffectiveHoleShape();
171
172 SHAPE_POLY_SET holePoly;
173 holeShape->TransformToPolygon( holePoly, pad->GetMaxError(), ERROR_INSIDE );
174
175 // This helps with fusing
176 holePoly.Deflate( m_platingThickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, pad->GetMaxError() );
177
178 for( PCB_LAYER_ID pcblayer : pad->GetLayerSet() )
179 {
180 if( pad->IsOnLayer( pcblayer ) )
181 m_poly_holes[pcblayer].Append( holePoly );
182 }
183
184 if( pad->HasHole() )
185 {
186 int platingThickness = pad->GetAttribute() == PAD_ATTRIB::PTH ? m_platingThickness : 0;
187
188 if( m_pcbModel->AddHole( *holeShape, platingThickness, F_Cu, B_Cu, false, aOrigin, true, true ) )
189 hasdata = true;
190
192 //if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
193 //{
194 // m_poly_holes[F_SilkS].Append( holePoly );
195 // m_poly_holes[B_SilkS].Append( holePoly );
196 //}
197 }
198
199 if( !m_params.m_NetFilter.IsEmpty() && !pad->GetNetname().Matches( m_params.m_NetFilter ) )
200 continue;
201
202 if( m_params.m_ExportPads )
203 {
204 if( m_pcbModel->AddPadShape( pad, aOrigin, false, castellated ? aClipPolygon : nullptr) )
205 hasdata = true;
206
207 if( m_params.m_ExportSoldermask )
208 {
209 for( PCB_LAYER_ID pcblayer : pad->GetLayerSet() )
210 {
211 if( pcblayer != F_Mask && pcblayer != B_Mask )
212 continue;
213
214 SHAPE_POLY_SET poly;
215 PCB_LAYER_ID cuLayer = ( pcblayer == F_Mask ) ? F_Cu : B_Cu;
216 pad->TransformShapeToPolygon( poly, cuLayer, pad->GetSolderMaskExpansion( cuLayer ),
217 pad->GetMaxError(), ERROR_INSIDE );
218
219 m_poly_shapes[pcblayer][wxEmptyString].Append( poly );
220 }
221 }
222 }
223
224 padsMatchingNetFilter.push_back( pad );
225 }
226
227 // Build 3D shapes of the footprint graphic items:
228 for( PCB_LAYER_ID pcblayer : m_layersToExport )
229 {
230 if( IsCopperLayer( pcblayer ) && !m_params.m_ExportTracksVias )
231 continue;
232
233 SHAPE_POLY_SET buffer;
234
235 aFootprint->TransformFPShapesToPolySet( buffer, pcblayer, 0, aFootprint->GetMaxError(), ERROR_INSIDE,
236 true, /* include text */
237 true, /* include shapes */
238 false /* include private items */ );
239
240 if( !IsCopperLayer( pcblayer ) )
241 {
242 m_poly_shapes[pcblayer][wxEmptyString].Append( buffer );
243 }
244 else
245 {
246 std::map<const SHAPE_POLY_SET::POLYGON*, PAD*> polyPadMap;
247
248 // Only add polygons colliding with any matching pads
249 for( const SHAPE_POLY_SET::POLYGON& poly : buffer.CPolygons() )
250 {
251 for( PAD* pad : padsMatchingNetFilter )
252 {
253 if( !pad->IsOnLayer( pcblayer ) )
254 continue;
255
256 std::shared_ptr<SHAPE_POLY_SET> padPoly = pad->GetEffectivePolygon( pcblayer );
257 SHAPE_POLY_SET gfxPoly( poly );
258
259 if( padPoly->Collide( &gfxPoly ) )
260 {
261 polyPadMap[&poly] = pad;
262 m_poly_shapes[pcblayer][pad->GetNetname()].Append( gfxPoly );
263 break;
264 }
265 }
266 }
267
268 if( m_params.m_NetFilter.empty() )
269 {
270 // Add polygons with no net
271 for( const SHAPE_POLY_SET::POLYGON& poly : buffer.CPolygons() )
272 {
273 auto it = polyPadMap.find( &poly );
274
275 if( it == polyPadMap.end() )
276 m_poly_shapes[pcblayer][wxEmptyString].Append( poly );
277 }
278 }
279 }
280 }
281
282 if( ( !(aFootprint->GetAttributes() & (FP_THROUGH_HOLE|FP_SMD)) ) && !m_params.m_IncludeUnspecified )
283 {
284 return hasdata;
285 }
286
287 if( ( aFootprint->GetAttributes() & FP_DNP ) && !m_params.m_IncludeDNP )
288 {
289 return hasdata;
290 }
291
292 // Prefetch the library for this footprint
293 // In case we need to resolve relative footprint paths
294 wxString libraryName = aFootprint->GetFPID().GetLibNickname();
295 wxString footprintBasePath = wxEmptyString;
296
297 double posX = aFootprint->GetPosition().x - aOrigin.x;
298 double posY = (aFootprint->GetPosition().y) - aOrigin.y;
299
300 if( m_board->GetProject() )
301 {
302 try
303 {
304 // FindRow() can throw an exception
305 const FP_LIB_TABLE_ROW* fpRow =
306 PROJECT_PCB::PcbFootprintLibs( m_board->GetProject() )->FindRow( libraryName, false );
307
308 if( fpRow )
309 footprintBasePath = fpRow->GetFullURI( true );
310 }
311 catch( ... )
312 {
313 // Do nothing if the libraryName is not found in lib table
314 }
315 }
316
317 // Exit early if we don't want to include footprint models
318 if( m_params.m_BoardOnly || !m_params.m_ExportComponents )
319 {
320 return hasdata;
321 }
322
323 bool componentFilter = !m_params.m_ComponentFilter.IsEmpty();
324 std::vector<wxString> componentFilterPatterns;
325
326 if( componentFilter )
327 {
328 wxStringTokenizer tokenizer( m_params.m_ComponentFilter, ", \t\r\n", wxTOKEN_STRTOK );
329
330 while( tokenizer.HasMoreTokens() )
331 componentFilterPatterns.push_back( tokenizer.GetNextToken() );
332
333 bool found = false;
334
335 for( const wxString& pattern : componentFilterPatterns )
336 {
337 if( aFootprint->GetReference().Matches( pattern ) )
338 {
339 found = true;
340 break;
341 }
342 }
343
344 if( !found )
345 return hasdata;
346 }
347
348 VECTOR2D newpos( pcbIUScale.IUTomm( posX ), pcbIUScale.IUTomm( posY ) );
349
350 for( const FP_3DMODEL& fp_model : aFootprint->Models() )
351 {
352 if( !fp_model.m_Show || fp_model.m_Filename.empty() )
353 continue;
354
355 std::vector<wxString> searchedPaths;
356 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
357 embeddedFilesStack.push_back( aFootprint->GetEmbeddedFiles() );
358 embeddedFilesStack.push_back( m_board->GetEmbeddedFiles() );
359
360 wxString mname = m_resolver->ResolvePath( fp_model.m_Filename, footprintBasePath,
361 std::move( embeddedFilesStack ) );
362
363 if( mname.empty() || !wxFileName::FileExists( mname ) )
364 {
365 // the error path will return an empty name sometimes, at least report back the original filename
366 if( mname.empty() )
367 mname = fp_model.m_Filename;
368
369 m_reporter->Report( wxString::Format( _( "Could not add 3D model for %s.\n"
370 "File not found: %s\n" ),
371 aFootprint->GetReference(),
372 mname ),
374 continue;
375 }
376
377 std::string fname( mname.ToUTF8() );
378 std::string refName( aFootprint->GetReference().ToUTF8() );
379
380 try
381 {
382 bool bottomSide = aFootprint->GetLayer() == B_Cu;
383
384 // the rotation is stored in degrees but opencascade wants radians
385 VECTOR3D modelRot = fp_model.m_Rotation;
386 modelRot *= M_PI;
387 modelRot /= 180.0;
388
389 if( m_pcbModel->AddComponent( fname, refName, bottomSide, newpos,
390 aFootprint->GetOrientation().AsRadians(),
391 fp_model.m_Offset, modelRot,
392 fp_model.m_Scale, m_params.m_SubstModels ) )
393 {
394 hasdata = true;
395 }
396 }
397 catch( const Standard_Failure& e )
398 {
399 m_reporter->Report( wxString::Format( _( "Could not add 3D model for %s.\n"
400 "OpenCASCADE error: %s\n" ),
401 aFootprint->GetReference(),
402 e.GetMessageString() ),
404 }
405
406 }
407
408 return hasdata;
409}
410
411
413{
414 bool skipCopper = !m_params.m_ExportTracksVias
415 || ( !m_params.m_NetFilter.IsEmpty()
416 && !aTrack->GetNetname().Matches( m_params.m_NetFilter ) );
417
418 if( m_params.m_ExportSoldermask && aTrack->IsOnLayer( F_Mask ) )
419 {
420 aTrack->TransformShapeToPolygon( m_poly_shapes[F_Mask][wxEmptyString], F_Mask,
421 aTrack->GetSolderMaskExpansion(), aTrack->GetMaxError(),
422 ERROR_INSIDE );
423 }
424
425 if( m_params.m_ExportSoldermask && aTrack->IsOnLayer( B_Mask ) )
426 {
427 aTrack->TransformShapeToPolygon( m_poly_shapes[B_Mask][wxEmptyString], B_Mask,
428 aTrack->GetSolderMaskExpansion(), aTrack->GetMaxError(),
429 ERROR_INSIDE );
430 }
431
432 if( aTrack->Type() == PCB_VIA_T )
433 {
434 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
435
436 std::shared_ptr<SHAPE_SEGMENT> holeShape = via->GetEffectiveHoleShape();
437 SHAPE_POLY_SET holePoly;
438 holeShape->TransformToPolygon( holePoly, via->GetMaxError(), ERROR_INSIDE );
439
440 // This helps with fusing
441 holePoly.Deflate( m_platingThickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, via->GetMaxError() );
442
443 LSET layers( via->GetLayerSet() & m_layersToExport );
444
445 PCB_LAYER_ID top_layer, bot_layer;
446 via->LayerPair( &top_layer, &bot_layer );
447
448 if( !skipCopper )
449 {
450 for( PCB_LAYER_ID pcblayer : layers )
451 {
452 const std::shared_ptr<SHAPE>& shape = via->GetEffectiveShape( pcblayer );
453
454 SHAPE_POLY_SET poly;
455 shape->TransformToPolygon( poly, via->GetMaxError(), ERROR_INSIDE );
456 m_poly_shapes[pcblayer][via->GetNetname()].Append( poly );
457 m_poly_holes[pcblayer].Append( holePoly );
458 }
459
460 m_pcbModel->AddBarrel( *holeShape, top_layer, bot_layer, true, aOrigin, via->GetNetname() );
461 }
462
464 //if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
465 //{
466 // m_poly_holes[F_SilkS].Append( holePoly );
467 // m_poly_holes[B_SilkS].Append( holePoly );
468 //}
469
470 m_pcbModel->AddHole( *holeShape, m_platingThickness, top_layer, bot_layer, true, aOrigin,
471 !m_params.m_FillAllVias, m_params.m_CutViasInBody );
472
473 return true;
474 }
475
476 if( skipCopper )
477 return true;
478
479 PCB_LAYER_ID pcblayer = aTrack->GetLayer();
480
481 if( !m_layersToExport.Contains( pcblayer ) )
482 return false;
483
484 aTrack->TransformShapeToPolygon( m_poly_shapes[pcblayer][aTrack->GetNetname()], pcblayer, 0,
485 aTrack->GetMaxError(), ERROR_INSIDE );
486
487 return true;
488}
489
490
492{
493 for( ZONE* zone : m_board->Zones() )
494 {
495 LSET layers = zone->GetLayerSet();
496
497 if( ( layers & LSET::AllCuMask() ).count() && !m_params.m_NetFilter.IsEmpty()
498 && !zone->GetNetname().Matches( m_params.m_NetFilter ) )
499 {
500 continue;
501 }
502
503 for( PCB_LAYER_ID layer : layers )
504 {
505 SHAPE_POLY_SET fill_shape;
506 zone->TransformSolidAreasShapesToPolygon( layer, fill_shape );
507 fill_shape.Unfracture();
508
509 fill_shape.SimplifyOutlines( ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel );
510
511 m_poly_shapes[layer][zone->GetNetname()].Append( fill_shape );
512 }
513 }
514}
515
516
518{
519 PCB_LAYER_ID pcblayer = aItem->GetLayer();
520 int maxError = aItem->GetMaxError();
521
522 if( !m_layersToExport.Contains( pcblayer ) )
523 return false;
524
525 if( IsCopperLayer( pcblayer ) && !m_params.m_ExportTracksVias )
526 return false;
527
528 if( IsInnerCopperLayer( pcblayer ) && !m_params.m_ExportInnerCopper )
529 return false;
530
531 switch( aItem->Type() )
532 {
533 case PCB_SHAPE_T:
534 {
535 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
536
537 if( IsCopperLayer( pcblayer ) && !m_params.m_NetFilter.IsEmpty()
538 && !graphic->GetNetname().Matches( m_params.m_NetFilter ) )
539 {
540 return true;
541 }
542
543 LINE_STYLE lineStyle = graphic->GetLineStyle();
544
545 if( lineStyle == LINE_STYLE::SOLID )
546 {
547 graphic->TransformShapeToPolySet( m_poly_shapes[pcblayer][graphic->GetNetname()], pcblayer, 0,
548 maxError, ERROR_INSIDE );
549 }
550 else
551 {
552 std::vector<SHAPE*> shapes = graphic->MakeEffectiveShapes( true );
553 const PCB_PLOT_PARAMS& plotParams = m_board->GetPlotOptions();
554 KIGFX::PCB_RENDER_SETTINGS renderSettings;
555
556 renderSettings.SetDashLengthRatio( plotParams.GetDashedLineDashRatio() );
557 renderSettings.SetGapLengthRatio( plotParams.GetDashedLineGapRatio() );
558
559 for( SHAPE* shape : shapes )
560 {
561 STROKE_PARAMS::Stroke( shape, lineStyle, graphic->GetWidth(), &renderSettings,
562 [&]( const VECTOR2I& a, const VECTOR2I& b )
563 {
564 SHAPE_SEGMENT seg( a, b, graphic->GetWidth() );
565 seg.TransformToPolygon( m_poly_shapes[pcblayer][graphic->GetNetname()],
566 maxError, ERROR_INSIDE );
567 } );
568 }
569
570 for( SHAPE* shape : shapes )
571 delete shape;
572 }
573
574 if( graphic->IsHatchedFill() )
575 m_poly_shapes[pcblayer][graphic->GetNetname()].Append( graphic->GetHatching() );
576
577 if( m_params.m_ExportSoldermask && graphic->IsOnLayer( F_Mask ) )
578 {
579 graphic->TransformShapeToPolygon( m_poly_shapes[F_Mask][wxEmptyString], F_Mask,
580 graphic->GetSolderMaskExpansion(), maxError, ERROR_INSIDE );
581 }
582
583 if( m_params.m_ExportSoldermask && graphic->IsOnLayer( B_Mask ) )
584 {
585 graphic->TransformShapeToPolygon( m_poly_shapes[B_Mask][wxEmptyString], B_Mask,
586 graphic->GetSolderMaskExpansion(), maxError, ERROR_INSIDE );
587 }
588
589 break;
590 }
591
592 case PCB_TEXT_T:
593 {
594 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
595
596 text->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
597 break;
598 }
599
600 case PCB_BARCODE_T:
601 {
602 PCB_BARCODE* barcode = static_cast<PCB_BARCODE*>( aItem );
603
604 barcode->TransformShapeToPolySet( m_poly_shapes[pcblayer][wxEmptyString], pcblayer, 0, maxError,
605 ERROR_INSIDE );
606 break;
607 }
608
609 case PCB_TEXTBOX_T:
610 {
611 PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( aItem );
612
613 // border
614 if( textbox->IsBorderEnabled() )
615 {
616 textbox->PCB_SHAPE::TransformShapeToPolygon( m_poly_shapes[pcblayer][wxEmptyString], pcblayer, 0,
617 maxError, ERROR_INSIDE );
618 }
619
620 // text
621 textbox->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
622 break;
623 }
624
625 case PCB_TABLE_T:
626 {
627 PCB_TABLE* table = static_cast<PCB_TABLE*>( aItem );
628
629 for( PCB_TABLECELL* cell : table->GetCells() )
630 {
631 cell->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
632 }
633
634 table->DrawBorders(
635 [&]( const VECTOR2I& ptA, const VECTOR2I& ptB, const STROKE_PARAMS& stroke )
636 {
637 SHAPE_SEGMENT seg( ptA, ptB, stroke.GetWidth() );
638 seg.TransformToPolygon( m_poly_shapes[pcblayer][wxEmptyString], maxError, ERROR_INSIDE );
639 } );
640
641 break;
642 }
643
644 default: wxFAIL_MSG( "buildGraphic3DShape: unhandled item type" );
645 }
646
647 return true;
648}
649
650
652{
653 // Specialize the STEP_PCB_MODEL generator for specific output format
654 // it can have some minor actions for the generator
655 switch( m_params.m_Format )
656 {
658 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STEP );
659 break;
660
662 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STEPZ );
663 break;
664
666 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_BREP );
667 break;
668
670 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_XAO );
671 break;
672
674 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_GLTF );
675 break;
676
678 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PLY );
679 break;
680
682 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STL );
683 break;
684
686 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_U3D );
687 break;
688
690 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PDF );
691 break;
692
693 default:
694 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_UNKNOWN );
695 break;
696 }
697}
698
699
701{
702 if( m_pcbModel )
703 return true;
704
705 SHAPE_POLY_SET pcbOutlines; // stores the board main outlines
706
707 if( !m_board->GetBoardPolygonOutlines( pcbOutlines,
708 /* error handler */ nullptr,
709 /* allows use arcs in outlines */ true ) )
710 {
711 wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) );
712 }
713
714 SHAPE_POLY_SET pcbOutlinesNoArcs = pcbOutlines;
715 pcbOutlinesNoArcs.ClearArcs();
716
717 VECTOR2D origin;
718
719 // Determine the coordinate system reference:
720 // Precedence of reference point is Drill Origin > Grid Origin > User Offset
721 if( m_params.m_UseDrillOrigin )
722 origin = m_board->GetDesignSettings().GetAuxOrigin();
723 else if( m_params.m_UseGridOrigin )
724 origin = m_board->GetDesignSettings().GetGridOrigin();
725 else
726 origin = m_params.m_Origin;
727
728 m_pcbModel = std::make_unique<STEP_PCB_MODEL>( m_pcbBaseName, m_reporter );
729
731
732 m_pcbModel->SetCopperColor( m_copperColor.r, m_copperColor.g, m_copperColor.b );
733 m_pcbModel->SetPadColor( m_padColor.r, m_padColor.g, m_padColor.b );
734
735 m_pcbModel->SetStackup( m_board->GetStackupOrDefault() );
736 m_pcbModel->SetEnabledLayers( m_layersToExport );
737 m_pcbModel->SetFuseShapes( m_params.m_FuseShapes );
738 m_pcbModel->SetNetFilter( m_params.m_NetFilter );
739
740 // Note: m_params.m_BoardOutlinesChainingEpsilon is used only to build the board outlines,
741 // not to set OCC chaining epsilon (much smaller)
742 //
743 // Set the min distance between 2 points for OCC to see these 2 points as merged
744 // OCC_MAX_DISTANCE_TO_MERGE_POINTS is acceptable for OCC, otherwise there are issues
745 // to handle the shapes chaining on copper layers, because the Z dist is 0.035 mm and the
746 // min dist must be much smaller (we use 0.001 mm giving good results)
747 m_pcbModel->OCCSetMergeMaxDistance( OCC_MAX_DISTANCE_TO_MERGE_POINTS );
748
749 // For copper layers, only pads and tracks are added, because adding everything on copper
750 // generate unreasonable file sizes and take a unreasonable calculation time.
751 for( FOOTPRINT* fp : m_board->Footprints() )
752 buildFootprint3DShapes( fp, origin, &pcbOutlinesNoArcs );
753
754 for( PCB_TRACK* track : m_board->Tracks() )
755 buildTrack3DShape( track, origin );
756
757 for( BOARD_ITEM* item : m_board->Drawings() )
758 buildGraphic3DShape( item, origin );
759
760 if( m_params.m_ExportZones )
761 buildZones3DShape( origin );
762
763 for( PCB_LAYER_ID pcblayer : m_layersToExport.Seq() )
764 {
765 SHAPE_POLY_SET holes = m_poly_holes[pcblayer];
766 holes.Simplify();
767
768 if( pcblayer == F_Mask || pcblayer == B_Mask )
769 {
770 // Mask layer is negative
771 SHAPE_POLY_SET mask = pcbOutlinesNoArcs;
772
773 for( auto& [netname, poly] : m_poly_shapes[pcblayer] )
774 {
775 poly.Simplify();
776
777 poly.SimplifyOutlines( pcbIUScale.mmToIU( 0.003 ) );
778 poly.Simplify();
779
780 mask.BooleanSubtract( poly );
781 }
782
783 mask.BooleanSubtract( holes );
784
785 m_pcbModel->AddPolygonShapes( &mask, pcblayer, origin, wxEmptyString );
786 }
787 else
788 {
789 for( auto& [netname, poly] : m_poly_shapes[pcblayer] )
790 {
791 poly.Simplify();
792
793 poly.SimplifyOutlines( pcbIUScale.mmToIU( 0.003 ) );
794 poly.Simplify();
795
796 // Subtract holes
797 poly.BooleanSubtract( holes );
798
799 // Clip to board outline
800 poly.BooleanIntersection( pcbOutlinesNoArcs );
801
802 m_pcbModel->AddPolygonShapes( &poly, pcblayer, origin, netname );
803 }
804 }
805 }
806
807 m_reporter->Report( wxT( "Create PCB solid model.\n" ), RPT_SEVERITY_DEBUG );
808
809 m_reporter->Report( wxString::Format( wxT( "Board outline: found %d initial points.\n" ),
810 pcbOutlines.FullPointCount() ),
812
813 if( !m_pcbModel->CreatePCB( pcbOutlines, origin, m_params.m_ExportBoardBody ) )
814 {
815 m_reporter->Report( _( "Could not create PCB solid model.\n" ), RPT_SEVERITY_ERROR );
816 return false;
817 }
818
819 return true;
820}
821
822
824{
825 // Display the export time, for statistics
826 int64_t stats_startExportTime = GetRunningMicroSecs();
827
828 // setup opencascade message log
829 struct SCOPED_PRINTER
830 {
831 Handle( Message_Printer ) m_handle;
832
833 SCOPED_PRINTER( const Handle( Message_Printer ) & aHandle ) : m_handle( aHandle )
834 {
835 Message::DefaultMessenger()->AddPrinter( m_handle );
836 };
837
838 ~SCOPED_PRINTER() { Message::DefaultMessenger()->RemovePrinter( m_handle ); }
839 };
840
841 Message::DefaultMessenger()->RemovePrinters( STANDARD_TYPE( Message_PrinterOStream ) );
842 SCOPED_PRINTER occtPrinter( new KICAD_PRINTER( m_reporter ) );
843
844 m_reporter->Report( wxT( "Determining PCB data.\n" ), RPT_SEVERITY_DEBUG );
845
846 if( m_params.m_OutputFile.IsEmpty() )
847 {
848 wxFileName fn = m_board->GetFileName();
849 fn.SetName( fn.GetName() );
850 fn.SetExt( m_params.GetDefaultExportExtension() );
851
852 m_params.m_OutputFile = fn.GetFullName();
853 }
854
856
857 if( m_params.m_ExportInnerCopper )
859
860 if( m_params.m_ExportSilkscreen )
861 {
864 }
865
866 if( m_params.m_ExportSoldermask )
867 {
870 }
871
872 m_layersToExport &= m_board->GetEnabledLayers();
873
874 try
875 {
876 m_reporter->Report( wxString::Format( wxT( "Build %s data.\n" ), m_params.GetFormatName() ),
878
879 if( !buildBoard3DShapes() )
880 {
881 m_reporter->Report( _( "\n"
882 "** Error building STEP board model. Export aborted. **\n" ),
884 return false;
885 }
886
887 m_reporter->Report( wxString::Format( wxT( "Writing %s file.\n" ), m_params.GetFormatName() ),
889
890 bool success = true;
892 success = m_pcbModel->WriteSTEP( m_outputFile, m_params.m_OptimizeStep, false );
893 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STEPZ )
894 success = m_pcbModel->WriteSTEP( m_outputFile, m_params.m_OptimizeStep, true );
895 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::BREP )
896 success = m_pcbModel->WriteBREP( m_outputFile );
897 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::XAO )
898 success = m_pcbModel->WriteXAO( m_outputFile );
899 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::GLB )
900 success = m_pcbModel->WriteGLTF( m_outputFile );
901 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PLY )
902 success = m_pcbModel->WritePLY( m_outputFile );
903 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STL )
904 success = m_pcbModel->WriteSTL( m_outputFile );
905 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::U3D )
906 success = m_pcbModel->WriteU3D( m_outputFile );
907 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PDF )
908 success = m_pcbModel->WritePDF( m_outputFile );
909
910 if( !success )
911 {
912 m_reporter->Report( wxString::Format( _( "\n"
913 "** Error writing %s file. **\n" ),
914 m_params.GetFormatName() ),
916 return false;
917 }
918 else
919 {
920 m_reporter->Report( wxString::Format( wxT( "%s file '%s' created.\n" ),
921 m_params.GetFormatName(),
922 m_outputFile ),
924 }
925 }
926 catch( const Standard_Failure& e )
927 {
928 m_reporter->Report( e.GetMessageString(), RPT_SEVERITY_ERROR );
929 m_reporter->Report( wxString::Format( _( "\n"
930 "** Error exporting %s file. Export aborted. **\n" ),
931 m_params.GetFormatName() ),
933 return false;
934 }
935 #ifndef DEBUG
936 catch( ... )
937 {
938 m_reporter->Report( wxString::Format( _( "\n"
939 "** Error exporting %s file. Export aborted. **\n" ),
940 m_params.GetFormatName() ),
942 return false;
943 }
944 #endif
945
946 // Display calculation time in seconds
947 double calculation_time = (double)( GetRunningMicroSecs() - stats_startExportTime) / 1e6;
948 m_reporter->Report( wxString::Format( _( "\n"
949 "Export time %.3f s\n" ),
950 calculation_time ),
952
953 return !m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR );
954}
@ 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:378
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
Handle(KICAD3D_INFO) KICAD3D_INFO
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:677
bool IsInnerCopperLayer(int aLayerId)
Test whether a layer is an inner (In1_Cu to In30_Cu) copper layer.
Definition layer_ids.h:699
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:946
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.