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