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