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