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
258 //if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
259 //{
260 // m_poly_holes[F_SilkS].Append( holePoly );
261 // m_poly_holes[B_SilkS].Append( holePoly );
262 //}
263
264 // Handle backdrills - secondary and tertiary drills defined in the padstack
265 const PADSTACK& padstack = pad->Padstack();
266 const PADSTACK::DRILL_PROPS& secondaryDrill = padstack.SecondaryDrill();
267 const PADSTACK::DRILL_PROPS& tertiaryDrill = padstack.TertiaryDrill();
268
269 // Process secondary drill (typically bottom backdrill)
270 if( secondaryDrill.size.x > 0 )
271 {
272 SHAPE_SEGMENT backdrillShape( pad->GetPosition(), pad->GetPosition(),
273 secondaryDrill.size.x );
274 m_pcbModel->AddBackdrill( backdrillShape, secondaryDrill.start,
275 secondaryDrill.end, aOrigin );
276
277 // Add backdrill holes to affected copper layers for 2D polygon subtraction
278 SHAPE_POLY_SET backdrillPoly;
279 backdrillShape.TransformToPolygon( backdrillPoly, pad->GetMaxError(), ERROR_INSIDE );
280
281 for( PCB_LAYER_ID layer : pad->GetLayerSet() )
282 {
283 if( isLayerInBackdrillSpan( layer, secondaryDrill.start, secondaryDrill.end ) )
284 m_poly_holes[layer].Append( backdrillPoly );
285 }
286
287 // Add knockouts for silkscreen and soldermask on the backdrill side
288 if( isLayerInBackdrillSpan( F_Cu, secondaryDrill.start, secondaryDrill.end ) )
289 {
290 m_poly_holes[F_SilkS].Append( backdrillPoly );
291 m_poly_holes[F_Mask].Append( backdrillPoly );
292 }
293 if( isLayerInBackdrillSpan( B_Cu, secondaryDrill.start, secondaryDrill.end ) )
294 {
295 m_poly_holes[B_SilkS].Append( backdrillPoly );
296 m_poly_holes[B_Mask].Append( backdrillPoly );
297 }
298 }
299
300 // Process tertiary drill (typically top backdrill)
301 if( tertiaryDrill.size.x > 0 )
302 {
303 SHAPE_SEGMENT backdrillShape( pad->GetPosition(), pad->GetPosition(),
304 tertiaryDrill.size.x );
305 m_pcbModel->AddBackdrill( backdrillShape, tertiaryDrill.start,
306 tertiaryDrill.end, aOrigin );
307
308 // Add backdrill holes to affected copper layers for 2D polygon subtraction
309 SHAPE_POLY_SET backdrillPoly;
310 backdrillShape.TransformToPolygon( backdrillPoly, pad->GetMaxError(), ERROR_INSIDE );
311
312 for( PCB_LAYER_ID layer : pad->GetLayerSet() )
313 {
314 if( isLayerInBackdrillSpan( layer, tertiaryDrill.start, tertiaryDrill.end ) )
315 m_poly_holes[layer].Append( backdrillPoly );
316 }
317
318 // Add knockouts for silkscreen and soldermask on the backdrill side
319 if( isLayerInBackdrillSpan( F_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
320 {
321 m_poly_holes[F_SilkS].Append( backdrillPoly );
322 m_poly_holes[F_Mask].Append( backdrillPoly );
323 }
324 if( isLayerInBackdrillSpan( B_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
325 {
326 m_poly_holes[B_SilkS].Append( backdrillPoly );
327 m_poly_holes[B_Mask].Append( backdrillPoly );
328 }
329 }
330
331 // Process post-machining (counterbore/countersink) on front and back
332 const PADSTACK::POST_MACHINING_PROPS& frontPM = padstack.FrontPostMachining();
333 const PADSTACK::POST_MACHINING_PROPS& backPM = padstack.BackPostMachining();
334
335 wxLogTrace( traceKiCad2Step, wxT( "PAD post-machining check: frontPM.mode.has_value=%d frontPM.size=%d frontPM.depth=%d frontPM.angle=%d" ),
336 frontPM.mode.has_value() ? 1 : 0, frontPM.size, frontPM.depth, frontPM.angle );
337 wxLogTrace( traceKiCad2Step, wxT( "PAD post-machining check: backPM.mode.has_value=%d backPM.size=%d backPM.depth=%d backPM.angle=%d" ),
338 backPM.mode.has_value() ? 1 : 0, backPM.size, backPM.depth, backPM.angle );
339
340 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
341 bool frontPMValid = frontPM.mode.has_value() && frontPM.size > 0 &&
342 ( ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && frontPM.depth > 0 ) ||
343 ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && frontPM.angle > 0 ) );
344
345 if( frontPMValid )
346 {
347 wxLogTrace( traceKiCad2Step, wxT( "PAD front post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
348 static_cast<int>( *frontPM.mode ) );
349
350 int pmAngle = ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? frontPM.angle : 0;
351
353 {
354 m_pcbModel->AddCounterbore( pad->GetPosition(), frontPM.size,
355 frontPM.depth, true, aOrigin );
356 }
358 {
359 m_pcbModel->AddCountersink( pad->GetPosition(), frontPM.size,
360 frontPM.depth, frontPM.angle, true, aOrigin );
361 }
362
363 // Add knockouts to all copper layers the feature crosses
364 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( frontPM.size, frontPM.depth,
365 pmAngle, true );
366 for( const auto& [layer, diameter] : knockouts )
367 {
368 SHAPE_POLY_SET pmPoly;
369 TransformCircleToPolygon( pmPoly, pad->GetPosition(), diameter / 2,
370 pad->GetMaxError(), ERROR_INSIDE );
371 m_poly_holes[layer].Append( pmPoly );
372 }
373
374 // Add knockout for silkscreen and soldermask on front side (full diameter)
375 SHAPE_POLY_SET pmPoly;
376 TransformCircleToPolygon( pmPoly, pad->GetPosition(), frontPM.size / 2,
377 pad->GetMaxError(), ERROR_INSIDE );
378 m_poly_holes[F_SilkS].Append( pmPoly );
379 m_poly_holes[F_Mask].Append( pmPoly );
380 }
381
382 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
383 bool backPMValid = backPM.mode.has_value() && backPM.size > 0 &&
384 ( ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && backPM.depth > 0 ) ||
385 ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && backPM.angle > 0 ) );
386
387 if( backPMValid )
388 {
389 wxLogTrace( traceKiCad2Step, wxT( "PAD back post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
390 static_cast<int>( *backPM.mode ) );
391
392 int pmAngle = ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? backPM.angle : 0;
393
395 {
396 m_pcbModel->AddCounterbore( pad->GetPosition(), backPM.size,
397 backPM.depth, false, aOrigin );
398 }
400 {
401 m_pcbModel->AddCountersink( pad->GetPosition(), backPM.size,
402 backPM.depth, backPM.angle, false, aOrigin );
403 }
404
405 // Add knockouts to all copper layers the feature crosses
406 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( backPM.size, backPM.depth,
407 pmAngle, false );
408 for( const auto& [layer, diameter] : knockouts )
409 {
410 SHAPE_POLY_SET pmPoly;
411 TransformCircleToPolygon( pmPoly, pad->GetPosition(), diameter / 2,
412 pad->GetMaxError(), ERROR_INSIDE );
413 m_poly_holes[layer].Append( pmPoly );
414 }
415
416 // Add knockout for silkscreen and soldermask on back side (full diameter)
417 SHAPE_POLY_SET pmPoly;
418 TransformCircleToPolygon( pmPoly, pad->GetPosition(), backPM.size / 2,
419 pad->GetMaxError(), ERROR_INSIDE );
420 m_poly_holes[B_SilkS].Append( pmPoly );
421 m_poly_holes[B_Mask].Append( pmPoly );
422 }
423 }
424
425 if( !netFilterMatches( pad->GetNetname() ) )
426 continue;
427
428 if( m_params.m_ExportPads )
429 {
430 if( m_pcbModel->AddPadShape( pad, aOrigin, false, castellated ? aClipPolygon : nullptr) )
431 hasdata = true;
432
433 if( m_params.m_ExportSoldermask )
434 {
435 for( PCB_LAYER_ID pcblayer : pad->GetLayerSet() )
436 {
437 if( pcblayer != F_Mask && pcblayer != B_Mask )
438 continue;
439
440 SHAPE_POLY_SET poly;
441 PCB_LAYER_ID cuLayer = ( pcblayer == F_Mask ) ? F_Cu : B_Cu;
442 pad->TransformShapeToPolygon( poly, cuLayer, pad->GetSolderMaskExpansion( cuLayer ),
443 pad->GetMaxError(), ERROR_INSIDE );
444
445 m_poly_shapes[pcblayer][wxEmptyString].Append( poly );
446 }
447 }
448 }
449
450 padsMatchingNetFilter.push_back( pad );
451 }
452
453 // Build 3D shapes of the footprint graphic items:
454 for( PCB_LAYER_ID pcblayer : m_layersToExport )
455 {
456 if( IsCopperLayer( pcblayer ) && !m_params.m_ExportTracksVias )
457 continue;
458
459 SHAPE_POLY_SET buffer;
460
461 aFootprint->TransformFPShapesToPolySet( buffer, pcblayer, 0, aFootprint->GetMaxError(), ERROR_INSIDE,
462 true, /* include text */
463 true, /* include shapes */
464 false /* include private items */ );
465
466 if( !IsCopperLayer( pcblayer ) )
467 {
468 m_poly_shapes[pcblayer][wxEmptyString].Append( buffer );
469 }
470 else
471 {
472 std::map<const SHAPE_POLY_SET::POLYGON*, PAD*> polyPadMap;
473
474 // Only add polygons colliding with any matching pads
475 for( const SHAPE_POLY_SET::POLYGON& poly : buffer.CPolygons() )
476 {
477 for( PAD* pad : padsMatchingNetFilter )
478 {
479 if( !pad->IsOnLayer( pcblayer ) )
480 continue;
481
482 std::shared_ptr<SHAPE_POLY_SET> padPoly = pad->GetEffectivePolygon( pcblayer );
483 SHAPE_POLY_SET gfxPoly( poly );
484
485 if( padPoly->Collide( &gfxPoly ) )
486 {
487 polyPadMap[&poly] = pad;
488 m_poly_shapes[pcblayer][pad->GetNetname()].Append( gfxPoly );
489 break;
490 }
491 }
492 }
493
494 if( m_params.m_NetFilter.empty() )
495 {
496 // Add polygons with no net
497 for( const SHAPE_POLY_SET::POLYGON& poly : buffer.CPolygons() )
498 {
499 auto it = polyPadMap.find( &poly );
500
501 if( it == polyPadMap.end() )
502 m_poly_shapes[pcblayer][wxEmptyString].Append( poly );
503 }
504 }
505 }
506 }
507
508 if( ( !(aFootprint->GetAttributes() & (FP_THROUGH_HOLE|FP_SMD)) ) && !m_params.m_IncludeUnspecified )
509 {
510 return hasdata;
511 }
512
513 if( aFootprint->GetDNPForVariant( m_board ? m_board->GetCurrentVariant() : wxString() )
514 && !m_params.m_IncludeDNP )
515 {
516 return hasdata;
517 }
518
519 // Prefetch the library for this footprint
520 // In case we need to resolve relative footprint paths
521 wxString libraryName = aFootprint->GetFPID().GetLibNickname();
522 wxString footprintBasePath = wxEmptyString;
523
524 double posX = aFootprint->GetPosition().x - aOrigin.x;
525 double posY = (aFootprint->GetPosition().y) - aOrigin.y;
526
527 if( m_board->GetProject() )
528 {
529 std::optional<LIBRARY_TABLE_ROW*> fpRow =
530 PROJECT_PCB::FootprintLibAdapter( m_board->GetProject() )->GetRow( libraryName );
531 if( fpRow )
532 footprintBasePath = LIBRARY_MANAGER::GetFullURI( *fpRow, true );
533 }
534
535 // Exit early if we don't want to include footprint models
536 if( m_params.m_BoardOnly || !m_params.m_ExportComponents )
537 {
538 return hasdata;
539 }
540
541 bool componentFilter = !m_params.m_ComponentFilter.IsEmpty();
542 std::vector<wxString> componentFilterPatterns;
543
544 if( componentFilter )
545 {
546 wxStringTokenizer tokenizer( m_params.m_ComponentFilter, ", \t\r\n", wxTOKEN_STRTOK );
547
548 while( tokenizer.HasMoreTokens() )
549 componentFilterPatterns.push_back( tokenizer.GetNextToken() );
550
551 bool found = false;
552
553 for( const wxString& pattern : componentFilterPatterns )
554 {
555 if( aFootprint->GetReference().Matches( pattern ) )
556 {
557 found = true;
558 break;
559 }
560 }
561
562 if( !found )
563 return hasdata;
564 }
565
566 VECTOR2D newpos( pcbIUScale.IUTomm( posX ), pcbIUScale.IUTomm( posY ) );
567
568 for( const FP_3DMODEL& fp_model : aFootprint->Models() )
569 {
570 if( !fp_model.m_Show || fp_model.m_Filename.empty() )
571 continue;
572
573 std::vector<wxString> searchedPaths;
574 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
575 embeddedFilesStack.push_back( aFootprint->GetEmbeddedFiles() );
576 embeddedFilesStack.push_back( m_board->GetEmbeddedFiles() );
577
578 wxString mainPath = m_resolver->ResolvePath( fp_model.m_Filename, footprintBasePath,
579 embeddedFilesStack );
580
581 if( mainPath.empty() || !wxFileName::FileExists( mainPath ) )
582 {
583 // the error path will return an empty name sometimes, at least report back the original filename
584 if( mainPath.empty() )
585 mainPath = fp_model.m_Filename;
586
587 m_reporter->Report( wxString::Format( _( "Could not add 3D model for %s.\n"
588 "File not found: %s\n" ),
589 aFootprint->GetReference(), mainPath ),
591 continue;
592 }
593
594 wxString baseName =
595 fp_model.m_Filename.AfterLast( '/' ).AfterLast( '\\' ).BeforeLast( '.' );
596
597 std::vector<wxString> altFilenames;
598
599 // Add embedded files to alternative filenames
600 if( fp_model.m_Filename.StartsWith( FILEEXT::KiCadUriPrefix + "://" ) )
601 {
602 for( const EMBEDDED_FILES* filesPtr : embeddedFilesStack )
603 {
604 const auto& map = filesPtr->EmbeddedFileMap();
605
606 for( auto& [fname, file] : map )
607 {
608 if( fname.BeforeLast( '.' ) == baseName )
609 {
610 wxFileName temp_file = filesPtr->GetTemporaryFileName( fname );
611
612 if( !temp_file.IsOk() )
613 continue;
614
615 wxString altPath = temp_file.GetFullPath();
616
617 if( mainPath == altPath )
618 continue;
619
620 altFilenames.emplace_back( altPath );
621 }
622 }
623 }
624 }
625
626 try
627 {
628 bool bottomSide = aFootprint->GetLayer() == B_Cu;
629
630 // the rotation is stored in degrees but opencascade wants radians
631 VECTOR3D modelRot = fp_model.m_Rotation;
632 modelRot *= M_PI;
633 modelRot /= 180.0;
634
635 if( m_pcbModel->AddComponent(
636 baseName, mainPath, altFilenames, aFootprint->GetReference(), bottomSide,
637 newpos, aFootprint->GetOrientation().AsRadians(), fp_model.m_Offset,
638 modelRot, fp_model.m_Scale, m_params.m_SubstModels ) )
639 {
640 hasdata = true;
641 }
642 }
643 catch( const Standard_Failure& e )
644 {
645 m_reporter->Report( wxString::Format( _( "Could not add 3D model for %s.\n"
646 "OpenCASCADE error: %s\n" ),
647 aFootprint->GetReference(),
648 e.GetMessageString() ),
650 }
651
652 }
653
654 if( aFootprint->HasExtrudedBody() && aFootprint->GetExtrudedBody()->m_show )
655 {
656 const EXTRUDED_3D_BODY* body = aFootprint->GetExtrudedBody();
657 SHAPE_POLY_SET outline;
658
659 if( GetExtrusionOutline( aFootprint, outline ) && outline.OutlineCount() > 0 )
660 {
661 VECTOR2I fpPos = aFootprint->GetPosition();
662 ApplyExtrusionTransform( outline, body, fpPos );
663
664 bool bottomSide = aFootprint->GetLayer() == B_Cu;
665 double standoff = pcbIUScale.IUTomm( body->m_standoff ) + body->m_offset.z;
666 double bodyThickness = pcbIUScale.IUTomm( body->m_height - body->m_standoff ) * body->m_scale.z;
667 double height = standoff + bodyThickness;
668
669 KIGFX::COLOR4D c = body->m_color;
670
673
674 uint32_t colorKey = EXTRUDED_3D_BODY::PackColorKey( c );
675
676 try
677 {
678 if( m_pcbModel->AddExtrudedBody( outline, bottomSide, standoff, height, aOrigin, colorKey,
679 body->m_material, aFootprint->GetReference() ) )
680 {
681 hasdata = true;
682 }
683 }
684 catch( const Standard_Failure& e )
685 {
686 m_reporter->Report( wxString::Format( _( "Could not add extruded body for %s.\n"
687 "OpenCASCADE error: %s\n" ),
688 aFootprint->GetReference(), e.GetMessageString() ),
690 }
691
692 // Add metallic pin extrusions for through-hole pads
693 if( standoff > 0.0 )
694 {
695 try
696 {
697 m_pcbModel->AddExtrudedPins( aFootprint, bottomSide, standoff, aOrigin );
698 }
699 catch( const Standard_Failure& e )
700 {
701 m_reporter->Report( wxString::Format( _( "Could not add extruded pins for %s.\n"
702 "OpenCASCADE error: %s\n" ),
703 aFootprint->GetReference(), e.GetMessageString() ),
705 }
706 }
707 }
708 }
709
710 return hasdata;
711}
712
713
715{
716 bool skipCopper = !m_params.m_ExportTracksVias || !netFilterMatches( aTrack->GetNetname() );
717
718 if( m_params.m_ExportSoldermask && aTrack->IsOnLayer( F_Mask ) )
719 {
720 aTrack->TransformShapeToPolygon( m_poly_shapes[F_Mask][wxEmptyString], F_Mask,
721 aTrack->GetSolderMaskExpansion(), aTrack->GetMaxError(),
722 ERROR_INSIDE );
723 }
724
725 if( m_params.m_ExportSoldermask && aTrack->IsOnLayer( B_Mask ) )
726 {
727 aTrack->TransformShapeToPolygon( m_poly_shapes[B_Mask][wxEmptyString], B_Mask,
728 aTrack->GetSolderMaskExpansion(), aTrack->GetMaxError(),
729 ERROR_INSIDE );
730 }
731
732 if( aTrack->Type() == PCB_VIA_T )
733 {
734 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
735
736 std::shared_ptr<SHAPE_SEGMENT> holeShape = via->GetEffectiveHoleShape();
737 SHAPE_POLY_SET holePoly;
738 holeShape->TransformToPolygon( holePoly, via->GetMaxError(), ERROR_INSIDE );
739
740 // This helps with fusing
741 holePoly.Deflate( m_platingThickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, via->GetMaxError() );
742
743 LSET layers( via->GetLayerSet() & m_layersToExport );
744
745 PCB_LAYER_ID top_layer, bot_layer;
746 via->LayerPair( &top_layer, &bot_layer );
747
748 if( !skipCopper )
749 {
750 for( PCB_LAYER_ID pcblayer : layers )
751 {
752 const std::shared_ptr<SHAPE>& shape = via->GetEffectiveShape( pcblayer );
753
754 SHAPE_POLY_SET poly;
755 shape->TransformToPolygon( poly, via->GetMaxError(), ERROR_INSIDE );
756 m_poly_shapes[pcblayer][via->GetNetname()].Append( poly );
757 m_poly_holes[pcblayer].Append( holePoly );
758 }
759
760 m_pcbModel->AddBarrel( *holeShape, top_layer, bot_layer, true, aOrigin, via->GetNetname() );
761 }
762
764 //if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
765 //{
766 // m_poly_holes[F_SilkS].Append( holePoly );
767 // m_poly_holes[B_SilkS].Append( holePoly );
768 //}
769
770 // Cut via holes in soldermask when the via is not tented.
771 // This ensures the mask has a proper hole through the via drill, not just the annular ring opening.
772 if( m_params.m_ExportSoldermask )
773 {
774 if( via->IsOnLayer( F_Mask ) )
775 m_poly_holes[F_Mask].Append( holePoly );
776
777 if( via->IsOnLayer( B_Mask ) )
778 m_poly_holes[B_Mask].Append( holePoly );
779 }
780
781 m_pcbModel->AddHole( *holeShape, m_platingThickness, top_layer, bot_layer, true, aOrigin,
782 !m_params.m_FillAllVias, m_params.m_CutViasInBody );
783
784 // Handle via backdrills - secondary and tertiary drills defined in the padstack
785 const PADSTACK& padstack = via->Padstack();
786 const PADSTACK::DRILL_PROPS& secondaryDrill = padstack.SecondaryDrill();
787 const PADSTACK::DRILL_PROPS& tertiaryDrill = padstack.TertiaryDrill();
788
789 // Process secondary drill (typically bottom backdrill)
790 if( secondaryDrill.size.x > 0 )
791 {
792 SHAPE_SEGMENT backdrillShape( via->GetPosition(), via->GetPosition(),
793 secondaryDrill.size.x );
794 m_pcbModel->AddBackdrill( backdrillShape, secondaryDrill.start,
795 secondaryDrill.end, aOrigin );
796
797 // Add backdrill holes to affected copper layers for 2D polygon subtraction
798 SHAPE_POLY_SET backdrillPoly;
799 backdrillShape.TransformToPolygon( backdrillPoly, via->GetMaxError(), ERROR_INSIDE );
800
801 for( PCB_LAYER_ID layer : via->GetLayerSet() )
802 {
803 if( isLayerInBackdrillSpan( layer, secondaryDrill.start, secondaryDrill.end ) )
804 m_poly_holes[layer].Append( backdrillPoly );
805 }
806
807 // Add knockouts for silkscreen and soldermask on the backdrill side
808 if( isLayerInBackdrillSpan( F_Cu, secondaryDrill.start, secondaryDrill.end ) )
809 {
810 m_poly_holes[F_SilkS].Append( backdrillPoly );
811 m_poly_holes[F_Mask].Append( backdrillPoly );
812 }
813 if( isLayerInBackdrillSpan( B_Cu, secondaryDrill.start, secondaryDrill.end ) )
814 {
815 m_poly_holes[B_SilkS].Append( backdrillPoly );
816 m_poly_holes[B_Mask].Append( backdrillPoly );
817 }
818 }
819
820 // Process tertiary drill (typically top backdrill)
821 if( tertiaryDrill.size.x > 0 )
822 {
823 SHAPE_SEGMENT backdrillShape( via->GetPosition(), via->GetPosition(),
824 tertiaryDrill.size.x );
825 m_pcbModel->AddBackdrill( backdrillShape, tertiaryDrill.start,
826 tertiaryDrill.end, aOrigin );
827
828 // Add backdrill holes to affected copper layers for 2D polygon subtraction
829 SHAPE_POLY_SET backdrillPoly;
830 backdrillShape.TransformToPolygon( backdrillPoly, via->GetMaxError(), ERROR_INSIDE );
831
832 for( PCB_LAYER_ID layer : via->GetLayerSet() )
833 {
834 if( isLayerInBackdrillSpan( layer, tertiaryDrill.start, tertiaryDrill.end ) )
835 m_poly_holes[layer].Append( backdrillPoly );
836 }
837
838 // Add knockouts for silkscreen and soldermask on the backdrill side
839 if( isLayerInBackdrillSpan( F_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
840 {
841 m_poly_holes[F_SilkS].Append( backdrillPoly );
842 m_poly_holes[F_Mask].Append( backdrillPoly );
843 }
844 if( isLayerInBackdrillSpan( B_Cu, tertiaryDrill.start, tertiaryDrill.end ) )
845 {
846 m_poly_holes[B_SilkS].Append( backdrillPoly );
847 m_poly_holes[B_Mask].Append( backdrillPoly );
848 }
849 }
850
851 // Process post-machining (counterbore/countersink) on front and back
852 const PADSTACK::POST_MACHINING_PROPS& frontPM = padstack.FrontPostMachining();
853 const PADSTACK::POST_MACHINING_PROPS& backPM = padstack.BackPostMachining();
854
855 wxLogTrace( traceKiCad2Step, wxT( "VIA post-machining check: frontPM.mode.has_value=%d frontPM.size=%d frontPM.depth=%d frontPM.angle=%d" ),
856 frontPM.mode.has_value() ? 1 : 0, frontPM.size, frontPM.depth, frontPM.angle );
857 wxLogTrace( traceKiCad2Step, wxT( "VIA post-machining check: backPM.mode.has_value=%d backPM.size=%d backPM.depth=%d backPM.angle=%d" ),
858 backPM.mode.has_value() ? 1 : 0, backPM.size, backPM.depth, backPM.angle );
859
860 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
861 bool frontPMValid = frontPM.mode.has_value() && frontPM.size > 0 &&
862 ( ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && frontPM.depth > 0 ) ||
863 ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && frontPM.angle > 0 ) );
864
865 if( frontPMValid )
866 {
867 wxLogTrace( traceKiCad2Step, wxT( "VIA front post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
868 static_cast<int>( *frontPM.mode ) );
869
870 int pmAngle = ( *frontPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? frontPM.angle : 0;
871
873 {
874 m_pcbModel->AddCounterbore( via->GetPosition(), frontPM.size,
875 frontPM.depth, true, aOrigin );
876 }
878 {
879 m_pcbModel->AddCountersink( via->GetPosition(), frontPM.size,
880 frontPM.depth, frontPM.angle, true, aOrigin );
881 }
882
883 // Add knockouts to all copper layers the feature crosses
884 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( frontPM.size, frontPM.depth,
885 pmAngle, true );
886 for( const auto& [layer, diameter] : knockouts )
887 {
888 SHAPE_POLY_SET pmPoly;
889 TransformCircleToPolygon( pmPoly, via->GetPosition(), diameter / 2,
890 via->GetMaxError(), ERROR_INSIDE );
891 m_poly_holes[layer].Append( pmPoly );
892 }
893
894 // Add knockout for silkscreen and soldermask on front side (full diameter)
895 SHAPE_POLY_SET pmPoly;
896 TransformCircleToPolygon( pmPoly, via->GetPosition(), frontPM.size / 2,
897 via->GetMaxError(), ERROR_INSIDE );
898 m_poly_holes[F_SilkS].Append( pmPoly );
899 m_poly_holes[F_Mask].Append( pmPoly );
900 }
901
902 // For counterbore, depth must be > 0. For countersink, depth can be 0 (calculated from diameter/angle)
903 bool backPMValid = backPM.mode.has_value() && backPM.size > 0 &&
904 ( ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE && backPM.depth > 0 ) ||
905 ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK && backPM.angle > 0 ) );
906
907 if( backPMValid )
908 {
909 wxLogTrace( traceKiCad2Step, wxT( "VIA back post-machining: mode=%d (COUNTERBORE=2, COUNTERSINK=3)" ),
910 static_cast<int>( *backPM.mode ) );
911
912 int pmAngle = ( *backPM.mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK ) ? backPM.angle : 0;
913
915 {
916 m_pcbModel->AddCounterbore( via->GetPosition(), backPM.size,
917 backPM.depth, false, aOrigin );
918 }
920 {
921 m_pcbModel->AddCountersink( via->GetPosition(), backPM.size,
922 backPM.depth, backPM.angle, false, aOrigin );
923 }
924
925 // Add knockouts to all copper layers the feature crosses
926 auto knockouts = m_pcbModel->GetCopperLayerKnockouts( backPM.size, backPM.depth,
927 pmAngle, false );
928 for( const auto& [layer, diameter] : knockouts )
929 {
930 SHAPE_POLY_SET pmPoly;
931 TransformCircleToPolygon( pmPoly, via->GetPosition(), diameter / 2,
932 via->GetMaxError(), ERROR_INSIDE );
933 m_poly_holes[layer].Append( pmPoly );
934 }
935
936 // Add knockout for silkscreen and soldermask on back side (full diameter)
937 SHAPE_POLY_SET pmPoly;
938 TransformCircleToPolygon( pmPoly, via->GetPosition(), backPM.size / 2,
939 via->GetMaxError(), ERROR_INSIDE );
940 m_poly_holes[B_SilkS].Append( pmPoly );
941 m_poly_holes[B_Mask].Append( pmPoly );
942 }
943
944 return true;
945 }
946
947 if( skipCopper )
948 return true;
949
950 PCB_LAYER_ID pcblayer = aTrack->GetLayer();
951
952 if( !m_layersToExport.Contains( pcblayer ) )
953 return false;
954
955 aTrack->TransformShapeToPolygon( m_poly_shapes[pcblayer][aTrack->GetNetname()], pcblayer, 0,
956 aTrack->GetMaxError(), ERROR_INSIDE );
957
958 return true;
959}
960
961
962void EXPORTER_STEP::buildZones3DShape( VECTOR2D aOrigin, bool aSolderMaskOnly )
963{
964 for( ZONE* zone : m_board->Zones() )
965 {
966 LSET layers = zone->GetLayerSet();
967
968 // Filter by net if a net filter is specified and zone is on copper layer(s)
969 if( ( layers & LSET::AllCuMask() ).count() && !netFilterMatches( zone->GetNetname() ) )
970 {
971 continue;
972 }
973
974 for( PCB_LAYER_ID layer : layers )
975 {
976 bool isMaskLayer = ( layer == F_Mask || layer == B_Mask );
977
978 // If we're only processing soldermask zones, skip non-mask layers
979 if( aSolderMaskOnly && !isMaskLayer )
980 continue;
981
982 // If we're doing full zone export, skip mask layers if they'll be handled separately
983 if( !aSolderMaskOnly && isMaskLayer && !m_params.m_ExportZones )
984 continue;
985
986 SHAPE_POLY_SET fill_shape;
987 zone->TransformSolidAreasShapesToPolygon( layer, fill_shape );
988 fill_shape.Unfracture();
989
990 fill_shape.SimplifyOutlines( ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel );
991
992 m_poly_shapes[layer][zone->GetNetname()].Append( fill_shape );
993 }
994 }
995}
996
997
999{
1000 PCB_LAYER_ID pcblayer = aItem->GetLayer();
1001 int maxError = aItem->GetMaxError();
1002
1003 if( !m_layersToExport.Contains( pcblayer ) )
1004 return false;
1005
1006 if( IsCopperLayer( pcblayer ) && !m_params.m_ExportTracksVias )
1007 return false;
1008
1009 if( IsInnerCopperLayer( pcblayer ) && !m_params.m_ExportInnerCopper )
1010 return false;
1011
1012 switch( aItem->Type() )
1013 {
1014 case PCB_SHAPE_T:
1015 {
1016 PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
1017
1018 if( IsCopperLayer( pcblayer ) && !netFilterMatches( graphic->GetNetname() ) )
1019 return true;
1020
1021 LINE_STYLE lineStyle = graphic->GetLineStyle();
1022
1023 if( lineStyle == LINE_STYLE::SOLID )
1024 {
1025 graphic->TransformShapeToPolySet( m_poly_shapes[pcblayer][graphic->GetNetname()], pcblayer, 0,
1026 maxError, ERROR_INSIDE );
1027 }
1028 else
1029 {
1030 std::vector<SHAPE*> shapes = graphic->MakeEffectiveShapes( true );
1031 const PCB_PLOT_PARAMS& plotParams = m_board->GetPlotOptions();
1032 KIGFX::PCB_RENDER_SETTINGS renderSettings;
1033
1034 renderSettings.SetDashLengthRatio( plotParams.GetDashedLineDashRatio() );
1035 renderSettings.SetGapLengthRatio( plotParams.GetDashedLineGapRatio() );
1036
1037 for( SHAPE* shape : shapes )
1038 {
1039 STROKE_PARAMS::Stroke( shape, lineStyle, graphic->GetWidth(), &renderSettings,
1040 [&]( const VECTOR2I& a, const VECTOR2I& b )
1041 {
1042 SHAPE_SEGMENT seg( a, b, graphic->GetWidth() );
1043 seg.TransformToPolygon( m_poly_shapes[pcblayer][graphic->GetNetname()],
1044 maxError, ERROR_INSIDE );
1045 } );
1046 }
1047
1048 for( SHAPE* shape : shapes )
1049 delete shape;
1050 }
1051
1052 if( graphic->IsHatchedFill() )
1053 m_poly_shapes[pcblayer][graphic->GetNetname()].Append( graphic->GetHatching() );
1054
1055 if( m_params.m_ExportSoldermask && graphic->IsOnLayer( F_Mask ) )
1056 {
1057 graphic->TransformShapeToPolygon( m_poly_shapes[F_Mask][wxEmptyString], F_Mask,
1058 graphic->GetSolderMaskExpansion(), maxError, ERROR_INSIDE );
1059 }
1060
1061 if( m_params.m_ExportSoldermask && graphic->IsOnLayer( B_Mask ) )
1062 {
1063 graphic->TransformShapeToPolygon( m_poly_shapes[B_Mask][wxEmptyString], B_Mask,
1064 graphic->GetSolderMaskExpansion(), maxError, ERROR_INSIDE );
1065 }
1066
1067 break;
1068 }
1069
1070 case PCB_TEXT_T:
1071 {
1072 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
1073
1074 text->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
1075 break;
1076 }
1077
1078 case PCB_BARCODE_T:
1079 {
1080 PCB_BARCODE* barcode = static_cast<PCB_BARCODE*>( aItem );
1081
1082 barcode->TransformShapeToPolySet( m_poly_shapes[pcblayer][wxEmptyString], pcblayer, 0, maxError,
1083 ERROR_INSIDE );
1084 break;
1085 }
1086
1087 case PCB_TEXTBOX_T:
1088 {
1089 PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( aItem );
1090
1091 // border
1092 if( textbox->IsBorderEnabled() )
1093 {
1094 textbox->PCB_SHAPE::TransformShapeToPolygon( m_poly_shapes[pcblayer][wxEmptyString], pcblayer, 0,
1095 maxError, ERROR_INSIDE );
1096 }
1097
1098 // text
1099 textbox->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
1100 break;
1101 }
1102
1103 case PCB_TABLE_T:
1104 {
1105 PCB_TABLE* table = static_cast<PCB_TABLE*>( aItem );
1106
1107 for( PCB_TABLECELL* cell : table->GetCells() )
1108 {
1109 cell->TransformTextToPolySet( m_poly_shapes[pcblayer][wxEmptyString], 0, maxError, ERROR_INSIDE );
1110 }
1111
1112 table->DrawBorders(
1113 [&]( const VECTOR2I& ptA, const VECTOR2I& ptB, const STROKE_PARAMS& stroke )
1114 {
1115 SHAPE_SEGMENT seg( ptA, ptB, stroke.GetWidth() );
1116 seg.TransformToPolygon( m_poly_shapes[pcblayer][wxEmptyString], maxError, ERROR_INSIDE );
1117 } );
1118
1119 break;
1120 }
1121
1122 default: wxFAIL_MSG( "buildGraphic3DShape: unhandled item type" );
1123 }
1124
1125 return true;
1126}
1127
1128
1130{
1131 // Specialize the STEP_PCB_MODEL generator for specific output format
1132 // it can have some minor actions for the generator
1133 switch( m_params.m_Format )
1134 {
1136 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STEP );
1137 break;
1138
1140 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STEPZ );
1141 break;
1142
1144 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_BREP );
1145 break;
1146
1148 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_XAO );
1149 break;
1150
1152 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_GLTF );
1153 break;
1154
1156 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PLY );
1157 break;
1158
1160 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STL );
1161 break;
1162
1164 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_U3D );
1165 break;
1166
1168 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PDF );
1169 break;
1170
1171 default:
1172 m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_UNKNOWN );
1173 break;
1174 }
1175}
1176
1177
1179{
1180 if( m_pcbModel )
1181 return true;
1182
1183 SHAPE_POLY_SET pcbOutlines; // stores the board main outlines
1184
1185 if( !m_board->GetBoardPolygonOutlines( pcbOutlines,
1186 /* infer outline if necessary */ true,
1187 /* error handler */ nullptr,
1188 /* allows use arcs in outlines */ true ) )
1189 {
1190 wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) );
1191 }
1192
1193 SHAPE_POLY_SET pcbOutlinesNoArcs = pcbOutlines;
1194 pcbOutlinesNoArcs.ClearArcs();
1195
1196 VECTOR2D origin;
1197
1198 // Determine the coordinate system reference:
1199 // Precedence of reference point is Drill Origin > Grid Origin > User Offset
1200 if( m_params.m_UseDrillOrigin )
1201 origin = m_board->GetDesignSettings().GetAuxOrigin();
1202 else if( m_params.m_UseGridOrigin )
1203 origin = m_board->GetDesignSettings().GetGridOrigin();
1204 else
1205 origin = m_params.m_Origin;
1206
1207 m_pcbModel = std::make_unique<STEP_PCB_MODEL>( m_pcbBaseName, m_reporter );
1208
1210
1211 m_pcbModel->SetCopperColor( m_copperColor.r, m_copperColor.g, m_copperColor.b );
1212 m_pcbModel->SetPadColor( m_padColor.r, m_padColor.g, m_padColor.b );
1213
1214 m_pcbModel->SetStackup( m_board->GetStackupOrDefault() );
1215 m_pcbModel->SetEnabledLayers( m_layersToExport );
1216 m_pcbModel->SetFuseShapes( m_params.m_FuseShapes );
1217 m_pcbModel->SetNetFilter( m_params.m_NetFilter );
1218 m_pcbModel->SetExtraPadThickness( m_params.m_ExtraPadThickness );
1219
1220 // Note: m_params.m_BoardOutlinesChainingEpsilon is used only to build the board outlines,
1221 // not to set OCC chaining epsilon (much smaller)
1222 //
1223 // Set the min distance between 2 points for OCC to see these 2 points as merged
1224 // OCC_MAX_DISTANCE_TO_MERGE_POINTS is acceptable for OCC, otherwise there are issues
1225 // to handle the shapes chaining on copper layers, because the Z dist is 0.035 mm and the
1226 // min dist must be much smaller (we use 0.001 mm giving good results)
1227 m_pcbModel->OCCSetMergeMaxDistance( OCC_MAX_DISTANCE_TO_MERGE_POINTS );
1228
1229 // For copper layers, only pads and tracks are added, because adding everything on copper
1230 // generate unreasonable file sizes and take a unreasonable calculation time.
1231 for( FOOTPRINT* fp : m_board->Footprints() )
1232 buildFootprint3DShapes( fp, origin, &pcbOutlinesNoArcs );
1233
1234 for( PCB_TRACK* track : m_board->Tracks() )
1235 buildTrack3DShape( track, origin );
1236
1237 for( BOARD_ITEM* item : m_board->Drawings() )
1238 buildGraphic3DShape( item, origin );
1239
1240 if( m_params.m_ExportZones )
1241 buildZones3DShape( origin );
1242
1243 // Process zones on soldermask layers even when copper zone export is disabled.
1244 // This ensures mask openings defined by zones are properly exported.
1245 if( m_params.m_ExportSoldermask && !m_params.m_ExportZones )
1246 buildZones3DShape( origin, true );
1247
1248 for( PCB_LAYER_ID pcblayer : m_layersToExport.Seq() )
1249 {
1250 SHAPE_POLY_SET holes = m_poly_holes[pcblayer];
1251 holes.Simplify();
1252
1253 if( pcblayer == F_Mask || pcblayer == B_Mask )
1254 {
1255 // Mask layer is negative
1256 SHAPE_POLY_SET mask = pcbOutlinesNoArcs;
1257
1258 for( auto& [netname, poly] : m_poly_shapes[pcblayer] )
1259 {
1260 poly.Simplify();
1261
1262 poly.SimplifyOutlines( pcbIUScale.mmToIU( 0.003 ) );
1263 poly.Simplify();
1264
1265 mask.BooleanSubtract( poly );
1266 }
1267
1268 mask.BooleanSubtract( holes );
1269
1270 m_pcbModel->AddPolygonShapes( &mask, pcblayer, origin, wxEmptyString );
1271 }
1272 else
1273 {
1274 for( auto& [netname, poly] : m_poly_shapes[pcblayer] )
1275 {
1276 poly.Simplify();
1277
1278 poly.SimplifyOutlines( pcbIUScale.mmToIU( 0.003 ) );
1279 poly.Simplify();
1280
1281 // Subtract holes
1282 poly.BooleanSubtract( holes );
1283
1284 // Clip to board outline
1285 poly.BooleanIntersection( pcbOutlinesNoArcs );
1286
1287 m_pcbModel->AddPolygonShapes( &poly, pcblayer, origin, netname );
1288 }
1289 }
1290 }
1291
1292 m_reporter->Report( wxT( "Create PCB solid model.\n" ), RPT_SEVERITY_DEBUG );
1293
1294 m_reporter->Report( wxString::Format( wxT( "Board outline: found %d initial points.\n" ),
1295 pcbOutlines.FullPointCount() ),
1297
1298 if( !m_pcbModel->CreatePCB( pcbOutlines, origin, m_params.m_ExportBoardBody ) )
1299 {
1300 m_reporter->Report( _( "Could not create PCB solid model.\n" ), RPT_SEVERITY_ERROR );
1301 return false;
1302 }
1303
1304 return true;
1305}
1306
1307
1309{
1310 // Display the export time, for statistics
1311 int64_t stats_startExportTime = GetRunningMicroSecs();
1312
1313 // setup opencascade message log
1314 struct SCOPED_PRINTER
1315 {
1316 Handle( Message_Printer ) m_handle;
1317
1318 SCOPED_PRINTER( const Handle( Message_Printer ) & aHandle ) : m_handle( aHandle )
1319 {
1320 Message::DefaultMessenger()->AddPrinter( m_handle );
1321 };
1322
1323 ~SCOPED_PRINTER() { Message::DefaultMessenger()->RemovePrinter( m_handle ); }
1324 };
1325
1326 Message::DefaultMessenger()->RemovePrinters( STANDARD_TYPE( Message_PrinterOStream ) );
1327 SCOPED_PRINTER occtPrinter( new KICAD_PRINTER( m_reporter ) );
1328
1329 m_reporter->Report( wxT( "Determining PCB data.\n" ), RPT_SEVERITY_DEBUG );
1330
1331 if( m_params.m_OutputFile.IsEmpty() )
1332 {
1333 wxFileName fn = m_board->GetFileName();
1334 fn.SetName( fn.GetName() );
1335 fn.SetExt( m_params.GetDefaultExportExtension() );
1336
1337 m_params.m_OutputFile = fn.GetFullName();
1338 }
1339
1341
1342 if( m_params.m_ExportInnerCopper )
1344
1345 if( m_params.m_ExportSilkscreen )
1346 {
1349 }
1350
1351 if( m_params.m_ExportSoldermask )
1352 {
1353 m_layersToExport.set( F_Mask );
1354 m_layersToExport.set( B_Mask );
1355 }
1356
1357 m_layersToExport &= m_board->GetEnabledLayers();
1358
1359 try
1360 {
1361 m_reporter->Report( wxString::Format( wxT( "Build %s data.\n" ), m_params.GetFormatName() ),
1363
1364 if( !buildBoard3DShapes() )
1365 {
1366 m_reporter->Report( _( "\n"
1367 "** Error building STEP board model. Export aborted. **\n" ),
1369 return false;
1370 }
1371
1372 m_reporter->Report( wxString::Format( wxT( "Writing %s file.\n" ), m_params.GetFormatName() ),
1374
1375 bool success = true;
1377 success = m_pcbModel->WriteSTEP( m_outputFile, m_params.m_OptimizeStep, false );
1378 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STEPZ )
1379 success = m_pcbModel->WriteSTEP( m_outputFile, m_params.m_OptimizeStep, true );
1380 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::BREP )
1381 success = m_pcbModel->WriteBREP( m_outputFile );
1382 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::XAO )
1383 success = m_pcbModel->WriteXAO( m_outputFile );
1384 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::GLB )
1385 success = m_pcbModel->WriteGLTF( m_outputFile );
1386 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PLY )
1387 success = m_pcbModel->WritePLY( m_outputFile );
1388 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STL )
1389 success = m_pcbModel->WriteSTL( m_outputFile );
1390 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::U3D )
1391 success = m_pcbModel->WriteU3D( m_outputFile );
1392 else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PDF )
1393 success = m_pcbModel->WritePDF( m_outputFile );
1394
1395 if( !success )
1396 {
1397 m_reporter->Report( wxString::Format( _( "\n"
1398 "** Error writing %s file. **\n" ),
1399 m_params.GetFormatName() ),
1401 return false;
1402 }
1403 else
1404 {
1405 m_reporter->Report( wxString::Format( wxT( "%s file '%s' created.\n" ),
1406 m_params.GetFormatName(),
1407 m_outputFile ),
1409 }
1410 }
1411 catch( const std::bad_alloc& )
1412 {
1413 m_reporter->Report( wxString::Format( _( "\n** Out of memory while exporting %s file. **\n"
1414 "The board may have too many objects (e.g., vias, tracks, components) "
1415 "to process with available system memory.\n"
1416 "Try disabling 'Fuse Shapes' option, reducing board complexity, "
1417 "or freeing up system memory.\n" ),
1418 m_params.GetFormatName() ),
1420 return false;
1421 }
1422 catch( const Standard_Failure& e )
1423 {
1424 wxString errorMsg = e.GetMessageString();
1425 m_reporter->Report( wxString::Format( _( "\nOpenCASCADE error: %s\n" ), errorMsg ),
1427
1428 // Check if this might be memory-related based on common OCC error patterns
1429 if( errorMsg.Contains( "alloc" ) || errorMsg.Contains( "memory" ) ||
1430 errorMsg.IsEmpty() )
1431 {
1432 m_reporter->Report( _( "This error may indicate insufficient memory. Consider disabling "
1433 "'Fuse Shapes', reducing the number of vias/components, or freeing "
1434 "system memory.\n" ),
1436 }
1437
1438 m_reporter->Report( wxString::Format( _( "** Error exporting %s file. Export aborted. **\n" ),
1439 m_params.GetFormatName() ),
1441 return false;
1442 }
1443 #ifndef DEBUG
1444 catch( ... )
1445 {
1446 m_reporter->Report( wxString::Format( _( "\n** Unexpected error while exporting %s file. **\n"
1447 "This may be caused by insufficient system memory, especially "
1448 "when exporting boards with many vias or components with 'Fuse Shapes' enabled.\n"
1449 "Try disabling 'Fuse Shapes', reducing board complexity, "
1450 "or freeing up system memory.\n" ),
1451 m_params.GetFormatName() ),
1453 return false;
1454 }
1455 #endif
1456
1457 // Display calculation time in seconds
1458 double calculation_time = (double)( GetRunningMicroSecs() - stats_startExportTime) / 1e6;
1459 m_reporter->Report( wxString::Format( _( "\n"
1460 "Export time %.3f s\n" ),
1461 calculation_time ),
1463
1464 return !m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR );
1465}
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: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: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.