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>
31#include <footprint.h>
32#include <pcb_textbox.h>
33#include <pcb_table.h>
34#include <pcb_tablecell.h>
35#include <pcb_track.h>
36#include <pcb_shape.h>
37#include <pcb_barcode.h>
38#include <pcb_painter.h>
39#include <pad.h>
40#include <zone.h>
42#include "step_pcb_model.h"
43
44#include <pgm_base.h>
45#include <reporter.h>
46#include <base_units.h>
47#include <filename_resolver.h>
48#include <trace_helpers.h>
49#include <project_pcb.h>
51
52#include <Message.hxx> // OpenCascade messenger
53#include <Message_PrinterOStream.hxx> // OpenCascade output messenger
54#include <Standard_Failure.hxx> // In open cascade
55
56#include <Standard_Version.hxx>
57
58#include <wx/crt.h>
59#include <wx/log.h>
60#include <core/profile.h> // To use GetRunningMicroSecs or another profiling utility
61
62#define OCC_VERSION_MIN 0x070500
63
64#if OCC_VERSION_HEX < OCC_VERSION_MIN
65#include <Message_Messenger.hxx>
66#endif
67
68
69class KICAD_PRINTER : public Message_Printer
70{
71public:
72 KICAD_PRINTER( REPORTER* aReporter ) :
73 m_reporter( aReporter )
74 {}
75
76protected:
77#if OCC_VERSION_HEX < OCC_VERSION_MIN
78 virtual void Send( const TCollection_ExtendedString& theString,
79 const Message_Gravity theGravity,
80 const Standard_Boolean theToPutEol ) const override
81 {
82 Send( TCollection_AsciiString( theString ), theGravity, theToPutEol );
83 }
84
85 virtual void Send( const TCollection_AsciiString& theString,
86 const Message_Gravity theGravity,
87 const Standard_Boolean theToPutEol ) const override
88#else
89 virtual void send( const TCollection_AsciiString& theString,
90 const Message_Gravity theGravity ) const override
91#endif
92 {
93 wxString msg( theString.ToCString() );
94
95#if OCC_VERSION_HEX < OCC_VERSION_MIN
96 if( theToPutEol )
97 msg += wxT( "\n" );
98#else
99 msg += wxT( "\n" );
100#endif
101
102 m_reporter->Report( msg, getSeverity( theGravity ) );
103 }
104
105private:
106 SEVERITY getSeverity( const Message_Gravity theGravity ) const
107 {
108 switch( theGravity )
109 {
110 case Message_Trace: return RPT_SEVERITY_DEBUG;
111 case Message_Info: return RPT_SEVERITY_DEBUG;
112 case Message_Warning: return RPT_SEVERITY_WARNING;
113 case Message_Alarm: return RPT_SEVERITY_WARNING;
114 case Message_Fail: return RPT_SEVERITY_ERROR;
115
116 // There are no other values, but gcc doesn't appear to be able to work that out.
117 default: return RPT_SEVERITY_UNDEFINED;
118 }
119 }
120
121private:
123};
124
125
127 REPORTER* aReporter ) :
128 m_params( aParams ),
129 m_reporter( aReporter ),
130 m_board( aBoard ),
131 m_pcbModel( nullptr )
132{
133 m_copperColor = COLOR4D( 0.7, 0.61, 0.0, 1.0 );
134
135 if( m_params.m_ExportComponents )
136 m_padColor = COLOR4D( 0.50, 0.50, 0.50, 1.0 );
137 else
139
140 // TODO: make configurable
141 m_platingThickness = pcbIUScale.mmToIU( 0.025 );
142
143 // Init m_pcbBaseName to the board short filename (no path, no ext)
144 // m_pcbName is used later to identify items in step file
145 wxFileName fn( aBoard->GetFileName() );
146 m_pcbBaseName = fn.GetName();
147
148 m_resolver = std::make_unique<FILENAME_RESOLVER>();
149 m_resolver->Set3DConfigDir( wxT( "" ) );
150 // needed to add the project to the search stack
151 m_resolver->SetProject( aBoard->GetProject() );
152 m_resolver->SetProgramBase( &Pgm() );
153}
154
155
159
160
162 PCB_LAYER_ID aEndLayer ) const
163{
164 if( !IsCopperLayer( aLayer ) )
165 return false;
166
167 // Quick check for exact match
168 if( aLayer == aStartLayer || aLayer == aEndLayer )
169 return true;
170
171 // Convert layers to a sortable index for comparison
172 // F_Cu = -1, In1_Cu through In30_Cu = 0-29, B_Cu = MAX_CU_LAYERS (32)
173 auto layerToIndex = []( PCB_LAYER_ID layer ) -> int
174 {
175 if( layer == F_Cu )
176 return -1;
177
178 if( layer == B_Cu )
179 return MAX_CU_LAYERS;
180
181 if( IsInnerCopperLayer( layer ) )
182 return layer - In1_Cu;
183
184 return -2; // Invalid copper layer
185 };
186
187 int startIdx = layerToIndex( aStartLayer );
188 int endIdx = layerToIndex( aEndLayer );
189 int layerIdx = layerToIndex( aLayer );
190
191 if( layerIdx == -2 )
192 return false;
193
194 int minIdx = std::min( startIdx, endIdx );
195 int maxIdx = std::max( startIdx, endIdx );
196
197 return ( layerIdx >= minIdx && layerIdx <= maxIdx );
198}
199
200
202 SHAPE_POLY_SET* aClipPolygon )
203{
204 bool hasdata = false;
205 std::vector<PAD*> padsMatchingNetFilter;
206
207 // Dump the pad holes into the PCB
208 for( PAD* pad : aFootprint->Pads() )
209 {
210 bool castellated = pad->GetProperty() == PAD_PROP::CASTELLATED;
211 std::shared_ptr<SHAPE_SEGMENT> holeShape = pad->GetEffectiveHoleShape();
212
213 SHAPE_POLY_SET holePoly;
214 holeShape->TransformToPolygon( holePoly, pad->GetMaxError(), ERROR_INSIDE );
215
216 // This helps with fusing
217 holePoly.Deflate( m_platingThickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, pad->GetMaxError() );
218
219 for( PCB_LAYER_ID pcblayer : pad->GetLayerSet() )
220 {
221 if( pad->IsOnLayer( pcblayer ) )
222 m_poly_holes[pcblayer].Append( holePoly );
223 }
224
225 if( pad->HasHole() )
226 {
227 int platingThickness = pad->GetAttribute() == PAD_ATTRIB::PTH ? m_platingThickness : 0;
228
229 if( m_pcbModel->AddHole( *holeShape, platingThickness, F_Cu, B_Cu, false, aOrigin, true, true ) )
230 hasdata = true;
231
233 //if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
234 //{
235 // m_poly_holes[F_SilkS].Append( holePoly );
236 // m_poly_holes[B_SilkS].Append( holePoly );
237 //}
238
239 // Handle backdrills - secondary and tertiary drills defined in the padstack
240 const PADSTACK& padstack = pad->Padstack();
241 const PADSTACK::DRILL_PROPS& secondaryDrill = padstack.SecondaryDrill();
242 const PADSTACK::DRILL_PROPS& tertiaryDrill = padstack.TertiaryDrill();
243
244 // Process secondary drill (typically bottom backdrill)
245 if( secondaryDrill.size.x > 0 )
246 {
247 SHAPE_SEGMENT backdrillShape( pad->GetPosition(), pad->GetPosition(),
248 secondaryDrill.size.x );
249 m_pcbModel->AddBackdrill( backdrillShape, secondaryDrill.start,
250 secondaryDrill.end, aOrigin );
251
252 // Add backdrill holes to affected copper layers for 2D polygon subtraction
253 SHAPE_POLY_SET backdrillPoly;
254 backdrillShape.TransformToPolygon( backdrillPoly, pad->GetMaxError(), ERROR_INSIDE );
255
256 for( PCB_LAYER_ID layer : pad->GetLayerSet() )
257 {
258 if( isLayerInBackdrillSpan( layer, secondaryDrill.start, secondaryDrill.end ) )
259 m_poly_holes[layer].Append( backdrillPoly );
260 }
261
262 // Add knockouts for silkscreen and soldermask on the backdrill side
263 if( isLayerInBackdrillSpan( F_Cu, secondaryDrill.start, secondaryDrill.end ) )
264 {
265 m_poly_holes[F_SilkS].Append( backdrillPoly );
266 m_poly_holes[F_Mask].Append( backdrillPoly );
267 }
268 if( isLayerInBackdrillSpan( B_Cu, secondaryDrill.start, secondaryDrill.end ) )
269 {
270 m_poly_holes[B_SilkS].Append( backdrillPoly );
271 m_poly_holes[B_Mask].Append( backdrillPoly );
272 }
273 }
274
275 // Process tertiary drill (typically top backdrill)
276 if( tertiaryDrill.size.x > 0 )
277 {
278 SHAPE_SEGMENT backdrillShape( pad->GetPosition(), pad->GetPosition(),
279 tertiaryDrill.size.x );
280 m_pcbModel->AddBackdrill( backdrillShape, tertiaryDrill.start,
281 tertiaryDrill.end, aOrigin );
282
283 // Add backdrill holes to affected copper layers for 2D polygon subtraction
284 SHAPE_POLY_SET backdrillPoly;
285 backdrillShape.TransformToPolygon( backdrillPoly, pad->GetMaxError(), ERROR_INSIDE );
286
287 for( PCB_LAYER_ID layer : pad->GetLayerSet() )
288 {
289 if( isLayerInBackdrillSpan( layer, tertiaryDrill.start, tertiaryDrill.end ) )
290 m_poly_holes[layer].Append( backdrillPoly );
291 }
292
293 // Add knockouts for silkscreen and soldermask on the backdrill side
294 if( isLayerInBackdrillSpan( F_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
295 {
296 m_poly_holes[F_SilkS].Append( backdrillPoly );
297 m_poly_holes[F_Mask].Append( backdrillPoly );
298 }
299 if( isLayerInBackdrillSpan( B_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
300 {
301 m_poly_holes[B_SilkS].Append( backdrillPoly );
302 m_poly_holes[B_Mask].Append( backdrillPoly );
303 }
304 }
305
306 // Process post-machining (counterbore/countersink) on front and back
307 const PADSTACK::POST_MACHINING_PROPS& frontPM = padstack.FrontPostMachining();
308 const PADSTACK::POST_MACHINING_PROPS& backPM = padstack.BackPostMachining();
309
310 wxLogTrace( traceKiCad2Step, wxT( "PAD post-machining check: frontPM.mode.has_value=%d frontPM.size=%d frontPM.depth=%d frontPM.angle=%d" ),
311 frontPM.mode.has_value() ? 1 : 0, frontPM.size, frontPM.depth, frontPM.angle );
312 wxLogTrace( traceKiCad2Step, wxT( "PAD post-machining check: backPM.mode.has_value=%d backPM.size=%d backPM.depth=%d backPM.angle=%d" ),
313 backPM.mode.has_value() ? 1 : 0, backPM.size, backPM.depth, backPM.angle );
314
315 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
316 bool frontPMValid = frontPM.mode.has_value() && frontPM.size > 0 &&
317 ( ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && frontPM.depth > 0 ) ||
318 ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && frontPM.angle > 0 ) );
319
320 if( frontPMValid )
321 {
322 wxLogTrace( traceKiCad2Step, wxT( "PAD front post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
323 static_cast<int>( *frontPM.mode ) );
324
325 int pmAngle = ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? frontPM.angle : 0;
326
328 {
329 m_pcbModel->AddCounterbore( pad->GetPosition(), frontPM.size,
330 frontPM.depth, true, aOrigin );
331 }
333 {
334 m_pcbModel->AddCountersink( pad->GetPosition(), frontPM.size,
335 frontPM.depth, frontPM.angle, true, aOrigin );
336 }
337
338 // Add knockouts to all copper layers the feature crosses
339 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( frontPM.size, frontPM.depth,
340 pmAngle, true );
341 for( const auto& [layer, diameter] : knockouts )
342 {
343 SHAPE_POLY_SET pmPoly;
344 TransformCircleToPolygon( pmPoly, pad->GetPosition(), diameter / 2,
345 pad->GetMaxError(), ERROR_INSIDE );
346 m_poly_holes[layer].Append( pmPoly );
347 }
348
349 // Add knockout for silkscreen and soldermask on front side (full diameter)
350 SHAPE_POLY_SET pmPoly;
351 TransformCircleToPolygon( pmPoly, pad->GetPosition(), frontPM.size / 2,
352 pad->GetMaxError(), ERROR_INSIDE );
353 m_poly_holes[F_SilkS].Append( pmPoly );
354 m_poly_holes[F_Mask].Append( pmPoly );
355 }
356
357 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
358 bool backPMValid = backPM.mode.has_value() && backPM.size > 0 &&
359 ( ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && backPM.depth > 0 ) ||
360 ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && backPM.angle > 0 ) );
361
362 if( backPMValid )
363 {
364 wxLogTrace( traceKiCad2Step, wxT( "PAD back post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
365 static_cast<int>( *backPM.mode ) );
366
367 int pmAngle = ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? backPM.angle : 0;
368
370 {
371 m_pcbModel->AddCounterbore( pad->GetPosition(), backPM.size,
372 backPM.depth, false, aOrigin );
373 }
375 {
376 m_pcbModel->AddCountersink( pad->GetPosition(), backPM.size,
377 backPM.depth, backPM.angle, false, aOrigin );
378 }
379
380 // Add knockouts to all copper layers the feature crosses
381 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( backPM.size, backPM.depth,
382 pmAngle, false );
383 for( const auto& [layer, diameter] : knockouts )
384 {
385 SHAPE_POLY_SET pmPoly;
386 TransformCircleToPolygon( pmPoly, pad->GetPosition(), diameter / 2,
387 pad->GetMaxError(), ERROR_INSIDE );
388 m_poly_holes[layer].Append( pmPoly );
389 }
390
391 // Add knockout for silkscreen and soldermask on back side (full diameter)
392 SHAPE_POLY_SET pmPoly;
393 TransformCircleToPolygon( pmPoly, pad->GetPosition(), backPM.size / 2,
394 pad->GetMaxError(), ERROR_INSIDE );
395 m_poly_holes[B_SilkS].Append( pmPoly );
396 m_poly_holes[B_Mask].Append( pmPoly );
397 }
398 }
399
400 if( !m_params.m_NetFilter.IsEmpty() && !pad->GetNetname().Matches( m_params.m_NetFilter ) )
401 continue;
402
403 if( m_params.m_ExportPads )
404 {
405 if( m_pcbModel->AddPadShape( pad, aOrigin, false, castellated ? aClipPolygon : nullptr) )
406 hasdata = true;
407
408 if( m_params.m_ExportSoldermask )
409 {
410 for( PCB_LAYER_ID pcblayer : pad->GetLayerSet() )
411 {
412 if( pcblayer != F_Mask && pcblayer != B_Mask )
413 continue;
414
415 SHAPE_POLY_SET poly;
416 PCB_LAYER_ID cuLayer = ( pcblayer == F_Mask ) ? F_Cu : B_Cu;
417 pad->TransformShapeToPolygon( poly, cuLayer, pad->GetSolderMaskExpansion( cuLayer ),
418 pad->GetMaxError(), ERROR_INSIDE );
419
420 m_poly_shapes[pcblayer][wxEmptyString].Append( poly );
421 }
422 }
423 }
424
425 padsMatchingNetFilter.push_back( pad );
426 }
427
428 // Build 3D shapes of the footprint graphic items:
429 for( PCB_LAYER_ID pcblayer : m_layersToExport )
430 {
431 if( IsCopperLayer( pcblayer ) && !m_params.m_ExportTracksVias )
432 continue;
433
434 SHAPE_POLY_SET buffer;
435
436 aFootprint->TransformFPShapesToPolySet( buffer, pcblayer, 0, aFootprint->GetMaxError(), ERROR_INSIDE,
437 true, /* include text */
438 true, /* include shapes */
439 false /* include private items */ );
440
441 if( !IsCopperLayer( pcblayer ) )
442 {
443 m_poly_shapes[pcblayer][wxEmptyString].Append( buffer );
444 }
445 else
446 {
447 std::map<const SHAPE_POLY_SET::POLYGON*, PAD*> polyPadMap;
448
449 // Only add polygons colliding with any matching pads
450 for( const SHAPE_POLY_SET::POLYGON& poly : buffer.CPolygons() )
451 {
452 for( PAD* pad : padsMatchingNetFilter )
453 {
454 if( !pad->IsOnLayer( pcblayer ) )
455 continue;
456
457 std::shared_ptr<SHAPE_POLY_SET> padPoly = pad->GetEffectivePolygon( pcblayer );
458 SHAPE_POLY_SET gfxPoly( poly );
459
460 if( padPoly->Collide( &gfxPoly ) )
461 {
462 polyPadMap[&poly] = pad;
463 m_poly_shapes[pcblayer][pad->GetNetname()].Append( gfxPoly );
464 break;
465 }
466 }
467 }
468
469 if( m_params.m_NetFilter.empty() )
470 {
471 // Add polygons with no net
472 for( const SHAPE_POLY_SET::POLYGON& poly : buffer.CPolygons() )
473 {
474 auto it = polyPadMap.find( &poly );
475
476 if( it == polyPadMap.end() )
477 m_poly_shapes[pcblayer][wxEmptyString].Append( poly );
478 }
479 }
480 }
481 }
482
483 if( ( !(aFootprint->GetAttributes() & (FP_THROUGH_HOLE|FP_SMD)) ) && !m_params.m_IncludeUnspecified )
484 {
485 return hasdata;
486 }
487
488 if( aFootprint->GetDNPForVariant( m_board ? m_board->GetCurrentVariant() : wxString() )
489 && !m_params.m_IncludeDNP )
490 {
491 return hasdata;
492 }
493
494 // Prefetch the library for this footprint
495 // In case we need to resolve relative footprint paths
496 wxString libraryName = aFootprint->GetFPID().GetLibNickname();
497 wxString footprintBasePath = wxEmptyString;
498
499 double posX = aFootprint->GetPosition().x - aOrigin.x;
500 double posY = (aFootprint->GetPosition().y) - aOrigin.y;
501
502 if( m_board->GetProject() )
503 {
504 std::optional<LIBRARY_TABLE_ROW*> fpRow =
505 PROJECT_PCB::FootprintLibAdapter( m_board->GetProject() )->GetRow( libraryName );
506 if( fpRow )
507 footprintBasePath = LIBRARY_MANAGER::GetFullURI( *fpRow, true );
508 }
509
510 // Exit early if we don't want to include footprint models
511 if( m_params.m_BoardOnly || !m_params.m_ExportComponents )
512 {
513 return hasdata;
514 }
515
516 bool componentFilter = !m_params.m_ComponentFilter.IsEmpty();
517 std::vector<wxString> componentFilterPatterns;
518
519 if( componentFilter )
520 {
521 wxStringTokenizer tokenizer( m_params.m_ComponentFilter, ", \t\r\n", wxTOKEN_STRTOK );
522
523 while( tokenizer.HasMoreTokens() )
524 componentFilterPatterns.push_back( tokenizer.GetNextToken() );
525
526 bool found = false;
527
528 for( const wxString& pattern : componentFilterPatterns )
529 {
530 if( aFootprint->GetReference().Matches( pattern ) )
531 {
532 found = true;
533 break;
534 }
535 }
536
537 if( !found )
538 return hasdata;
539 }
540
541 VECTOR2D newpos( pcbIUScale.IUTomm( posX ), pcbIUScale.IUTomm( posY ) );
542
543 for( const FP_3DMODEL& fp_model : aFootprint->Models() )
544 {
545 if( !fp_model.m_Show || fp_model.m_Filename.empty() )
546 continue;
547
548 std::vector<wxString> searchedPaths;
549 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
550 embeddedFilesStack.push_back( aFootprint->GetEmbeddedFiles() );
551 embeddedFilesStack.push_back( m_board->GetEmbeddedFiles() );
552
553 wxString mname = m_resolver->ResolvePath( fp_model.m_Filename, footprintBasePath,
554 std::move( embeddedFilesStack ) );
555
556 if( mname.empty() || !wxFileName::FileExists( mname ) )
557 {
558 // the error path will return an empty name sometimes, at least report back the original filename
559 if( mname.empty() )
560 mname = fp_model.m_Filename;
561
562 m_reporter->Report( wxString::Format( _( "Could not add 3D model for %s.\n"
563 "File not found: %s\n" ),
564 aFootprint->GetReference(),
565 mname ),
567 continue;
568 }
569
570 std::string fname( mname.ToUTF8() );
571 std::string refName( aFootprint->GetReference().ToUTF8() );
572
573 try
574 {
575 bool bottomSide = aFootprint->GetLayer() == B_Cu;
576
577 // the rotation is stored in degrees but opencascade wants radians
578 VECTOR3D modelRot = fp_model.m_Rotation;
579 modelRot *= M_PI;
580 modelRot /= 180.0;
581
582 if( m_pcbModel->AddComponent( fname, refName, bottomSide, newpos,
583 aFootprint->GetOrientation().AsRadians(),
584 fp_model.m_Offset, modelRot,
585 fp_model.m_Scale, m_params.m_SubstModels ) )
586 {
587 hasdata = true;
588 }
589 }
590 catch( const Standard_Failure& e )
591 {
592 m_reporter->Report( wxString::Format( _( "Could not add 3D model for %s.\n"
593 "OpenCASCADE error: %s\n" ),
594 aFootprint->GetReference(),
595 e.GetMessageString() ),
597 }
598
599 }
600
601 return hasdata;
602}
603
604
606{
607 bool skipCopper = !m_params.m_ExportTracksVias
608 || ( !m_params.m_NetFilter.IsEmpty()
609 && !aTrack->GetNetname().Matches( m_params.m_NetFilter ) );
610
611 if( m_params.m_ExportSoldermask && aTrack->IsOnLayer( F_Mask ) )
612 {
613 aTrack->TransformShapeToPolygon( m_poly_shapes[F_Mask][wxEmptyString], F_Mask,
614 aTrack->GetSolderMaskExpansion(), aTrack->GetMaxError(),
615 ERROR_INSIDE );
616 }
617
618 if( m_params.m_ExportSoldermask && aTrack->IsOnLayer( B_Mask ) )
619 {
620 aTrack->TransformShapeToPolygon( m_poly_shapes[B_Mask][wxEmptyString], B_Mask,
621 aTrack->GetSolderMaskExpansion(), aTrack->GetMaxError(),
622 ERROR_INSIDE );
623 }
624
625 if( aTrack->Type() == PCB_VIA_T )
626 {
627 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
628
629 std::shared_ptr<SHAPE_SEGMENT> holeShape = via->GetEffectiveHoleShape();
630 SHAPE_POLY_SET holePoly;
631 holeShape->TransformToPolygon( holePoly, via->GetMaxError(), ERROR_INSIDE );
632
633 // This helps with fusing
634 holePoly.Deflate( m_platingThickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, via->GetMaxError() );
635
636 LSET layers( via->GetLayerSet() & m_layersToExport );
637
638 PCB_LAYER_ID top_layer, bot_layer;
639 via->LayerPair( &top_layer, &bot_layer );
640
641 if( !skipCopper )
642 {
643 for( PCB_LAYER_ID pcblayer : layers )
644 {
645 const std::shared_ptr<SHAPE>& shape = via->GetEffectiveShape( pcblayer );
646
647 SHAPE_POLY_SET poly;
648 shape->TransformToPolygon( poly, via->GetMaxError(), ERROR_INSIDE );
649 m_poly_shapes[pcblayer][via->GetNetname()].Append( poly );
650 m_poly_holes[pcblayer].Append( holePoly );
651 }
652
653 m_pcbModel->AddBarrel( *holeShape, top_layer, bot_layer, true, aOrigin, via->GetNetname() );
654 }
655
657 //if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
658 //{
659 // m_poly_holes[F_SilkS].Append( holePoly );
660 // m_poly_holes[B_SilkS].Append( holePoly );
661 //}
662
663 m_pcbModel->AddHole( *holeShape, m_platingThickness, top_layer, bot_layer, true, aOrigin,
664 !m_params.m_FillAllVias, m_params.m_CutViasInBody );
665
666 // Handle via backdrills - secondary and tertiary drills defined in the padstack
667 const PADSTACK& padstack = via->Padstack();
668 const PADSTACK::DRILL_PROPS& secondaryDrill = padstack.SecondaryDrill();
669 const PADSTACK::DRILL_PROPS& tertiaryDrill = padstack.TertiaryDrill();
670
671 // Process secondary drill (typically bottom backdrill)
672 if( secondaryDrill.size.x > 0 )
673 {
674 SHAPE_SEGMENT backdrillShape( via->GetPosition(), via->GetPosition(),
675 secondaryDrill.size.x );
676 m_pcbModel->AddBackdrill( backdrillShape, secondaryDrill.start,
677 secondaryDrill.end, aOrigin );
678
679 // Add backdrill holes to affected copper layers for 2D polygon subtraction
680 SHAPE_POLY_SET backdrillPoly;
681 backdrillShape.TransformToPolygon( backdrillPoly, via->GetMaxError(), ERROR_INSIDE );
682
683 for( PCB_LAYER_ID layer : via->GetLayerSet() )
684 {
685 if( isLayerInBackdrillSpan( layer, secondaryDrill.start, secondaryDrill.end ) )
686 m_poly_holes[layer].Append( backdrillPoly );
687 }
688
689 // Add knockouts for silkscreen and soldermask on the backdrill side
690 if( isLayerInBackdrillSpan( F_Cu, secondaryDrill.start, secondaryDrill.end ) )
691 {
692 m_poly_holes[F_SilkS].Append( backdrillPoly );
693 m_poly_holes[F_Mask].Append( backdrillPoly );
694 }
695 if( isLayerInBackdrillSpan( B_Cu, secondaryDrill.start, secondaryDrill.end ) )
696 {
697 m_poly_holes[B_SilkS].Append( backdrillPoly );
698 m_poly_holes[B_Mask].Append( backdrillPoly );
699 }
700 }
701
702 // Process tertiary drill (typically top backdrill)
703 if( tertiaryDrill.size.x > 0 )
704 {
705 SHAPE_SEGMENT backdrillShape( via->GetPosition(), via->GetPosition(),
706 tertiaryDrill.size.x );
707 m_pcbModel->AddBackdrill( backdrillShape, tertiaryDrill.start,
708 tertiaryDrill.end, aOrigin );
709
710 // Add backdrill holes to affected copper layers for 2D polygon subtraction
711 SHAPE_POLY_SET backdrillPoly;
712 backdrillShape.TransformToPolygon( backdrillPoly, via->GetMaxError(), ERROR_INSIDE );
713
714 for( PCB_LAYER_ID layer : via->GetLayerSet() )
715 {
716 if( isLayerInBackdrillSpan( layer, tertiaryDrill.start, tertiaryDrill.end ) )
717 m_poly_holes[layer].Append( backdrillPoly );
718 }
719
720 // Add knockouts for silkscreen and soldermask on the backdrill side
721 if( isLayerInBackdrillSpan( F_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
722 {
723 m_poly_holes[F_SilkS].Append( backdrillPoly );
724 m_poly_holes[F_Mask].Append( backdrillPoly );
725 }
726 if( isLayerInBackdrillSpan( B_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
727 {
728 m_poly_holes[B_SilkS].Append( backdrillPoly );
729 m_poly_holes[B_Mask].Append( backdrillPoly );
730 }
731 }
732
733 // Process post-machining (counterbore/countersink) on front and back
734 const PADSTACK::POST_MACHINING_PROPS& frontPM = padstack.FrontPostMachining();
735 const PADSTACK::POST_MACHINING_PROPS& backPM = padstack.BackPostMachining();
736
737 wxLogTrace( traceKiCad2Step, wxT( "VIA post-machining check: frontPM.mode.has_value=%d frontPM.size=%d frontPM.depth=%d frontPM.angle=%d" ),
738 frontPM.mode.has_value() ? 1 : 0, frontPM.size, frontPM.depth, frontPM.angle );
739 wxLogTrace( traceKiCad2Step, wxT( "VIA post-machining check: backPM.mode.has_value=%d backPM.size=%d backPM.depth=%d backPM.angle=%d" ),
740 backPM.mode.has_value() ? 1 : 0, backPM.size, backPM.depth, backPM.angle );
741
742 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
743 bool frontPMValid = frontPM.mode.has_value() && frontPM.size > 0 &&
744 ( ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && frontPM.depth > 0 ) ||
745 ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && frontPM.angle > 0 ) );
746
747 if( frontPMValid )
748 {
749 wxLogTrace( traceKiCad2Step, wxT( "VIA front post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
750 static_cast<int>( *frontPM.mode ) );
751
752 int pmAngle = ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? frontPM.angle : 0;
753
755 {
756 m_pcbModel->AddCounterbore( via->GetPosition(), frontPM.size,
757 frontPM.depth, true, aOrigin );
758 }
760 {
761 m_pcbModel->AddCountersink( via->GetPosition(), frontPM.size,
762 frontPM.depth, frontPM.angle, true, aOrigin );
763 }
764
765 // Add knockouts to all copper layers the feature crosses
766 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( frontPM.size, frontPM.depth,
767 pmAngle, true );
768 for( const auto& [layer, diameter] : knockouts )
769 {
770 SHAPE_POLY_SET pmPoly;
771 TransformCircleToPolygon( pmPoly, via->GetPosition(), diameter / 2,
772 via->GetMaxError(), ERROR_INSIDE );
773 m_poly_holes[layer].Append( pmPoly );
774 }
775
776 // Add knockout for silkscreen and soldermask on front side (full diameter)
777 SHAPE_POLY_SET pmPoly;
778 TransformCircleToPolygon( pmPoly, via->GetPosition(), frontPM.size / 2,
779 via->GetMaxError(), ERROR_INSIDE );
780 m_poly_holes[F_SilkS].Append( pmPoly );
781 m_poly_holes[F_Mask].Append( pmPoly );
782 }
783
784 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
785 bool backPMValid = backPM.mode.has_value() && backPM.size > 0 &&
786 ( ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && backPM.depth > 0 ) ||
787 ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && backPM.angle > 0 ) );
788
789 if( backPMValid )
790 {
791 wxLogTrace( traceKiCad2Step, wxT( "VIA back post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
792 static_cast<int>( *backPM.mode ) );
793
794 int pmAngle = ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? backPM.angle : 0;
795
797 {
798 m_pcbModel->AddCounterbore( via->GetPosition(), backPM.size,
799 backPM.depth, false, aOrigin );
800 }
802 {
803 m_pcbModel->AddCountersink( via->GetPosition(), backPM.size,
804 backPM.depth, backPM.angle, false, aOrigin );
805 }
806
807 // Add knockouts to all copper layers the feature crosses
808 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( backPM.size, backPM.depth,
809 pmAngle, false );
810 for( const auto& [layer, diameter] : knockouts )
811 {
812 SHAPE_POLY_SET pmPoly;
813 TransformCircleToPolygon( pmPoly, via->GetPosition(), diameter / 2,
814 via->GetMaxError(), ERROR_INSIDE );
815 m_poly_holes[layer].Append( pmPoly );
816 }
817
818 // Add knockout for silkscreen and soldermask on back side (full diameter)
819 SHAPE_POLY_SET pmPoly;
820 TransformCircleToPolygon( pmPoly, via->GetPosition(), backPM.size / 2,
821 via->GetMaxError(), ERROR_INSIDE );
822 m_poly_holes[B_SilkS].Append( pmPoly );
823 m_poly_holes[B_Mask].Append( pmPoly );
824 }
825
826 return true;
827 }
828
829 if( skipCopper )
830 return true;
831
832 PCB_LAYER_ID pcblayer = aTrack->GetLayer();
833
834 if( !m_layersToExport.Contains( pcblayer ) )
835 return false;
836
837 aTrack->TransformShapeToPolygon( m_poly_shapes[pcblayer][aTrack->GetNetname()], pcblayer, 0,
838 aTrack->GetMaxError(), ERROR_INSIDE );
839
840 return true;
841}
842
843
845{
846 for( ZONE* zone : m_board->Zones() )
847 {
848 LSET layers = zone->GetLayerSet();
849
850 if( ( layers & LSET::AllCuMask() ).count() && !m_params.m_NetFilter.IsEmpty()
851 && !zone->GetNetname().Matches( m_params.m_NetFilter ) )
852 {
853 continue;
854 }
855
856 for( PCB_LAYER_ID layer : layers )
857 {
858 SHAPE_POLY_SET fill_shape;
859 zone->TransformSolidAreasShapesToPolygon( layer, fill_shape );
860 fill_shape.Unfracture();
861
862 fill_shape.SimplifyOutlines( ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel );
863
864 m_poly_shapes[layer][zone->GetNetname()].Append( fill_shape );
865 }
866 }
867}
868
869
871{
872 PCB_LAYER_ID pcblayer = aItem->GetLayer();
873 int maxError = aItem->GetMaxError();
874
875 if( !m_layersToExport.Contains( pcblayer ) )
876 return false;
877
878 if( IsCopperLayer( pcblayer ) && !m_params.m_ExportTracksVias )
879 return false;
880
881 if( IsInnerCopperLayer( pcblayer ) && !m_params.m_ExportInnerCopper )
882 return false;
883
884 switch( aItem->Type() )
885 {
886 case PCB_SHAPE_T:
887 {
888 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
889
890 if( IsCopperLayer( pcblayer ) && !m_params.m_NetFilter.IsEmpty()
891 && !graphic->GetNetname().Matches( m_params.m_NetFilter ) )
892 {
893 return true;
894 }
895
896 LINE_STYLE lineStyle = graphic->GetLineStyle();
897
898 if( lineStyle == LINE_STYLE::SOLID )
899 {
900 graphic->TransformShapeToPolySet( m_poly_shapes[pcblayer][graphic->GetNetname()], pcblayer, 0,
901 maxError, ERROR_INSIDE );
902 }
903 else
904 {
905 std::vector<SHAPE*> shapes = graphic->MakeEffectiveShapes( true );
906 const PCB_PLOT_PARAMS& plotParams = m_board->GetPlotOptions();
907 KIGFX::PCB_RENDER_SETTINGS renderSettings;
908
909 renderSettings.SetDashLengthRatio( plotParams.GetDashedLineDashRatio() );
910 renderSettings.SetGapLengthRatio( plotParams.GetDashedLineGapRatio() );
911
912 for( SHAPE* shape : shapes )
913 {
914 STROKE_PARAMS::Stroke( shape, lineStyle, graphic->GetWidth(), &renderSettings,
915 [&]( const VECTOR2I& a, const VECTOR2I& b )
916 {
917 SHAPE_SEGMENT seg( a, b, graphic->GetWidth() );
918 seg.TransformToPolygon( m_poly_shapes[pcblayer][graphic->GetNetname()],
919 maxError, ERROR_INSIDE );
920 } );
921 }
922
923 for( SHAPE* shape : shapes )
924 delete shape;
925 }
926
927 if( graphic->IsHatchedFill() )
928 m_poly_shapes[pcblayer][graphic->GetNetname()].Append( graphic->GetHatching() );
929
930 if( m_params.m_ExportSoldermask && graphic->IsOnLayer( F_Mask ) )
931 {
932 graphic->TransformShapeToPolygon( m_poly_shapes[F_Mask][wxEmptyString], F_Mask,
933 graphic->GetSolderMaskExpansion(), maxError, ERROR_INSIDE );
934 }
935
936 if( m_params.m_ExportSoldermask && graphic->IsOnLayer( B_Mask ) )
937 {
938 graphic->TransformShapeToPolygon( m_poly_shapes[B_Mask][wxEmptyString], B_Mask,
939 graphic->GetSolderMaskExpansion(), maxError, ERROR_INSIDE );
940 }
941
942 break;
943 }
944
945 case PCB_TEXT_T:
946 {
947 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
948
949 text->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
950 break;
951 }
952
953 case PCB_BARCODE_T:
954 {
955 PCB_BARCODE* barcode = static_cast<PCB_BARCODE*>( aItem );
956
957 barcode->TransformShapeToPolySet( m_poly_shapes[pcblayer][wxEmptyString], pcblayer, 0, maxError,
958 ERROR_INSIDE );
959 break;
960 }
961
962 case PCB_TEXTBOX_T:
963 {
964 PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( aItem );
965
966 // border
967 if( textbox->IsBorderEnabled() )
968 {
969 textbox->PCB_SHAPE::TransformShapeToPolygon( m_poly_shapes[pcblayer][wxEmptyString], pcblayer, 0,
970 maxError, ERROR_INSIDE );
971 }
972
973 // text
974 textbox->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
975 break;
976 }
977
978 case PCB_TABLE_T:
979 {
980 PCB_TABLE* table = static_cast<PCB_TABLE*>( aItem );
981
982 for( PCB_TABLECELL* cell : table->GetCells() )
983 {
984 cell->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
985 }
986
987 table->DrawBorders(
988 [&]( const VECTOR2I& ptA, const VECTOR2I& ptB, const STROKE_PARAMS& stroke )
989 {
990 SHAPE_SEGMENT seg( ptA, ptB, stroke.GetWidth() );
991 seg.TransformToPolygon( m_poly_shapes[pcblayer][wxEmptyString], maxError, ERROR_INSIDE );
992 } );
993
994 break;
995 }
996
997 default: wxFAIL_MSG( "buildGraphic3DShape: unhandled item type" );
998 }
999
1000 return true;
1001}
1002
1003
1005{
1006 // Specialize the STEP_PCB_MODEL generator for specific output format
1007 // it can have some minor actions for the generator
1008 switch( m_params.m_Format )
1009 {
1011 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STEP );
1012 break;
1013
1015 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STEPZ );
1016 break;
1017
1019 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_BREP );
1020 break;
1021
1023 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_XAO );
1024 break;
1025
1027 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_GLTF );
1028 break;
1029
1031 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PLY );
1032 break;
1033
1035 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STL );
1036 break;
1037
1039 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_U3D );
1040 break;
1041
1043 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PDF );
1044 break;
1045
1046 default:
1047 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_UNKNOWN );
1048 break;
1049 }
1050}
1051
1052
1054{
1055 if( m_pcbModel )
1056 return true;
1057
1058 SHAPE_POLY_SET pcbOutlines; // stores the board main outlines
1059
1060 if( !m_board->GetBoardPolygonOutlines( pcbOutlines,
1061 /* infer outline if necessary */ true,
1062 /* error handler */ nullptr,
1063 /* allows use arcs in outlines */ true ) )
1064 {
1065 wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) );
1066 }
1067
1068 SHAPE_POLY_SET pcbOutlinesNoArcs = pcbOutlines;
1069 pcbOutlinesNoArcs.ClearArcs();
1070
1071 VECTOR2D origin;
1072
1073 // Determine the coordinate system reference:
1074 // Precedence of reference point is Drill Origin > Grid Origin > User Offset
1075 if( m_params.m_UseDrillOrigin )
1076 origin = m_board->GetDesignSettings().GetAuxOrigin();
1077 else if( m_params.m_UseGridOrigin )
1078 origin = m_board->GetDesignSettings().GetGridOrigin();
1079 else
1080 origin = m_params.m_Origin;
1081
1082 m_pcbModel = std::make_unique<STEP_PCB_MODEL>( m_pcbBaseName, m_reporter );
1083
1085
1086 m_pcbModel->SetCopperColor( m_copperColor.r, m_copperColor.g, m_copperColor.b );
1087 m_pcbModel->SetPadColor( m_padColor.r, m_padColor.g, m_padColor.b );
1088
1089 m_pcbModel->SetStackup( m_board->GetStackupOrDefault() );
1090 m_pcbModel->SetEnabledLayers( m_layersToExport );
1091 m_pcbModel->SetFuseShapes( m_params.m_FuseShapes );
1092 m_pcbModel->SetNetFilter( m_params.m_NetFilter );
1093 m_pcbModel->SetExtraPadThickness( m_params.m_ExtraPadThickness );
1094
1095 // Note: m_params.m_BoardOutlinesChainingEpsilon is used only to build the board outlines,
1096 // not to set OCC chaining epsilon (much smaller)
1097 //
1098 // Set the min distance between 2 points for OCC to see these 2 points as merged
1099 // OCC_MAX_DISTANCE_TO_MERGE_POINTS is acceptable for OCC, otherwise there are issues
1100 // to handle the shapes chaining on copper layers, because the Z dist is 0.035 mm and the
1101 // min dist must be much smaller (we use 0.001 mm giving good results)
1102 m_pcbModel->OCCSetMergeMaxDistance( OCC_MAX_DISTANCE_TO_MERGE_POINTS );
1103
1104 // For copper layers, only pads and tracks are added, because adding everything on copper
1105 // generate unreasonable file sizes and take a unreasonable calculation time.
1106 for( FOOTPRINT* fp : m_board->Footprints() )
1107 buildFootprint3DShapes( fp, origin, &pcbOutlinesNoArcs );
1108
1109 for( PCB_TRACK* track : m_board->Tracks() )
1110 buildTrack3DShape( track, origin );
1111
1112 for( BOARD_ITEM* item : m_board->Drawings() )
1113 buildGraphic3DShape( item, origin );
1114
1115 if( m_params.m_ExportZones )
1116 buildZones3DShape( origin );
1117
1118 for( PCB_LAYER_ID pcblayer : m_layersToExport.Seq() )
1119 {
1120 SHAPE_POLY_SET holes = m_poly_holes[pcblayer];
1121 holes.Simplify();
1122
1123 if( pcblayer == F_Mask || pcblayer == B_Mask )
1124 {
1125 // Mask layer is negative
1126 SHAPE_POLY_SET mask = pcbOutlinesNoArcs;
1127
1128 for( auto& [netname, poly] : m_poly_shapes[pcblayer] )
1129 {
1130 poly.Simplify();
1131
1132 poly.SimplifyOutlines( pcbIUScale.mmToIU( 0.003 ) );
1133 poly.Simplify();
1134
1135 mask.BooleanSubtract( poly );
1136 }
1137
1138 mask.BooleanSubtract( holes );
1139
1140 m_pcbModel->AddPolygonShapes( &mask, pcblayer, origin, wxEmptyString );
1141 }
1142 else
1143 {
1144 for( auto& [netname, poly] : m_poly_shapes[pcblayer] )
1145 {
1146 poly.Simplify();
1147
1148 poly.SimplifyOutlines( pcbIUScale.mmToIU( 0.003 ) );
1149 poly.Simplify();
1150
1151 // Subtract holes
1152 poly.BooleanSubtract( holes );
1153
1154 // Clip to board outline
1155 poly.BooleanIntersection( pcbOutlinesNoArcs );
1156
1157 m_pcbModel->AddPolygonShapes( &poly, pcblayer, origin, netname );
1158 }
1159 }
1160 }
1161
1162 m_reporter->Report( wxT( "Create PCB solid model.\n" ), RPT_SEVERITY_DEBUG );
1163
1164 m_reporter->Report( wxString::Format( wxT( "Board outline: found %d initial points.\n" ),
1165 pcbOutlines.FullPointCount() ),
1167
1168 if( !m_pcbModel->CreatePCB( pcbOutlines, origin, m_params.m_ExportBoardBody ) )
1169 {
1170 m_reporter->Report( _( "Could not create PCB solid model.\n" ), RPT_SEVERITY_ERROR );
1171 return false;
1172 }
1173
1174 return true;
1175}
1176
1177
1179{
1180 // Display the export time, for statistics
1181 int64_t stats_startExportTime = GetRunningMicroSecs();
1182
1183 // setup opencascade message log
1184 struct SCOPED_PRINTER
1185 {
1186 Handle( Message_Printer ) m_handle;
1187
1188 SCOPED_PRINTER( const Handle( Message_Printer ) & aHandle ) : m_handle( aHandle )
1189 {
1190 Message::DefaultMessenger()->AddPrinter( m_handle );
1191 };
1192
1193 ~SCOPED_PRINTER() { Message::DefaultMessenger()->RemovePrinter( m_handle ); }
1194 };
1195
1196 Message::DefaultMessenger()->RemovePrinters( STANDARD_TYPE( Message_PrinterOStream ) );
1197 SCOPED_PRINTER occtPrinter( new KICAD_PRINTER( m_reporter ) );
1198
1199 m_reporter->Report( wxT( "Determining PCB data.\n" ), RPT_SEVERITY_DEBUG );
1200
1201 if( m_params.m_OutputFile.IsEmpty() )
1202 {
1203 wxFileName fn = m_board->GetFileName();
1204 fn.SetName( fn.GetName() );
1205 fn.SetExt( m_params.GetDefaultExportExtension() );
1206
1207 m_params.m_OutputFile = fn.GetFullName();
1208 }
1209
1211
1212 if( m_params.m_ExportInnerCopper )
1214
1215 if( m_params.m_ExportSilkscreen )
1216 {
1219 }
1220
1221 if( m_params.m_ExportSoldermask )
1222 {
1223 m_layersToExport.set( F_Mask );
1224 m_layersToExport.set( B_Mask );
1225 }
1226
1227 m_layersToExport &= m_board->GetEnabledLayers();
1228
1229 try
1230 {
1231 m_reporter->Report( wxString::Format( wxT( "Build %s data.\n" ), m_params.GetFormatName() ),
1233
1234 if( !buildBoard3DShapes() )
1235 {
1236 m_reporter->Report( _( "\n"
1237 "** Error building STEP board model. Export aborted. **\n" ),
1239 return false;
1240 }
1241
1242 m_reporter->Report( wxString::Format( wxT( "Writing %s file.\n" ), m_params.GetFormatName() ),
1244
1245 bool success = true;
1247 success = m_pcbModel->WriteSTEP( m_outputFile, m_params.m_OptimizeStep, false );
1248 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STEPZ )
1249 success = m_pcbModel->WriteSTEP( m_outputFile, m_params.m_OptimizeStep, true );
1250 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::BREP )
1251 success = m_pcbModel->WriteBREP( m_outputFile );
1252 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::XAO )
1253 success = m_pcbModel->WriteXAO( m_outputFile );
1254 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::GLB )
1255 success = m_pcbModel->WriteGLTF( m_outputFile );
1256 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PLY )
1257 success = m_pcbModel->WritePLY( m_outputFile );
1258 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STL )
1259 success = m_pcbModel->WriteSTL( m_outputFile );
1260 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::U3D )
1261 success = m_pcbModel->WriteU3D( m_outputFile );
1262 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PDF )
1263 success = m_pcbModel->WritePDF( m_outputFile );
1264
1265 if( !success )
1266 {
1267 m_reporter->Report( wxString::Format( _( "\n"
1268 "** Error writing %s file. **\n" ),
1269 m_params.GetFormatName() ),
1271 return false;
1272 }
1273 else
1274 {
1275 m_reporter->Report( wxString::Format( wxT( "%s file '%s' created.\n" ),
1276 m_params.GetFormatName(),
1277 m_outputFile ),
1279 }
1280 }
1281 catch( const Standard_Failure& e )
1282 {
1283 m_reporter->Report( e.GetMessageString(), RPT_SEVERITY_ERROR );
1284 m_reporter->Report( wxString::Format( _( "\n"
1285 "** Error exporting %s file. Export aborted. **\n" ),
1286 m_params.GetFormatName() ),
1288 return false;
1289 }
1290 #ifndef DEBUG
1291 catch( ... )
1292 {
1293 m_reporter->Report( wxString::Format( _( "\n"
1294 "** Error exporting %s file. Export aborted. **\n" ),
1295 m_params.GetFormatName() ),
1297 return false;
1298 }
1299 #endif
1300
1301 // Display calculation time in seconds
1302 double calculation_time = (double)( GetRunningMicroSecs() - stats_startExportTime) / 1e6;
1303 m_reporter->Report( wxString::Format( _( "\n"
1304 "Export time %.3f s\n" ),
1305 calculation_time ),
1307
1308 return !m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR );
1309}
@ 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:83
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:236
virtual void TransformShapeToPolySet(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, KIGFX::RENDER_SETTINGS *aRenderSettings=nullptr) const
Convert the item shape to a polyset.
Definition board_item.h:429
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:579
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
bool isLayerInBackdrillSpan(PCB_LAYER_ID aLayer, PCB_LAYER_ID aStartLayer, PCB_LAYER_ID aEndLayer) const
Check if a copper layer is within a backdrill layer span (inclusive).
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
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:328
std::deque< PAD * > & Pads()
Definition footprint.h:304
int GetAttributes() const
Definition footprint.h:407
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:337
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:349
bool GetDNPForVariant(const wxString &aVariantName) const
Get the DNP status for a specific variant.
std::vector< FP_3DMODEL > & Models()
Definition footprint.h:321
const wxString & GetReference() const
Definition footprint.h:741
EMBEDDED_FILES * GetEmbeddedFiles() override
Definition footprint.h:1193
VECTOR2I GetPosition() const override
Definition footprint.h:325
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:105
PCB specific render settings.
Definition pcb_painter.h:82
void SetGapLengthRatio(double aRatio)
void SetDashLengthRatio(double aRatio)
std::optional< LIBRARY_TABLE_ROW * > GetRow(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
Like LIBRARY_MANAGER::GetRow but filtered to the LIBRARY_TABLE_TYPE of this adapter.
std::optional< wxString > GetFullURI(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, bool aSubstituted=false) const
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition lib_id.h:87
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
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:157
POST_MACHINING_PROPS & FrontPostMachining()
Definition padstack.h:350
DRILL_PROPS & TertiaryDrill()
Definition padstack.h:347
DRILL_PROPS & SecondaryDrill()
Definition padstack.h:344
POST_MACHINING_PROPS & BackPostMachining()
Definition padstack.h:353
Definition pad.h:55
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, KIGFX::RENDER_SETTINGS *aRenderSettings=nullptr) 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 FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
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
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
@ ROUND_ALL_CORNERS
All angles are rounded.
#define _(s)
@ FP_SMD
Definition footprint.h:83
@ FP_THROUGH_HOLE
Definition footprint.h:82
const wxChar *const traceKiCad2Step
Flag to enable KiCad2Step debug tracing.
Handle(KICAD3D_INFO) KICAD3D_INFO
#define MAX_CU_LAYERS
Definition layer_ids.h:176
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
@ In1_Cu
Definition layer_ids.h:66
@ B_SilkS
Definition layer_ids.h:101
@ F_Cu
Definition layer_ids.h:64
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ CASTELLATED
a pad with a castellated through hole
Definition padstack.h:121
BARCODE class definition.
PGM_BASE & Pgm()
The global program "get" accessor.
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.
! The properties of a padstack drill. Drill position is always the pad position (origin).
Definition padstack.h:261
PCB_LAYER_ID start
Definition padstack.h:264
PCB_LAYER_ID end
Definition padstack.h:265
VECTOR2I size
Drill diameter (x == y) or slot dimensions (x != y)
Definition padstack.h:262
std::optional< PAD_DRILL_POST_MACHINING_MODE > mode
Definition padstack.h:275
#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.