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"
44
45#include <pgm_base.h>
46#include <reporter.h>
47#include <base_units.h>
48#include <filename_resolver.h>
49#include <trace_helpers.h>
50#include <project_pcb.h>
52
53#include <new> // std::bad_alloc
54#include <Message.hxx> // OpenCascade messenger
55#include <Message_PrinterOStream.hxx> // OpenCascade output messenger
56#include <Standard_Failure.hxx> // In open cascade
57
58#include <Standard_Version.hxx>
59
60#include <wx/crt.h>
61#include <wx/log.h>
62#include <wx/tokenzr.h>
63#include <core/profile.h> // To use GetRunningMicroSecs or another profiling utility
64
65#define OCC_VERSION_MIN 0x070500
66
67#if OCC_VERSION_HEX < OCC_VERSION_MIN
68#include <Message_Messenger.hxx>
69#endif
70
71
72class KICAD_PRINTER : public Message_Printer
73{
74public:
75 KICAD_PRINTER( REPORTER* aReporter ) :
76 m_reporter( aReporter )
77 {}
78
79protected:
80#if OCC_VERSION_HEX < OCC_VERSION_MIN
81 virtual void Send( const TCollection_ExtendedString& theString,
82 const Message_Gravity theGravity,
83 const bool theToPutEol ) const override
84 {
85 Send( TCollection_AsciiString( theString ), theGravity, theToPutEol );
86 }
87
88 virtual void Send( const TCollection_AsciiString& theString,
89 const Message_Gravity theGravity,
90 const bool theToPutEol ) const override
91#else
92 virtual void send( const TCollection_AsciiString& theString,
93 const Message_Gravity theGravity ) const override
94#endif
95 {
96 wxString msg( theString.ToCString() );
97
98#if OCC_VERSION_HEX < OCC_VERSION_MIN
99 if( theToPutEol )
100 msg += wxT( "\n" );
101#else
102 msg += wxT( "\n" );
103#endif
104
105 m_reporter->Report( msg, getSeverity( theGravity ) );
106 }
107
108private:
109 SEVERITY getSeverity( const Message_Gravity theGravity ) const
110 {
111 switch( theGravity )
112 {
113 case Message_Trace: return RPT_SEVERITY_DEBUG;
114 case Message_Info: return RPT_SEVERITY_DEBUG;
115 case Message_Warning: return RPT_SEVERITY_WARNING;
116 case Message_Alarm: return RPT_SEVERITY_WARNING;
117 case Message_Fail: return RPT_SEVERITY_ERROR;
118
119 // There are no other values, but gcc doesn't appear to be able to work that out.
120 default: return RPT_SEVERITY_UNDEFINED;
121 }
122 }
123
124private:
126};
127
128
130 REPORTER* aReporter ) :
131 m_params( aParams ),
132 m_reporter( aReporter ),
133 m_board( aBoard ),
134 m_pcbModel( nullptr )
135{
136 m_copperColor = COLOR4D( 0.7, 0.61, 0.0, 1.0 );
137
138 if( m_params.m_ExportComponents )
139 m_padColor = COLOR4D( 0.50, 0.50, 0.50, 1.0 );
140 else
142
143 // TODO: make configurable
144 m_platingThickness = pcbIUScale.mmToIU( 0.025 );
145
146 // Init m_pcbBaseName to the board short filename (no path, no ext)
147 // m_pcbName is used later to identify items in step file
148 wxFileName fn( aBoard->GetFileName() );
149 m_pcbBaseName = fn.GetName();
150
151 m_resolver = std::make_unique<FILENAME_RESOLVER>();
152 m_resolver->Set3DConfigDir( wxT( "" ) );
153 // needed to add the project to the search stack
154 m_resolver->SetProject( aBoard->GetProject() );
155 m_resolver->SetProgramBase( &Pgm() );
156}
157
158
162
163
165 PCB_LAYER_ID aEndLayer ) const
166{
167 if( !IsCopperLayer( aLayer ) )
168 return false;
169
170 // Quick check for exact match
171 if( aLayer == aStartLayer || aLayer == aEndLayer )
172 return true;
173
174 // Convert layers to a sortable index for comparison
175 // F_Cu = -1, In1_Cu through In30_Cu = 0-29, B_Cu = MAX_CU_LAYERS (32)
176 auto layerToIndex = []( PCB_LAYER_ID layer ) -> int
177 {
178 if( layer == F_Cu )
179 return -1;
180
181 if( layer == B_Cu )
182 return MAX_CU_LAYERS;
183
184 if( IsInnerCopperLayer( layer ) )
185 return layer - In1_Cu;
186
187 return -2; // Invalid copper layer
188 };
189
190 int startIdx = layerToIndex( aStartLayer );
191 int endIdx = layerToIndex( aEndLayer );
192 int layerIdx = layerToIndex( aLayer );
193
194 if( layerIdx == -2 )
195 return false;
196
197 int minIdx = std::min( startIdx, endIdx );
198 int maxIdx = std::max( startIdx, endIdx );
199
200 return ( layerIdx >= minIdx && layerIdx <= maxIdx );
201}
202
203
204bool EXPORTER_STEP::netFilterMatches( const wxString& netname ) const
205{
206 if( m_params.m_NetFilter.IsEmpty() )
207 return true;
208
209 wxArrayString parts = wxSplit( m_params.m_NetFilter, ',' );
210
211 for( wxString token : parts )
212 {
213 token.Trim( true ).Trim( false );
214
215 if( token.IsEmpty() )
216 continue;
217
218 if( netname.Matches( token ) )
219 return true;
220 }
221
222 return false;
223}
224
225
227 SHAPE_POLY_SET* aClipPolygon )
228{
229 bool hasdata = false;
230 std::vector<PAD*> padsMatchingNetFilter;
231
232 // Dump the pad holes into the PCB
233 for( PAD* pad : aFootprint->Pads() )
234 {
235 bool castellated = pad->GetProperty() == PAD_PROP::CASTELLATED;
236 std::shared_ptr<SHAPE_SEGMENT> holeShape = pad->GetEffectiveHoleShape();
237
238 SHAPE_POLY_SET holePoly;
239 holeShape->TransformToPolygon( holePoly, pad->GetMaxError(), ERROR_INSIDE );
240
241 // This helps with fusing
242 holePoly.Deflate( m_platingThickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, pad->GetMaxError() );
243
244 for( PCB_LAYER_ID pcblayer : pad->GetLayerSet() )
245 {
246 if( pad->IsOnLayer( pcblayer ) )
247 m_poly_holes[pcblayer].Append( holePoly );
248 }
249
250 if( pad->HasHole() )
251 {
252 int platingThickness = pad->GetAttribute() == PAD_ATTRIB::PTH ? m_platingThickness : 0;
253
254 if( m_pcbModel->AddHole( *holeShape, platingThickness, F_Cu, B_Cu, false, aOrigin, true, true ) )
255 hasdata = true;
256
257 // Use the drill shape directly. holePoly is shrunk to fit the copper barrel.
258 if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
259 {
260 SHAPE_POLY_SET silkHole;
261 holeShape->TransformToPolygon( silkHole, pad->GetMaxError(), ERROR_INSIDE );
262
263 if( m_layersToExport.Contains( F_SilkS ) )
264 m_poly_holes[F_SilkS].Append( silkHole );
265
266 if( m_layersToExport.Contains( B_SilkS ) )
267 m_poly_holes[B_SilkS].Append( silkHole );
268 }
269
270 // Handle backdrills - secondary and tertiary drills defined in the padstack
271 const PADSTACK& padstack = pad->Padstack();
272 const PADSTACK::DRILL_PROPS& secondaryDrill = padstack.SecondaryDrill();
273 const PADSTACK::DRILL_PROPS& tertiaryDrill = padstack.TertiaryDrill();
274
275 // Process secondary drill (typically bottom backdrill)
276 if( secondaryDrill.size.x > 0 )
277 {
278 SHAPE_SEGMENT backdrillShape( pad->GetPosition(), pad->GetPosition(),
279 secondaryDrill.size.x );
280 m_pcbModel->AddBackdrill( backdrillShape, secondaryDrill.start,
281 secondaryDrill.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, secondaryDrill.start, secondaryDrill.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, secondaryDrill.start, secondaryDrill.end ) )
295 {
296 m_poly_holes[F_SilkS].Append( backdrillPoly );
297 m_poly_holes[F_Mask].Append( backdrillPoly );
298 }
299 if( isLayerInBackdrillSpan( B_Cu, secondaryDrill.start, secondaryDrill.end ) )
300 {
301 m_poly_holes[B_SilkS].Append( backdrillPoly );
302 m_poly_holes[B_Mask].Append( backdrillPoly );
303 }
304 }
305
306 // Process tertiary drill (typically top backdrill)
307 if( tertiaryDrill.size.x > 0 )
308 {
309 SHAPE_SEGMENT backdrillShape( pad->GetPosition(), pad->GetPosition(),
310 tertiaryDrill.size.x );
311 m_pcbModel->AddBackdrill( backdrillShape, tertiaryDrill.start,
312 tertiaryDrill.end, aOrigin );
313
314 // Add backdrill holes to affected copper layers for 2D polygon subtraction
315 SHAPE_POLY_SET backdrillPoly;
316 backdrillShape.TransformToPolygon( backdrillPoly, pad->GetMaxError(), ERROR_INSIDE );
317
318 for( PCB_LAYER_ID layer : pad->GetLayerSet() )
319 {
320 if( isLayerInBackdrillSpan( layer, tertiaryDrill.start, tertiaryDrill.end ) )
321 m_poly_holes[layer].Append( backdrillPoly );
322 }
323
324 // Add knockouts for silkscreen and soldermask on the backdrill side
325 if( isLayerInBackdrillSpan( F_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
326 {
327 m_poly_holes[F_SilkS].Append( backdrillPoly );
328 m_poly_holes[F_Mask].Append( backdrillPoly );
329 }
330 if( isLayerInBackdrillSpan( B_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
331 {
332 m_poly_holes[B_SilkS].Append( backdrillPoly );
333 m_poly_holes[B_Mask].Append( backdrillPoly );
334 }
335 }
336
337 // Process post-machining (counterbore/countersink) on front and back
338 const PADSTACK::POST_MACHINING_PROPS& frontPM = padstack.FrontPostMachining();
339 const PADSTACK::POST_MACHINING_PROPS& backPM = padstack.BackPostMachining();
340
341 wxLogTrace( traceKiCad2Step, wxT( "PAD post-machining check: frontPM.mode.has_value=%d frontPM.size=%d frontPM.depth=%d frontPM.angle=%d" ),
342 frontPM.mode.has_value() ? 1 : 0, frontPM.size, frontPM.depth, frontPM.angle );
343 wxLogTrace( traceKiCad2Step, wxT( "PAD post-machining check: backPM.mode.has_value=%d backPM.size=%d backPM.depth=%d backPM.angle=%d" ),
344 backPM.mode.has_value() ? 1 : 0, backPM.size, backPM.depth, backPM.angle );
345
346 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
347 bool frontPMValid = frontPM.mode.has_value() && frontPM.size > 0 &&
348 ( ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && frontPM.depth > 0 ) ||
349 ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && frontPM.angle > 0 ) );
350
351 if( frontPMValid )
352 {
353 wxLogTrace( traceKiCad2Step, wxT( "PAD front post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
354 static_cast<int>( *frontPM.mode ) );
355
356 int pmAngle = ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? frontPM.angle : 0;
357
359 {
360 m_pcbModel->AddCounterbore( pad->GetPosition(), frontPM.size,
361 frontPM.depth, true, aOrigin );
362 }
364 {
365 m_pcbModel->AddCountersink( pad->GetPosition(), frontPM.size,
366 frontPM.depth, frontPM.angle, true, aOrigin );
367 }
368
369 // Add knockouts to all copper layers the feature crosses
370 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( frontPM.size, frontPM.depth,
371 pmAngle, true );
372 for( const auto& [layer, diameter] : knockouts )
373 {
374 SHAPE_POLY_SET pmPoly;
375 TransformCircleToPolygon( pmPoly, pad->GetPosition(), diameter / 2,
376 pad->GetMaxError(), ERROR_INSIDE );
377 m_poly_holes[layer].Append( pmPoly );
378 }
379
380 // Add knockout for silkscreen and soldermask on front side (full diameter)
381 SHAPE_POLY_SET pmPoly;
382 TransformCircleToPolygon( pmPoly, pad->GetPosition(), frontPM.size / 2,
383 pad->GetMaxError(), ERROR_INSIDE );
384 m_poly_holes[F_SilkS].Append( pmPoly );
385 m_poly_holes[F_Mask].Append( pmPoly );
386 }
387
388 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
389 bool backPMValid = backPM.mode.has_value() && backPM.size > 0 &&
390 ( ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && backPM.depth > 0 ) ||
391 ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && backPM.angle > 0 ) );
392
393 if( backPMValid )
394 {
395 wxLogTrace( traceKiCad2Step, wxT( "PAD back post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
396 static_cast<int>( *backPM.mode ) );
397
398 int pmAngle = ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? backPM.angle : 0;
399
401 {
402 m_pcbModel->AddCounterbore( pad->GetPosition(), backPM.size,
403 backPM.depth, false, aOrigin );
404 }
406 {
407 m_pcbModel->AddCountersink( pad->GetPosition(), backPM.size,
408 backPM.depth, backPM.angle, false, aOrigin );
409 }
410
411 // Add knockouts to all copper layers the feature crosses
412 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( backPM.size, backPM.depth,
413 pmAngle, false );
414 for( const auto& [layer, diameter] : knockouts )
415 {
416 SHAPE_POLY_SET pmPoly;
417 TransformCircleToPolygon( pmPoly, pad->GetPosition(), diameter / 2,
418 pad->GetMaxError(), ERROR_INSIDE );
419 m_poly_holes[layer].Append( pmPoly );
420 }
421
422 // Add knockout for silkscreen and soldermask on back side (full diameter)
423 SHAPE_POLY_SET pmPoly;
424 TransformCircleToPolygon( pmPoly, pad->GetPosition(), backPM.size / 2,
425 pad->GetMaxError(), ERROR_INSIDE );
426 m_poly_holes[B_SilkS].Append( pmPoly );
427 m_poly_holes[B_Mask].Append( pmPoly );
428 }
429 }
430
431 if( !netFilterMatches( pad->GetNetname() ) )
432 continue;
433
434 if( m_params.m_ExportPads )
435 {
436 if( m_pcbModel->AddPadShape( pad, aOrigin, false, castellated ? aClipPolygon : nullptr) )
437 hasdata = true;
438
439 if( m_params.m_ExportSoldermask )
440 {
441 for( PCB_LAYER_ID pcblayer : pad->GetLayerSet() )
442 {
443 if( pcblayer != F_Mask && pcblayer != B_Mask )
444 continue;
445
446 SHAPE_POLY_SET poly;
447 PCB_LAYER_ID cuLayer = ( pcblayer == F_Mask ) ? F_Cu : B_Cu;
448 pad->TransformShapeToPolygon( poly, cuLayer, pad->GetSolderMaskExpansion( cuLayer ),
449 pad->GetMaxError(), ERROR_INSIDE );
450
451 m_poly_shapes[pcblayer][wxEmptyString].Append( poly );
452 }
453 }
454 }
455
456 padsMatchingNetFilter.push_back( pad );
457 }
458
459 // Build 3D shapes of the footprint graphic items:
460 for( PCB_LAYER_ID pcblayer : m_layersToExport )
461 {
462 if( IsCopperLayer( pcblayer ) && !m_params.m_ExportTracksVias )
463 continue;
464
465 SHAPE_POLY_SET buffer;
466
467 aFootprint->TransformFPShapesToPolySet( buffer, pcblayer, 0, aFootprint->GetMaxError(), ERROR_INSIDE,
468 true, /* include text */
469 true, /* include shapes */
470 false /* include private items */ );
471
472 if( !IsCopperLayer( pcblayer ) )
473 {
474 m_poly_shapes[pcblayer][wxEmptyString].Append( buffer );
475 }
476 else
477 {
478 std::map<const SHAPE_POLY_SET::POLYGON*, PAD*> polyPadMap;
479
480 // Only add polygons colliding with any matching pads
481 for( const SHAPE_POLY_SET::POLYGON& poly : buffer.CPolygons() )
482 {
483 for( PAD* pad : padsMatchingNetFilter )
484 {
485 if( !pad->IsOnLayer( pcblayer ) )
486 continue;
487
488 std::shared_ptr<SHAPE_POLY_SET> padPoly = pad->GetEffectivePolygon( pcblayer );
489 SHAPE_POLY_SET gfxPoly( poly );
490
491 if( padPoly->Collide( &gfxPoly ) )
492 {
493 polyPadMap[&poly] = pad;
494 m_poly_shapes[pcblayer][pad->GetNetname()].Append( gfxPoly );
495 break;
496 }
497 }
498 }
499
500 if( m_params.m_NetFilter.empty() )
501 {
502 // Add polygons with no net
503 for( const SHAPE_POLY_SET::POLYGON& poly : buffer.CPolygons() )
504 {
505 auto it = polyPadMap.find( &poly );
506
507 if( it == polyPadMap.end() )
508 m_poly_shapes[pcblayer][wxEmptyString].Append( poly );
509 }
510 }
511 }
512 }
513
514 if( ( !(aFootprint->GetAttributes() & (FP_THROUGH_HOLE|FP_SMD)) ) && !m_params.m_IncludeUnspecified )
515 {
516 return hasdata;
517 }
518
519 if( aFootprint->GetDNPForVariant( m_board ? m_board->GetCurrentVariant() : wxString() )
520 && !m_params.m_IncludeDNP )
521 {
522 return hasdata;
523 }
524
525 // Prefetch the library for this footprint
526 // In case we need to resolve relative footprint paths
527 wxString libraryName = aFootprint->GetFPID().GetLibNickname();
528 wxString footprintBasePath = wxEmptyString;
529
530 double posX = aFootprint->GetPosition().x - aOrigin.x;
531 double posY = (aFootprint->GetPosition().y) - aOrigin.y;
532
533 if( m_board->GetProject() )
534 {
535 std::optional<LIBRARY_TABLE_ROW*> fpRow =
536 PROJECT_PCB::FootprintLibAdapter( m_board->GetProject() )->GetRow( libraryName );
537 if( fpRow )
538 footprintBasePath = LIBRARY_MANAGER::GetFullURI( *fpRow, true );
539 }
540
541 // Exit early if we don't want to include footprint models
542 if( m_params.m_BoardOnly || !m_params.m_ExportComponents )
543 {
544 return hasdata;
545 }
546
547 bool componentFilter = !m_params.m_ComponentFilter.IsEmpty();
548 std::vector<wxString> componentFilterPatterns;
549
550 if( componentFilter )
551 {
552 wxStringTokenizer tokenizer( m_params.m_ComponentFilter, ", \t\r\n", wxTOKEN_STRTOK );
553
554 while( tokenizer.HasMoreTokens() )
555 componentFilterPatterns.push_back( tokenizer.GetNextToken() );
556
557 bool found = false;
558
559 for( const wxString& pattern : componentFilterPatterns )
560 {
561 if( aFootprint->GetReference().Matches( pattern ) )
562 {
563 found = true;
564 break;
565 }
566 }
567
568 if( !found )
569 return hasdata;
570 }
571
572 VECTOR2D newpos( pcbIUScale.IUTomm( posX ), pcbIUScale.IUTomm( posY ) );
573
574 for( const FP_3DMODEL& fp_model : aFootprint->Models() )
575 {
576 if( !fp_model.m_Show || fp_model.m_Filename.empty() )
577 continue;
578
579 std::vector<wxString> searchedPaths;
580 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
581 embeddedFilesStack.push_back( aFootprint->GetEmbeddedFiles() );
582 embeddedFilesStack.push_back( m_board->GetEmbeddedFiles() );
583
584 wxString mainPath = m_resolver->ResolvePath( fp_model.m_Filename, footprintBasePath,
585 embeddedFilesStack );
586
587 if( mainPath.empty() || !wxFileName::FileExists( mainPath ) )
588 {
589 // the error path will return an empty name sometimes, at least report back the original filename
590 if( mainPath.empty() )
591 mainPath = fp_model.m_Filename;
592
593 m_reporter->Report( wxString::Format( _( "Could not add 3D model for %s.\n"
594 "File not found: %s\n" ),
595 aFootprint->GetReference(), mainPath ),
597 continue;
598 }
599
600 wxString baseName =
601 fp_model.m_Filename.AfterLast( '/' ).AfterLast( '\\' ).BeforeLast( '.' );
602
603 std::vector<wxString> altFilenames;
604
605 // Add embedded files to alternative filenames
606 if( fp_model.m_Filename.StartsWith( FILEEXT::KiCadUriPrefix + "://" ) )
607 {
608 for( const EMBEDDED_FILES* filesPtr : embeddedFilesStack )
609 {
610 const auto& map = filesPtr->EmbeddedFileMap();
611
612 for( auto& [fname, file] : map )
613 {
614 if( fname.BeforeLast( '.' ) == baseName )
615 {
616 wxFileName temp_file = filesPtr->GetTemporaryFileName( fname );
617
618 if( !temp_file.IsOk() )
619 continue;
620
621 wxString altPath = temp_file.GetFullPath();
622
623 if( mainPath == altPath )
624 continue;
625
626 altFilenames.emplace_back( altPath );
627 }
628 }
629 }
630 }
631
632 try
633 {
634 bool bottomSide = aFootprint->GetLayer() == B_Cu;
635
636 // the rotation is stored in degrees but opencascade wants radians
637 VECTOR3D modelRot = fp_model.m_Rotation;
638 modelRot *= M_PI;
639 modelRot /= 180.0;
640
641 if( m_pcbModel->AddComponent(
642 baseName, mainPath, altFilenames, aFootprint->GetReference(), bottomSide,
643 newpos, aFootprint->GetOrientation().AsRadians(), fp_model.m_Offset,
644 modelRot, fp_model.m_Scale, m_params.m_SubstModels ) )
645 {
646 hasdata = true;
647 }
648 }
649 catch( const Standard_Failure& e )
650 {
651 m_reporter->Report( wxString::Format( _( "Could not add 3D model for %s.\n"
652 "OpenCASCADE error: %s\n" ),
653 aFootprint->GetReference(),
654 e.GetMessageString() ),
656 }
657
658 }
659
660 if( aFootprint->HasExtrudedBody() && aFootprint->GetExtrudedBody()->m_show )
661 {
662 const EXTRUDED_3D_BODY* body = aFootprint->GetExtrudedBody();
663 SHAPE_POLY_SET outline;
664
665 if( GetExtrusionOutline( aFootprint, outline ) && outline.OutlineCount() > 0 )
666 {
667 VECTOR2I fpPos = aFootprint->GetPosition();
668 ApplyExtrusionTransform( outline, body, fpPos );
669
670 bool bottomSide = aFootprint->GetLayer() == B_Cu;
671 double standoff = pcbIUScale.IUTomm( body->m_standoff ) + body->m_offset.z;
672 double bodyThickness = pcbIUScale.IUTomm( body->m_height - body->m_standoff ) * body->m_scale.z;
673 double height = standoff + bodyThickness;
674
675 KIGFX::COLOR4D c = body->m_color;
676
679
680 uint32_t colorKey = EXTRUDED_3D_BODY::PackColorKey( c );
681
682 try
683 {
684 if( m_pcbModel->AddExtrudedBody( outline, bottomSide, standoff, height, aOrigin, colorKey,
685 body->m_material, aFootprint->GetReference() ) )
686 {
687 hasdata = true;
688 }
689 }
690 catch( const Standard_Failure& e )
691 {
692 m_reporter->Report( wxString::Format( _( "Could not add extruded body for %s.\n"
693 "OpenCASCADE error: %s\n" ),
694 aFootprint->GetReference(), e.GetMessageString() ),
696 }
697
698 // Add metallic pin extrusions for through-hole pads
699 if( standoff > 0.0 )
700 {
701 try
702 {
703 m_pcbModel->AddExtrudedPins( aFootprint, bottomSide, standoff, aOrigin );
704 }
705 catch( const Standard_Failure& e )
706 {
707 m_reporter->Report( wxString::Format( _( "Could not add extruded pins for %s.\n"
708 "OpenCASCADE error: %s\n" ),
709 aFootprint->GetReference(), e.GetMessageString() ),
711 }
712 }
713 }
714 }
715
716 return hasdata;
717}
718
719
721{
722 bool skipCopper = !m_params.m_ExportTracksVias || !netFilterMatches( aTrack->GetNetname() );
723
724 if( m_params.m_ExportSoldermask && aTrack->IsOnLayer( F_Mask ) )
725 {
726 aTrack->TransformShapeToPolygon( m_poly_shapes[F_Mask][wxEmptyString], F_Mask,
727 aTrack->GetSolderMaskExpansion(), aTrack->GetMaxError(),
728 ERROR_INSIDE );
729 }
730
731 if( m_params.m_ExportSoldermask && aTrack->IsOnLayer( B_Mask ) )
732 {
733 aTrack->TransformShapeToPolygon( m_poly_shapes[B_Mask][wxEmptyString], B_Mask,
734 aTrack->GetSolderMaskExpansion(), aTrack->GetMaxError(),
735 ERROR_INSIDE );
736 }
737
738 if( aTrack->Type() == PCB_VIA_T )
739 {
740 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
741
742 std::shared_ptr<SHAPE_SEGMENT> holeShape = via->GetEffectiveHoleShape();
743 SHAPE_POLY_SET holePoly;
744 holeShape->TransformToPolygon( holePoly, via->GetMaxError(), ERROR_INSIDE );
745
746 // This helps with fusing
747 holePoly.Deflate( m_platingThickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, via->GetMaxError() );
748
749 LSET layers( via->GetLayerSet() & m_layersToExport );
750
751 PCB_LAYER_ID top_layer, bot_layer;
752 via->LayerPair( &top_layer, &bot_layer );
753
754 if( !skipCopper )
755 {
756 for( PCB_LAYER_ID pcblayer : layers )
757 {
758 const std::shared_ptr<SHAPE>& shape = via->GetEffectiveShape( pcblayer );
759
760 SHAPE_POLY_SET poly;
761 shape->TransformToPolygon( poly, via->GetMaxError(), ERROR_INSIDE );
762 m_poly_shapes[pcblayer][via->GetNetname()].Append( poly );
763 m_poly_holes[pcblayer].Append( holePoly );
764 }
765
766 m_pcbModel->AddBarrel( *holeShape, top_layer, bot_layer, true, aOrigin, via->GetNetname() );
767 }
768
769 // Use the drill shape directly. holePoly is shrunk to fit the copper barrel.
770 if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
771 {
772 SHAPE_POLY_SET silkHole;
773 holeShape->TransformToPolygon( silkHole, via->GetMaxError(), ERROR_INSIDE );
774
775 if( top_layer == F_Cu && m_layersToExport.Contains( F_SilkS ) )
776 m_poly_holes[F_SilkS].Append( silkHole );
777
778 if( bot_layer == B_Cu && m_layersToExport.Contains( B_SilkS ) )
779 m_poly_holes[B_SilkS].Append( silkHole );
780 }
781
782 // Cut via holes in soldermask when the via is not tented.
783 // This ensures the mask has a proper hole through the via drill, not just the annular ring opening.
784 if( m_params.m_ExportSoldermask )
785 {
786 if( via->IsOnLayer( F_Mask ) )
787 m_poly_holes[F_Mask].Append( holePoly );
788
789 if( via->IsOnLayer( B_Mask ) )
790 m_poly_holes[B_Mask].Append( holePoly );
791 }
792
793 m_pcbModel->AddHole( *holeShape, m_platingThickness, top_layer, bot_layer, true, aOrigin,
794 !m_params.m_FillAllVias, m_params.m_CutViasInBody );
795
796 // Handle via backdrills - secondary and tertiary drills defined in the padstack
797 const PADSTACK& padstack = via->Padstack();
798 const PADSTACK::DRILL_PROPS& secondaryDrill = padstack.SecondaryDrill();
799 const PADSTACK::DRILL_PROPS& tertiaryDrill = padstack.TertiaryDrill();
800
801 // Process secondary drill (typically bottom backdrill)
802 if( secondaryDrill.size.x > 0 )
803 {
804 SHAPE_SEGMENT backdrillShape( via->GetPosition(), via->GetPosition(),
805 secondaryDrill.size.x );
806 m_pcbModel->AddBackdrill( backdrillShape, secondaryDrill.start,
807 secondaryDrill.end, aOrigin );
808
809 // Add backdrill holes to affected copper layers for 2D polygon subtraction
810 SHAPE_POLY_SET backdrillPoly;
811 backdrillShape.TransformToPolygon( backdrillPoly, via->GetMaxError(), ERROR_INSIDE );
812
813 for( PCB_LAYER_ID layer : via->GetLayerSet() )
814 {
815 if( isLayerInBackdrillSpan( layer, secondaryDrill.start, secondaryDrill.end ) )
816 m_poly_holes[layer].Append( backdrillPoly );
817 }
818
819 // Add knockouts for silkscreen and soldermask on the backdrill side
820 if( isLayerInBackdrillSpan( F_Cu, secondaryDrill.start, secondaryDrill.end ) )
821 {
822 m_poly_holes[F_SilkS].Append( backdrillPoly );
823 m_poly_holes[F_Mask].Append( backdrillPoly );
824 }
825 if( isLayerInBackdrillSpan( B_Cu, secondaryDrill.start, secondaryDrill.end ) )
826 {
827 m_poly_holes[B_SilkS].Append( backdrillPoly );
828 m_poly_holes[B_Mask].Append( backdrillPoly );
829 }
830 }
831
832 // Process tertiary drill (typically top backdrill)
833 if( tertiaryDrill.size.x > 0 )
834 {
835 SHAPE_SEGMENT backdrillShape( via->GetPosition(), via->GetPosition(),
836 tertiaryDrill.size.x );
837 m_pcbModel->AddBackdrill( backdrillShape, tertiaryDrill.start,
838 tertiaryDrill.end, aOrigin );
839
840 // Add backdrill holes to affected copper layers for 2D polygon subtraction
841 SHAPE_POLY_SET backdrillPoly;
842 backdrillShape.TransformToPolygon( backdrillPoly, via->GetMaxError(), ERROR_INSIDE );
843
844 for( PCB_LAYER_ID layer : via->GetLayerSet() )
845 {
846 if( isLayerInBackdrillSpan( layer, tertiaryDrill.start, tertiaryDrill.end ) )
847 m_poly_holes[layer].Append( backdrillPoly );
848 }
849
850 // Add knockouts for silkscreen and soldermask on the backdrill side
851 if( isLayerInBackdrillSpan( F_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
852 {
853 m_poly_holes[F_SilkS].Append( backdrillPoly );
854 m_poly_holes[F_Mask].Append( backdrillPoly );
855 }
856 if( isLayerInBackdrillSpan( B_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
857 {
858 m_poly_holes[B_SilkS].Append( backdrillPoly );
859 m_poly_holes[B_Mask].Append( backdrillPoly );
860 }
861 }
862
863 // Process post-machining (counterbore/countersink) on front and back
864 const PADSTACK::POST_MACHINING_PROPS& frontPM = padstack.FrontPostMachining();
865 const PADSTACK::POST_MACHINING_PROPS& backPM = padstack.BackPostMachining();
866
867 wxLogTrace( traceKiCad2Step, wxT( "VIA post-machining check: frontPM.mode.has_value=%d frontPM.size=%d frontPM.depth=%d frontPM.angle=%d" ),
868 frontPM.mode.has_value() ? 1 : 0, frontPM.size, frontPM.depth, frontPM.angle );
869 wxLogTrace( traceKiCad2Step, wxT( "VIA post-machining check: backPM.mode.has_value=%d backPM.size=%d backPM.depth=%d backPM.angle=%d" ),
870 backPM.mode.has_value() ? 1 : 0, backPM.size, backPM.depth, backPM.angle );
871
872 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
873 bool frontPMValid = frontPM.mode.has_value() && frontPM.size > 0 &&
874 ( ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && frontPM.depth > 0 ) ||
875 ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && frontPM.angle > 0 ) );
876
877 if( frontPMValid )
878 {
879 wxLogTrace( traceKiCad2Step, wxT( "VIA front post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
880 static_cast<int>( *frontPM.mode ) );
881
882 int pmAngle = ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? frontPM.angle : 0;
883
885 {
886 m_pcbModel->AddCounterbore( via->GetPosition(), frontPM.size,
887 frontPM.depth, true, aOrigin );
888 }
890 {
891 m_pcbModel->AddCountersink( via->GetPosition(), frontPM.size,
892 frontPM.depth, frontPM.angle, true, aOrigin );
893 }
894
895 // Add knockouts to all copper layers the feature crosses
896 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( frontPM.size, frontPM.depth,
897 pmAngle, true );
898 for( const auto& [layer, diameter] : knockouts )
899 {
900 SHAPE_POLY_SET pmPoly;
901 TransformCircleToPolygon( pmPoly, via->GetPosition(), diameter / 2,
902 via->GetMaxError(), ERROR_INSIDE );
903 m_poly_holes[layer].Append( pmPoly );
904 }
905
906 // Add knockout for silkscreen and soldermask on front side (full diameter)
907 SHAPE_POLY_SET pmPoly;
908 TransformCircleToPolygon( pmPoly, via->GetPosition(), frontPM.size / 2,
909 via->GetMaxError(), ERROR_INSIDE );
910 m_poly_holes[F_SilkS].Append( pmPoly );
911 m_poly_holes[F_Mask].Append( pmPoly );
912 }
913
914 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
915 bool backPMValid = backPM.mode.has_value() && backPM.size > 0 &&
916 ( ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && backPM.depth > 0 ) ||
917 ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && backPM.angle > 0 ) );
918
919 if( backPMValid )
920 {
921 wxLogTrace( traceKiCad2Step, wxT( "VIA back post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
922 static_cast<int>( *backPM.mode ) );
923
924 int pmAngle = ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? backPM.angle : 0;
925
927 {
928 m_pcbModel->AddCounterbore( via->GetPosition(), backPM.size,
929 backPM.depth, false, aOrigin );
930 }
932 {
933 m_pcbModel->AddCountersink( via->GetPosition(), backPM.size,
934 backPM.depth, backPM.angle, false, aOrigin );
935 }
936
937 // Add knockouts to all copper layers the feature crosses
938 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( backPM.size, backPM.depth,
939 pmAngle, false );
940 for( const auto& [layer, diameter] : knockouts )
941 {
942 SHAPE_POLY_SET pmPoly;
943 TransformCircleToPolygon( pmPoly, via->GetPosition(), diameter / 2,
944 via->GetMaxError(), ERROR_INSIDE );
945 m_poly_holes[layer].Append( pmPoly );
946 }
947
948 // Add knockout for silkscreen and soldermask on back side (full diameter)
949 SHAPE_POLY_SET pmPoly;
950 TransformCircleToPolygon( pmPoly, via->GetPosition(), backPM.size / 2,
951 via->GetMaxError(), ERROR_INSIDE );
952 m_poly_holes[B_SilkS].Append( pmPoly );
953 m_poly_holes[B_Mask].Append( pmPoly );
954 }
955
956 return true;
957 }
958
959 if( skipCopper )
960 return true;
961
962 PCB_LAYER_ID pcblayer = aTrack->GetLayer();
963
964 if( !m_layersToExport.Contains( pcblayer ) )
965 return false;
966
967 aTrack->TransformShapeToPolygon( m_poly_shapes[pcblayer][aTrack->GetNetname()], pcblayer, 0,
968 aTrack->GetMaxError(), ERROR_INSIDE );
969
970 return true;
971}
972
973
974void EXPORTER_STEP::buildZones3DShape( VECTOR2D aOrigin, bool aSolderMaskOnly )
975{
976 for( ZONE* zone : m_board->Zones() )
977 {
978 LSET layers = zone->GetLayerSet();
979
980 // Filter by net if a net filter is specified and zone is on copper layer(s)
981 if( ( layers & LSET::AllCuMask() ).count() && !netFilterMatches( zone->GetNetname() ) )
982 {
983 continue;
984 }
985
986 for( PCB_LAYER_ID layer : layers )
987 {
988 bool isMaskLayer = ( layer == F_Mask || layer == B_Mask );
989
990 // If we're only processing soldermask zones, skip non-mask layers
991 if( aSolderMaskOnly && !isMaskLayer )
992 continue;
993
994 // If we're doing full zone export, skip mask layers if they'll be handled separately
995 if( !aSolderMaskOnly && isMaskLayer && !m_params.m_ExportZones )
996 continue;
997
998 SHAPE_POLY_SET fill_shape;
999 zone->TransformSolidAreasShapesToPolygon( layer, fill_shape );
1000 fill_shape.Unfracture();
1001
1002 fill_shape.SimplifyOutlines( ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel );
1003
1004 m_poly_shapes[layer][zone->GetNetname()].Append( fill_shape );
1005 }
1006 }
1007}
1008
1009
1011{
1012 PCB_LAYER_ID pcblayer = aItem->GetLayer();
1013 int maxError = aItem->GetMaxError();
1014
1015 if( !m_layersToExport.Contains( pcblayer ) )
1016 return false;
1017
1018 if( IsCopperLayer( pcblayer ) && !m_params.m_ExportTracksVias )
1019 return false;
1020
1021 if( IsInnerCopperLayer( pcblayer ) && !m_params.m_ExportInnerCopper )
1022 return false;
1023
1024 switch( aItem->Type() )
1025 {
1026 case PCB_SHAPE_T:
1027 {
1028 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
1029
1030 if( IsCopperLayer( pcblayer ) && !netFilterMatches( graphic->GetNetname() ) )
1031 return true;
1032
1033 LINE_STYLE lineStyle = graphic->GetLineStyle();
1034
1035 if( lineStyle == LINE_STYLE::SOLID )
1036 {
1037 graphic->TransformShapeToPolySet( m_poly_shapes[pcblayer][graphic->GetNetname()], pcblayer, 0,
1038 maxError, ERROR_INSIDE );
1039 }
1040 else
1041 {
1042 std::vector<SHAPE*> shapes = graphic->MakeEffectiveShapes( true );
1043 const PCB_PLOT_PARAMS& plotParams = m_board->GetPlotOptions();
1044 KIGFX::PCB_RENDER_SETTINGS renderSettings;
1045
1046 renderSettings.SetDashLengthRatio( plotParams.GetDashedLineDashRatio() );
1047 renderSettings.SetGapLengthRatio( plotParams.GetDashedLineGapRatio() );
1048
1049 for( SHAPE* shape : shapes )
1050 {
1051 STROKE_PARAMS::Stroke( shape, lineStyle, graphic->GetWidth(), &renderSettings,
1052 [&]( const VECTOR2I& a, const VECTOR2I& b )
1053 {
1054 SHAPE_SEGMENT seg( a, b, graphic->GetWidth() );
1055 seg.TransformToPolygon( m_poly_shapes[pcblayer][graphic->GetNetname()],
1056 maxError, ERROR_INSIDE );
1057 } );
1058 }
1059
1060 for( SHAPE* shape : shapes )
1061 delete shape;
1062 }
1063
1064 if( graphic->IsHatchedFill() )
1065 m_poly_shapes[pcblayer][graphic->GetNetname()].Append( graphic->GetHatching() );
1066
1067 if( m_params.m_ExportSoldermask && graphic->IsOnLayer( F_Mask ) )
1068 {
1069 graphic->TransformShapeToPolygon( m_poly_shapes[F_Mask][wxEmptyString], F_Mask,
1070 graphic->GetSolderMaskExpansion(), maxError, ERROR_INSIDE );
1071 }
1072
1073 if( m_params.m_ExportSoldermask && graphic->IsOnLayer( B_Mask ) )
1074 {
1075 graphic->TransformShapeToPolygon( m_poly_shapes[B_Mask][wxEmptyString], B_Mask,
1076 graphic->GetSolderMaskExpansion(), maxError, ERROR_INSIDE );
1077 }
1078
1079 break;
1080 }
1081
1082 case PCB_TEXT_T:
1083 {
1084 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
1085
1086 text->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
1087 break;
1088 }
1089
1090 case PCB_BARCODE_T:
1091 {
1092 PCB_BARCODE* barcode = static_cast<PCB_BARCODE*>( aItem );
1093
1094 barcode->TransformShapeToPolySet( m_poly_shapes[pcblayer][wxEmptyString], pcblayer, 0, maxError,
1095 ERROR_INSIDE );
1096 break;
1097 }
1098
1099 case PCB_TEXTBOX_T:
1100 {
1101 PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( aItem );
1102
1103 // border
1104 if( textbox->IsBorderEnabled() )
1105 {
1106 textbox->PCB_SHAPE::TransformShapeToPolygon( m_poly_shapes[pcblayer][wxEmptyString], pcblayer, 0,
1107 maxError, ERROR_INSIDE );
1108 }
1109
1110 // text
1111 textbox->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
1112 break;
1113 }
1114
1115 case PCB_TABLE_T:
1116 {
1117 PCB_TABLE* table = static_cast<PCB_TABLE*>( aItem );
1118
1119 for( PCB_TABLECELL* cell : table->GetCells() )
1120 {
1121 cell->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
1122 }
1123
1124 table->DrawBorders(
1125 [&]( const VECTOR2I& ptA, const VECTOR2I& ptB, const STROKE_PARAMS& stroke )
1126 {
1127 SHAPE_SEGMENT seg( ptA, ptB, stroke.GetWidth() );
1128 seg.TransformToPolygon( m_poly_shapes[pcblayer][wxEmptyString], maxError, ERROR_INSIDE );
1129 } );
1130
1131 break;
1132 }
1133
1134 default: wxFAIL_MSG( "buildGraphic3DShape: unhandled item type" );
1135 }
1136
1137 return true;
1138}
1139
1140
1142{
1143 // Specialize the STEP_PCB_MODEL generator for specific output format
1144 // it can have some minor actions for the generator
1145 switch( m_params.m_Format )
1146 {
1148 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STEP );
1149 break;
1150
1152 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STEPZ );
1153 break;
1154
1156 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_BREP );
1157 break;
1158
1160 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_XAO );
1161 break;
1162
1164 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_GLTF );
1165 break;
1166
1168 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PLY );
1169 break;
1170
1172 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STL );
1173 break;
1174
1176 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_U3D );
1177 break;
1178
1180 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PDF );
1181 break;
1182
1183 default:
1184 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_UNKNOWN );
1185 break;
1186 }
1187}
1188
1189
1191{
1192 if( m_pcbModel )
1193 return true;
1194
1195 SHAPE_POLY_SET pcbOutlines; // stores the board main outlines
1196
1197 if( !m_board->GetBoardPolygonOutlines( pcbOutlines,
1198 /* infer outline if necessary */ true,
1199 /* error handler */ nullptr,
1200 /* allows use arcs in outlines */ true ) )
1201 {
1202 wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) );
1203 }
1204
1205 SHAPE_POLY_SET pcbOutlinesNoArcs = pcbOutlines;
1206 pcbOutlinesNoArcs.ClearArcs();
1207
1208 VECTOR2D origin;
1209
1210 // Determine the coordinate system reference:
1211 // Precedence of reference point is Drill Origin > Grid Origin > User Offset
1212 if( m_params.m_UseDrillOrigin )
1213 origin = m_board->GetDesignSettings().GetAuxOrigin();
1214 else if( m_params.m_UseGridOrigin )
1215 origin = m_board->GetDesignSettings().GetGridOrigin();
1216 else
1217 origin = m_params.m_Origin;
1218
1219 m_pcbModel = std::make_unique<STEP_PCB_MODEL>( m_pcbBaseName, m_reporter );
1220
1222
1223 m_pcbModel->SetCopperColor( m_copperColor.r, m_copperColor.g, m_copperColor.b );
1224 m_pcbModel->SetPadColor( m_padColor.r, m_padColor.g, m_padColor.b );
1225
1226 m_pcbModel->SetStackup( m_board->GetStackupOrDefault() );
1227 m_pcbModel->SetEnabledLayers( m_layersToExport );
1228 m_pcbModel->SetFuseShapes( m_params.m_FuseShapes );
1229 m_pcbModel->SetNetFilter( m_params.m_NetFilter );
1230 m_pcbModel->SetExtraPadThickness( m_params.m_ExtraPadThickness );
1231
1232 // Note: m_params.m_BoardOutlinesChainingEpsilon is used only to build the board outlines,
1233 // not to set OCC chaining epsilon (much smaller)
1234 //
1235 // Set the min distance between 2 points for OCC to see these 2 points as merged
1236 // OCC_MAX_DISTANCE_TO_MERGE_POINTS is acceptable for OCC, otherwise there are issues
1237 // to handle the shapes chaining on copper layers, because the Z dist is 0.035 mm and the
1238 // min dist must be much smaller (we use 0.001 mm giving good results)
1239 m_pcbModel->OCCSetMergeMaxDistance( OCC_MAX_DISTANCE_TO_MERGE_POINTS );
1240
1241 // For copper layers, only pads and tracks are added, because adding everything on copper
1242 // generate unreasonable file sizes and take a unreasonable calculation time.
1243 for( FOOTPRINT* fp : m_board->Footprints() )
1244 buildFootprint3DShapes( fp, origin, &pcbOutlinesNoArcs );
1245
1246 for( PCB_TRACK* track : m_board->Tracks() )
1247 buildTrack3DShape( track, origin );
1248
1249 for( BOARD_ITEM* item : m_board->Drawings() )
1250 buildGraphic3DShape( item, origin );
1251
1252 if( m_params.m_ExportZones )
1253 buildZones3DShape( origin );
1254
1255 // Process zones on soldermask layers even when copper zone export is disabled.
1256 // This ensures mask openings defined by zones are properly exported.
1257 if( m_params.m_ExportSoldermask && !m_params.m_ExportZones )
1258 buildZones3DShape( origin, true );
1259
1260 for( PCB_LAYER_ID pcblayer : m_layersToExport.Seq() )
1261 {
1262 SHAPE_POLY_SET holes = m_poly_holes[pcblayer];
1263 holes.Simplify();
1264
1265 if( pcblayer == F_Mask || pcblayer == B_Mask )
1266 {
1267 // Mask layer is negative
1268 SHAPE_POLY_SET mask = pcbOutlinesNoArcs;
1269
1270 for( auto& [netname, poly] : m_poly_shapes[pcblayer] )
1271 {
1272 poly.Simplify();
1273
1274 poly.SimplifyOutlines( pcbIUScale.mmToIU( 0.003 ) );
1275 poly.Simplify();
1276
1277 mask.BooleanSubtract( poly );
1278 }
1279
1280 mask.BooleanSubtract( holes );
1281
1282 m_pcbModel->AddPolygonShapes( &mask, pcblayer, origin, wxEmptyString );
1283 }
1284 else
1285 {
1286 for( auto& [netname, poly] : m_poly_shapes[pcblayer] )
1287 {
1288 poly.Simplify();
1289
1290 poly.SimplifyOutlines( pcbIUScale.mmToIU( 0.003 ) );
1291 poly.Simplify();
1292
1293 // Subtract holes
1294 poly.BooleanSubtract( holes );
1295
1296 // Clip to board outline
1297 poly.BooleanIntersection( pcbOutlinesNoArcs );
1298
1299 m_pcbModel->AddPolygonShapes( &poly, pcblayer, origin, netname );
1300 }
1301 }
1302 }
1303
1304 m_reporter->Report( wxT( "Create PCB solid model.\n" ), RPT_SEVERITY_DEBUG );
1305
1306 m_reporter->Report( wxString::Format( wxT( "Board outline: found %d initial points.\n" ),
1307 pcbOutlines.FullPointCount() ),
1309
1310 if( !m_pcbModel->CreatePCB( pcbOutlines, origin, m_params.m_ExportBoardBody ) )
1311 {
1312 m_reporter->Report( _( "Could not create PCB solid model.\n" ), RPT_SEVERITY_ERROR );
1313 return false;
1314 }
1315
1316 return true;
1317}
1318
1319
1321{
1322 // Display the export time, for statistics
1323 int64_t stats_startExportTime = GetRunningMicroSecs();
1324
1325 // setup opencascade message log
1326 struct SCOPED_PRINTER
1327 {
1328 Handle( Message_Printer ) m_handle;
1329
1330 SCOPED_PRINTER( const Handle( Message_Printer ) & aHandle ) : m_handle( aHandle )
1331 {
1332 Message::DefaultMessenger()->AddPrinter( m_handle );
1333 };
1334
1335 ~SCOPED_PRINTER() { Message::DefaultMessenger()->RemovePrinter( m_handle ); }
1336 };
1337
1338 Message::DefaultMessenger()->RemovePrinters( STANDARD_TYPE( Message_PrinterOStream ) );
1339 SCOPED_PRINTER occtPrinter( new KICAD_PRINTER( m_reporter ) );
1340
1341 m_reporter->Report( wxT( "Determining PCB data.\n" ), RPT_SEVERITY_DEBUG );
1342
1343 if( m_params.m_OutputFile.IsEmpty() )
1344 {
1345 wxFileName fn = m_board->GetFileName();
1346 fn.SetName( fn.GetName() );
1347 fn.SetExt( m_params.GetDefaultExportExtension() );
1348
1349 m_params.m_OutputFile = fn.GetFullName();
1350 }
1351
1353
1354 if( m_params.m_ExportInnerCopper )
1356
1357 if( m_params.m_ExportSilkscreen )
1358 {
1361 }
1362
1363 if( m_params.m_ExportSoldermask )
1364 {
1365 m_layersToExport.set( F_Mask );
1366 m_layersToExport.set( B_Mask );
1367 }
1368
1369 m_layersToExport &= m_board->GetEnabledLayers();
1370
1371 try
1372 {
1373 m_reporter->Report( wxString::Format( wxT( "Build %s data.\n" ), m_params.GetFormatName() ),
1375
1376 if( !buildBoard3DShapes() )
1377 {
1378 m_reporter->Report( _( "\n"
1379 "** Error building STEP board model. Export aborted. **\n" ),
1381 return false;
1382 }
1383
1384 m_reporter->Report( wxString::Format( wxT( "Writing %s file.\n" ), m_params.GetFormatName() ),
1386
1387 bool success = true;
1389 success = m_pcbModel->WriteSTEP( m_outputFile, m_params.m_OptimizeStep, false );
1390 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STEPZ )
1391 success = m_pcbModel->WriteSTEP( m_outputFile, m_params.m_OptimizeStep, true );
1392 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::BREP )
1393 success = m_pcbModel->WriteBREP( m_outputFile );
1394 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::XAO )
1395 success = m_pcbModel->WriteXAO( m_outputFile );
1396 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::GLB )
1397 success = m_pcbModel->WriteGLTF( m_outputFile );
1398 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PLY )
1399 success = m_pcbModel->WritePLY( m_outputFile );
1400 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STL )
1401 success = m_pcbModel->WriteSTL( m_outputFile );
1402 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::U3D )
1403 success = m_pcbModel->WriteU3D( m_outputFile );
1404 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PDF )
1405 success = m_pcbModel->WritePDF( m_outputFile );
1406
1407 if( !success )
1408 {
1409 m_reporter->Report( wxString::Format( _( "\n"
1410 "** Error writing %s file. **\n" ),
1411 m_params.GetFormatName() ),
1413 return false;
1414 }
1415 else
1416 {
1417 m_reporter->Report( wxString::Format( wxT( "%s file '%s' created.\n" ),
1418 m_params.GetFormatName(),
1419 m_outputFile ),
1421 }
1422 }
1423 catch( const std::bad_alloc& )
1424 {
1425 m_reporter->Report( wxString::Format( _( "\n** Out of memory while exporting %s file. **\n"
1426 "The board may have too many objects (e.g., vias, tracks, components) "
1427 "to process with available system memory.\n"
1428 "Try disabling 'Fuse Shapes' option, reducing board complexity, "
1429 "or freeing up system memory.\n" ),
1430 m_params.GetFormatName() ),
1432 return false;
1433 }
1434 catch( const Standard_Failure& e )
1435 {
1436 wxString errorMsg = e.GetMessageString();
1437 m_reporter->Report( wxString::Format( _( "\nOpenCASCADE error: %s\n" ), errorMsg ),
1439
1440 // Check if this might be memory-related based on common OCC error patterns
1441 if( errorMsg.Contains( "alloc" ) || errorMsg.Contains( "memory" ) ||
1442 errorMsg.IsEmpty() )
1443 {
1444 m_reporter->Report( _( "This error may indicate insufficient memory. Consider disabling "
1445 "'Fuse Shapes', reducing the number of vias/components, or freeing "
1446 "system memory.\n" ),
1448 }
1449
1450 m_reporter->Report( wxString::Format( _( "** Error exporting %s file. Export aborted. **\n" ),
1451 m_params.GetFormatName() ),
1453 return false;
1454 }
1455 #ifndef DEBUG
1456 catch( ... )
1457 {
1458 m_reporter->Report( wxString::Format( _( "\n** Unexpected error while exporting %s file. **\n"
1459 "This may be caused by insufficient system memory, especially "
1460 "when exporting boards with many vias or components with 'Fuse Shapes' enabled.\n"
1461 "Try disabling 'Fuse Shapes', reducing board complexity, "
1462 "or freeing up system memory.\n" ),
1463 m_params.GetFormatName() ),
1465 return false;
1466 }
1467 #endif
1468
1469 // Display calculation time in seconds
1470 double calculation_time = (double)( GetRunningMicroSecs() - stats_startExportTime) / 1e6;
1471 m_reporter->Report( wxString::Format( _( "\n"
1472 "Export time %.3f s\n" ),
1473 calculation_time ),
1475
1476 return !m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR );
1477}
void ApplyExtrusionTransform(SHAPE_POLY_SET &aOutline, const EXTRUDED_3D_BODY *aBody, const VECTOR2I &aFpPos)
Apply 2D extrusion transforms (rotation, scale, offset) to an outline.
bool GetExtrusionOutline(const FOOTPRINT *aFootprint, SHAPE_POLY_SET &aOutline, PCB_LAYER_ID aLayerOverride)
Get the extrusion outline polygon for a footprint in board coordinates.
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:268
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:461
int GetMaxError() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:323
const wxString & GetFileName() const
Definition board.h:360
PROJECT * GetProject() const
Definition board.h:587
double AsRadians() const
Definition eda_angle.h:120
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:112
const SHAPE_POLY_SET & GetHatching() const
virtual std::vector< SHAPE * > MakeEffectiveShapes(bool aEdgeOnly=false) const
Make a set of SHAPE objects representing the EDA_SHAPE.
Definition eda_shape.h:457
bool IsHatchedFill() const
Definition eda_shape.h:144
LINE_STYLE GetLineStyle() const
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).
bool netFilterMatches(const wxString &netname) const
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
void buildZones3DShape(VECTOR2D aOrigin, bool aSolderMaskOnly=false)
KIGFX::COLOR4D m_color
Definition footprint.h:110
VECTOR3D m_offset
Definition footprint.h:116
static KIGFX::COLOR4D GetDefaultColor(EXTRUSION_MATERIAL aMaterial)
Definition footprint.h:118
static uint32_t PackColorKey(const KIGFX::COLOR4D &aColor)
Definition footprint.h:137
VECTOR3D m_scale
Definition footprint.h:114
EXTRUSION_MATERIAL m_material
Definition footprint.h:111
EDA_ANGLE GetOrientation() const
Definition footprint.h:408
const EXTRUDED_3D_BODY * GetExtrudedBody() const
Definition footprint.h:398
bool HasExtrudedBody() const
Definition footprint.h:397
std::deque< PAD * > & Pads()
Definition footprint.h:377
int GetAttributes() const
Definition footprint.h:495
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:417
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:429
bool GetDNPForVariant(const wxString &aVariantName) const
Get the DNP status for a specific variant.
std::vector< FP_3DMODEL > & Models()
Definition footprint.h:394
const wxString & GetReference() const
Definition footprint.h:829
EMBEDDED_FILES * GetEmbeddedFiles() override
Definition footprint.h:1293
VECTOR2I GetPosition() const override
Definition footprint.h:405
REPORTER * m_reporter
virtual void Send(const TCollection_AsciiString &theString, const Message_Gravity theGravity, const bool theToPutEol) const override
SEVERITY getSeverity(const Message_Gravity theGravity) const
KICAD_PRINTER(REPORTER *aReporter)
virtual void Send(const TCollection_ExtendedString &theString, const Message_Gravity theGravity, const bool theToPutEol) const override
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:402
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)
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:634
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:599
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:577
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:360
DRILL_PROPS & TertiaryDrill()
Definition padstack.h:357
DRILL_PROPS & SecondaryDrill()
Definition padstack.h:354
POST_MACHINING_PROPS & BackPostMachining()
Definition padstack.h:363
Definition pad.h:65
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:75
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)
int OutlineCount() const
Return the number of outlines in the set.
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:128
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:86
@ FP_THROUGH_HOLE
Definition footprint.h:85
static const std::string KiCadUriPrefix
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:679
bool IsInnerCopperLayer(int aLayerId)
Test whether a layer is an inner (In1_Cu to In30_Cu) copper layer.
Definition layer_ids.h:701
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:266
PCB_LAYER_ID start
Definition padstack.h:269
PCB_LAYER_ID end
Definition padstack.h:270
VECTOR2I size
Drill diameter (x == y) or slot dimensions (x != y)
Definition padstack.h:267
std::optional< PAD_DRILL_POST_MACHINING_MODE > mode
Definition padstack.h:281
std::vector< std::vector< std::string > > table
#define M_PI
wxLogTrace helper definitions.
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:85
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:94
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:90
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:89
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:98
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:91
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
VECTOR2< double > VECTOR2D
Definition vector2d.h:686
VECTOR3< double > VECTOR3D
Definition vector3.h:230
Definition of file extensions used in Kicad.