KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_io_ipc2581.cpp
Go to the documentation of this file.
1
19
20#include "pcb_io_ipc2581.h"
21#include "ipc2581_types.h"
22
23#include <base_units.h>
24#include <bezier_curves.h>
25#include <board.h>
28#include <build_version.h>
29#include <callback_gal.h>
33#include <font/font.h>
34#include <footprint.h>
35#include <hash.h>
36#include <hash_eda.h>
37#include <padstack.h>
38#include <pad.h>
39#include <pcb_dimension.h>
40#include <pcb_field.h>
41#include <pcb_shape.h>
42#include <pcb_textbox.h>
43#include <pcb_track.h>
44#include <pgm_base.h>
45#include <progress_reporter.h>
47#include <string_utils.h>
48#include <wx_fstream_progress.h>
49
53
54#include <wx/log.h>
55#include <wx/numformatter.h>
56#include <wx/xml/xml.h>
57#include <properties/property.h>
59
60
66static const wxChar traceIpc2581[] = wxT( "KICAD_IPC_2581" );
67
68
69// Extend the padstack identity with secondary/tertiary drill (backdrill) and
70// post-machining data so pads/vias with identical geometry but different
71// backdrill configuration do not collapse onto the same padstack entry.
72static void mixBackdrillIntoPadstackHash( size_t& aHash, const PADSTACK& aPadstack )
73{
74 auto mixDrill = [&]( const PADSTACK::DRILL_PROPS& aDrill )
75 {
76 hash_combine( aHash, static_cast<int>( aDrill.start ),
77 static_cast<int>( aDrill.end ), aDrill.size.x, aDrill.size.y,
78 static_cast<int>( aDrill.shape ), aDrill.is_capped.has_value(),
79 aDrill.is_capped.value_or( false ), aDrill.is_filled.has_value(),
80 aDrill.is_filled.value_or( false ) );
81 };
82
83 auto mixPostMachining = [&]( const PADSTACK::POST_MACHINING_PROPS& aPost )
84 {
85 hash_combine( aHash, aPost.mode.has_value(),
86 static_cast<int>(
87 aPost.mode.value_or( PAD_DRILL_POST_MACHINING_MODE::UNKNOWN ) ),
88 aPost.size, aPost.depth, aPost.angle );
89 };
90
91 mixDrill( aPadstack.SecondaryDrill() );
92 mixDrill( aPadstack.TertiaryDrill() );
93 mixPostMachining( aPadstack.FrontPostMachining() );
94 mixPostMachining( aPadstack.BackPostMachining() );
95}
96
97
98static size_t ipcPadstackHash( const PCB_VIA* aVia )
99{
100 size_t hash = hash_fp_item( aVia, 0 );
101 mixBackdrillIntoPadstackHash( hash, aVia->Padstack() );
102 return hash;
103}
104
105
106static size_t ipcPadstackHash( const PAD* aPad )
107{
108 size_t hash = hash_fp_item( aPad, 0 );
109 mixBackdrillIntoPadstackHash( hash, aPad->Padstack() );
110 return hash;
111}
112
113
117static const std::map<wxString, surfaceFinishType> surfaceFinishMap =
118{
119 { wxEmptyString, surfaceFinishType::NONE },
120 { wxT( "ENIG" ), surfaceFinishType::ENIG_N },
121 { wxT( "ENEPIG" ), surfaceFinishType::ENEPIG_N },
122 { wxT( "HAL SNPB" ), surfaceFinishType::S },
123 { wxT( "HAL LEAD-FREE" ), surfaceFinishType::S },
124 { wxT( "HARD GOLD" ), surfaceFinishType::G },
125 { wxT( "IMMERSION TIN" ), surfaceFinishType::ISN },
126 { wxT( "IMMERSION NICKEL" ), surfaceFinishType::N },
127 { wxT( "IMMERSION SILVER" ), surfaceFinishType::IAG },
128 { wxT( "IMMERSION GOLD" ), surfaceFinishType::DIG },
129 { wxT( "HT_OSP" ), surfaceFinishType::HT_OSP },
130 { wxT( "OSP" ), surfaceFinishType::OSP },
131 { wxT( "NONE" ), surfaceFinishType::NONE },
132 { wxT( "NOT SPECIFIED" ), surfaceFinishType::NONE },
133 { wxT( "USER DEFINED" ), surfaceFinishType::NONE },
134};
135
136
140static const std::map<surfaceFinishType, wxString> surfaceFinishTypeToString =
141{
142 { surfaceFinishType::ENIG_N, wxT( "ENIG-N" ) },
143 { surfaceFinishType::ENEPIG_N, wxT( "ENEPIG-N" ) },
144 { surfaceFinishType::OSP, wxT( "OSP" ) },
145 { surfaceFinishType::HT_OSP, wxT( "HT_OSP" ) },
146 { surfaceFinishType::IAG, wxT( "IAg" ) },
147 { surfaceFinishType::ISN, wxT( "ISn" ) },
148 { surfaceFinishType::G, wxT( "G" ) },
149 { surfaceFinishType::N, wxT( "N" ) },
150 { surfaceFinishType::DIG, wxT( "DIG" ) },
151 { surfaceFinishType::S, wxT( "S" ) },
152 { surfaceFinishType::OTHER, wxT( "OTHER" ) },
153};
154
155
156static surfaceFinishType getSurfaceFinishType( const wxString& aFinish )
157{
158 auto it = surfaceFinishMap.find( aFinish.Upper() );
159 return ( it != surfaceFinishMap.end() ) ? it->second : surfaceFinishType::OTHER;
160}
161
162
168
169
171{
172 for( FOOTPRINT* fp : m_loaded_footprints )
173 delete fp;
174
175 m_loaded_footprints.clear();
176}
177
178
180{
181 std::vector<FOOTPRINT*> retval;
182
183 for( FOOTPRINT* fp : m_loaded_footprints )
184 retval.push_back( static_cast<FOOTPRINT*>( fp->Clone() ) );
185
186 return retval;
187}
188
189
190void PCB_IO_IPC2581::insertNode( wxXmlNode* aParent, wxXmlNode* aNode )
191{
192 // insertNode places the node at the start of the list of children
193
194 if( aParent->GetChildren() )
195 aNode->SetNext( aParent->GetChildren() );
196 else
197 aNode->SetNext( nullptr );
198
199 aParent->SetChildren( aNode );
200 aNode->SetParent( aParent );
201 m_total_bytes += 2 * aNode->GetName().size() + 5;
202}
203
204
205void PCB_IO_IPC2581::insertNodeAfter( wxXmlNode* aPrev, wxXmlNode* aNode )
206{
207 // insertNode places the node directly after aPrev
208
209 aNode->SetNext( aPrev->GetNext() );
210 aPrev->SetNext( aNode );
211 aNode->SetParent( aPrev->GetParent() );
212 m_total_bytes += 2 * aNode->GetName().size() + 5;
213}
214
215
216void PCB_IO_IPC2581::deleteNode( wxXmlNode*& aNode )
217{
218 // When deleting a node, invalidate the appendNode optimization cache if it points
219 // to the node being deleted or any of its descendants
221 {
222 wxXmlNode* check = m_lastAppendedNode;
223
224 while( check )
225 {
226 if( check == aNode )
227 {
228 m_lastAppendedNode = nullptr;
229 break;
230 }
231
232 check = check->GetParent();
233 }
234 }
235
236 delete aNode;
237 aNode = nullptr;
238}
239
240
241wxXmlNode* PCB_IO_IPC2581::insertNode( wxXmlNode* aParent, const wxString& aName )
242{
243 // Opening tag, closing tag, brackets and the closing slash
244 m_total_bytes += 2 * aName.size() + 5;
245 wxXmlNode* node = new wxXmlNode( wxXML_ELEMENT_NODE, aName );
246 insertNode( aParent, node );
247 return node;
248}
249
250
251void PCB_IO_IPC2581::appendNode( wxXmlNode* aParent, wxXmlNode* aNode )
252{
253 // AddChild iterates through the entire list of children, so we want to avoid
254 // that if possible. When we share a parent and our next sibling is null,
255 // then we are the last child and can just append to the end of the list.
256
257 if( m_lastAppendedNode && m_lastAppendedNode->GetParent() == aParent
258 && m_lastAppendedNode->GetNext() == nullptr )
259 {
260 aNode->SetParent( aParent );
261 m_lastAppendedNode->SetNext( aNode );
262 }
263 else
264 {
265 aParent->AddChild( aNode );
266 }
267
268 m_lastAppendedNode = aNode;
269
270 // Opening tag, closing tag, brackets and the closing slash
271 m_total_bytes += 2 * aNode->GetName().size() + 5;
272}
273
274
275wxXmlNode* PCB_IO_IPC2581::appendNode( wxXmlNode* aParent, const wxString& aName )
276{
277 wxXmlNode* node = new wxXmlNode( wxXML_ELEMENT_NODE, aName );
278
279 appendNode( aParent, node );
280 return node;
281}
282
283
284wxString PCB_IO_IPC2581::sanitizeId( const wxString& aStr ) const
285{
286 wxString str;
287
288 if( m_version == 'C' )
289 {
290 str = aStr;
291 str.Replace( wxT( ":" ), wxT( "_" ) );
292 }
293 else
294 {
295 for( wxString::const_iterator iter = aStr.begin(); iter != aStr.end(); ++iter )
296 {
297 if( !m_acceptable_chars.count( *iter ) )
298 str.Append( '_' );
299 else
300 str.Append( *iter );
301 }
302 }
303
304 return str;
305}
306
307
308wxString PCB_IO_IPC2581::genString( const wxString& aStr, const char* aPrefix ) const
309{
310 // Build a key using the prefix and original string so that repeated calls for the same
311 // element return the same generated name.
312 wxString key = aPrefix ? wxString( aPrefix ) + wxT( ":" ) + aStr : aStr;
313
314 auto it = m_generated_names.find( key );
315
316 if( it != m_generated_names.end() )
317 return it->second;
318
319 wxString str = sanitizeId( aStr );
320
321 wxString base = str;
322 wxString name = base;
323 int suffix = 1;
324
325 while( m_element_names.count( name ) )
326 name = wxString::Format( "%s_%d", base, suffix++ );
327
328 m_element_names.insert( name );
329 m_generated_names[key] = name;
330
331 return name;
332}
333
334
335wxString PCB_IO_IPC2581::genLayerString( PCB_LAYER_ID aLayer, const char* aPrefix ) const
336{
337 return genString( m_board->GetLayerName( aLayer ), aPrefix );
338}
339
340
342 const char* aPrefix ) const
343{
344 return genString( wxString::Format( wxS( "%s_%s" ),
345 m_board->GetLayerName( aTop ),
346 m_board->GetLayerName( aBottom ) ), aPrefix );
347}
348
349
350wxString PCB_IO_IPC2581::pinName( const PAD* aPad ) const
351{
352 wxString name = aPad->GetNumber();
353
354 FOOTPRINT* fp = aPad->GetParentFootprint();
355 size_t ii = 0;
356
357 if( name.empty() && fp )
358 {
359 for( ii = 0; ii < fp->GetPadCount(); ++ii )
360 {
361 if( fp->Pads()[ii] == aPad )
362 break;
363 }
364 }
365
366 // Pins are required to have names, so if our pad doesn't have a name, we need to
367 // generate one that is unique
368 if( aPad->GetAttribute() == PAD_ATTRIB::NPTH )
369 name = wxString::Format( "NPTH%zu", ii );
370 else if( name.empty() )
371 name = wxString::Format( "PAD%zu", ii );
372
373 // Pins are scoped per-package, so we only sanitize; uniqueness is handled by
374 // the per-package pin_nodes map in addPackage().
375 return sanitizeId( name );
376}
377
378
380{
381 auto tryInsert =
382 [&]( const wxString& aName )
383 {
384 if( m_footprint_refdes_dict.count( aName ) )
385 {
386 if( m_footprint_refdes_dict.at( aName ) != aFootprint )
387 return false;
388 }
389 else
390 {
391 m_footprint_refdes_dict.insert( { aName, aFootprint } );
392 }
393
394 return true;
395 };
396
397 if( m_footprint_refdes_reverse_dict.count( aFootprint ) )
398 return m_footprint_refdes_reverse_dict.at( aFootprint );
399
400 wxString ref = aFootprint->GetReference();
401
402 if( ref.IsEmpty() )
403 ref = wxT( "NOREF_" ) + aFootprint->m_Uuid.AsString().Left( 8 );
404
405 wxString baseName = genString( ref, "CMP" );
406 wxString name = baseName;
407 int suffix = 1;
408
409 while( !tryInsert( name ) )
410 name = wxString::Format( "%s_%d", baseName, suffix++ );
411
413
414 return name;
415}
416
417
418wxString PCB_IO_IPC2581::floatVal( double aVal, int aSigFig ) const
419{
420 wxString str = wxString::FromCDouble( aVal, aSigFig == -1 ? m_sigfig : aSigFig );
421
422 // Remove all but the last trailing zeros from str
423 while( str.EndsWith( wxT( "00" ) ) )
424 str.RemoveLast();
425
426 // We don't want to output -0.0 as this value is just 0 for fabs
427 if( str == wxT( "-0.0" ) )
428 return wxT( "0.0" );
429
430 return str;
431}
432
433
434void PCB_IO_IPC2581::addXY( wxXmlNode* aNode, const VECTOR2I& aVec, const char* aXName,
435 const char* aYName )
436{
437 if( aXName )
438 addAttribute( aNode, aXName, floatVal( m_scale * aVec.x ) );
439 else
440 addAttribute( aNode, "x", floatVal( m_scale * aVec.x ) );
441
442 if( aYName )
443 addAttribute( aNode, aYName, floatVal( -m_scale * aVec.y ) );
444 else
445 addAttribute( aNode, "y", floatVal( -m_scale * aVec.y ) );
446}
447
448
449void PCB_IO_IPC2581::addAttribute( wxXmlNode* aNode, const wxString& aName, const wxString& aValue )
450{
451 m_total_bytes += aName.size() + aValue.size() + 4;
452 aNode->AddAttribute( aName, aValue );
453}
454
455
457{
458 wxXmlNode* xmlHeaderNode = new wxXmlNode(wxXML_ELEMENT_NODE, "IPC-2581");
459 addAttribute( xmlHeaderNode, "revision", m_version);
460 addAttribute( xmlHeaderNode, "xmlns", "http://webstds.ipc.org/2581");
461 addAttribute( xmlHeaderNode, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
462 addAttribute( xmlHeaderNode, "xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
463
464 if( m_version == 'B' )
465 {
466 addAttribute( xmlHeaderNode, "xsi:schemaLocation",
467 "http://webstds.ipc.org/2581 http://webstds.ipc.org/2581/IPC-2581B1.xsd" );
468 }
469 else
470 {
471 addAttribute( xmlHeaderNode, "xsi:schemaLocation",
472 "http://webstds.ipc.org/2581 http://webstds.ipc.org/2581/IPC-2581C.xsd" );
473 }
474
475 m_xml_doc->SetRoot( xmlHeaderNode );
476
477 return xmlHeaderNode;
478}
479
480
482{
484 m_progressReporter->AdvancePhase( _( "Generating content section" ) );
485
486 m_contentNode = appendNode( m_xml_root, "Content" );
487 wxXmlNode* contentNode = m_contentNode;
488 addAttribute( contentNode, "roleRef", "Owner" );
489
490 wxXmlNode* node = appendNode( contentNode, "FunctionMode" );
491 addAttribute( node, "mode", "ASSEMBLY" );
492
493 // This element is deprecated in revision 'C' and later
494 if( m_version == 'B' )
495 addAttribute( node, "level", "3" );
496
497 node = appendNode( contentNode, "StepRef" );
498 wxFileName fn( m_board->GetFileName() );
499 addAttribute( node, "name", genString( fn.GetName(), "BOARD" ) );
500
501 wxXmlNode* color_node = generateContentStackup( contentNode );
502
503 if( m_version == 'C' )
504 {
505 contentNode->AddChild( color_node );
506 m_line_node = appendNode( contentNode, "DictionaryLineDesc" );
508
509 wxXmlNode* fillNode = appendNode( contentNode, "DictionaryFillDesc" );
510 addAttribute( fillNode, "units", m_units_str );
511
512 m_shape_std_node = appendNode( contentNode, "DictionaryStandard" );
514
515 m_shape_user_node = appendNode( contentNode, "DictionaryUser" );
517 }
518 else
519 {
520 m_shape_std_node = appendNode( contentNode, "DictionaryStandard" );
522
523 m_shape_user_node = appendNode( contentNode, "DictionaryUser" );
525
526 m_line_node = appendNode( contentNode, "DictionaryLineDesc" );
528
529 contentNode->AddChild( color_node );
530 }
531
532 return contentNode;
533}
534
535
536void PCB_IO_IPC2581::addLocationNode( wxXmlNode* aNode, double aX, double aY )
537{
538 wxXmlNode* location_node = appendNode( aNode, "Location" );
539 addXY( location_node, VECTOR2I( aX, aY ) );
540}
541
542
543void PCB_IO_IPC2581::addLocationNode( wxXmlNode* aNode, const PAD& aPad, bool aRelative )
544{
545 VECTOR2D pos{};
546
547 if( aRelative )
548 pos = aPad.GetFPRelativePosition();
549 else
550 pos = aPad.GetPosition();
551
552 if( aPad.GetOffset( PADSTACK::ALL_LAYERS ).x != 0 || aPad.GetOffset( PADSTACK::ALL_LAYERS ).y != 0 )
553 pos += aPad.GetOffset( PADSTACK::ALL_LAYERS );
554
555 addLocationNode( aNode, pos.x, pos.y );
556}
557
558
559void PCB_IO_IPC2581::addLocationNode( wxXmlNode* aNode, const PCB_SHAPE& aShape )
560{
561 VECTOR2D pos{};
562
563 switch( aShape.GetShape() )
564 {
565 // Rectangles in KiCad are mapped by their corner while IPC2581 uses the center
567 pos = aShape.GetPosition()
568 + VECTOR2I( aShape.GetRectangleWidth() / 2.0, aShape.GetRectangleHeight() / 2.0 );
569 break;
570 // Both KiCad and IPC2581 use the center of the circle
571 case SHAPE_T::CIRCLE:
572 pos = aShape.GetPosition();
573 break;
574
575 // KiCad uses the exact points on the board, so we want the reference location to be 0,0
576 case SHAPE_T::POLY:
577 case SHAPE_T::BEZIER:
578 case SHAPE_T::SEGMENT:
579 case SHAPE_T::ARC:
580 pos = VECTOR2D( 0, 0 );
581 break;
582
584 wxFAIL;
585 }
586
587 addLocationNode( aNode, pos.x, pos.y );
588}
589
590
591size_t PCB_IO_IPC2581::lineHash( int aWidth, LINE_STYLE aDashType )
592{
593 size_t hash = hash_val( aWidth );
594 hash_combine( hash, aDashType );
595
596 return hash;
597}
598
599
601{
602 return hash_fp_item( &aShape, HASH_POS | REL_COORD );
603}
604
605
606wxXmlNode* PCB_IO_IPC2581::generateContentStackup( wxXmlNode* aContentNode )
607{
608
609 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
610 BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
611 stackup.SynchronizeWithBoard( &bds );
612
613 wxXmlNode* color_node = new wxXmlNode( wxXML_ELEMENT_NODE, "DictionaryColor" );
614
615 for( BOARD_STACKUP_ITEM* item: stackup.GetList() )
616 {
617 wxString layer_name = item->GetLayerName();
618 int sub_layer_count = 1;
619
620 if( layer_name.empty() )
621 layer_name = m_board->GetLayerName( item->GetBrdLayerId() );
622
623 layer_name = genString( layer_name, "LAYER" );
624
625 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
626 {
627 layer_name = genString( wxString::Format( "DIELECTRIC_%d", item->GetDielectricLayerId() ),
628 "LAYER" );
629 sub_layer_count = item->GetSublayersCount();
630 }
631 else
632 {
633 m_layer_name_map.emplace( item->GetBrdLayerId(), layer_name );
634 }
635
636 for( int sub_idx = 0; sub_idx < sub_layer_count; sub_idx++ )
637 {
638 wxString sub_layer_name = layer_name;
639
640 if( sub_idx > 0 )
641 sub_layer_name += wxString::Format( "_%d", sub_idx );
642
643 wxXmlNode* node = appendNode( aContentNode, "LayerRef" );
644 addAttribute( node, "name", sub_layer_name );
645
646 if( !IsPrmSpecified( item->GetColor( sub_idx ) ) )
647 continue;
648
649 wxXmlNode* entry_color = appendNode( color_node, "EntryColor" );
650 addAttribute( entry_color, "id", genString( sub_layer_name, "COLOR" ) );
651 wxXmlNode* color = appendNode( entry_color, "Color" );
652
653 wxString colorName = item->GetColor( sub_idx );
654
655 if( colorName.StartsWith( wxT( "#" ) ) ) // This is a user defined color,
656 // not in standard color list.
657 {
658 COLOR4D layer_color( colorName );
659 addAttribute( color, "r", wxString::Format( "%d",
660 KiROUND( layer_color.r * 255 ) ) );
661 addAttribute( color, "g", wxString::Format( "%d",
662 KiROUND( layer_color.g * 255 ) ) );
663 addAttribute( color, "b", wxString::Format( "%d",
664 KiROUND( layer_color.b * 255 ) ) );
665 }
666 else
667 {
668 for( const FAB_LAYER_COLOR& fab_color : GetStandardColors( item->GetType() ) )
669 {
670 if( fab_color.GetName() == colorName )
671 {
672 addAttribute( color, "r", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).r * 255 ) ) );
673 addAttribute( color, "g", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).g * 255 ) ) );
674 addAttribute( color, "b", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).b * 255 ) ) );
675 break;
676 }
677 }
678 }
679 }
680 }
681
682 return color_node;
683}
684
685
686void PCB_IO_IPC2581::addFillDesc( wxXmlNode* aNode, FILL_T aFill, bool aForce )
687{
688 if( aFill == FILL_T::FILLED_SHAPE )
689 {
690 // By default, we do not fill shapes because FILL is the default value for most.
691 // But for some outlines, we may need to force a fill.
692 if( aForce )
693 {
694 wxXmlNode* fillDesc_node = appendNode( aNode, "FillDesc" );
695 addAttribute( fillDesc_node, "fillProperty", "FILL" );
696 }
697 }
698 else
699 {
700 wxXmlNode* fillDesc_node = appendNode( aNode, "FillDesc" );
701 addAttribute( fillDesc_node, "fillProperty", "HOLLOW" );
702 }
703}
704
705
706void PCB_IO_IPC2581::addLineDesc( wxXmlNode* aNode, int aWidth, LINE_STYLE aDashType, bool aForce )
707{
708 wxCHECK_RET( aNode, "aNode is null" );
709
710 if( aWidth < 0 )
711 return;
712
713 wxXmlNode* entry_node = nullptr;
714
715 if( !aForce )
716 {
717 size_t hash = lineHash( aWidth, aDashType );
718 wxString name = wxString::Format( "LINE_%zu", m_line_dict.size() + 1 );
719 auto[ iter, inserted ] = m_line_dict.emplace( hash, name );
720
721 // Either add a new entry or reference an existing one
722 wxXmlNode* lineDesc_node = appendNode( aNode, "LineDescRef" );
723 addAttribute( lineDesc_node, "id", iter->second );
724
725 if( !inserted )
726 return;
727
728 entry_node = appendNode( m_line_node, "EntryLineDesc" );
729 addAttribute( entry_node, "id", name );
730 }
731 else
732 {
733 // Force the LineDesc to be added directly to the parent node
734 entry_node = aNode;
735 }
736
737 wxXmlNode* line_node = appendNode( entry_node, "LineDesc" );
738 addAttribute( line_node, "lineWidth", floatVal( m_scale * aWidth ) );
739 addAttribute( line_node, "lineEnd", "ROUND" );
740
741 switch( aDashType )
742 {
743 case LINE_STYLE::DOT:
744 addAttribute( line_node, "lineProperty", "DOTTED" );
745 break;
746 case LINE_STYLE::DASH:
747 addAttribute( line_node, "lineProperty", "DASHED" );
748 break;
750 addAttribute( line_node, "lineProperty", "CENTER" );
751 break;
753 addAttribute( line_node, "lineProperty", "PHANTOM" );
754 break;
755 default:
756 break;
757 }
758}
759
760
761void PCB_IO_IPC2581::addKnockoutText( wxXmlNode* aContentNode, PCB_TEXT* aText )
762{
763 SHAPE_POLY_SET finalPoly;
764
765 aText->TransformTextToPolySet( finalPoly, 0, ARC_HIGH_DEF, ERROR_INSIDE );
766 finalPoly.Fracture();
767
768 const int outlineCount = finalPoly.OutlineCount();
769
770 if( outlineCount == 0 )
771 return;
772
773 // The IPC-2581 schema allows only one top-level Feature under Features/Marking,
774 // so wrap multiple glyph contours in a UserSpecial (a UserPrimitive Feature that
775 // may contain any number of child Features).
776
777 if( outlineCount == 1 )
778 {
779 addContourNode( aContentNode, finalPoly, 0 );
780 return;
781 }
782
783 wxXmlNode* special_node = appendNode( aContentNode, "UserSpecial" );
784
785 for( int ii = 0; ii < outlineCount; ++ii )
786 addContourNode( special_node, finalPoly, ii );
787}
788
789
790void PCB_IO_IPC2581::addText( wxXmlNode* aContentNode, EDA_TEXT* aText,
791 const KIFONT::METRICS& aFontMetrics )
792{
794 KIFONT::FONT* font = aText->GetDrawFont( nullptr );
795 TEXT_ATTRIBUTES attrs = aText->GetAttributes();
796
798 attrs.m_Angle = aText->GetDrawRotation();
799 attrs.m_Multiline = false;
800
801 wxXmlNode* text_node = appendNode( aContentNode, "UserSpecial" );
802
803 std::list<VECTOR2I> pts;
804
805 auto push_pts =
806 [&]()
807 {
808 if( pts.size() < 2 )
809 return;
810
811 wxXmlNode* line_node = nullptr;
812
813 // Polylines are only allowed for more than 3 points (in version B).
814 // Otherwise, we have to use a line
815 if( pts.size() < 3 )
816 {
817 line_node = appendNode( text_node, "Line" );
818 addXY( line_node, pts.front(), "startX", "startY" );
819 addXY( line_node, pts.back(), "endX", "endY" );
820 }
821 else
822 {
823 line_node = appendNode( text_node, "Polyline" );
824 wxXmlNode* point_node = appendNode( line_node, "PolyBegin" );
825 addXY( point_node, pts.front() );
826
827 auto iter = pts.begin();
828
829 for( ++iter; iter != pts.end(); ++iter )
830 {
831 wxXmlNode* step_node = appendNode( line_node, "PolyStepSegment" );
832 addXY( step_node, *iter );
833 }
834
835 }
836
837 addLineDesc( line_node, attrs.m_StrokeWidth, LINE_STYLE::SOLID );
838 pts.clear();
839 };
840
841 CALLBACK_GAL callback_gal( empty_opts,
842 // Stroke callback
843 [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
844 {
845 if( !pts.empty() )
846 {
847 if( aPt1 == pts.back() )
848 pts.push_back( aPt2 );
849 else if( aPt2 == pts.front() )
850 pts.push_front( aPt1 );
851 else if( aPt1 == pts.front() )
852 pts.push_front( aPt2 );
853 else if( aPt2 == pts.back() )
854 pts.push_back( aPt1 );
855 else
856 {
857 push_pts();
858 pts.push_back( aPt1 );
859 pts.push_back( aPt2 );
860 }
861 }
862 else
863 {
864 pts.push_back( aPt1 );
865 pts.push_back( aPt2 );
866 }
867 },
868 // Polygon callback
869 [&]( const SHAPE_LINE_CHAIN& aPoly )
870 {
871 if( aPoly.PointCount() < 3 )
872 return;
873
874 wxXmlNode* outline_node = appendNode( text_node, "Outline" );
875 wxXmlNode* poly_node = appendNode( outline_node, "Polygon" );
876 addLineDesc( outline_node, 0, LINE_STYLE::SOLID );
877
878 const std::vector<VECTOR2I>& polyPts = aPoly.CPoints();
879 wxXmlNode* point_node = appendNode( poly_node, "PolyBegin" );
880 addXY( point_node, polyPts.front() );
881
882 for( size_t ii = 1; ii < polyPts.size(); ++ii )
883 {
884 wxXmlNode* poly_step_node =
885 appendNode( poly_node, "PolyStepSegment" );
886 addXY( poly_step_node, polyPts[ii] );
887 }
888
889 point_node = appendNode( poly_node, "PolyStepSegment" );
890 addXY( point_node, polyPts.front() );
891 } );
892
893 //TODO: handle multiline text
894
895 font->Draw( &callback_gal, aText->GetShownText( true ), aText->GetTextPos(), attrs,
896 aFontMetrics );
897
898 if( !pts.empty() )
899 push_pts();
900
901 if( text_node->GetChildren() == nullptr )
902 {
903 aContentNode->RemoveChild( text_node );
904 delete text_node;
905 }
906}
907
908
909void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAYER_ID aLayer )
910{
911 size_t hash = hash_fp_item( &aPad, 0 );
912 auto iter = m_std_shape_dict.find( hash );
913
914 if( iter != m_std_shape_dict.end() )
915 {
916 wxXmlNode* shape_node = appendNode( aContentNode, "StandardPrimitiveRef" );
917 addAttribute( shape_node, "id", iter->second );
918 return;
919 }
920
921 int maxError = m_board->GetDesignSettings().m_MaxError;
922 wxString name;
923 VECTOR2I expansion{ 0, 0 };
924
925 if( LSET( { F_Mask, B_Mask } ).Contains( aLayer ) )
926 expansion.x = expansion.y = 2 * aPad.GetSolderMaskExpansion( PADSTACK::ALL_LAYERS );
927
928 if( LSET( { F_Paste, B_Paste } ).Contains( aLayer ) )
929 expansion = 2 * aPad.GetSolderPasteMargin( PADSTACK::ALL_LAYERS );
930
931 switch( aPad.GetShape( PADSTACK::ALL_LAYERS ) )
932 {
934 {
935 name = wxString::Format( "CIRCLE_%zu", m_std_shape_dict.size() + 1 );
936 m_std_shape_dict.emplace( hash, name );
937
938 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
939 addAttribute( entry_node, "id", name );
940
941 wxXmlNode* circle_node = appendNode( entry_node, "Circle" );
942 circle_node->AddAttribute( "diameter",
943 floatVal( m_scale * ( expansion.x + aPad.GetSizeX() ) ) );
944 break;
945 }
946
948 {
949 name = wxString::Format( "RECT_%zu", m_std_shape_dict.size() + 1 );
950 m_std_shape_dict.emplace( hash, name );
951
952 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
953 addAttribute( entry_node, "id", name );
954
955 wxXmlNode* rect_node = appendNode( entry_node, "RectCenter" );
956 VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
957 addAttribute( rect_node, "width", floatVal( m_scale * std::abs( pad_size.x ) ) );
958 addAttribute( rect_node, "height", floatVal( m_scale * std::abs( pad_size.y ) ) );
959
960 break;
961
962 }
963 case PAD_SHAPE::OVAL:
964 {
965 name = wxString::Format( "OVAL_%zu", m_std_shape_dict.size() + 1 );
966 m_std_shape_dict.emplace( hash, name );
967
968 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
969 addAttribute( entry_node, "id", name );
970
971 wxXmlNode* oval_node = appendNode( entry_node, "Oval" );
972 VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
973 addAttribute( oval_node, "width", floatVal( m_scale * pad_size.x ) );
974 addAttribute( oval_node, "height", floatVal( m_scale * pad_size.y ) );
975
976 break;
977 }
978
980 {
981 name = wxString::Format( "ROUNDRECT_%zu", m_std_shape_dict.size() + 1 );
982 m_std_shape_dict.emplace( hash, name );
983
984 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
985 addAttribute( entry_node, "id", name );
986
987 wxXmlNode* roundrect_node = appendNode( entry_node, "RectRound" );
988 VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
989 addAttribute( roundrect_node, "width", floatVal( m_scale * pad_size.x ) );
990 addAttribute( roundrect_node, "height", floatVal( m_scale * pad_size.y ) );
991 roundrect_node->AddAttribute( "radius",
993 addAttribute( roundrect_node, "upperRight", "true" );
994 addAttribute( roundrect_node, "upperLeft", "true" );
995 addAttribute( roundrect_node, "lowerRight", "true" );
996 addAttribute( roundrect_node, "lowerLeft", "true" );
997
998 break;
999 }
1000
1002 {
1003 name = wxString::Format( "RECTCHAMFERED_%zu", m_std_shape_dict.size() + 1 );
1004 m_std_shape_dict.emplace( hash, name );
1005
1006 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
1007 addAttribute( entry_node, "id", name );
1008
1009 wxXmlNode* chamfered_node = appendNode( entry_node, "RectCham" );
1010 VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
1011 addAttribute( chamfered_node, "width", floatVal( m_scale * pad_size.x ) );
1012 addAttribute( chamfered_node, "height", floatVal( m_scale * pad_size.y ) );
1013
1014 int shorterSide = std::min( pad_size.x, pad_size.y );
1015 int chamfer = std::max( 0, KiROUND( aPad.GetChamferRectRatio( PADSTACK::ALL_LAYERS ) * shorterSide ) );
1016
1017 addAttribute( chamfered_node, "chamfer", floatVal( m_scale * chamfer ) );
1018
1019 int positions = aPad.GetChamferPositions( PADSTACK::ALL_LAYERS );
1020
1021 if( positions & RECT_CHAMFER_TOP_LEFT )
1022 addAttribute( chamfered_node, "upperLeft", "true" );
1023 if( positions & RECT_CHAMFER_TOP_RIGHT )
1024 addAttribute( chamfered_node, "upperRight", "true" );
1025 if( positions & RECT_CHAMFER_BOTTOM_LEFT )
1026 addAttribute( chamfered_node, "lowerLeft", "true" );
1027 if( positions & RECT_CHAMFER_BOTTOM_RIGHT )
1028 addAttribute( chamfered_node, "lowerRight", "true" );
1029
1030 break;
1031 }
1032
1034 {
1035 name = wxString::Format( "TRAPEZOID_%zu", m_std_shape_dict.size() + 1 );
1036 m_std_shape_dict.emplace( hash, name );
1037
1038 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
1039 addAttribute( entry_node, "id", name );
1040
1041 VECTOR2I pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS );
1042 VECTOR2I trap_delta = aPad.GetDelta( PADSTACK::ALL_LAYERS );
1043 SHAPE_POLY_SET outline;
1044 outline.NewOutline();
1045 int dx = pad_size.x / 2;
1046 int dy = pad_size.y / 2;
1047 int ddx = trap_delta.x / 2;
1048 int ddy = trap_delta.y / 2;
1049
1050 outline.Append( -dx - ddy, dy + ddx );
1051 outline.Append( dx + ddy, dy - ddx );
1052 outline.Append( dx - ddy, -dy + ddx );
1053 outline.Append( -dx + ddy, -dy - ddx );
1054
1055 // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate()
1056 // which can create bad shapes if margin.x is < 0
1057 if( expansion.x )
1058 {
1060 maxError );
1061 }
1062
1063 addContourNode( entry_node, outline );
1064
1065 break;
1066 }
1067 case PAD_SHAPE::CUSTOM:
1068 {
1069 name = wxString::Format( "CUSTOM_%zu", m_std_shape_dict.size() + 1 );
1070 m_std_shape_dict.emplace( hash, name );
1071
1072 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
1073 addAttribute( entry_node, "id", name );
1074
1075 SHAPE_POLY_SET shape;
1077
1078 if( expansion != VECTOR2I( 0, 0 ) )
1079 {
1080 shape.InflateWithLinkedHoles( std::max( expansion.x, expansion.y ),
1082 }
1083
1084 addContourNode( entry_node, shape );
1085 break;
1086 }
1087 default:
1088 Report( _( "Pad has unsupported type; it was skipped." ), RPT_SEVERITY_WARNING );
1089 break;
1090 }
1091
1092 if( !name.empty() )
1093 {
1094 m_std_shape_dict.emplace( hash, name );
1095 wxXmlNode* shape_node = appendNode( aContentNode, "StandardPrimitiveRef" );
1096 addAttribute( shape_node, "id", name );
1097 }
1098}
1099
1100
1101void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PCB_SHAPE& aShape, bool aInline )
1102{
1103 size_t hash = shapeHash( aShape );
1104 auto iter = m_user_shape_dict.find( hash );
1105 wxString name;
1106
1107 // When not inline, check for existing shape in dictionary and reference it
1108 if( !aInline && iter != m_user_shape_dict.end() )
1109 {
1110 wxXmlNode* shape_node = appendNode( aContentNode, "UserPrimitiveRef" );
1111 addAttribute( shape_node, "id", iter->second );
1112 return;
1113 }
1114
1115 switch( aShape.GetShape() )
1116 {
1117 case SHAPE_T::CIRCLE:
1118 {
1119 if( aInline )
1120 {
1121 // For inline shapes (e.g., in Marking elements), output geometry directly as a
1122 // Polyline with two arcs forming a circle
1123 int radius = aShape.GetRadius();
1124 int width = aShape.GetStroke().GetWidth();
1125 LINE_STYLE dash = aShape.GetStroke().GetLineStyle();
1126
1127 wxXmlNode* polyline_node = appendNode( aContentNode, "Polyline" );
1128
1129 // Create a circle using two semicircular arcs
1130 // Start at the rightmost point of the circle
1131 VECTOR2I center = aShape.GetCenter();
1132 VECTOR2I start( center.x + radius, center.y );
1133 VECTOR2I mid( center.x - radius, center.y );
1134
1135 wxXmlNode* begin_node = appendNode( polyline_node, "PolyBegin" );
1136 addXY( begin_node, start );
1137
1138 // First arc from start to mid (top semicircle)
1139 wxXmlNode* arc1_node = appendNode( polyline_node, "PolyStepCurve" );
1140 addXY( arc1_node, mid );
1141 addXY( arc1_node, center, "centerX", "centerY" );
1142 addAttribute( arc1_node, "clockwise", "true" );
1143
1144 // Second arc from mid back to start (bottom semicircle)
1145 wxXmlNode* arc2_node = appendNode( polyline_node, "PolyStepCurve" );
1146 addXY( arc2_node, start );
1147 addXY( arc2_node, center, "centerX", "centerY" );
1148 addAttribute( arc2_node, "clockwise", "true" );
1149
1150 if( width > 0 )
1151 addLineDesc( polyline_node, width, dash, true );
1152
1153 break;
1154 }
1155
1156 name = wxString::Format( "UCIRCLE_%zu", m_user_shape_dict.size() + 1 );
1157 m_user_shape_dict.emplace( hash, name );
1158 int diameter = aShape.GetRadius() * 2.0;
1159 int width = aShape.GetStroke().GetWidth();
1160 LINE_STYLE dash = aShape.GetStroke().GetLineStyle();
1161
1162
1163 wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" );
1164 addAttribute( entry_node, "id", name );
1165 wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" );
1166
1167 wxXmlNode* circle_node = appendNode( special_node, "Circle" );
1168
1169 if( aShape.GetFillMode() == FILL_T::NO_FILL )
1170 {
1171 addAttribute( circle_node, "diameter", floatVal( m_scale * diameter ) );
1172 addLineDesc( circle_node, width, dash, true );
1173 }
1174 else
1175 {
1176 // IPC2581 does not allow strokes on filled elements
1177 addAttribute( circle_node, "diameter", floatVal( m_scale * ( diameter + width ) ) );
1178 }
1179
1180 addFillDesc( circle_node, aShape.GetFillMode() );
1181
1182 break;
1183 }
1184
1185 case SHAPE_T::RECTANGLE:
1186 {
1187 if( aInline )
1188 {
1189 // For inline shapes, output as a Polyline with the rectangle corners
1190 int stroke_width = aShape.GetStroke().GetWidth();
1191 LINE_STYLE dash = aShape.GetStroke().GetLineStyle();
1192
1193 wxXmlNode* polyline_node = appendNode( aContentNode, "Polyline" );
1194
1195 // Get the rectangle corners. Use GetRectCorners for proper handling
1196 std::vector<VECTOR2I> corners = aShape.GetRectCorners();
1197
1198 wxXmlNode* begin_node = appendNode( polyline_node, "PolyBegin" );
1199 addXY( begin_node, corners[0] );
1200
1201 for( size_t i = 1; i < corners.size(); ++i )
1202 {
1203 wxXmlNode* step_node = appendNode( polyline_node, "PolyStepSegment" );
1204 addXY( step_node, corners[i] );
1205 }
1206
1207 // Close the rectangle
1208 wxXmlNode* close_node = appendNode( polyline_node, "PolyStepSegment" );
1209 addXY( close_node, corners[0] );
1210
1211 if( stroke_width > 0 )
1212 addLineDesc( polyline_node, stroke_width, dash, true );
1213
1214 break;
1215 }
1216
1217 name = wxString::Format( "URECT_%zu", m_user_shape_dict.size() + 1 );
1218 m_user_shape_dict.emplace( hash, name );
1219
1220 wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" );
1221 addAttribute( entry_node, "id", name );
1222 wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" );
1223
1224 int width = std::abs( aShape.GetRectangleWidth() );
1225 int height = std::abs( aShape.GetRectangleHeight() );
1226 int stroke_width = aShape.GetStroke().GetWidth();
1227
1228 wxXmlNode* rect_node = appendNode( special_node, "RectRound" );
1229 addLineDesc( rect_node, aShape.GetStroke().GetWidth(), aShape.GetStroke().GetLineStyle(),
1230 true );
1231
1232 if( aShape.GetFillMode() == FILL_T::NO_FILL )
1233 {
1234 addAttribute( rect_node, "upperRight", "false" );
1235 addAttribute( rect_node, "upperLeft", "false" );
1236 addAttribute( rect_node, "lowerRight", "false" );
1237 addAttribute( rect_node, "lowerLeft", "false" );
1238 }
1239 else
1240 {
1241 addAttribute( rect_node, "upperRight", "true" );
1242 addAttribute( rect_node, "upperLeft", "true" );
1243 addAttribute( rect_node, "lowerRight", "true" );
1244 addAttribute( rect_node, "lowerLeft", "true" );
1245 width += stroke_width;
1246 height += stroke_width;
1247 }
1248
1249 addFillDesc( rect_node, aShape.GetFillMode() );
1250
1251 addAttribute( rect_node, "width", floatVal( m_scale * width ) );
1252 addAttribute( rect_node, "height", floatVal( m_scale * height ) );
1253 addAttribute( rect_node, "radius", floatVal( m_scale * ( stroke_width / 2.0 ) ) );
1254
1255 break;
1256 }
1257
1258 case SHAPE_T::POLY:
1259 {
1260 if( aInline )
1261 {
1262 // For inline shapes, output as Polyline elements directly
1263 const SHAPE_POLY_SET& poly_set = aShape.GetPolyShape();
1264 int stroke_width = aShape.GetStroke().GetWidth();
1265 LINE_STYLE dash = aShape.GetStroke().GetLineStyle();
1266
1267 for( int ii = 0; ii < poly_set.OutlineCount(); ++ii )
1268 {
1269 const SHAPE_LINE_CHAIN& outline = poly_set.Outline( ii );
1270
1271 if( outline.PointCount() < 2 )
1272 continue;
1273
1274 wxXmlNode* polyline_node = appendNode( aContentNode, "Polyline" );
1275 const std::vector<VECTOR2I>& pts = outline.CPoints();
1276
1277 wxXmlNode* begin_node = appendNode( polyline_node, "PolyBegin" );
1278 addXY( begin_node, pts[0] );
1279
1280 for( size_t jj = 1; jj < pts.size(); ++jj )
1281 {
1282 wxXmlNode* step_node = appendNode( polyline_node, "PolyStepSegment" );
1283 addXY( step_node, pts[jj] );
1284 }
1285
1286 // Close the polygon if needed
1287 if( pts.size() > 2 && pts.front() != pts.back() )
1288 {
1289 wxXmlNode* close_node = appendNode( polyline_node, "PolyStepSegment" );
1290 addXY( close_node, pts[0] );
1291 }
1292
1293 if( stroke_width > 0 )
1294 addLineDesc( polyline_node, stroke_width, dash, true );
1295 }
1296
1297 break;
1298 }
1299
1300 name = wxString::Format( "UPOLY_%zu", m_user_shape_dict.size() + 1 );
1301 m_user_shape_dict.emplace( hash, name );
1302
1303 wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" );
1304 addAttribute( entry_node, "id", name );
1305
1306 // If we are stroking a polygon, we need two contours. This is only allowed
1307 // inside a "UserSpecial" shape
1308 wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" );
1309
1310 const SHAPE_POLY_SET& poly_set = aShape.GetPolyShape();
1311
1312 for( int ii = 0; ii < poly_set.OutlineCount(); ++ii )
1313 {
1314 if( aShape.GetFillMode() != FILL_T::NO_FILL )
1315 {
1316 // IPC2581 does not allow strokes on filled elements
1317 addContourNode( special_node, poly_set, ii, FILL_T::FILLED_SHAPE, 0,
1319 }
1320
1321 addContourNode( special_node, poly_set, ii, FILL_T::NO_FILL,
1322 aShape.GetStroke().GetWidth(), aShape.GetStroke().GetLineStyle() );
1323 }
1324
1325 break;
1326 }
1327
1328 case SHAPE_T::ARC:
1329 {
1330 wxXmlNode* arc_node = appendNode( aContentNode, "Arc" );
1331 addXY( arc_node, aShape.GetStart(), "startX", "startY" );
1332 addXY( arc_node, aShape.GetEnd(), "endX", "endY" );
1333 addXY( arc_node, aShape.GetCenter(), "centerX", "centerY" );
1334
1335 //N.B. because our coordinate system is flipped, we need to flip the arc direction
1336 addAttribute( arc_node, "clockwise", !aShape.IsClockwiseArc() ? "true" : "false" );
1337
1338 if( aShape.GetStroke().GetWidth() > 0 )
1339 {
1340 addLineDesc( arc_node, aShape.GetStroke().GetWidth(),
1341 aShape.GetStroke().GetLineStyle(), true );
1342 }
1343
1344 break;
1345 }
1346
1347 case SHAPE_T::BEZIER:
1348 {
1349 wxXmlNode* polyline_node = appendNode( aContentNode, "Polyline" );
1350 std::vector<VECTOR2I> ctrlPoints = { aShape.GetStart(), aShape.GetBezierC1(),
1351 aShape.GetBezierC2(), aShape.GetEnd() };
1352 BEZIER_POLY converter( ctrlPoints );
1353 std::vector<VECTOR2I> points;
1354 converter.GetPoly( points, ARC_HIGH_DEF );
1355
1356 wxXmlNode* point_node = appendNode( polyline_node, "PolyBegin" );
1357 addXY( point_node, points[0] );
1358
1359 for( size_t i = 1; i < points.size(); i++ )
1360 {
1361 wxXmlNode* seg_node = appendNode( polyline_node, "PolyStepSegment" );
1362 addXY( seg_node, points[i] );
1363 }
1364
1365 if( aShape.GetStroke().GetWidth() > 0 )
1366 {
1367 addLineDesc( polyline_node, aShape.GetStroke().GetWidth(),
1368 aShape.GetStroke().GetLineStyle(), true );
1369 }
1370
1371 break;
1372 }
1373
1374 case SHAPE_T::SEGMENT:
1375 {
1376 wxXmlNode* line_node = appendNode( aContentNode, "Line" );
1377 addXY( line_node, aShape.GetStart(), "startX", "startY" );
1378 addXY( line_node, aShape.GetEnd(), "endX", "endY" );
1379
1380 if( aShape.GetStroke().GetWidth() > 0 )
1381 {
1382 addLineDesc( line_node, aShape.GetStroke().GetWidth(),
1383 aShape.GetStroke().GetLineStyle(), true );
1384 }
1385
1386 break;
1387 }
1388
1389 case SHAPE_T::UNDEFINED:
1390 wxFAIL;
1391 }
1392
1393 // Only add UserPrimitiveRef when not in inline mode and a dictionary entry was created
1394 if( !aInline && !name.empty() )
1395 {
1396 wxXmlNode* shape_node = appendNode( aContentNode, "UserPrimitiveRef" );
1397 addAttribute( shape_node, "id", name );
1398 }
1399
1400}
1401
1402
1403void PCB_IO_IPC2581::addSlotCavity( wxXmlNode* aNode, const PAD& aPad, const wxString& aName )
1404{
1405 wxXmlNode* slotNode = appendNode( aNode, "SlotCavity" );
1406 addAttribute( slotNode, "name", aName );
1407 addAttribute( slotNode, "platingStatus", aPad.GetAttribute() == PAD_ATTRIB::PTH ? "PLATED"
1408 : "NONPLATED" );
1409 addAttribute( slotNode, "plusTol", "0.0" );
1410 addAttribute( slotNode, "minusTol", "0.0" );
1411
1412 if( m_version > 'B' )
1413 addLocationNode( slotNode, aPad, false );
1414
1415 // Normally only oblong drill shapes should reach this code path since m_slot_holes
1416 // is filtered to pads where DrillSizeX != DrillSizeY. However, use a fallback to
1417 // ensure valid XML is always generated.
1419 {
1420 VECTOR2I drill_size = aPad.GetDrillSize();
1421 EDA_ANGLE rotation = aPad.GetOrientation().Normalize();
1422
1423 // IPC-2581C requires width >= height for Oval primitive
1424 // Swap dimensions if needed and adjust rotation accordingly
1425 if( drill_size.y > drill_size.x )
1426 {
1427 std::swap( drill_size.x, drill_size.y );
1428 rotation = ( rotation + ANGLE_90 ).Normalize();
1429 }
1430
1431 // Add Xform if rotation is needed (must come before Feature per IPC-2581C schema)
1432 if( rotation != ANGLE_0 )
1433 {
1434 wxXmlNode* xformNode = appendNode( slotNode, "Xform" );
1435 addAttribute( xformNode, "rotation", floatVal( rotation.AsDegrees() ) );
1436 }
1437
1438 // Use IPC-2581 Oval primitive for oblong slots
1439 wxXmlNode* ovalNode = appendNode( slotNode, "Oval" );
1440 addAttribute( ovalNode, "width", floatVal( m_scale * drill_size.x ) );
1441 addAttribute( ovalNode, "height", floatVal( m_scale * drill_size.y ) );
1442 }
1443 else
1444 {
1445 // Fallback to polygon outline for non-oblong shapes
1446 SHAPE_POLY_SET poly_set;
1447 int maxError = m_board->GetDesignSettings().m_MaxError;
1448 aPad.TransformHoleToPolygon( poly_set, 0, maxError, ERROR_INSIDE );
1449
1450 addOutlineNode( slotNode, poly_set );
1451 }
1452}
1453
1454
1456{
1457 wxXmlNode* logisticNode = appendNode( m_xml_root, "LogisticHeader" );
1458
1459 wxXmlNode* roleNode = appendNode( logisticNode, "Role" );
1460 addAttribute( roleNode, "id", "Owner" );
1461 addAttribute( roleNode, "roleFunction", "SENDER" );
1462
1463 m_enterpriseNode = appendNode( logisticNode, "Enterprise" );
1464 addAttribute( m_enterpriseNode, "id", "UNKNOWN" );
1465 addAttribute( m_enterpriseNode, "code", "NONE" );
1466
1467 wxXmlNode* personNode = appendNode( logisticNode, "Person" );
1468 addAttribute( personNode, "name", "UNKNOWN" );
1469 addAttribute( personNode, "enterpriseRef", "UNKNOWN" );
1470 addAttribute( personNode, "roleRef", "Owner" );
1471
1472 return logisticNode;
1473}
1474
1475
1477{
1478 if( m_progressReporter )
1479 m_progressReporter->AdvancePhase( _( "Generating history section" ) );
1480
1481 wxXmlNode* historyNode = appendNode( m_xml_root, "HistoryRecord" );
1482 addAttribute( historyNode, "number", "1" );
1483 addAttribute( historyNode, "origination", wxDateTime::Now().FormatISOCombined() );
1484 addAttribute( historyNode, "software", "KiCad EDA" );
1485 addAttribute( historyNode, "lastChange", wxDateTime::Now().FormatISOCombined() );
1486
1487 wxXmlNode* fileRevisionNode = appendNode( historyNode, "FileRevision" );
1488 addAttribute( fileRevisionNode, "fileRevisionId", "1" );
1489 addAttribute( fileRevisionNode, "comment", "NO COMMENT" );
1490 addAttribute( fileRevisionNode, "label", "NO LABEL" );
1491
1492 wxXmlNode* softwarePackageNode = appendNode( fileRevisionNode, "SoftwarePackage" );
1493 addAttribute( softwarePackageNode, "name", "KiCad" );
1494 addAttribute( softwarePackageNode, "revision", GetMajorMinorPatchVersion() );
1495 addAttribute( softwarePackageNode, "vendor", "KiCad EDA" );
1496
1497 wxXmlNode* certificationNode = appendNode( softwarePackageNode, "Certification" );
1498 addAttribute( certificationNode, "certificationStatus", "SELFTEST" );
1499
1500 return historyNode;
1501}
1502
1503
1504wxXmlNode* PCB_IO_IPC2581::generateBOMSection( wxXmlNode* aEcadNode )
1505{
1506 if( m_progressReporter )
1507 m_progressReporter->AdvancePhase( _( "Generating BOM section" ) );
1508
1509 struct REFDES
1510 {
1511 wxString m_name;
1512 wxString m_pkg;
1513 bool m_populate;
1514 wxString m_layer;
1515 };
1516
1517 struct BOM_ENTRY
1518 {
1519 BOM_ENTRY()
1520 {
1521 m_refdes = new std::vector<REFDES>();
1522 m_props = new std::map<wxString, wxString>();
1523 m_count = 0;
1524 m_pads = 0;
1525 }
1526
1527 ~BOM_ENTRY()
1528 {
1529 delete m_refdes;
1530 delete m_props;
1531 }
1532
1533 wxString m_OEMDesignRef; // String combining LIB+FP+VALUE
1534 int m_count;
1535 int m_pads;
1536 wxString m_type;
1537 wxString m_description;
1538
1539 std::vector<REFDES>* m_refdes;
1540 std::map<wxString, wxString>* m_props;
1541 };
1542
1543 std::set<std::unique_ptr<struct BOM_ENTRY>,
1544 std::function<bool( const std::unique_ptr<struct BOM_ENTRY>&,
1545 const std::unique_ptr<struct BOM_ENTRY>& )>> bom_entries(
1546 []( const std::unique_ptr<struct BOM_ENTRY>& a,
1547 const std::unique_ptr<struct BOM_ENTRY>& b )
1548 {
1549 return a->m_OEMDesignRef < b->m_OEMDesignRef;
1550 } );
1551
1552 for( FOOTPRINT* fp_it : m_board->Footprints() )
1553 {
1554 std::unique_ptr<FOOTPRINT> fp( static_cast<FOOTPRINT*>( fp_it->Clone() ) );
1555 fp->SetParentGroup( nullptr );
1556 fp->SetPosition( {0, 0} );
1557 fp->SetOrientation( ANGLE_0 );
1558
1559 // Normalize to unflipped state to match hash computed in addPackage
1560 if( fp->IsFlipped() )
1561 fp->Flip( fp->GetPosition(), FLIP_DIRECTION::TOP_BOTTOM );
1562
1563 size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD );
1564 auto iter = m_footprint_dict.find( hash );
1565
1566 if( iter == m_footprint_dict.end() )
1567 {
1568 Report( wxString::Format( _( "Footprint %s not found in dictionary; BOM data may be incomplete." ),
1569 fp->GetFPID().GetLibItemName().wx_str() ),
1571 continue;
1572 }
1573
1574 auto entry = std::make_unique<struct BOM_ENTRY>();
1575
1578 if( auto it = m_OEMRef_dict.find( fp_it ); it != m_OEMRef_dict.end() )
1579 {
1580 entry->m_OEMDesignRef = it->second;
1581 }
1582 else
1583 {
1584 Report( wxString::Format( _( "Component \"%s\" missing OEM reference; BOM entry will be skipped." ),
1585 fp->GetFPID().GetLibItemName().wx_str() ),
1587 }
1588
1589 entry->m_OEMDesignRef = genString( entry->m_OEMDesignRef, "REF" );
1590 entry->m_count = 1;
1591 entry->m_pads = fp->GetPadCount();
1592
1593 // TODO: The options are "ELECTRICAL", "MECHANICAL", "PROGRAMMABLE", "DOCUMENT", "MATERIAL"
1594 // We need to figure out how to determine this.
1595 const wxString variantName = m_board ? m_board->GetCurrentVariant() : wxString();
1596
1597 if( entry->m_pads == 0 || fp_it->GetExcludedFromBOMForVariant( variantName ) )
1598 entry->m_type = "DOCUMENT";
1599 else
1600 entry->m_type = "ELECTRICAL";
1601
1602 // Use the footprint's Description field if it exists
1603 const PCB_FIELD* descField = fp_it->GetField( FIELD_T::DESCRIPTION );
1604
1605 if( descField && !descField->GetShownText( false ).IsEmpty() )
1606 entry->m_description = descField->GetShownText( false );
1607
1608 auto[ bom_iter, inserted ] = bom_entries.insert( std::move( entry ) );
1609
1610 if( !inserted )
1611 ( *bom_iter )->m_count++;
1612
1613 REFDES refdes;
1614 refdes.m_name = componentName( fp_it );
1615 refdes.m_pkg = fp->GetFPID().GetLibItemName().wx_str();
1616 refdes.m_populate = !fp->GetDNPForVariant( variantName )
1617 && !fp->GetExcludedFromBOMForVariant( variantName );
1618 refdes.m_layer = m_layer_name_map[fp_it->GetLayer()];
1619
1620 ( *bom_iter )->m_refdes->push_back( refdes );
1621
1622 // TODO: This amalgamates all the properties from all the footprints. We need to decide
1623 // if we want to group footprints by their properties
1624 for( PCB_FIELD* prop : fp->GetFields() )
1625 {
1626 // We don't include Reference, Datasheet, or Description in BOM characteristics.
1627 // Value and any user-defined fields are included. Reference is captured above,
1628 // and Description is used for the BomItem description attribute.
1629 if( prop->IsMandatory() && !prop->IsValue() )
1630 continue;
1631
1632 ( *bom_iter )->m_props->emplace( prop->GetName(), prop->GetShownText( false ) );
1633 }
1634 }
1635
1636 if( bom_entries.empty() )
1637 return nullptr;
1638
1639 wxFileName fn( m_board->GetFileName() );
1640
1641 wxXmlNode* bomNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Bom" );
1642 m_xml_root->InsertChild( bomNode, aEcadNode );
1643 addAttribute( bomNode, "name", genString( fn.GetName(), "BOM" ) );
1644
1645 wxXmlNode* bomHeaderNode = appendNode( bomNode, "BomHeader" );
1646 wxString bomRevision = m_bomRev;
1647
1648 if( bomRevision.IsEmpty() )
1649 bomRevision = m_board->GetTitleBlock().GetRevision();
1650
1651 if( bomRevision.IsEmpty() )
1652 bomRevision = wxS( "1.0" );
1653
1654 addAttribute( bomHeaderNode, "revision", bomRevision );
1655 addAttribute( bomHeaderNode, "assembly", genString( fn.GetName() ) );
1656
1657 wxXmlNode* stepRefNode = appendNode( bomHeaderNode, "StepRef" );
1658 addAttribute( stepRefNode, "name", genString( fn.GetName(), "BOARD" ) );
1659
1660 for( const auto& entry : bom_entries )
1661 {
1662 wxXmlNode* bomEntryNode = appendNode( bomNode, "BomItem" );
1663 addAttribute( bomEntryNode, "OEMDesignNumberRef", entry->m_OEMDesignRef );
1664 addAttribute( bomEntryNode, "quantity", wxString::Format( "%d", entry->m_count ) );
1665 addAttribute( bomEntryNode, "pinCount", wxString::Format( "%d", entry->m_pads ) );
1666 addAttribute( bomEntryNode, "category", entry->m_type );
1667
1668 if( !entry->m_description.IsEmpty() )
1669 addAttribute( bomEntryNode, "description", entry->m_description );
1670
1671 for( const REFDES& refdes : *( entry->m_refdes ) )
1672 {
1673 wxXmlNode* refdesNode = appendNode( bomEntryNode, "RefDes" );
1674 addAttribute( refdesNode, "name", refdes.m_name );
1675 addAttribute( refdesNode, "packageRef", genString( refdes.m_pkg, "PKG" ) );
1676 addAttribute( refdesNode, "populate", refdes.m_populate ? "true" : "false" );
1677 addAttribute( refdesNode, "layerRef", refdes.m_layer );
1678 }
1679
1680 wxXmlNode* characteristicsNode = appendNode( bomEntryNode, "Characteristics" );
1681 addAttribute( characteristicsNode, "category", entry->m_type );
1682
1683 for( const auto& prop : *( entry->m_props ) )
1684 {
1685 wxXmlNode* textualDefNode = appendNode( characteristicsNode, "Textual" );
1686 addAttribute( textualDefNode, "definitionSource", "KICAD" );
1687 addAttribute( textualDefNode, "textualCharacteristicName", prop.first );
1688 addAttribute( textualDefNode, "textualCharacteristicValue", prop.second );
1689 }
1690 }
1691
1692 return bomNode;
1693}
1694
1695
1697{
1698 if( m_progressReporter )
1699 m_progressReporter->AdvancePhase( _( "Generating CAD data" ) );
1700
1701 wxXmlNode* ecadNode = appendNode( m_xml_root, "Ecad" );
1702 addAttribute( ecadNode, "name", "Design" );
1703
1704 addCadHeader( ecadNode );
1705
1706 wxXmlNode* cadDataNode = appendNode( ecadNode, "CadData" );
1707 generateCadLayers( cadDataNode );
1708 generateDrillLayers( cadDataNode );
1709 generateAuxilliaryLayers( cadDataNode );
1710 generateStackup( cadDataNode );
1711 generateStepSection( cadDataNode );
1712
1714
1715 return ecadNode;
1716}
1717
1718
1719void PCB_IO_IPC2581::generateCadSpecs( wxXmlNode* aCadLayerNode )
1720{
1721 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
1722 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
1723 stackup.SynchronizeWithBoard( &dsnSettings );
1724
1725 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
1726 std::set<PCB_LAYER_ID> added_layers;
1727
1728 for( int i = 0; i < stackup.GetCount(); i++ )
1729 {
1730 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
1731
1732 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
1733 {
1734 wxString ly_name = stackup_item->GetLayerName();
1735
1736 if( ly_name.IsEmpty() )
1737 {
1738 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
1739 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
1740
1741 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1742 {
1743 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
1744
1745 if( sublayer_id > 0 )
1746 ly_name += wxString::Format( "_%d", sublayer_id );
1747 }
1748 }
1749
1750 ly_name = genString( ly_name, "SPEC_LAYER" );
1751
1752 wxXmlNode* specNode = appendNode( aCadLayerNode, "Spec" );
1753 addAttribute( specNode, "name", ly_name );
1754 wxXmlNode* generalNode = appendNode( specNode, "General" );
1755 addAttribute( generalNode, "type", "MATERIAL" );
1756 wxXmlNode* propertyNode = appendNode( generalNode, "Property" );
1757
1758 switch ( stackup_item->GetType() )
1759 {
1761 {
1762 addAttribute( propertyNode, "text", "COPPER" );
1763 wxXmlNode* conductorNode = appendNode( specNode, "Conductor" );
1764 addAttribute( conductorNode, "type", "CONDUCTIVITY" );
1765 propertyNode = appendNode( conductorNode, "Property" );
1766 addAttribute( propertyNode, "unit", wxT( "SIEMENS/M" ) );
1767 addAttribute( propertyNode, "value", wxT( "5.959E7" ) );
1768 break;
1769 }
1771 {
1772 addAttribute( propertyNode, "text", stackup_item->GetMaterial() );
1773 propertyNode = appendNode( generalNode, "Property" );
1774 addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
1775 stackup_item->GetTypeName() ) );
1776 wxXmlNode* dielectricNode = appendNode( specNode, "Dielectric" );
1777 addAttribute( dielectricNode, "type", "DIELECTRIC_CONSTANT" );
1778 propertyNode = appendNode( dielectricNode, "Property" );
1779 addAttribute( propertyNode, "value",
1780 floatVal( stackup_item->GetEpsilonR( sublayer_id ) ) );
1781 dielectricNode = appendNode( specNode, "Dielectric" );
1782 addAttribute( dielectricNode, "type", "LOSS_TANGENT" );
1783 propertyNode = appendNode( dielectricNode, "Property" );
1784 addAttribute( propertyNode, "value",
1785 floatVal( stackup_item->GetLossTangent( sublayer_id ) ) );
1786 break;
1787 }
1789 addAttribute( propertyNode, "text", stackup_item->GetTypeName() );
1790 propertyNode = appendNode( generalNode, "Property" );
1791 addAttribute( propertyNode, "text", wxString::Format( "Color : %s",
1792 stackup_item->GetColor() ) );
1793 propertyNode = appendNode( generalNode, "Property" );
1794 addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
1795 stackup_item->GetTypeName() ) );
1796 break;
1798 {
1799 addAttribute( propertyNode, "text", "SOLDERMASK" );
1800 propertyNode = appendNode( generalNode, "Property" );
1801 addAttribute( propertyNode, "text", wxString::Format( "Color : %s",
1802 stackup_item->GetColor() ) );
1803 propertyNode = appendNode( generalNode, "Property" );
1804 addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
1805 stackup_item->GetTypeName() ) );
1806
1807 // Generate Epsilon R if > 1.0 (value <= 1.0 means not specified)
1808 if( stackup_item->GetEpsilonR( sublayer_id ) > 1.0 )
1809 {
1810 wxXmlNode* dielectricNode = appendNode( specNode, "Dielectric" );
1811 addAttribute( dielectricNode, "type", "DIELECTRIC_CONSTANT" );
1812 propertyNode = appendNode( dielectricNode, "Property" );
1813 addAttribute( propertyNode, "value", floatVal( stackup_item->GetEpsilonR( sublayer_id ) ) );
1814 }
1815
1816 // Generate LossTangent if > 0.0 (value <= 0.0 means not specified)
1817 if( stackup_item->GetLossTangent( sublayer_id ) > 0.0 )
1818 {
1819 wxXmlNode* dielectricNode = appendNode( specNode, "Dielectric" );
1820 addAttribute( dielectricNode, "type", "LOSS_TANGENT" );
1821 propertyNode = appendNode( dielectricNode, "Property" );
1822 addAttribute( propertyNode, "value", floatVal( stackup_item->GetLossTangent( sublayer_id ) ) );
1823 }
1824 break;
1825 }
1826 default:
1827 break;
1828 }
1829 }
1830 }
1831
1832 // SurfaceFinish is only defined as a SpecificationType in IPC-2581C
1833 if( m_version > 'B' )
1834 {
1835 surfaceFinishType finishType = getSurfaceFinishType( stackup.m_FinishType );
1836
1837 if( finishType != surfaceFinishType::NONE )
1838 {
1839 wxXmlNode* specNode = appendNode( aCadLayerNode, "Spec" );
1840 addAttribute( specNode, "name", "SURFACE_FINISH" );
1841
1842 wxXmlNode* surfaceFinishNode = appendNode( specNode, "SurfaceFinish" );
1843 addAttribute( surfaceFinishNode, "type", surfaceFinishTypeToString.at( finishType ) );
1844
1845 if( finishType == surfaceFinishType::OTHER )
1846 addAttribute( surfaceFinishNode, "comment", stackup.m_FinishType );
1847 }
1848 }
1849}
1850
1851
1852void PCB_IO_IPC2581::addCadHeader( wxXmlNode* aEcadNode )
1853{
1854 wxXmlNode* cadHeaderNode = appendNode( aEcadNode, "CadHeader" );
1855 addAttribute( cadHeaderNode, "units", m_units_str );
1856
1857 m_cad_header_node = cadHeaderNode;
1858
1859 generateCadSpecs( cadHeaderNode );
1860}
1861
1862
1864{
1865 return ( aLayer >= F_Cu && aLayer <= User_9 ) || aLayer == UNDEFINED_LAYER;
1866}
1867
1868
1869void PCB_IO_IPC2581::addLayerAttributes( wxXmlNode* aNode, PCB_LAYER_ID aLayer )
1870{
1871 switch( aLayer )
1872 {
1873 case F_Adhes:
1874 case B_Adhes:
1875 addAttribute( aNode, "layerFunction", "GLUE" );
1876 addAttribute( aNode, "polarity", "POSITIVE" );
1877 addAttribute( aNode, "side", aLayer == F_Adhes ? "TOP" : "BOTTOM" );
1878 break;
1879 case F_Paste:
1880 case B_Paste:
1881 addAttribute( aNode, "layerFunction", "SOLDERPASTE" );
1882 addAttribute( aNode, "polarity", "POSITIVE" );
1883 addAttribute( aNode, "side", aLayer == F_Paste ? "TOP" : "BOTTOM" );
1884 break;
1885 case F_SilkS:
1886 case B_SilkS:
1887 addAttribute( aNode, "layerFunction", "SILKSCREEN" );
1888 addAttribute( aNode, "polarity", "POSITIVE" );
1889 addAttribute( aNode, "side", aLayer == F_SilkS ? "TOP" : "BOTTOM" );
1890 break;
1891 case F_Mask:
1892 case B_Mask:
1893 addAttribute( aNode, "layerFunction", "SOLDERMASK" );
1894 addAttribute( aNode, "polarity", "POSITIVE" );
1895 addAttribute( aNode, "side", aLayer == F_Mask ? "TOP" : "BOTTOM" );
1896 break;
1897 case Edge_Cuts:
1898 addAttribute( aNode, "layerFunction", "BOARD_OUTLINE" );
1899 addAttribute( aNode, "polarity", "POSITIVE" );
1900 addAttribute( aNode, "side", "ALL" );
1901 break;
1902 case B_CrtYd:
1903 case F_CrtYd:
1904 addAttribute( aNode, "layerFunction", "COURTYARD" );
1905 addAttribute( aNode, "polarity", "POSITIVE" );
1906 addAttribute( aNode, "side", aLayer == F_CrtYd ? "TOP" : "BOTTOM" );
1907 break;
1908 case B_Fab:
1909 case F_Fab:
1910 addAttribute( aNode, "layerFunction", "ASSEMBLY" );
1911 addAttribute( aNode, "polarity", "POSITIVE" );
1912 addAttribute( aNode, "side", aLayer == F_Fab ? "TOP" : "BOTTOM" );
1913 break;
1914 case Dwgs_User:
1915 case Cmts_User:
1916 case Eco1_User:
1917 case Eco2_User:
1918 case Margin:
1919 case User_1:
1920 case User_2:
1921 case User_3:
1922 case User_4:
1923 case User_5:
1924 case User_6:
1925 case User_7:
1926 case User_8:
1927 case User_9:
1928 addAttribute( aNode, "layerFunction", "DOCUMENT" );
1929 addAttribute( aNode, "polarity", "POSITIVE" );
1930 addAttribute( aNode, "side", "NONE" );
1931 break;
1932
1933 default:
1934 if( IsCopperLayer( aLayer ) )
1935 {
1936 addAttribute( aNode, "layerFunction", "CONDUCTOR" );
1937 addAttribute( aNode, "polarity", "POSITIVE" );
1938 addAttribute( aNode, "side",
1939 aLayer == F_Cu ? "TOP"
1940 : aLayer == B_Cu ? "BOTTOM"
1941 : "INTERNAL" );
1942 }
1943
1944 break; // Do not handle other layers
1945 }
1946}
1947
1948
1949void PCB_IO_IPC2581::generateStackup( wxXmlNode* aCadLayerNode )
1950{
1951 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
1952 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
1953 stackup.SynchronizeWithBoard( &dsnSettings );
1954
1955 // Coating layers reference the SurfaceFinish Spec which is only valid in IPC-2581C
1956 surfaceFinishType finishType = getSurfaceFinishType( stackup.m_FinishType );
1957 bool hasCoating = ( m_version > 'B' && finishType != surfaceFinishType::NONE );
1958
1959 wxXmlNode* stackupNode = appendNode( aCadLayerNode, "Stackup" );
1960 addAttribute( stackupNode, "name", "Primary_Stackup" );
1961 addAttribute( stackupNode, "overallThickness", floatVal( m_scale * stackup.BuildBoardThicknessFromStackup() ) );
1962 addAttribute( stackupNode, "tolPlus", "0.0" );
1963 addAttribute( stackupNode, "tolMinus", "0.0" );
1964 addAttribute( stackupNode, "whereMeasured", "MASK" );
1965
1966 if( m_version > 'B' )
1967 addAttribute( stackupNode, "stackupStatus", "PROPOSED" );
1968
1969 wxXmlNode* stackupGroup = appendNode( stackupNode, "StackupGroup" );
1970 addAttribute( stackupGroup, "name", "Primary_Stackup_Group" );
1971 addAttribute( stackupGroup, "thickness", floatVal( m_scale * stackup.BuildBoardThicknessFromStackup() ) );
1972 addAttribute( stackupGroup, "tolPlus", "0.0" );
1973 addAttribute( stackupGroup, "tolMinus", "0.0" );
1974
1975 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
1976 std::set<PCB_LAYER_ID> added_layers;
1977 int sequence = 0;
1978
1979 for( int i = 0; i < stackup.GetCount(); i++ )
1980 {
1981 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
1982
1983 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
1984 {
1985 PCB_LAYER_ID layer_id = stackup_item->GetBrdLayerId();
1986
1987 // Insert top coating layer before F.Cu
1988 if( hasCoating && layer_id == F_Cu && sublayer_id == 0 )
1989 {
1990 wxXmlNode* coatingLayer = appendNode( stackupGroup, "StackupLayer" );
1991 addAttribute( coatingLayer, "layerOrGroupRef", "COATING_TOP" );
1992 addAttribute( coatingLayer, "thickness", "0.0" );
1993 addAttribute( coatingLayer, "tolPlus", "0.0" );
1994 addAttribute( coatingLayer, "tolMinus", "0.0" );
1995 addAttribute( coatingLayer, "sequence", wxString::Format( "%d", sequence++ ) );
1996
1997 wxXmlNode* specRefNode = appendNode( coatingLayer, "SpecRef" );
1998 addAttribute( specRefNode, "id", "SURFACE_FINISH" );
1999 }
2000
2001 wxXmlNode* stackupLayer = appendNode( stackupGroup, "StackupLayer" );
2002 wxString ly_name = stackup_item->GetLayerName();
2003
2004 if( ly_name.IsEmpty() )
2005 {
2006 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
2007 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
2008
2009 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
2010 {
2011 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
2012
2013 if( sublayer_id > 0 )
2014 ly_name += wxString::Format( "_%d", sublayer_id );
2015 }
2016 }
2017
2018 wxString spec_name = genString( ly_name, "SPEC_LAYER" );
2019 ly_name = genString( ly_name, "LAYER" );
2020
2021 addAttribute( stackupLayer, "layerOrGroupRef", ly_name );
2022 addAttribute( stackupLayer, "thickness", floatVal( m_scale * stackup_item->GetThickness() ) );
2023 addAttribute( stackupLayer, "tolPlus", "0.0" );
2024 addAttribute( stackupLayer, "tolMinus", "0.0" );
2025 addAttribute( stackupLayer, "sequence", wxString::Format( "%d", sequence++ ) );
2026
2027 wxXmlNode* specLayerNode = appendNode( stackupLayer, "SpecRef" );
2028 addAttribute( specLayerNode, "id", spec_name );
2029
2030 // Insert bottom coating layer after B.Cu
2031 if( hasCoating && layer_id == B_Cu && sublayer_id == stackup_item->GetSublayersCount() - 1 )
2032 {
2033 wxXmlNode* coatingLayer = appendNode( stackupGroup, "StackupLayer" );
2034 addAttribute( coatingLayer, "layerOrGroupRef", "COATING_BOTTOM" );
2035 addAttribute( coatingLayer, "thickness", "0.0" );
2036 addAttribute( coatingLayer, "tolPlus", "0.0" );
2037 addAttribute( coatingLayer, "tolMinus", "0.0" );
2038 addAttribute( coatingLayer, "sequence", wxString::Format( "%d", sequence++ ) );
2039
2040 wxXmlNode* specRefNode = appendNode( coatingLayer, "SpecRef" );
2041 addAttribute( specRefNode, "id", "SURFACE_FINISH" );
2042 }
2043 }
2044 }
2045}
2046
2047
2048void PCB_IO_IPC2581::generateCadLayers( wxXmlNode* aCadLayerNode )
2049{
2050
2051 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
2052 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
2053 stackup.SynchronizeWithBoard( &dsnSettings );
2054
2055 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
2056 std::set<PCB_LAYER_ID> added_layers;
2057
2058 for( int i = 0; i < stackup.GetCount(); i++ )
2059 {
2060 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
2061
2062 if( !isValidLayerFor2581( stackup_item->GetBrdLayerId() ) )
2063 continue;
2064
2065 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
2066 {
2067 wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" );
2068 wxString ly_name = stackup_item->GetLayerName();
2069
2070 if( ly_name.IsEmpty() )
2071 {
2072
2073 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
2074 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
2075
2076 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
2077 {
2078 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
2079
2080 if( sublayer_id > 0 )
2081 ly_name += wxString::Format( "_%d", sublayer_id );
2082 }
2083 }
2084
2085 ly_name = genString( ly_name, "LAYER" );
2086
2087 addAttribute( cadLayerNode, "name", ly_name );
2088
2089 if( stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
2090 {
2091 if( stackup_item->GetTypeName() == KEY_CORE )
2092 addAttribute( cadLayerNode, "layerFunction", "DIELCORE" );
2093 else
2094 addAttribute( cadLayerNode, "layerFunction", "DIELPREG" );
2095
2096 addAttribute( cadLayerNode, "polarity", "POSITIVE" );
2097 addAttribute( cadLayerNode, "side", "INTERNAL" );
2098 continue;
2099 }
2100 else
2101 {
2102 added_layers.insert( stackup_item->GetBrdLayerId() );
2103 addLayerAttributes( cadLayerNode, stackup_item->GetBrdLayerId() );
2104 m_layer_name_map.emplace( stackup_item->GetBrdLayerId(), ly_name );
2105 }
2106 }
2107 }
2108
2109 LSEQ layer_seq = m_board->GetEnabledLayers().Seq();
2110
2111 for( PCB_LAYER_ID layer : layer_seq )
2112 {
2113 if( added_layers.find( layer ) != added_layers.end() || !isValidLayerFor2581( layer ) )
2114 continue;
2115
2116 wxString ly_name = genLayerString( layer, "LAYER" );
2117 m_layer_name_map.emplace( layer, ly_name );
2118 added_layers.insert( layer );
2119 wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" );
2120 addAttribute( cadLayerNode, "name", ly_name );
2121
2122 addLayerAttributes( cadLayerNode, layer );
2123 }
2124
2125 // COATINGCOND layers reference the SurfaceFinish Spec which is only valid in IPC-2581C
2126 if( m_version > 'B' )
2127 {
2128 surfaceFinishType finishType = getSurfaceFinishType( stackup.m_FinishType );
2129
2130 if( finishType != surfaceFinishType::NONE )
2131 {
2132 wxXmlNode* topCoatingNode = appendNode( aCadLayerNode, "Layer" );
2133 addAttribute( topCoatingNode, "name", "COATING_TOP" );
2134 addAttribute( topCoatingNode, "layerFunction", "COATINGCOND" );
2135 addAttribute( topCoatingNode, "side", "TOP" );
2136 addAttribute( topCoatingNode, "polarity", "POSITIVE" );
2137
2138 wxXmlNode* botCoatingNode = appendNode( aCadLayerNode, "Layer" );
2139 addAttribute( botCoatingNode, "name", "COATING_BOTTOM" );
2140 addAttribute( botCoatingNode, "layerFunction", "COATINGCOND" );
2141 addAttribute( botCoatingNode, "side", "BOTTOM" );
2142 addAttribute( botCoatingNode, "polarity", "POSITIVE" );
2143 }
2144 }
2145}
2146
2147
2148void PCB_IO_IPC2581::generateDrillLayers( wxXmlNode* aCadLayerNode )
2149{
2150 for( BOARD_ITEM* item : m_board->Tracks() )
2151 {
2152 if( item->Type() == PCB_VIA_T )
2153 {
2154 PCB_VIA* via = static_cast<PCB_VIA*>( item );
2155 m_drill_layers[std::make_pair( via->TopLayer(), via->BottomLayer() )].push_back( via );
2156 }
2157 }
2158
2159 for( FOOTPRINT* fp : m_board->Footprints() )
2160 {
2161 for( PAD* pad : fp->Pads() )
2162 {
2163 if( pad->HasDrilledHole() )
2164 m_drill_layers[std::make_pair( F_Cu, B_Cu )].push_back( pad );
2165 else if( pad->HasHole() )
2166 m_slot_holes[std::make_pair( F_Cu, B_Cu )].push_back( pad );
2167 }
2168 }
2169
2170 for( const auto& [layers, vec] : m_drill_layers )
2171 {
2172 wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" );
2173 drillNode->AddAttribute( "name", genLayersString( layers.first, layers.second, "DRILL" ) );
2174 addAttribute( drillNode, "layerFunction", "DRILL" );
2175 addAttribute( drillNode, "polarity", "POSITIVE" );
2176 addAttribute( drillNode, "side", "ALL" );
2177
2178 wxXmlNode* spanNode = appendNode( drillNode, "Span" );
2179 addAttribute( spanNode, "fromLayer", genLayerString( layers.first, "LAYER" ) );
2180 addAttribute( spanNode, "toLayer", genLayerString( layers.second, "LAYER" ) );
2181 }
2182
2183 for( const auto& [layers, vec] : m_slot_holes )
2184 {
2185 wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" );
2186 drillNode->AddAttribute( "name", genLayersString( layers.first, layers.second, "SLOT" ) );
2187
2188 addAttribute( drillNode, "layerFunction", "ROUT" );
2189 addAttribute( drillNode, "polarity", "POSITIVE" );
2190 addAttribute( drillNode, "side", "ALL" );
2191
2192 wxXmlNode* spanNode = appendNode( drillNode, "Span" );
2193 addAttribute( spanNode, "fromLayer", genLayerString( layers.first, "LAYER" ) );
2194 addAttribute( spanNode, "toLayer", genLayerString( layers.second, "LAYER" ) );
2195 }
2196}
2197
2198
2199void PCB_IO_IPC2581::generateAuxilliaryLayers( wxXmlNode* aCadLayerNode )
2200{
2201 for( BOARD_ITEM* item : m_board->Tracks() )
2202 {
2203 if( item->Type() != PCB_VIA_T )
2204 continue;
2205
2206 PCB_VIA* via = static_cast<PCB_VIA*>( item );
2207
2208 std::vector<std::tuple<auxLayerType, PCB_LAYER_ID, PCB_LAYER_ID>> new_layers;
2209
2210 if( via->Padstack().IsFilled().value_or( false ) )
2211 new_layers.emplace_back( auxLayerType::FILLING, via->TopLayer(), via->BottomLayer() );
2212
2213 if( via->Padstack().IsCapped().value_or( false ) )
2214 new_layers.emplace_back( auxLayerType::CAPPING, via->TopLayer(), via->BottomLayer() );
2215
2216 for( PCB_LAYER_ID layer : { via->TopLayer(), via->BottomLayer() } )
2217 {
2218 if( via->Padstack().IsPlugged( layer ).value_or( false ) )
2219 new_layers.emplace_back( auxLayerType::PLUGGING, layer, UNDEFINED_LAYER );
2220
2221 if( via->Padstack().IsCovered( layer ).value_or( false ) )
2222 new_layers.emplace_back( auxLayerType::COVERING, layer, UNDEFINED_LAYER );
2223
2224 if( via->Padstack().IsTented( layer ).value_or( false ) )
2225 new_layers.emplace_back( auxLayerType::TENTING, layer, UNDEFINED_LAYER );
2226 }
2227
2228 for( auto& tuple : new_layers )
2229 m_auxilliary_Layers[tuple].push_back( via );
2230 }
2231
2232 for( const auto& [layers, vec] : m_auxilliary_Layers )
2233 {
2234 bool add_node = true;
2235
2236 wxString name;
2237 wxString layerFunction;
2238
2239 // clang-format off: suggestion is inconsitent
2240 switch( std::get<0>(layers) )
2241 {
2243 name = "COVERING";
2244 layerFunction = "COATINGNONCOND";
2245 break;
2247 name = "PLUGGING";
2248 layerFunction = "HOLEFILL";
2249 break;
2251 name = "TENTING";
2252 layerFunction = "COATINGNONCOND";
2253 break;
2255 name = "FILLING";
2256 layerFunction = "HOLEFILL";
2257 break;
2259 name = "CAPPING";
2260 layerFunction = "COATINGCOND";
2261 break;
2262 default:
2263 add_node = false;
2264 break;
2265 }
2266 // clang-format on: suggestion is inconsitent
2267
2268 if( add_node && !vec.empty() )
2269 {
2270 wxXmlNode* node = appendNode( aCadLayerNode, "Layer" );
2271 addAttribute( node, "layerFunction", layerFunction );
2272 addAttribute( node, "polarity", "POSITIVE" );
2273
2274 if( std::get<2>( layers ) == UNDEFINED_LAYER )
2275 {
2276 addAttribute( node, "name", genLayerString( std::get<1>( layers ), TO_UTF8( name ) ) );
2277 addAttribute( node, "side", IsFrontLayer( std::get<1>( layers ) ) ? "TOP" : "BOTTOM" );
2278 }
2279 else
2280 {
2281 addAttribute( node, "name",
2282 genLayersString( std::get<1>( layers ), std::get<2>( layers ), TO_UTF8( name ) ) );
2283
2284 const bool first_external = std::get<1>( layers ) == F_Cu || std::get<1>( layers ) == B_Cu;
2285 const bool second_external = std::get<2>( layers ) == F_Cu || std::get<2>( layers ) == B_Cu;
2286
2287 if( first_external )
2288 {
2289 if( second_external )
2290 addAttribute( node, "side", "ALL" );
2291 else
2292 addAttribute( node, "side", "TOP" );
2293 }
2294 else
2295 {
2296 if( second_external )
2297 addAttribute( node, "side", "BOTTOM" );
2298 else
2299 addAttribute( node, "side", "INTERNAL" );
2300 }
2301
2302 wxXmlNode* spanNode = appendNode( node, "Span" );
2303 addAttribute( spanNode, "fromLayer", genLayerString( std::get<1>( layers ), "LAYER" ) );
2304 addAttribute( spanNode, "toLayer", genLayerString( std::get<2>( layers ), "LAYER" ) );
2305 }
2306 }
2307 }
2308}
2309
2310
2311void PCB_IO_IPC2581::generateStepSection( wxXmlNode* aCadNode )
2312{
2313 wxXmlNode* stepNode = appendNode( aCadNode, "Step" );
2314 wxFileName fn( m_board->GetFileName() );
2315 addAttribute( stepNode, "name", genString( fn.GetName(), "BOARD" ) );
2316
2317 if( m_version > 'B' )
2318 addAttribute( stepNode, "type", "BOARD" );
2319
2320 wxXmlNode* datumNode = appendNode( stepNode, "Datum" );
2321 addAttribute( datumNode, "x", "0.0" );
2322 addAttribute( datumNode, "y", "0.0" );
2323
2324 generateProfile( stepNode );
2325 generateComponents( stepNode );
2326
2327 m_last_padstack = insertNode( stepNode, "NonstandardAttribute" );
2328 addAttribute( m_last_padstack, "name", "FOOTPRINT_COUNT" );
2329 addAttribute( m_last_padstack, "type", "INTEGER" );
2330 addAttribute( m_last_padstack, "value", wxString::Format( "%zu", m_board->Footprints().size() ) );
2331
2332 generateLayerFeatures( stepNode );
2333 generateLayerSetDrill( stepNode );
2334 generateLayerSetAuxilliary( stepNode );
2335}
2336
2337
2338void PCB_IO_IPC2581::addPad( wxXmlNode* aContentNode, const PAD* aPad, PCB_LAYER_ID aLayer )
2339{
2340 wxXmlNode* padNode = appendNode( aContentNode, "Pad" );
2341 FOOTPRINT* fp = aPad->GetParentFootprint();
2342
2343 addPadStack( padNode, aPad );
2344
2345 if( aPad->GetOrientation() != ANGLE_0 )
2346 {
2347 wxXmlNode* xformNode = appendNode( padNode, "Xform" );
2348 EDA_ANGLE angle = aPad->GetOrientation().Normalize();
2349
2350 xformNode->AddAttribute( "rotation", floatVal( angle.AsDegrees() ) );
2351 }
2352
2353 addLocationNode( padNode, *aPad, false );
2354 addShape( padNode, *aPad, aLayer );
2355
2356 if( fp )
2357 {
2358 wxXmlNode* pinRefNode = appendNode( padNode, "PinRef" );
2359
2360 addAttribute( pinRefNode, "componentRef", componentName( fp ) );
2361 addAttribute( pinRefNode, "pin", pinName( aPad ) );
2362 }
2363}
2364
2365
2366void PCB_IO_IPC2581::addVia( wxXmlNode* aContentNode, const PCB_VIA* aVia, PCB_LAYER_ID aLayer )
2367{
2368 if( !aVia->FlashLayer( aLayer ) )
2369 return;
2370
2371 wxXmlNode* padNode = appendNode( aContentNode, "Pad" );
2372
2373 addPadStack( padNode, aVia );
2374 addLocationNode( padNode, aVia->GetPosition().x, aVia->GetPosition().y );
2375
2376 PAD dummy( nullptr );
2377 int hole = aVia->GetDrillValue();
2378 dummy.SetDrillSize( VECTOR2I( hole, hole ) );
2379 dummy.SetPosition( aVia->GetStart() );
2380 dummy.SetSize( aLayer, VECTOR2I( aVia->GetWidth( aLayer ), aVia->GetWidth( aLayer ) ) );
2381
2382 addShape( padNode, dummy, aLayer );
2383}
2384
2385
2386void PCB_IO_IPC2581::addPadStack( wxXmlNode* aPadNode, const PAD* aPad )
2387{
2388 size_t hash = ipcPadstackHash( aPad );
2389 wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 );
2390 auto [ th_pair, success ] = m_padstack_dict.emplace( hash, name );
2391
2392 addAttribute( aPadNode, "padstackDefRef", th_pair->second );
2393
2394 // If we did not insert a new padstack, then we have already added it to the XML
2395 // and we don't need to add it again.
2396 if( !success )
2397 return;
2398
2399 wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" );
2400 addAttribute( padStackDefNode, "name", name );
2402 m_padstacks.push_back( padStackDefNode );
2403
2404 if( m_last_padstack )
2405 {
2406 insertNodeAfter( m_last_padstack, padStackDefNode );
2407 m_last_padstack = padStackDefNode;
2408 }
2409
2410 // Only handle round holes here because IPC2581 does not support non-round holes
2411 // These will be handled in a slot layer
2412 if( aPad->HasDrilledHole() )
2413 {
2414 wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" );
2415 padStackHoleNode->AddAttribute( "name",
2416 wxString::Format( "%s%d_%d",
2417 aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PTH" : "NPTH",
2418 aPad->GetDrillSizeX(), aPad->GetDrillSizeY() ) );
2419
2420 addAttribute( padStackHoleNode, "diameter", floatVal( m_scale * aPad->GetDrillSizeX() ) );
2421 addAttribute( padStackHoleNode, "platingStatus",
2422 aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" );
2423 addAttribute( padStackHoleNode, "plusTol", "0.0" );
2424 addAttribute( padStackHoleNode, "minusTol", "0.0" );
2425 addXY( padStackHoleNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ) );
2426 }
2427
2428 LSEQ layer_seq = aPad->GetLayerSet().Seq();
2429
2430 for( PCB_LAYER_ID layer : layer_seq )
2431 {
2432 if( !m_board->IsLayerEnabled( layer ) )
2433 continue;
2434
2435 wxXmlNode* padStackPadDefNode = appendNode( padStackDefNode, "PadstackPadDef" );
2436 addAttribute( padStackPadDefNode, "layerRef", m_layer_name_map[layer] );
2437 addAttribute( padStackPadDefNode, "padUse", "REGULAR" );
2438 addLocationNode( padStackPadDefNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ).x, aPad->GetOffset( PADSTACK::ALL_LAYERS ).y );
2439
2440 if( aPad->HasHole() || !aPad->FlashLayer( layer ) )
2441 {
2442 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
2443 shape.SetStart( aPad->GetOffset( PADSTACK::ALL_LAYERS ) );
2444 shape.SetEnd( shape.GetStart() + aPad->GetDrillSize() / 2 );
2445 addShape( padStackPadDefNode, shape );
2446 }
2447 else
2448 {
2449 addShape( padStackPadDefNode, *aPad, layer );
2450 }
2451 }
2452}
2453
2454
2455void PCB_IO_IPC2581::addPadStack( wxXmlNode* aContentNode, const PCB_VIA* aVia )
2456{
2457 size_t hash = ipcPadstackHash( aVia );
2458 wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 );
2459 auto [ via_pair, success ] = m_padstack_dict.emplace( hash, name );
2460
2461 addAttribute( aContentNode, "padstackDefRef", via_pair->second );
2462
2463 // If we did not insert a new padstack, then we have already added it to the XML
2464 // and we don't need to add it again.
2465 if( !success )
2466 return;
2467
2468 wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" );
2469 insertNodeAfter( m_last_padstack, padStackDefNode );
2470 m_last_padstack = padStackDefNode;
2471 addAttribute( padStackDefNode, "name", name );
2473
2474 wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" );
2475 addAttribute( padStackHoleNode, "name", wxString::Format( "PH%d", aVia->GetDrillValue() ) );
2476 padStackHoleNode->AddAttribute( "diameter", floatVal( m_scale * aVia->GetDrillValue() ) );
2477 addAttribute( padStackHoleNode, "platingStatus", "VIA" );
2478 addAttribute( padStackHoleNode, "plusTol", "0.0" );
2479 addAttribute( padStackHoleNode, "minusTol", "0.0" );
2480 addAttribute( padStackHoleNode, "x", "0.0" );
2481 addAttribute( padStackHoleNode, "y", "0.0" );
2482
2483 LSEQ layer_seq = aVia->GetLayerSet().Seq();
2484
2485 auto addPadShape{ [&]( PCB_LAYER_ID aLayer, const PCB_VIA* aViaShape, const wxString& aLayerRef,
2486 bool aDrill ) -> void
2487 {
2488 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
2489
2490 if( aDrill )
2491 shape.SetEnd( { KiROUND( aViaShape->GetDrillValue() / 2.0 ), 0 } );
2492 else
2493 shape.SetEnd( { KiROUND( aViaShape->GetWidth( aLayer ) / 2.0 ), 0 } );
2494
2495 wxXmlNode* padStackPadDefNode =
2496 appendNode( padStackDefNode, "PadstackPadDef" );
2497 addAttribute( padStackPadDefNode, "layerRef", aLayerRef );
2498 addAttribute( padStackPadDefNode, "padUse", "REGULAR" );
2499
2500 addLocationNode( padStackPadDefNode, 0.0, 0.0 );
2501 addShape( padStackPadDefNode, shape );
2502 } };
2503
2504 for( PCB_LAYER_ID layer : layer_seq )
2505 {
2506 if( !aVia->FlashLayer( layer ) || !m_board->IsLayerEnabled( layer ) )
2507 continue;
2508
2509 addPadShape( layer, aVia, m_layer_name_map[layer], false );
2510 }
2511
2512 if( aVia->Padstack().IsFilled().value_or( false ) )
2513 addPadShape( UNDEFINED_LAYER, aVia, genLayersString( aVia->TopLayer(), aVia->BottomLayer(), "FILLING" ), true );
2514
2515 if( aVia->Padstack().IsCapped().value_or( false ) )
2516 addPadShape( UNDEFINED_LAYER, aVia, genLayersString( aVia->TopLayer(), aVia->BottomLayer(), "CAPPING" ), true );
2517
2518 for( PCB_LAYER_ID layer : { aVia->TopLayer(), aVia->BottomLayer() } )
2519 {
2520 if( aVia->Padstack().IsPlugged( layer ).value_or( false ) )
2521 addPadShape( layer, aVia, genLayerString( layer, "PLUGGING" ), true );
2522
2523 if( aVia->Padstack().IsCovered( layer ).value_or( false ) )
2524 addPadShape( layer, aVia, genLayerString( layer, "COVERING" ), false );
2525
2526 if( aVia->Padstack().IsTented( layer ).value_or( false ) )
2527 addPadShape( layer, aVia, genLayerString( layer, "TENTING" ), false );
2528 }
2529}
2530
2531
2532void PCB_IO_IPC2581::ensureBackdrillSpecs( const wxString& aPadstackName, const PADSTACK& aPadstack )
2533{
2534 if( m_padstack_backdrill_specs.find( aPadstackName ) != m_padstack_backdrill_specs.end() )
2535 return;
2536
2537 const PADSTACK::DRILL_PROPS& secondary = aPadstack.SecondaryDrill();
2538 const PADSTACK::DRILL_PROPS& tertiary = aPadstack.TertiaryDrill();
2539
2540 auto hasBackdrill = []( const PADSTACK::DRILL_PROPS& aDrill )
2541 {
2542 return aDrill.start != UNDEFINED_LAYER && aDrill.end != UNDEFINED_LAYER
2543 && ( aDrill.size.x > 0 || aDrill.size.y > 0 );
2544 };
2545
2546 if( !hasBackdrill( secondary ) && !hasBackdrill( tertiary ) )
2547 return;
2548
2549 if( !m_cad_header_node )
2550 return;
2551
2552 auto layerHasRef = [&]( PCB_LAYER_ID aLayer ) -> bool
2553 {
2554 return m_layer_name_map.find( aLayer ) != m_layer_name_map.end();
2555 };
2556
2557 if( hasBackdrill( secondary )
2558 && ( !layerHasRef( secondary.start ) || !layerHasRef( secondary.end ) ) )
2559 {
2560 return;
2561 }
2562
2563 if( hasBackdrill( tertiary )
2564 && ( !layerHasRef( tertiary.start ) || !layerHasRef( tertiary.end ) ) )
2565 {
2566 return;
2567 }
2568
2569 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
2570 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
2571 stackup.SynchronizeWithBoard( &dsnSettings );
2572
2573 auto createSpec = [&]( const PADSTACK::DRILL_PROPS& aDrill, const wxString& aSpecName ) -> wxString
2574 {
2575 if( aDrill.start == UNDEFINED_LAYER || aDrill.end == UNDEFINED_LAYER )
2576 return wxString();
2577
2578 auto startLayer = m_layer_name_map.find( aDrill.start );
2579 auto endLayer = m_layer_name_map.find( aDrill.end );
2580
2581 if( startLayer == m_layer_name_map.end() || endLayer == m_layer_name_map.end() )
2582 return wxString();
2583
2584 wxXmlNode* specNode = appendNode( m_cad_header_node, "Spec" );
2585 addAttribute( specNode, "name", aSpecName );
2586
2587 wxXmlNode* backdrillNode = appendNode( specNode, "Backdrill" );
2588 addAttribute( backdrillNode, "startLayerRef", startLayer->second );
2589 addAttribute( backdrillNode, "mustNotCutLayerRef", endLayer->second );
2590
2591 int stubLength = stackup.GetLayerDistance( aDrill.start, aDrill.end );
2592
2593 if( stubLength < 0 )
2594 stubLength = 0;
2595
2596 addAttribute( backdrillNode, "maxStubLength", floatVal( m_scale * stubLength ) );
2597
2599
2600 if( aDrill.start == F_Cu )
2601 pm_mode = aPadstack.FrontPostMachining().mode.value_or( PAD_DRILL_POST_MACHINING_MODE::UNKNOWN );
2602 else if( aDrill.start == B_Cu )
2603 pm_mode = aPadstack.BackPostMachining().mode.value_or( PAD_DRILL_POST_MACHINING_MODE::UNKNOWN );
2604
2605 bool isPostMachined = ( pm_mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE ||
2607
2608 addAttribute( backdrillNode, "postMachining", isPostMachined ? wxT( "true" )
2609 : wxT( "false" ) );
2610
2611 m_backdrill_spec_nodes[aSpecName] = specNode;
2612
2613 return aSpecName;
2614 };
2615
2616 int specIndex = m_backdrill_spec_index + 1;
2617
2618 const PADSTACK::DRILL_PROPS& primary = aPadstack.Drill();
2619 wxString primarySpec = createSpec( primary, wxString::Format( wxT( "BD_%dA" ), specIndex ) );
2620
2621 wxString secondarySpec = createSpec( secondary, wxString::Format( wxT( "BD_%dB" ), specIndex ) );
2622 wxString tertiarySpec = createSpec( tertiary, wxString::Format( wxT( "BD_%dC" ), specIndex ) );
2623
2624 if( primarySpec.IsEmpty() && secondarySpec.IsEmpty() && tertiarySpec.IsEmpty() )
2625 return;
2626
2627 m_backdrill_spec_index = specIndex;
2628 m_padstack_backdrill_specs.emplace( aPadstackName,
2629 std::array<wxString, 3>{ primarySpec, secondarySpec,
2630 tertiarySpec } );
2631}
2632
2633
2634void PCB_IO_IPC2581::addBackdrillSpecRefs( wxXmlNode* aHoleNode, const wxString& aPadstackName )
2635{
2636 auto it = m_padstack_backdrill_specs.find( aPadstackName );
2637
2638 if( it == m_padstack_backdrill_specs.end() )
2639 return;
2640
2641 auto addRef = [&]( const wxString& aSpecName )
2642 {
2643 if( aSpecName.IsEmpty() )
2644 return;
2645
2646 wxXmlNode* specRefNode = appendNode( aHoleNode, "SpecRef" );
2647 addAttribute( specRefNode, "id", aSpecName );
2648 m_backdrill_spec_used.insert( aSpecName );
2649 };
2650
2651 for( const wxString& specName : it->second )
2652 addRef( specName );
2653}
2654
2655
2657{
2658 if( !m_cad_header_node )
2659 return;
2660
2661 auto it = m_backdrill_spec_nodes.begin();
2662
2663 while( it != m_backdrill_spec_nodes.end() )
2664 {
2665 if( m_backdrill_spec_used.find( it->first ) == m_backdrill_spec_used.end() )
2666 {
2667 wxXmlNode* specNode = it->second;
2668
2669 if( specNode )
2670 {
2671 m_cad_header_node->RemoveChild( specNode );
2672 deleteNode( specNode );
2673 }
2674
2675 it = m_backdrill_spec_nodes.erase( it );
2676 }
2677 else
2678 {
2679 ++it;
2680 }
2681 }
2682}
2683
2684
2685bool PCB_IO_IPC2581::addPolygonNode( wxXmlNode* aParentNode,
2686 const SHAPE_LINE_CHAIN& aPolygon, FILL_T aFillType,
2687 int aWidth, LINE_STYLE aDashType )
2688{
2689 wxXmlNode* polygonNode = nullptr;
2690
2691 if( aPolygon.PointCount() < 3 )
2692 return false;
2693
2694 auto make_node =
2695 [&]()
2696 {
2697 polygonNode = appendNode( aParentNode, "Polygon" );
2698 wxXmlNode* polybeginNode = appendNode( polygonNode, "PolyBegin" );
2699
2700 const std::vector<VECTOR2I>& pts = aPolygon.CPoints();
2701 addXY( polybeginNode, pts[0] );
2702
2703 for( size_t ii = 1; ii < pts.size(); ++ii )
2704 {
2705 wxXmlNode* polyNode = appendNode( polygonNode, "PolyStepSegment" );
2706 addXY( polyNode, pts[ii] );
2707 }
2708
2709 wxXmlNode* polyendNode = appendNode( polygonNode, "PolyStepSegment" );
2710 addXY( polyendNode, pts[0] );
2711 };
2712
2713 // Allow the case where we don't want line/fill information in the polygon
2714 if( aFillType == FILL_T::NO_FILL )
2715 {
2716 make_node();
2717 // If we specify a line width, we need to add a LineDescRef node and
2718 // since this is only valid for a non-filled polygon, we need to create
2719 // the fillNode as well
2720 if( aWidth > 0 )
2721 addLineDesc( polygonNode, aWidth, aDashType, true );
2722 }
2723 else
2724 {
2725 wxCHECK( aWidth == 0, false );
2726 make_node();
2727 }
2728
2729 addFillDesc( polygonNode, aFillType );
2730
2731 return true;
2732}
2733
2734
2735bool PCB_IO_IPC2581::addPolygonCutouts( wxXmlNode* aParentNode,
2736 const SHAPE_POLY_SET::POLYGON& aPolygon )
2737{
2738 for( size_t ii = 1; ii < aPolygon.size(); ++ii )
2739 {
2740 wxCHECK2( aPolygon[ii].PointCount() >= 3, continue );
2741
2742 wxXmlNode* cutoutNode = appendNode( aParentNode, "Cutout" );
2743 wxXmlNode* polybeginNode = appendNode( cutoutNode, "PolyBegin" );
2744
2745 const std::vector<VECTOR2I>& hole = aPolygon[ii].CPoints();
2746 addXY( polybeginNode, hole[0] );
2747
2748 for( size_t jj = 1; jj < hole.size(); ++jj )
2749 {
2750 wxXmlNode* polyNode = appendNode( cutoutNode, "PolyStepSegment" );
2751 addXY( polyNode, hole[jj] );
2752 }
2753
2754 wxXmlNode* polyendNode = appendNode( cutoutNode, "PolyStepSegment" );
2755 addXY( polyendNode, hole[0] );
2756 }
2757
2758 return true;
2759}
2760
2761
2762bool PCB_IO_IPC2581::addOutlineNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
2763 int aWidth, LINE_STYLE aDashType )
2764{
2765 if( aPolySet.OutlineCount() == 0 )
2766 return false;
2767
2768 wxXmlNode* outlineNode = appendNode( aParentNode, "Outline" );
2769
2770 const SHAPE_POLY_SET* source = &aPolySet;
2771 SHAPE_POLY_SET merged;
2772
2773 if( aPolySet.OutlineCount() > 1 )
2774 {
2775 merged = aPolySet;
2776 merged.Simplify();
2777
2778 if( merged.OutlineCount() > 0 )
2779 source = &merged;
2780 }
2781
2782 for( int ii = 0; ii < source->OutlineCount(); ++ii )
2783 {
2784 if( !addPolygonNode( outlineNode, source->Outline( ii ) ) )
2785 wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to outline" ) );
2786 }
2787
2788 if( !outlineNode->GetChildren() )
2789 {
2790 aParentNode->RemoveChild( outlineNode );
2791 deleteNode( outlineNode );
2792 return false;
2793 }
2794
2795 addLineDesc( outlineNode, aWidth, aDashType );
2796
2797 return true;
2798}
2799
2800
2801bool PCB_IO_IPC2581::addContourNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
2802 int aOutline, FILL_T aFillType, int aWidth, LINE_STYLE aDashType )
2803{
2804 if( aPolySet.OutlineCount() < ( aOutline + 1 ) )
2805 return false;
2806
2807 wxXmlNode* contourNode = appendNode( aParentNode, "Contour" );
2808
2809 if( addPolygonNode( contourNode, aPolySet.Outline( aOutline ), aFillType, aWidth, aDashType ) )
2810 {
2811 // Do not attempt to add cutouts to shapes that are already hollow
2812 if( aFillType != FILL_T::NO_FILL )
2813 addPolygonCutouts( contourNode, aPolySet.Polygon( aOutline ) );
2814 }
2815 else
2816 {
2817 aParentNode->RemoveChild( contourNode );
2818 deleteNode( contourNode );
2819 return false;
2820 }
2821
2822 return true;
2823}
2824
2825
2826void PCB_IO_IPC2581::generateProfile( wxXmlNode* aStepNode )
2827{
2828 SHAPE_POLY_SET board_outline;
2829
2830 if( ! m_board->GetBoardPolygonOutlines( board_outline, false ) )
2831 {
2832 Report( _( "Board outline is invalid or missing. Please run DRC." ), RPT_SEVERITY_ERROR );
2833 return;
2834 }
2835
2836 wxXmlNode* profileNode = appendNode( aStepNode, "Profile" );
2837
2838 if( !addPolygonNode( profileNode, board_outline.Outline( 0 ) ) )
2839 {
2840 wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to profile" ) );
2841 aStepNode->RemoveChild( profileNode );
2842 deleteNode( profileNode );
2843 return;
2844 }
2845
2846 addPolygonCutouts( profileNode, board_outline.Polygon( 0 ) );
2847}
2848
2849
2850static bool isOppositeSideSilk( const FOOTPRINT* aFootprint, PCB_LAYER_ID aLayer )
2851{
2852 if( !aFootprint )
2853 return false;
2854
2855 if( aLayer != F_SilkS && aLayer != B_SilkS )
2856 return false;
2857
2858 if( aFootprint->IsFlipped() )
2859 return aLayer == F_SilkS;
2860
2861 return aLayer == B_SilkS;
2862}
2863
2864
2865wxXmlNode* PCB_IO_IPC2581::addPackage( wxXmlNode* aContentNode, FOOTPRINT* aFp )
2866{
2867 std::unique_ptr<FOOTPRINT> fp( static_cast<FOOTPRINT*>( aFp->Clone() ) );
2868 fp->SetParentGroup( nullptr );
2869 fp->SetPosition( { 0, 0 } );
2870 fp->SetOrientation( ANGLE_0 );
2871
2872 // Track original flipped state before normalization. This is needed to correctly
2873 // determine OtherSideView content per IPC-2581C. After flipping, layer IDs swap,
2874 // so for bottom components, B_SilkS/B_Fab after flip is actually the primary view.
2875 bool wasFlipped = fp->IsFlipped();
2876
2877 // Normalize package geometry to the unflipped footprint coordinate system.
2878 if( fp->IsFlipped() )
2879 fp->Flip( fp->GetPosition(), FLIP_DIRECTION::TOP_BOTTOM );
2880
2881 size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD );
2882 wxString name = genString( wxString::Format( "%s_%zu",
2883 fp->GetFPID().GetLibItemName().wx_str(),
2884 m_footprint_dict.size() + 1 ) );
2885
2886 auto [ iter, success ] = m_footprint_dict.emplace( hash, name );
2887 addAttribute( aContentNode, "packageRef", iter->second );
2888
2889 if( !success)
2890 return nullptr;
2891
2892 // Package and Component nodes are at the same level, so we need to find the parent
2893 // which should be the Step node
2894 wxXmlNode* packageNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Package" );
2895 wxXmlNode* otherSideViewNode = nullptr; // Only set this if we have elements on the back side
2896
2897 addAttribute( packageNode, "name", name );
2898 addAttribute( packageNode, "type", "OTHER" ); // TODO: Replace with actual package type once we encode this
2899
2900 // We don't specially identify pin 1 in our footprints, so we need to guess
2901 if( fp->FindPadByNumber( "1" ) )
2902 addAttribute( packageNode, "pinOne", "1" );
2903 else if ( fp->FindPadByNumber( "A1" ) )
2904 addAttribute( packageNode, "pinOne", "A1" );
2905 else if ( fp->FindPadByNumber( "A" ) )
2906 addAttribute( packageNode, "pinOne", "A" );
2907 else if ( fp->FindPadByNumber( "a" ) )
2908 addAttribute( packageNode, "pinOne", "a" );
2909 else if ( fp->FindPadByNumber( "a1" ) )
2910 addAttribute( packageNode, "pinOne", "a1" );
2911 else if ( fp->FindPadByNumber( "Anode" ) )
2912 addAttribute( packageNode, "pinOne", "Anode" );
2913 else if ( fp->FindPadByNumber( "ANODE" ) )
2914 addAttribute( packageNode, "pinOne", "ANODE" );
2915 else
2916 addAttribute( packageNode, "pinOne", "UNKNOWN" );
2917
2918 // Infer pinOneOrientation from pin 1 position relative to package centroid.
2919 // IPC-2581C 8.2.3.6 requires a comment attribute when OTHER is used.
2920 PAD* pinOnePad = fp->FindPadByNumber( "1" );
2921
2922 if( !pinOnePad )
2923 pinOnePad = fp->FindPadByNumber( "A1" );
2924
2925 if( pinOnePad && fp->Pads().size() >= 2 )
2926 {
2927 VECTOR2I pinPos = pinOnePad->GetFPRelativePosition();
2928 BOX2I fpBBox = fp->GetBoundingBox();
2929 VECTOR2I center = fpBBox.GetCenter();
2930
2931 // Use 5% of each dimension as the centerline tolerance band
2932 int tolX = fpBBox.GetWidth() / 20;
2933 int tolY = fpBBox.GetHeight() / 20;
2934
2935 bool onCenterX = std::abs( pinPos.x - center.x ) <= tolX;
2936 bool onCenterY = std::abs( pinPos.y - center.y ) <= tolY;
2937
2938 const char* orientation = "OTHER";
2939
2940 if( onCenterX && onCenterY )
2941 orientation = "CENTER";
2942 else if( onCenterX && pinPos.y < center.y )
2943 orientation = "UPPER_CENTER";
2944 else if( onCenterX && pinPos.y > center.y )
2945 orientation = "LOWER_CENTER";
2946 else if( onCenterY && pinPos.x < center.x )
2947 orientation = "LEFT";
2948 else if( onCenterY && pinPos.x > center.x )
2949 orientation = "RIGHT";
2950 else if( pinPos.x < center.x && pinPos.y < center.y )
2951 orientation = "UPPER_LEFT";
2952 else if( pinPos.x > center.x && pinPos.y < center.y )
2953 orientation = "UPPER_RIGHT";
2954 else if( pinPos.x < center.x && pinPos.y > center.y )
2955 orientation = "LOWER_LEFT";
2956 else
2957 orientation = "LOWER_RIGHT";
2958
2959 addAttribute( packageNode, "pinOneOrientation", orientation );
2960 }
2961 else
2962 {
2963 addAttribute( packageNode, "pinOneOrientation", "OTHER" );
2964 addAttribute( packageNode, "comment", "Pin 1 orientation could not be determined" );
2965 }
2966
2967 // After normalization: F_CrtYd is top, B_CrtYd is bottom.
2968 // For bottom components (wasFlipped), these are swapped from original orientation.
2969 const SHAPE_POLY_SET& courtyard_primary = wasFlipped ? fp->GetCourtyard( B_CrtYd )
2970 : fp->GetCourtyard( F_CrtYd );
2971 const SHAPE_POLY_SET& courtyard_other = wasFlipped ? fp->GetCourtyard( F_CrtYd )
2972 : fp->GetCourtyard( B_CrtYd );
2973
2974 if( courtyard_primary.OutlineCount() > 0 )
2975 {
2976 addOutlineNode( packageNode, courtyard_primary, courtyard_primary.Outline( 0 ).Width(),
2978 }
2979 else
2980 {
2981 SHAPE_POLY_SET bbox = fp->GetBoundingHull();
2982 addOutlineNode( packageNode, bbox );
2983 }
2984
2985 if( courtyard_other.OutlineCount() > 0 )
2986 {
2987 if( m_version > 'B' )
2988 {
2989 otherSideViewNode = new wxXmlNode( wxXML_ELEMENT_NODE, "OtherSideView" );
2990 addOutlineNode( otherSideViewNode, courtyard_other, courtyard_other.Outline( 0 ).Width(),
2992 }
2993 }
2994
2995 wxXmlNode* pickupPointNode = appendNode( packageNode, "PickupPoint" );
2996 addAttribute( pickupPointNode, "x", "0.0" );
2997 addAttribute( pickupPointNode, "y", "0.0" );
2998
2999 std::map<PCB_LAYER_ID, std::map<bool, std::vector<BOARD_ITEM*>>> elements;
3000
3001 for( BOARD_ITEM* item : fp->GraphicalItems() )
3002 {
3003 PCB_LAYER_ID layer = item->GetLayer();
3004
3008 if( layer != F_SilkS && layer != B_SilkS && layer != F_Fab && layer != B_Fab )
3009 continue;
3010
3011 if( m_version == 'B' && isOppositeSideSilk( fp.get(), layer ) )
3012 continue;
3013
3014 bool is_abs = true;
3015
3016 if( item->Type() == PCB_SHAPE_T )
3017 {
3018 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3019
3020 // Circles and Rectanges only have size information so we need to place them in
3021 // a separate node that has a location
3022 if( shape->GetShape() == SHAPE_T::CIRCLE || shape->GetShape() == SHAPE_T::RECTANGLE )
3023 is_abs = false;
3024 }
3025
3026 elements[item->GetLayer()][is_abs].push_back( item );
3027 }
3028
3029 auto add_base_node =
3030 [&]( PCB_LAYER_ID aLayer ) -> wxXmlNode*
3031 {
3032 wxXmlNode* parent = packageNode;
3033
3034 // Determine if this layer content should go in OtherSideView.
3035 // Per IPC-2581C, OtherSideView contains geometry visible from the opposite
3036 // side of the package body from the primary view.
3037 //
3038 // For non-flipped (top) components: B_SilkS/B_Fab → OtherSideView
3039 // For flipped (bottom) components after normalization: F_SilkS/F_Fab → OtherSideView
3040 // (because after flip, B_SilkS/B_Fab contains the original primary graphics)
3041 bool is_other_side = wasFlipped ? ( aLayer == F_SilkS || aLayer == F_Fab )
3042 : ( aLayer == B_SilkS || aLayer == B_Fab );
3043
3044 if( is_other_side && m_version > 'B' )
3045 {
3046 if( !otherSideViewNode )
3047 otherSideViewNode = new wxXmlNode( wxXML_ELEMENT_NODE, "OtherSideView" );
3048
3049 parent = otherSideViewNode;
3050 }
3051
3052 wxString nodeName;
3053
3054 if( aLayer == F_SilkS || aLayer == B_SilkS )
3055 nodeName = "SilkScreen";
3056 else if( aLayer == F_Fab || aLayer == B_Fab )
3057 nodeName = "AssemblyDrawing";
3058 else
3059 wxASSERT( false );
3060
3061 wxXmlNode* new_node = appendNode( parent, nodeName );
3062 return new_node;
3063 };
3064
3065 auto add_marking_node =
3066 [&]( wxXmlNode* aNode ) -> wxXmlNode*
3067 {
3068 wxXmlNode* marking_node = appendNode( aNode, "Marking" );
3069 addAttribute( marking_node, "markingUsage", "NONE" );
3070 return marking_node;
3071 };
3072
3073 std::map<PCB_LAYER_ID, wxXmlNode*> layer_nodes;
3074 std::map<PCB_LAYER_ID, BOX2I> layer_bbox;
3075
3076 for( auto layer : { F_Fab, B_Fab } )
3077 {
3078 if( elements.find( layer ) != elements.end() )
3079 {
3080 if( elements[layer][true].size() > 0 )
3081 layer_bbox[layer] = elements[layer][true][0]->GetBoundingBox();
3082 else if( elements[layer][false].size() > 0 )
3083 layer_bbox[layer] = elements[layer][false][0]->GetBoundingBox();
3084 }
3085 }
3086
3087 for( auto& [layer, map] : elements )
3088 {
3089 wxXmlNode* layer_node = add_base_node( layer );
3090 wxXmlNode* marking_node = add_marking_node( layer_node );
3091 wxXmlNode* group_node = appendNode( marking_node, "UserSpecial" );
3092 bool update_bbox = false;
3093
3094 if( layer == F_Fab || layer == B_Fab )
3095 {
3096 layer_nodes[layer] = layer_node;
3097 update_bbox = true;
3098 }
3099
3100 for( auto& [is_abs, vec] : map )
3101 {
3102 for( BOARD_ITEM* item : vec )
3103 {
3104 wxXmlNode* output_node = nullptr;
3105
3106 if( update_bbox )
3107 layer_bbox[layer].Merge( item->GetBoundingBox() );
3108
3109 if( !is_abs )
3110 output_node = add_marking_node( layer_node );
3111 else
3112 output_node = group_node;
3113
3114 switch( item->Type() )
3115 {
3116 case PCB_TEXT_T:
3117 {
3118 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
3119
3120 if( text->IsKnockout() )
3121 addKnockoutText( output_node, text );
3122 else
3123 addText( output_node, text, text->GetFontMetrics() );
3124
3125 break;
3126 }
3127
3128 case PCB_TEXTBOX_T:
3129 {
3130 PCB_TEXTBOX* text = static_cast<PCB_TEXTBOX*>( item );
3131 addText( output_node, text, text->GetFontMetrics() );
3132
3133 // We want to force this to be a polygon to get absolute coordinates
3134 if( text->IsBorderEnabled() )
3135 {
3136 SHAPE_POLY_SET poly_set;
3137 text->GetEffectiveShape()->TransformToPolygon( poly_set, 0, ERROR_INSIDE );
3138 addContourNode( output_node, poly_set, 0, FILL_T::NO_FILL,
3139 text->GetBorderWidth() );
3140 }
3141
3142 break;
3143 }
3144
3145 case PCB_SHAPE_T:
3146 {
3147 if( !is_abs )
3148 addLocationNode( output_node, *static_cast<PCB_SHAPE*>( item ) );
3149
3150 // When in Marking context (!is_abs), use inline geometry to avoid
3151 // unresolved UserPrimitiveRef errors in validators like Vu2581
3152 addShape( output_node, *static_cast<PCB_SHAPE*>( item ), !is_abs );
3153
3154 break;
3155 }
3156
3157 default: break;
3158 }
3159 }
3160 }
3161
3162 if( group_node->GetChildren() == nullptr )
3163 {
3164 marking_node->RemoveChild( group_node );
3165 layer_node->RemoveChild( marking_node );
3166 delete group_node;
3167 delete marking_node;
3168 }
3169 }
3170
3171 for( auto&[layer, bbox] : layer_bbox )
3172 {
3173 if( bbox.GetWidth() > 0 )
3174 {
3175 wxXmlNode* outlineNode = insertNode( layer_nodes[layer], "Outline" );
3176
3177 SHAPE_LINE_CHAIN outline;
3178 std::vector<VECTOR2I> points( 4 );
3179 points[0] = bbox.GetPosition();
3180 points[2] = bbox.GetEnd();
3181 points[1].x = points[0].x;
3182 points[1].y = points[2].y;
3183 points[3].x = points[2].x;
3184 points[3].y = points[0].y;
3185
3186 outline.Append( points );
3187 addPolygonNode( outlineNode, outline, FILL_T::NO_FILL, 0 );
3188 addLineDesc( outlineNode, 0, LINE_STYLE::SOLID );
3189 }
3190 }
3191
3192 std::map<wxString, wxXmlNode*> pin_nodes;
3193
3194 for( size_t ii = 0; ii < fp->Pads().size(); ++ii )
3195 {
3196 PAD* pad = fp->Pads()[ii];
3197 wxString pin_name = pinName( pad );
3198 wxXmlNode* pinNode = nullptr;
3199
3200 auto [ it, inserted ] = pin_nodes.emplace( pin_name, nullptr );
3201
3202 if( inserted )
3203 {
3204 pinNode = appendNode( packageNode, "Pin" );
3205 it->second = pinNode;
3206
3207 addAttribute( pinNode, "number", pin_name );
3208
3209 m_net_pin_dict[pad->GetNetCode()].emplace_back(
3210 genString( fp->GetReference(), "CMP" ), pin_name );
3211
3212 if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
3213 addAttribute( pinNode, "electricalType", "MECHANICAL" );
3214 else if( pad->IsOnCopperLayer() )
3215 addAttribute( pinNode, "electricalType", "ELECTRICAL" );
3216 else
3217 addAttribute( pinNode, "electricalType", "UNDEFINED" );
3218
3219 if( pad->HasHole() )
3220 addAttribute( pinNode, "type", "THRU" );
3221 else
3222 addAttribute( pinNode, "type", "SURFACE" );
3223
3224 if( pad->GetFPRelativeOrientation() != ANGLE_0 )//|| fp->IsFlipped() )
3225 {
3226 wxXmlNode* xformNode = appendNode( pinNode, "Xform" );
3227 EDA_ANGLE pad_angle = pad->GetFPRelativeOrientation().Normalize();
3228
3229 if( fp->IsFlipped() )
3230 pad_angle = pad_angle.Invert().Normalize();
3231
3232 if( pad_angle != ANGLE_0 )
3233 xformNode->AddAttribute( "rotation", floatVal( pad_angle.AsDegrees() ) );
3234 }
3235
3236 addLocationNode( pinNode, *pad, true );
3237 addShape( pinNode, *pad, pad->GetLayer() );
3238 }
3239
3240 // We just need the padstack, we don't need the reference here. The reference will be
3241 // created in the LayerFeature set
3242 wxXmlNode dummy;
3243 addPadStack( &dummy, pad );
3244 }
3245
3246 if( otherSideViewNode )
3247 packageNode->AddChild( otherSideViewNode );
3248
3249 return packageNode;
3250}
3251
3252
3253void PCB_IO_IPC2581::generateComponents( wxXmlNode* aStepNode )
3254{
3255 std::vector<wxXmlNode*> componentNodes;
3256 std::vector<wxXmlNode*> packageNodes;
3257 std::set<wxString> packageNames;
3258
3259 bool generate_unique = m_OEMRef.empty();
3260
3261 for( FOOTPRINT* fp : m_board->Footprints() )
3262 {
3263 wxXmlNode* componentNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Component" );
3264 addAttribute( componentNode, "refDes", componentName( fp ) );
3265 wxXmlNode* pkg = addPackage( componentNode, fp );
3266
3267 if( pkg )
3268 packageNodes.push_back( pkg );
3269
3270 wxString name;
3271
3272 PCB_FIELD* field = nullptr;
3273
3274 if( !generate_unique )
3275 field = fp->GetField( m_OEMRef );
3276
3277 if( field && !field->GetText().empty() )
3278 {
3279 name = field->GetShownText( false );
3280 }
3281 else
3282 {
3283 name = wxString::Format( "%s_%s_%s", fp->GetFPID().GetFullLibraryName(),
3284 fp->GetFPID().GetLibItemName().wx_str(),
3285 fp->GetValue() );
3286 }
3287
3288 if( !m_OEMRef_dict.emplace( fp, name ).second )
3289 Report( _( "Duplicate footprint pointers encountered; IPC-2581 output may be incorrect." ),
3291
3292 addAttribute( componentNode, "part", genString( name, "REF" ) );
3293 addAttribute( componentNode, "layerRef", m_layer_name_map[fp->GetLayer()] );
3294
3295 if( fp->GetAttributes() & FP_THROUGH_HOLE )
3296 addAttribute( componentNode, "mountType", "THMT" );
3297 else if( fp->GetAttributes() & FP_SMD )
3298 addAttribute( componentNode, "mountType", "SMT" );
3299 else
3300 addAttribute( componentNode, "mountType", "OTHER" );
3301
3302 if( fp->GetOrientation() != ANGLE_0 || fp->IsFlipped() )
3303 {
3304 wxXmlNode* xformNode = appendNode( componentNode, "Xform" );
3305
3306 EDA_ANGLE fp_angle = fp->GetOrientation().Normalize();
3307
3308 if( fp_angle != ANGLE_0 )
3309 addAttribute( xformNode, "rotation", floatVal( fp_angle.AsDegrees(), 2 ) );
3310
3311 if( fp->IsFlipped() )
3312 addAttribute( xformNode, "mirror", "true" );
3313 }
3314
3315 addLocationNode( componentNode, fp->GetPosition().x, fp->GetPosition().y );
3316
3317 componentNodes.push_back( componentNode );
3318 }
3319
3320 for( wxXmlNode* padstack : m_padstacks )
3321 {
3322 insertNode( aStepNode, padstack );
3323 m_last_padstack = padstack;
3324 }
3325
3326 for( wxXmlNode* pkg : packageNodes )
3327 aStepNode->AddChild( pkg );
3328
3329 for( wxXmlNode* cmp : componentNodes )
3330 aStepNode->AddChild( cmp );
3331}
3332
3333
3334void PCB_IO_IPC2581::generateLogicalNets( wxXmlNode* aStepNode )
3335{
3336 for( auto& [ net, pin_pair] : m_net_pin_dict )
3337 {
3338 wxXmlNode* netNode = appendNode( aStepNode, "LogicalNet" );
3339 addAttribute( netNode, "name",
3340 genString( m_board->GetNetInfo().GetNetItem( net )->GetNetname(), "NET" ) ) ;
3341
3342 for( auto& [cmp, pin] : pin_pair )
3343 {
3344 wxXmlNode* netPinNode = appendNode( netNode, "PinRef" );
3345 addAttribute( netPinNode, "componentRef", cmp );
3346 addAttribute( netPinNode, "pin", pin );
3347 }
3348 //TODO: Finish
3349 }
3350}
3351
3352//TODO: Add PhyNetGroup section
3353
3354void PCB_IO_IPC2581::generateLayerFeatures( wxXmlNode* aStepNode )
3355{
3356 LSEQ layers = m_board->GetEnabledLayers().Seq();
3357 const NETINFO_LIST& nets = m_board->GetNetInfo();
3358 std::vector<std::unique_ptr<FOOTPRINT>> footprints;
3359
3360 // To avoid the overhead of repeatedly cycling through the layers and nets,
3361 // we pre-sort the board items into a map of layer -> net -> items
3362 std::map<PCB_LAYER_ID, std::map<int, std::vector<BOARD_ITEM*>>> elements;
3363
3364 std::for_each( m_board->Tracks().begin(), m_board->Tracks().end(),
3365 [&layers, &elements]( PCB_TRACK* aTrack )
3366 {
3367 if( aTrack->Type() == PCB_VIA_T )
3368 {
3369 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
3370
3371 for( PCB_LAYER_ID layer : layers )
3372 {
3373 if( via->FlashLayer( layer ) )
3374 elements[layer][via->GetNetCode()].push_back( via );
3375 }
3376 }
3377 else
3378 {
3379 elements[aTrack->GetLayer()][aTrack->GetNetCode()].push_back( aTrack );
3380 }
3381 } );
3382
3383 std::for_each( m_board->Zones().begin(), m_board->Zones().end(),
3384 [ &elements ]( ZONE* zone )
3385 {
3386 LSEQ zone_layers = zone->GetLayerSet().Seq();
3387
3388 for( PCB_LAYER_ID layer : zone_layers )
3389 elements[layer][zone->GetNetCode()].push_back( zone );
3390 } );
3391
3392 for( BOARD_ITEM* item : m_board->Drawings() )
3393 {
3394 if( BOARD_CONNECTED_ITEM* conn_it = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
3395 elements[conn_it->GetLayer()][conn_it->GetNetCode()].push_back( conn_it );
3396 else
3397 elements[item->GetLayer()][0].push_back( item );
3398 }
3399
3400 for( FOOTPRINT* fp : m_board->Footprints() )
3401 {
3402 for( PCB_FIELD* field : fp->GetFields() )
3403 elements[field->GetLayer()][0].push_back( field );
3404
3405 for( BOARD_ITEM* item : fp->GraphicalItems() )
3406 elements[item->GetLayer()][0].push_back( item );
3407
3408 for( PAD* pad : fp->Pads() )
3409 {
3410 LSEQ pad_layers = pad->GetLayerSet().Seq();
3411
3412 for( PCB_LAYER_ID layer : pad_layers )
3413 {
3414 if( pad->FlashLayer( layer ) )
3415 elements[layer][pad->GetNetCode()].push_back( pad );
3416 }
3417
3418 // SMD pads have implicit solder mask and paste openings that are not in the layer
3419 // set. Add them to the corresponding tech layers if the pad is on a copper layer.
3420 if( pad->IsOnLayer( F_Cu ) && pad->FlashLayer( F_Cu ) )
3421 {
3422 if( !pad->IsOnLayer( F_Mask ) )
3423 elements[F_Mask][pad->GetNetCode()].push_back( pad );
3424
3425 if( !pad->IsOnLayer( F_Paste ) )
3426 elements[F_Paste][pad->GetNetCode()].push_back( pad );
3427 }
3428
3429 if( pad->IsOnLayer( B_Cu ) && pad->FlashLayer( B_Cu ) )
3430 {
3431 if( !pad->IsOnLayer( B_Mask ) )
3432 elements[B_Mask][pad->GetNetCode()].push_back( pad );
3433
3434 if( !pad->IsOnLayer( B_Paste ) )
3435 elements[B_Paste][pad->GetNetCode()].push_back( pad );
3436 }
3437 }
3438 }
3439
3440 for( PCB_LAYER_ID layer : layers )
3441 {
3442 if( m_progressReporter )
3443 m_progressReporter->SetMaxProgress( nets.GetNetCount() * layers.size() );
3444
3445 wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" );
3446 addAttribute( layerNode, "layerRef", m_layer_name_map[layer] );
3447
3448 auto process_net = [&] ( int net )
3449 {
3450 std::vector<BOARD_ITEM*>& vec = elements[layer][net];
3451
3452 if( vec.empty() )
3453 return;
3454
3455 std::stable_sort( vec.begin(), vec.end(),
3456 []( BOARD_ITEM* a, BOARD_ITEM* b )
3457 {
3458 if( a->GetParentFootprint() == b->GetParentFootprint() )
3459 return a->Type() < b->Type();
3460
3461 return a->GetParentFootprint() < b->GetParentFootprint();
3462 } );
3463
3464 generateLayerSetNet( layerNode, layer, vec );
3465 };
3466
3467 for( const NETINFO_ITEM* net : nets )
3468 {
3469 if( m_progressReporter )
3470 {
3471 m_progressReporter->Report( wxString::Format( _( "Exporting Layer %s, Net %s" ),
3472 m_board->GetLayerName( layer ),
3473 net->GetNetname() ) );
3474 m_progressReporter->AdvanceProgress();
3475 }
3476
3477 process_net( net->GetNetCode() );
3478 }
3479
3480 if( layerNode->GetChildren() == nullptr )
3481 {
3482 aStepNode->RemoveChild( layerNode );
3483 deleteNode( layerNode );
3484 }
3485 }
3486}
3487
3488
3489void PCB_IO_IPC2581::generateLayerSetDrill( wxXmlNode* aLayerNode )
3490{
3491 int hole_count = 1;
3492
3493 for( const auto& [layers, vec] : m_drill_layers )
3494 {
3495 wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
3496 layerNode->AddAttribute( "layerRef", genLayersString( layers.first, layers.second, "DRILL" ) );
3497
3498 for( BOARD_ITEM* item : vec )
3499 {
3500 if( item->Type() == PCB_VIA_T )
3501 {
3502 PCB_VIA* via = static_cast<PCB_VIA*>( item );
3503 auto it = m_padstack_dict.find( ipcPadstackHash( via ) );
3504
3505 if( it == m_padstack_dict.end() )
3506 {
3507 Report( _( "Via uses unsupported padstack; omitted from drill data." ),
3509 continue;
3510 }
3511
3512 wxXmlNode* padNode = appendNode( layerNode, "Set" );
3513 addAttribute( padNode, "geometry", it->second );
3514
3515 if( via->GetNetCode() > 0 )
3516 addAttribute( padNode, "net", genString( via->GetNetname(), "NET" ) );
3517
3518 wxXmlNode* holeNode = appendNode( padNode, "Hole" );
3519 addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
3520 addAttribute( holeNode, "diameter", floatVal( m_scale * via->GetDrillValue() ) );
3521 addAttribute( holeNode, "platingStatus", "VIA" );
3522 addAttribute( holeNode, "plusTol", "0.0" );
3523 addAttribute( holeNode, "minusTol", "0.0" );
3524 addXY( holeNode, via->GetPosition() );
3525 addBackdrillSpecRefs( holeNode, it->second );
3526 }
3527 else if( item->Type() == PCB_PAD_T )
3528 {
3529 PAD* pad = static_cast<PAD*>( item );
3530 auto it = m_padstack_dict.find( ipcPadstackHash( pad ) );
3531
3532 if( it == m_padstack_dict.end() )
3533 {
3534 Report( _( "Pad uses unsupported padstack; hole was omitted from drill data." ),
3536 continue;
3537 }
3538
3539 wxXmlNode* padNode = appendNode( layerNode, "Set" );
3540 addAttribute( padNode, "geometry", it->second );
3541
3542 if( pad->GetNetCode() > 0 )
3543 addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) );
3544
3545 wxXmlNode* holeNode = appendNode( padNode, "Hole" );
3546 addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
3547 addAttribute( holeNode, "diameter", floatVal( m_scale * pad->GetDrillSizeX() ) );
3548 addAttribute( holeNode, "platingStatus",
3549 pad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" );
3550 addAttribute( holeNode, "plusTol", "0.0" );
3551 addAttribute( holeNode, "minusTol", "0.0" );
3552 addXY( holeNode, pad->GetPosition() );
3553 addBackdrillSpecRefs( holeNode, it->second );
3554 }
3555 }
3556 }
3557
3558 hole_count = 1;
3559
3560 for( const auto& [layers, vec] : m_slot_holes )
3561 {
3562 wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
3563 layerNode->AddAttribute( "layerRef", genLayersString( layers.first, layers.second, "SLOT" ) );
3564
3565 for( PAD* pad : vec )
3566 {
3567 wxXmlNode* padNode = appendNode( layerNode, "Set" );
3568
3569 if( pad->GetNetCode() > 0 )
3570 addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) );
3571
3572 addSlotCavity( padNode, *pad, wxString::Format( "SLOT%d", hole_count++ ) );
3573 }
3574 }
3575}
3576
3577
3578void PCB_IO_IPC2581::generateLayerSetNet( wxXmlNode* aLayerNode, PCB_LAYER_ID aLayer,
3579 std::vector<BOARD_ITEM*>& aItems )
3580{
3581 auto it = aItems.begin();
3582 wxXmlNode* layerSetNode = appendNode( aLayerNode, "Set" );
3583 wxXmlNode* featureSetNode = appendNode( layerSetNode, "Features" );
3584 wxXmlNode* specialNode = appendNode( featureSetNode, "UserSpecial" );
3585
3586 bool has_via = false;
3587 bool has_pad = false;
3588
3589 wxXmlNode* padSetNode = nullptr;
3590
3591 wxXmlNode* viaSetNode = nullptr;
3592
3593 wxXmlNode* teardropLayerSetNode = nullptr;
3594 wxXmlNode* teardropFeatureSetNode = nullptr;
3595
3596 bool teardrop_warning = false;
3597
3598 if( BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( *it );
3599 IsCopperLayer( aLayer ) && item )
3600 {
3601 if( item->GetNetCode() > 0 )
3602 addAttribute( layerSetNode, "net", genString( item->GetNetname(), "NET" ) );
3603 }
3604
3605 auto add_track =
3606 [&]( PCB_TRACK* track )
3607 {
3608 if( track->Type() == PCB_TRACE_T )
3609 {
3610 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
3611 shape.SetStart( track->GetStart() );
3612 shape.SetEnd( track->GetEnd() );
3613 shape.SetWidth( track->GetWidth() );
3614 addShape( specialNode, shape );
3615 }
3616 else if( track->Type() == PCB_ARC_T )
3617 {
3618 PCB_ARC* arc = static_cast<PCB_ARC*>( track );
3619 PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
3620 shape.SetArcGeometry( arc->GetStart(), arc->GetMid(), arc->GetEnd() );
3621 shape.SetWidth( arc->GetWidth() );
3622 addShape( specialNode, shape );
3623 }
3624 else
3625 {
3626 if( !viaSetNode )
3627 {
3628 if( !has_pad )
3629 {
3630 viaSetNode = layerSetNode;
3631 has_via = true;
3632 }
3633 else
3634 {
3635 viaSetNode = appendNode( layerSetNode, "Set" );
3636
3637 if( track->GetNetCode() > 0 )
3638 addAttribute( viaSetNode, "net", genString( track->GetNetname(), "NET" ) );
3639 }
3640
3641 addAttribute( viaSetNode, "padUsage", "VIA" );
3642 }
3643
3644 addVia( viaSetNode, static_cast<PCB_VIA*>( track ), aLayer );
3645 }
3646 };
3647
3648 auto add_zone =
3649 [&]( ZONE* zone )
3650 {
3651 wxXmlNode* zoneFeatureNode = specialNode;
3652
3653 if( zone->IsTeardropArea() )
3654 {
3655 if( m_version > 'B' )
3656 {
3657 if( !teardropFeatureSetNode )
3658 {
3659 teardropLayerSetNode = appendNode( aLayerNode, "Set" );
3660 addAttribute( teardropLayerSetNode, "geometryUsage", "TEARDROP" );
3661
3662 if( zone->GetNetCode() > 0 )
3663 {
3664 addAttribute( teardropLayerSetNode, "net",
3665 genString( zone->GetNetname(), "NET" ) );
3666 }
3667
3668 wxXmlNode* new_teardrops = appendNode( teardropLayerSetNode, "Features" );
3669 addLocationNode( new_teardrops, 0.0, 0.0 );
3670 teardropFeatureSetNode = appendNode( new_teardrops, "UserSpecial" );
3671 }
3672
3673 zoneFeatureNode = teardropFeatureSetNode;
3674 }
3675 else if( !teardrop_warning )
3676 {
3677 Report( _( "Teardrops are not supported in IPC-2581 revision B; they were exported as zones." ),
3679 teardrop_warning = true;
3680 }
3681 }
3682 else
3683 {
3684 if( FOOTPRINT* fp = zone->GetParentFootprint() )
3685 {
3686 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3687 wxString refDes = componentName( fp );
3688 addAttribute( tempSetNode, "componentRef", refDes );
3689 wxXmlNode* newFeatures = appendNode( tempSetNode, "Features" );
3690 addLocationNode( newFeatures, 0.0, 0.0 );
3691 zoneFeatureNode = appendNode( newFeatures, "UserSpecial" );
3692 }
3693 }
3694
3695 SHAPE_POLY_SET& zone_shape = *zone->GetFilledPolysList( aLayer );
3696
3697 for( int ii = 0; ii < zone_shape.OutlineCount(); ++ii )
3698 addContourNode( zoneFeatureNode, zone_shape, ii );
3699 };
3700
3701 auto add_shape =
3702 [&] ( PCB_SHAPE* shape )
3703 {
3704 FOOTPRINT* fp = shape->GetParentFootprint();
3705
3706 if( fp )
3707 {
3708 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3709
3710 if( m_version > 'B' )
3711 addAttribute( tempSetNode, "geometryUsage", "GRAPHIC" );
3712
3713 bool link_to_component = true;
3714
3715 if( m_version == 'B' && isOppositeSideSilk( fp, shape->GetLayer() ) )
3716 link_to_component = false;
3717
3718 if( link_to_component )
3719 addAttribute( tempSetNode, "componentRef", componentName( fp ) );
3720
3721 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3722
3723 addLocationNode( tempFeature, *shape );
3724 addShape( tempFeature, *shape );
3725 }
3726 else if( shape->GetShape() == SHAPE_T::CIRCLE
3727 || shape->GetShape() == SHAPE_T::RECTANGLE
3728 || shape->GetShape() == SHAPE_T::POLY )
3729 {
3730 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3731
3732 if( shape->GetNetCode() > 0 )
3733 addAttribute( tempSetNode, "net", genString( shape->GetNetname(), "NET" ) );
3734
3735 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3736 addLocationNode( tempFeature, *shape );
3737 addShape( tempFeature, *shape );
3738 }
3739 else
3740 {
3741 addShape( specialNode, *shape );
3742 }
3743 };
3744
3745 auto add_text =
3746 [&] ( BOARD_ITEM* text )
3747 {
3748 EDA_TEXT* text_item = nullptr;
3749 FOOTPRINT* fp = text->GetParentFootprint();
3750
3751 if( PCB_TEXT* pcb_text = dynamic_cast<PCB_TEXT*>( text ) )
3752 text_item = static_cast<EDA_TEXT*>( pcb_text );
3753 else if( PCB_TEXTBOX* pcb_textbox = dynamic_cast<PCB_TEXTBOX*>( text ) )
3754 text_item = static_cast<EDA_TEXT*>( pcb_textbox );
3755
3756 if( !text_item || !text_item->IsVisible() || text_item->GetShownText( false ).empty() )
3757 return;
3758
3759 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3760
3761 if( m_version > 'B' )
3762 addAttribute( tempSetNode, "geometryUsage", "TEXT" );
3763
3764 bool link_to_component = fp != nullptr;
3765
3766 if( m_version == 'B' && fp && isOppositeSideSilk( fp, text->GetLayer() ) )
3767 link_to_component = false;
3768
3769 if( link_to_component )
3770 addAttribute( tempSetNode, "componentRef", componentName( fp ) );
3771
3772 wxXmlNode* nonStandardAttributeNode = appendNode( tempSetNode, "NonstandardAttribute" );
3773 addAttribute( nonStandardAttributeNode, "name", "TEXT" );
3774 addAttribute( nonStandardAttributeNode, "value", text_item->GetShownText( false ) );
3775 addAttribute( nonStandardAttributeNode, "type", "STRING" );
3776
3777 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3778 addLocationNode( tempFeature, 0.0, 0.0 );
3779
3780 if( text->Type() == PCB_TEXT_T && static_cast<PCB_TEXT*>( text )->IsKnockout() )
3781 addKnockoutText( tempFeature, static_cast<PCB_TEXT*>( text ) );
3782 else
3783 addText( tempFeature, text_item, text->GetFontMetrics() );
3784
3785 if( text->Type() == PCB_TEXTBOX_T )
3786 {
3787 PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( text );
3788
3789 if( textbox->IsBorderEnabled() )
3790 addShape( tempFeature, *static_cast<PCB_SHAPE*>( textbox ) );
3791 }
3792 };
3793
3794 auto add_pad =
3795 [&]( PAD* pad )
3796 {
3797 if( !padSetNode )
3798 {
3799 if( !has_via )
3800 {
3801 padSetNode = layerSetNode;
3802 has_pad = true;
3803 }
3804 else
3805 {
3806 padSetNode = appendNode( aLayerNode, "Set" );
3807
3808 if( pad->GetNetCode() > 0 )
3809 addAttribute( padSetNode, "net", genString( pad->GetNetname(), "NET" ) );
3810 }
3811 }
3812
3813 FOOTPRINT* fp = pad->GetParentFootprint();
3814
3815 if( fp && fp->IsFlipped() )
3816 addPad( padSetNode, pad, FlipLayer( aLayer ) );
3817 else
3818 addPad( padSetNode, pad, aLayer );
3819 };
3820
3821 for( BOARD_ITEM* item : aItems )
3822 {
3823 switch( item->Type() )
3824 {
3825 case PCB_TRACE_T:
3826 case PCB_ARC_T:
3827 case PCB_VIA_T:
3828 add_track( static_cast<PCB_TRACK*>( item ) );
3829 break;
3830
3831 case PCB_ZONE_T:
3832 add_zone( static_cast<ZONE*>( item ) );
3833 break;
3834
3835 case PCB_PAD_T:
3836 add_pad( static_cast<PAD*>( item ) );
3837 break;
3838
3839 case PCB_SHAPE_T:
3840 add_shape( static_cast<PCB_SHAPE*>( item ) );
3841 break;
3842
3843 case PCB_TEXT_T:
3844 case PCB_TEXTBOX_T:
3845 case PCB_FIELD_T:
3846 add_text( item );
3847 break;
3848
3849 case PCB_DIMENSION_T:
3850 case PCB_TARGET_T:
3851 case PCB_DIM_ALIGNED_T:
3852 case PCB_DIM_LEADER_T:
3853 case PCB_DIM_CENTER_T:
3854 case PCB_DIM_RADIAL_T:
3856 //TODO: Add support for dimensions
3857 break;
3858
3859 default:
3860 wxLogTrace( traceIpc2581, wxS( "Unhandled type %s" ),
3861 ENUM_MAP<KICAD_T>::Instance().ToString( item->Type() ) );
3862 }
3863 }
3864
3865 if( specialNode->GetChildren() == nullptr )
3866 {
3867 featureSetNode->RemoveChild( specialNode );
3868 deleteNode( specialNode );
3869 }
3870
3871 if( featureSetNode->GetChildren() == nullptr )
3872 {
3873 layerSetNode->RemoveChild( featureSetNode );
3874 deleteNode( featureSetNode );
3875 }
3876
3877 if( layerSetNode->GetChildren() == nullptr )
3878 {
3879 aLayerNode->RemoveChild( layerSetNode );
3880 deleteNode( layerSetNode );
3881 }
3882}
3883
3885{
3886 for( const auto& [layers, vec] : m_auxilliary_Layers )
3887 {
3888 bool add_node = true;
3889
3890 wxString name;
3891 bool hole = false;
3892
3893 // clang-format off: suggestion is inconsitent
3894 switch( std::get<0>(layers) )
3895 {
3897 name = "COVERING";
3898 break;
3900 name = "PLUGGING";
3901 hole = true;
3902 break;
3904 name = "TENTING";
3905 break;
3907 name = "FILLING";
3908 hole = true;
3909 break;
3911 name = "CAPPING";
3912 hole = true;
3913 break;
3914 default:
3915 add_node = false;
3916 break;
3917 }
3918 // clang-format on: suggestion is inconsitent
3919
3920 if( !add_node )
3921 continue;
3922
3923 wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" );
3924 if( std::get<2>( layers ) == UNDEFINED_LAYER )
3925 layerNode->AddAttribute( "layerRef", genLayerString( std::get<1>( layers ), TO_UTF8( name ) ) );
3926 else
3927 layerNode->AddAttribute( "layerRef", genLayersString( std::get<1>( layers ),
3928 std::get<2>( layers ), TO_UTF8( name ) ) );
3929
3930 for( BOARD_ITEM* item : vec )
3931 {
3932 if( item->Type() == PCB_VIA_T )
3933 {
3934 PCB_VIA* via = static_cast<PCB_VIA*>( item );
3935
3936 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
3937
3938 if( hole )
3939 shape.SetEnd( { KiROUND( via->GetDrillValue() / 2.0 ), 0 } );
3940 else
3941 shape.SetEnd( { KiROUND( via->GetWidth( std::get<1>( layers ) ) / 2.0 ), 0 } );
3942
3943 wxXmlNode* padNode = appendNode( layerNode, "Pad" );
3944 addPadStack( padNode, via );
3945
3946 addLocationNode( padNode, 0.0, 0.0 );
3947 addShape( padNode, shape );
3948 }
3949 }
3950 }
3951}
3952
3953
3955{
3956 if( m_progressReporter )
3957 m_progressReporter->AdvancePhase( _( "Generating BOM section" ) );
3958
3959 // Per IPC-2581 schema, Avl requires at least one AvlItem child element.
3960 // Don't emit Avl section if there are no items.
3961 if( m_OEMRef_dict.empty() )
3962 return nullptr;
3963
3964 wxXmlNode* avl = appendNode( m_xml_root, "Avl" );
3965 addAttribute( avl, "name", "Primary_Vendor_List" );
3966
3967 wxXmlNode* header = appendNode( avl, "AvlHeader" );
3968 addAttribute( header, "title", "BOM" );
3969 addAttribute( header, "source", "KiCad" );
3970 addAttribute( header, "author", "OWNER" );
3971 addAttribute( header, "datetime", wxDateTime::Now().FormatISOCombined() );
3972 addAttribute( header, "version", "1" );
3973
3974 std::set<wxString> unique_parts;
3975 std::map<wxString,wxString> unique_vendors;
3976
3977 for( auto& [fp, name] : m_OEMRef_dict )
3978 {
3979 auto [ it, success ] = unique_parts.insert( name );
3980
3981 if( !success )
3982 continue;
3983
3984 wxXmlNode* part = appendNode( avl, "AvlItem" );
3985 addAttribute( part, "OEMDesignNumber", genString( name, "REF" ) );
3986
3987 PCB_FIELD* nums[2] = { fp->GetField( m_mpn ), fp->GetField( m_distpn ) };
3988 PCB_FIELD* company[2] = { fp->GetField( m_mfg ), nullptr };
3989 wxString company_name[2] = { m_mfg, m_dist };
3990
3991 for ( int ii = 0; ii < 2; ++ii )
3992 {
3993 if( nums[ii] )
3994 {
3995 wxString mpn_name = nums[ii]->GetShownText( false );
3996
3997 if( mpn_name.empty() )
3998 continue;
3999
4000 wxXmlNode* vmpn = appendNode( part, "AvlVmpn" );
4001 addAttribute( vmpn, "qualified", "false" );
4002 addAttribute( vmpn, "chosen", "false" );
4003
4004 wxXmlNode* mpn = appendNode( vmpn, "AvlMpn" );
4005 addAttribute( mpn, "name", mpn_name );
4006
4007 wxXmlNode* vendor = appendNode( vmpn, "AvlVendor" );
4008
4009 wxString vendor_name = wxT( "UNKNOWN" );
4010
4011 // If the field resolves, then use that field content unless it is empty
4012 if( !ii && company[ii] )
4013 {
4014 wxString tmp = company[ii]->GetShownText( false );
4015
4016 if( !tmp.empty() )
4017 vendor_name = tmp;
4018 }
4019 // If it doesn't resolve but there is content from the dialog, use the static content
4020 else if( !ii && !company_name[ii].empty() )
4021 {
4022 vendor_name = company_name[ii];
4023 }
4024 else if( ii && !m_dist.empty() )
4025 {
4026 vendor_name = m_dist;
4027 }
4028
4029 auto [vendor_id, inserted] = unique_vendors.emplace(
4030 vendor_name,
4031 wxString::Format( "VENDOR_%zu", unique_vendors.size() ) );
4032
4033 addAttribute( vendor, "enterpriseRef", vendor_id->second );
4034
4035 if( inserted )
4036 {
4037 wxXmlNode* new_vendor = new wxXmlNode( wxXML_ELEMENT_NODE, "Enterprise" );
4038 addAttribute( new_vendor, "id", vendor_id->second );
4039 addAttribute( new_vendor, "name", vendor_name );
4040 addAttribute( new_vendor, "code", "NONE" );
4041 insertNodeAfter( m_enterpriseNode, new_vendor );
4042 m_enterpriseNode = new_vendor;
4043 }
4044 }
4045 }
4046 }
4047
4048 return avl;
4049}
4050
4051
4052void PCB_IO_IPC2581::SaveBoard( const wxString& aFileName, BOARD* aBoard,
4053 const std::map<std::string, UTF8>* aProperties )
4054{
4055 // Clean up any previous export state to allow multiple exports per plugin instance
4056 delete m_xml_doc;
4057 m_xml_doc = nullptr;
4058 m_xml_root = nullptr;
4059 m_contentNode = nullptr;
4060 m_lastAppendedNode = nullptr;
4061
4062 m_board = aBoard;
4064 m_backdrill_spec_nodes.clear();
4065 m_backdrill_spec_used.clear();
4067 m_cad_header_node = nullptr;
4068 m_layer_name_map.clear();
4069
4070 // Clear all internal dictionaries and caches
4071 m_user_shape_dict.clear();
4072 m_shape_user_node = nullptr;
4073 m_std_shape_dict.clear();
4074 m_shape_std_node = nullptr;
4075 m_line_dict.clear();
4076 m_line_node = nullptr;
4077 m_padstack_dict.clear();
4078 m_padstacks.clear();
4079 m_last_padstack = nullptr;
4080 m_footprint_dict.clear();
4083 m_OEMRef_dict.clear();
4084 m_net_pin_dict.clear();
4085 m_drill_layers.clear();
4086 m_slot_holes.clear();
4087 m_auxilliary_Layers.clear();
4088 m_element_names.clear();
4089 m_generated_names.clear();
4090 m_acceptable_chars.clear();
4091 m_total_bytes = 0;
4092
4093 m_units_str = "MILLIMETER";
4094 m_scale = 1.0 / PCB_IU_PER_MM;
4095 m_sigfig = 6;
4096
4097 if( auto it = aProperties->find( "units" ); it != aProperties->end() )
4098 {
4099 if( it->second == "inch" )
4100 {
4101 m_units_str = "INCH";
4102 m_scale = ( 1.0 / 25.4 ) / PCB_IU_PER_MM;
4103 }
4104 }
4105
4106 if( auto it = aProperties->find( "sigfig" ); it != aProperties->end() )
4107 m_sigfig = std::stoi( it->second );
4108
4109 if( auto it = aProperties->find( "version" ); it != aProperties->end() )
4110 m_version = it->second.c_str()[0];
4111
4112 if( auto it = aProperties->find( "OEMRef" ); it != aProperties->end() )
4113 m_OEMRef = it->second.wx_str();
4114
4115 if( auto it = aProperties->find( "mpn" ); it != aProperties->end() )
4116 m_mpn = it->second.wx_str();
4117
4118 if( auto it = aProperties->find( "mfg" ); it != aProperties->end() )
4119 m_mfg = it->second.wx_str();
4120
4121 if( auto it = aProperties->find( "dist" ); it != aProperties->end() )
4122 m_dist = it->second.wx_str();
4123
4124 if( auto it = aProperties->find( "distpn" ); it != aProperties->end() )
4125 m_distpn = it->second.wx_str();
4126
4127 if( auto it = aProperties->find( "bomrev" ); it != aProperties->end() )
4128 m_bomRev = it->second.wx_str();
4129
4130 if( m_version == 'B' )
4131 {
4132 for( char c = 'a'; c <= 'z'; ++c )
4133 m_acceptable_chars.insert( c );
4134
4135 for( char c = 'A'; c <= 'Z'; ++c )
4136 m_acceptable_chars.insert( c );
4137
4138 for( char c = '0'; c <= '9'; ++c )
4139 m_acceptable_chars.insert( c );
4140
4141 // Add special characters
4142 std::string specialChars = "_\\-.+><";
4143
4144 for( char c : specialChars )
4145 m_acceptable_chars.insert( c );
4146 }
4147
4148 m_xml_doc = new wxXmlDocument();
4150
4152
4153 if( m_progressReporter )
4154 {
4155 m_progressReporter->SetNumPhases( 7 );
4156 m_progressReporter->BeginPhase( 1 );
4157 m_progressReporter->Report( _( "Generating logistic section" ) );
4158 }
4159
4162
4163 wxXmlNode* ecad_node = generateEcadSection();
4164 wxXmlNode* bom_node = generateBOMSection( ecad_node );
4165 wxXmlNode* avl_node = generateAvlSection();
4166
4167 // Insert BomRef/AvlRef into Content section per IPC-2581C 4.1.1.2.
4168 // They go after LayerRef and before Dictionary* nodes.
4169 if( m_contentNode && ( bom_node || avl_node ) )
4170 {
4171 wxXmlNode* insertBefore = nullptr;
4172
4173 for( wxXmlNode* child = m_contentNode->GetChildren(); child; child = child->GetNext() )
4174 {
4175 if( child->GetName().StartsWith( "Dictionary" ) )
4176 {
4177 insertBefore = child;
4178 break;
4179 }
4180 }
4181
4182 auto insertRef =
4183 [&]( const wxString& aNodeName, wxXmlNode* aSection )
4184 {
4185 if( !aSection )
4186 return;
4187
4188 wxXmlNode* ref = new wxXmlNode( wxXML_ELEMENT_NODE, aNodeName );
4189 ref->AddAttribute( "name", aSection->GetAttribute( "name" ) );
4190
4191 if( insertBefore )
4192 m_contentNode->InsertChild( ref, insertBefore );
4193 else
4194 m_contentNode->AddChild( ref );
4195 };
4196
4197 insertRef( "BomRef", bom_node );
4198 insertRef( "AvlRef", avl_node );
4199 }
4200
4201 if( m_progressReporter )
4202 {
4203 m_progressReporter->AdvancePhase( _( "Saving file" ) );
4204 }
4205
4206 wxFileOutputStreamWithProgress out_stream( aFileName );
4207 double written_bytes = 0.0;
4208 double last_yield = 0.0;
4209
4210 // This is a rough estimation of the size of the spaces in the file
4211 // We just need to total to be slightly larger than the value of the
4212 // progress bar, so accurately counting spaces is not terribly important
4214
4215 auto update_progress = [&]( size_t aBytes )
4216 {
4217 written_bytes += aBytes;
4218 double percent = written_bytes / static_cast<double>( m_total_bytes );
4219
4220 if( m_progressReporter )
4221 {
4222 // Only update every percent
4223 if( last_yield + 0.01 < percent )
4224 {
4225 last_yield = percent;
4226 m_progressReporter->SetCurrentProgress( percent );
4227 }
4228 }
4229 };
4230
4231 out_stream.SetProgressCallback( update_progress );
4232
4233 if( !m_xml_doc->Save( out_stream ) )
4234 {
4235 Report( _( "Failed to save IPC-2581 data to buffer." ), RPT_SEVERITY_ERROR );
4236 return;
4237 }
4238}
const char * name
@ ERROR_INSIDE
constexpr int ARC_HIGH_DEF
Definition base_units.h:141
constexpr double PCB_IU_PER_MM
Pcbnew IU is 1 nanometer.
Definition base_units.h:72
bool IsPrmSpecified(const wxString &aPrmValue)
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_SILKSCREEN
@ BS_ITEM_TYPE_DIELECTRIC
@ BS_ITEM_TYPE_SOLDERMASK
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
wxString GetMajorMinorPatchVersion()
Get the major, minor and patch version in a string major.minor.patch This is extracted by CMake from ...
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
Container for design settings for a BOARD object.
BOARD_STACKUP & GetStackupDescriptor()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual bool IsKnockout() const
Definition board_item.h:355
FOOTPRINT * GetParentFootprint() const
VECTOR2I GetFPRelativePosition() const
Manage one layer needed to make a physical board.
wxString GetTypeName() const
int GetSublayersCount() const
double GetEpsilonR(int aDielectricSubLayer=0) const
wxString GetColor(int aDielectricSubLayer=0) const
wxString GetLayerName() const
PCB_LAYER_ID GetBrdLayerId() const
int GetThickness(int aDielectricSubLayer=0) const
BOARD_STACKUP_ITEM_TYPE GetType() const
wxString GetMaterial(int aDielectricSubLayer=0) const
int GetDielectricLayerId() const
double GetLossTangent(int aDielectricSubLayer=0) const
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
int GetCount() const
bool SynchronizeWithBoard(BOARD_DESIGN_SETTINGS *aSettings)
Synchronize the BOARD_STACKUP_ITEM* list with the board.
int BuildBoardThicknessFromStackup() const
int GetLayerDistance(PCB_LAYER_ID aFirstLayer, PCB_LAYER_ID aSecondLayer) const
Calculate the distance (height) between the two given copper layers.
wxString m_FinishType
The name of external copper finish.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:323
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr const Vec GetCenter() const
Definition box2.h:230
constexpr size_type GetHeight() const
Definition box2.h:215
EDA_ANGLE Normalize()
Definition eda_angle.h:229
double AsDegrees() const
Definition eda_angle.h:116
EDA_ANGLE Invert() const
Definition eda_angle.h:173
const KIID m_Uuid
Definition eda_item.h:528
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:275
FILL_T GetFillMode() const
Definition eda_shape.h:158
int GetRectangleWidth() const
SHAPE_POLY_SET & GetPolyShape()
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:185
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:232
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:194
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:190
std::vector< VECTOR2I > GetRectCorners() const
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:236
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Set the three controlling points for an arc.
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:272
int GetRectangleHeight() const
bool IsClockwiseArc() const
void SetWidth(int aWidth)
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:93
const VECTOR2I & GetTextPos() const
Definition eda_text.h:298
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:114
virtual bool IsVisible() const
Definition eda_text.h:212
virtual EDA_ANGLE GetDrawRotation() const
Definition eda_text.h:404
virtual KIFONT::FONT * GetDrawFont(const RENDER_SETTINGS *aSettings) const
Definition eda_text.cpp:666
const TEXT_ATTRIBUTES & GetAttributes() const
Definition eda_text.h:256
int GetEffectiveTextPenWidth(int aDefaultPenWidth=0) const
The EffectiveTextPenWidth uses the text thickness if > 1 or aDefaultPenWidth.
Definition eda_text.cpp:465
virtual wxString GetShownText(bool aAllowExtraText, int aDepth=0) const
Return the string actually shown after processing of the base text.
Definition eda_text.h:125
static ENUM_MAP< T > & Instance()
Definition property.h:721
unsigned GetPadCount(INCLUDE_NPTH_T aIncludeNPTH=INCLUDE_NPTH_T(INCLUDE_NPTH)) const
Return the number of pads.
EDA_ITEM * Clone() const override
Invoke a function on all children.
std::deque< PAD * > & Pads()
Definition footprint.h:377
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:417
bool IsFlipped() const
Definition footprint.h:602
const wxString & GetReference() const
Definition footprint.h:829
wxString m_name
Name of the IO loader.
Definition io_base.h:234
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:240
virtual void Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Definition io_base.cpp:124
FONT is an abstract base class for both outline and stroke fonts.
Definition font.h:98
void Draw(KIGFX::GAL *aGal, const wxString &aText, const VECTOR2I &aPosition, const VECTOR2I &aCursor, const TEXT_ATTRIBUTES &aAttributes, const METRICS &aFontMetrics, std::optional< VECTOR2I > aMousePos=std::nullopt, wxString *aActiveUrl=nullptr) const
Draw a string.
Definition font.cpp:250
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
double r
Red component.
Definition color4d.h:393
double g
Green component.
Definition color4d.h:394
double b
Blue component.
Definition color4d.h:395
wxString AsString() const
Definition kiid.cpp:244
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition lseq.h:47
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:313
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
Handle the data for a net.
Definition netinfo.h:50
Container for NETINFO_ITEM elements, which are the nets.
Definition netinfo.h:193
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:157
std::optional< bool > IsFilled() const
std::optional< bool > IsTented(PCB_LAYER_ID aSide) const
Checks if this padstack is tented (covered in soldermask) on the given side.
POST_MACHINING_PROPS & FrontPostMachining()
Definition padstack.h:360
std::optional< bool > IsPlugged(PCB_LAYER_ID aSide) const
DRILL_PROPS & TertiaryDrill()
Definition padstack.h:357
DRILL_PROPS & Drill()
Definition padstack.h:351
std::optional< bool > IsCapped() const
std::optional< bool > IsCovered(PCB_LAYER_ID aSide) const
DRILL_PROPS & SecondaryDrill()
Definition padstack.h:354
POST_MACHINING_PROPS & BackPostMachining()
Definition padstack.h:363
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:55
LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition pad.h:560
int GetSizeX() const
Definition pad.h:285
void MergePrimitivesAsPolygon(PCB_LAYER_ID aLayer, SHAPE_POLY_SET *aMergedPolygon, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Merge all basic shapes to a SHAPE_POLY_SET.
Definition pad.cpp:3256
int GetRoundRectCornerRadius(PCB_LAYER_ID aLayer) const
Definition pad.cpp:890
bool FlashLayer(int aLayer, bool aOnlyCheckIfPermitted=false) const
Check to see whether the pad should be flashed on the specific layer.
Definition pad.cpp:439
int GetDrillSizeY() const
Definition pad.h:321
const VECTOR2I & GetDrillSize() const
Definition pad.h:317
PAD_ATTRIB GetAttribute() const
Definition pad.h:563
const wxString & GetNumber() const
Definition pad.h:137
const VECTOR2I & GetDelta(PCB_LAYER_ID aLayer) const
Definition pad.h:304
VECTOR2I GetPosition() const override
Definition pad.h:209
int GetDrillSizeX() const
Definition pad.h:319
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition pad.h:196
int GetSolderMaskExpansion(PCB_LAYER_ID aLayer) const
Definition pad.cpp:1679
const PADSTACK & Padstack() const
Definition pad.h:333
const VECTOR2I & GetOffset(PCB_LAYER_ID aLayer) const
Definition pad.h:329
EDA_ANGLE GetOrientation() const
Return the rotation angle of the pad.
Definition pad.h:420
PAD_DRILL_SHAPE GetDrillShape() const
Definition pad.h:437
int GetChamferPositions(PCB_LAYER_ID aLayer) const
Definition pad.h:845
double GetChamferRectRatio(PCB_LAYER_ID aLayer) const
Definition pad.h:828
VECTOR2I GetSolderPasteMargin(PCB_LAYER_ID aLayer) const
Usually < 0 (mask shape smaller than pad)because the margin can be dependent on the pad size,...
Definition pad.cpp:1742
bool HasDrilledHole() const override
Definition pad.h:112
bool HasHole() const override
Definition pad.h:107
bool TransformHoleToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Build the corner list of the polygonal drill shape in the board coordinate system.
Definition pad.cpp:2573
const VECTOR2I & GetSize(PCB_LAYER_ID aLayer) const
Definition pad.h:264
const VECTOR2I & GetMid() const
Definition pcb_track.h:290
wxString GetShownText(bool aAllowExtraText, int aDepth=0) const override
Return the string actually shown after processing of the base text.
void addText(wxXmlNode *aContentNode, EDA_TEXT *aShape, const KIFONT::METRICS &aFontMetrics)
wxString floatVal(double aVal, int aSigFig=-1) const
void generateLayerSetDrill(wxXmlNode *aStepNode)
wxString genLayerString(PCB_LAYER_ID aLayer, const char *aPrefix) const
wxXmlNode * generateContentSection()
Creates the Content section of the XML file.
wxXmlNode * appendNode(wxXmlNode *aParent, const wxString &aName)
void addBackdrillSpecRefs(wxXmlNode *aHoleNode, const wxString &aPadstackName)
void generateDrillLayers(wxXmlNode *aCadLayerNode)
wxString sanitizeId(const wxString &aStr) const
wxXmlNode * addPackage(wxXmlNode *aStepNode, FOOTPRINT *aFootprint)
void generateComponents(wxXmlNode *aStepNode)
std::map< wxString, std::array< wxString, 3 > > m_padstack_backdrill_specs
bool addContourNode(wxXmlNode *aParentNode, const SHAPE_POLY_SET &aPolySet, int aOutline=0, FILL_T aFillType=FILL_T::FILLED_SHAPE, int aWidth=0, LINE_STYLE aDashType=LINE_STYLE::SOLID)
wxString componentName(FOOTPRINT *aFootprint)
void addShape(wxXmlNode *aContentNode, const PCB_SHAPE &aShape, bool aInline=false)
bool addOutlineNode(wxXmlNode *aParentNode, const SHAPE_POLY_SET &aPolySet, int aWidth=0, LINE_STYLE aDashType=LINE_STYLE::SOLID)
void generateStackup(wxXmlNode *aCadLayerNode)
std::map< std::tuple< auxLayerType, PCB_LAYER_ID, PCB_LAYER_ID >, std::vector< BOARD_ITEM * > > m_auxilliary_Layers
bool addPolygonNode(wxXmlNode *aParentNode, const SHAPE_LINE_CHAIN &aPolygon, FILL_T aFillType=FILL_T::FILLED_SHAPE, int aWidth=0, LINE_STYLE aDashType=LINE_STYLE::SOLID)
void generateLayerSetNet(wxXmlNode *aLayerNode, PCB_LAYER_ID aLayer, std::vector< BOARD_ITEM * > &aItems)
bool isValidLayerFor2581(PCB_LAYER_ID aLayer)
void insertNodeAfter(wxXmlNode *aPrev, wxXmlNode *aNode)
void generateCadLayers(wxXmlNode *aCadLayerNode)
std::vector< wxXmlNode * > m_padstacks
wxString pinName(const PAD *aPad) const
void generateCadSpecs(wxXmlNode *aCadLayerNode)
void addVia(wxXmlNode *aContentNode, const PCB_VIA *aVia, PCB_LAYER_ID aLayer)
void addSlotCavity(wxXmlNode *aContentNode, const PAD &aPad, const wxString &aName)
void deleteNode(wxXmlNode *&aNode)
wxXmlNode * m_last_padstack
size_t lineHash(int aWidth, LINE_STYLE aDashType)
std::map< size_t, wxString > m_std_shape_dict
void addAttribute(wxXmlNode *aNode, const wxString &aName, const wxString &aValue)
wxXmlNode * m_shape_user_node
wxXmlNode * generateAvlSection()
Creates the Approved Vendor List section.
wxXmlNode * generateHistorySection()
Creates the history section.
wxXmlNode * m_contentNode
void addXY(wxXmlNode *aNode, const VECTOR2I &aVec, const char *aXName=nullptr, const char *aYName=nullptr)
wxXmlNode * m_shape_std_node
void addPadStack(wxXmlNode *aContentNode, const PAD *aPad)
std::map< FOOTPRINT *, wxString > m_OEMRef_dict
void clearLoadedFootprints()
Frees the memory allocated for the loaded footprints in m_loaded_footprints.
std::map< wxString, wxXmlNode * > m_backdrill_spec_nodes
std::map< std::pair< PCB_LAYER_ID, PCB_LAYER_ID >, std::vector< PAD * > > m_slot_holes
std::map< size_t, wxString > m_footprint_dict
wxXmlNode * generateLogisticSection()
Creates the logistical data header.
std::set< wxString > m_element_names
std::map< size_t, wxString > m_line_dict
void addLineDesc(wxXmlNode *aNode, int aWidth, LINE_STYLE aDashType, bool aForce=false)
void addKnockoutText(wxXmlNode *aContentNode, PCB_TEXT *aText)
std::map< size_t, wxString > m_padstack_dict
std::map< std::pair< PCB_LAYER_ID, PCB_LAYER_ID >, std::vector< BOARD_ITEM * > > m_drill_layers
std::map< wxString, FOOTPRINT * > m_footprint_refdes_dict
void generateProfile(wxXmlNode *aStepNode)
void generateLayerFeatures(wxXmlNode *aStepNode)
void SaveBoard(const wxString &aFileName, BOARD *aBoard, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aBoard to a storage file in a format that this PCB_IO implementation knows about or it can be u...
wxXmlNode * generateBOMSection(wxXmlNode *aEcadNode)
Creates the BOM section.
wxXmlNode * generateContentStackup(wxXmlNode *aContentNode)
void generateAuxilliaryLayers(wxXmlNode *aCadLayerNode)
wxXmlNode * m_cad_header_node
void addCadHeader(wxXmlNode *aEcadNode)
void generateLayerSetAuxilliary(wxXmlNode *aStepNode)
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
const std::map< std::string, UTF8 > * m_props
wxString genLayersString(PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom, const char *aPrefix) const
void generateLogicalNets(wxXmlNode *aStepNode)
void addFillDesc(wxXmlNode *aNode, FILL_T aFillType, bool aForce=false)
std::map< size_t, wxString > m_user_shape_dict
size_t shapeHash(const PCB_SHAPE &aShape)
~PCB_IO_IPC2581() override
wxString genString(const wxString &aStr, const char *aPrefix=nullptr) const
wxXmlNode * m_xml_root
std::vector< FOOTPRINT * > m_loaded_footprints
bool addPolygonCutouts(wxXmlNode *aParentNode, const SHAPE_POLY_SET::POLYGON &aPolygon)
void pruneUnusedBackdrillSpecs()
std::set< wxUniChar > m_acceptable_chars
void addLayerAttributes(wxXmlNode *aNode, PCB_LAYER_ID aLayer)
wxXmlNode * generateXmlHeader()
Creates the XML header for IPC-2581.
void ensureBackdrillSpecs(const wxString &aPadstackName, const PADSTACK &aPadstack)
wxXmlNode * generateEcadSection()
Creates the ECAD section.
wxXmlDocument * m_xml_doc
wxXmlNode * insertNode(wxXmlNode *aParent, const wxString &aName)
void addPad(wxXmlNode *aContentNode, const PAD *aPad, PCB_LAYER_ID aLayer)
void generateStepSection(wxXmlNode *aCadNode)
std::map< PCB_LAYER_ID, wxString > m_layer_name_map
std::map< wxString, wxString > m_generated_names
std::set< wxString > m_backdrill_spec_used
void addLocationNode(wxXmlNode *aContentNode, double aX, double aY)
wxXmlNode * m_line_node
std::map< int, std::vector< std::pair< wxString, wxString > > > m_net_pin_dict
std::map< FOOTPRINT *, wxString > m_footprint_refdes_reverse_dict
wxXmlNode * m_lastAppendedNode
Optimization for appendNode to avoid O(n) child traversal.
wxXmlNode * m_enterpriseNode
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:81
STROKE_PARAMS GetStroke() const override
Definition pcb_shape.h:91
VECTOR2I GetPosition() const override
Definition pcb_shape.h:79
bool IsBorderEnabled() const
Disables the border, this is done by changing the stroke internally.
void TransformTextToPolySet(SHAPE_POLY_SET &aBuffer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc) const
Function TransformTextToPolySet Convert the text to a polygonSet describing the actual character stro...
Definition pcb_text.cpp:610
const VECTOR2I & GetStart() const
Definition pcb_track.h:97
const VECTOR2I & GetEnd() const
Definition pcb_track.h:94
virtual int GetWidth() const
Definition pcb_track.h:91
PCB_LAYER_ID BottomLayer() const
VECTOR2I GetPosition() const override
Definition pcb_track.h:557
bool FlashLayer(int aLayer) const
Check to see whether the via should have a pad on the specific layer.
const PADSTACK & Padstack() const
Definition pcb_track.h:406
int GetWidth() const override
PCB_LAYER_ID TopLayer() const
int GetDrillValue() const
Calculate the drill value for vias (m_drill if > 0, or default drill value for the board).
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int Width() const
Get the current width of the segments in the chain.
int PointCount() const
Return the number of points (vertices) in this line chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const std::vector< VECTOR2I > & CPoints() const
Represent a set of closed polygons.
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the 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.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
void InflateWithLinkedHoles(int aFactor, CORNER_STRATEGY aCornerStrategy, int aMaxError)
Perform outline inflation/deflation, using round corners.
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
int GetWidth() const
LINE_STYLE GetLineStyle() const
Handle a list of polygons defining a copper zone.
Definition zone.h:74
void SetProgressCallback(std::function< void(size_t)> aCallback)
@ ROUND_ALL_CORNERS
All angles are rounded.
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ UNDEFINED
Definition eda_shape.h:47
@ SEGMENT
Definition eda_shape.h:48
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:49
FILL_T
Definition eda_shape.h:59
@ NO_FILL
Definition eda_shape.h:60
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:61
@ FP_SMD
Definition footprint.h:86
@ FP_THROUGH_HOLE
Definition footprint.h:85
static const wxChar traceIpc2581[]
This program source code file is part of KiCad, a free EDA CAD application.
static constexpr void hash_combine(std::size_t &seed)
This is a dummy function to take the final case of hash_combine below.
Definition hash.h:32
static constexpr std::size_t hash_val(const Types &... args)
Definition hash.h:51
size_t hash_fp_item(const EDA_ITEM *aItem, int aFlags)
Calculate hash of an EDA_ITEM.
Definition hash_eda.cpp:58
Hashing functions for EDA_ITEMs.
@ HASH_POS
Definition hash_eda.h:47
@ REL_COORD
Use coordinates relative to the parent object.
Definition hash_eda.h:50
surfaceFinishType
IPC-6012 surface finish types from Table 3-3 "Final Finish and Coating Requirements".
@ OTHER
Non-standard finish.
@ DIG
Direct Immersion Gold.
@ IAG
Immersion Silver.
@ ISN
Immersion Tin.
@ S
Solder (HASL/SMOBC)
@ HT_OSP
High Temperature OSP.
@ ENEPIG_N
ENEPIG for soldering (normal gold thickness)
@ NONE
No surface finish / not specified - skip coating layer generation.
@ OSP
Organic Solderability Preservative.
@ G
Gold (hard gold)
@ ENIG_N
ENIG for soldering (normal gold thickness)
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayerId, int aCopperLayersCount)
Definition layer_id.cpp:173
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:782
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:679
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ User_8
Definition layer_ids.h:131
@ F_CrtYd
Definition layer_ids.h:116
@ B_Adhes
Definition layer_ids.h:103
@ Edge_Cuts
Definition layer_ids.h:112
@ Dwgs_User
Definition layer_ids.h:107
@ F_Paste
Definition layer_ids.h:104
@ Cmts_User
Definition layer_ids.h:108
@ User_6
Definition layer_ids.h:129
@ User_7
Definition layer_ids.h:130
@ F_Adhes
Definition layer_ids.h:102
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ User_5
Definition layer_ids.h:128
@ Eco1_User
Definition layer_ids.h:109
@ F_Mask
Definition layer_ids.h:97
@ B_Paste
Definition layer_ids.h:105
@ User_9
Definition layer_ids.h:132
@ F_Fab
Definition layer_ids.h:119
@ Margin
Definition layer_ids.h:113
@ F_SilkS
Definition layer_ids.h:100
@ B_CrtYd
Definition layer_ids.h:115
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ Eco2_User
Definition layer_ids.h:110
@ User_3
Definition layer_ids.h:126
@ User_1
Definition layer_ids.h:124
@ B_SilkS
Definition layer_ids.h:101
@ User_4
Definition layer_ids.h:127
@ User_2
Definition layer_ids.h:125
@ F_Cu
Definition layer_ids.h:64
@ B_Fab
Definition layer_ids.h:118
bool IsValidLayer(int aLayerId)
Test whether a given integer is a valid layer index, i.e.
Definition layer_ids.h:657
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:29
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
PAD_DRILL_POST_MACHINING_MODE
Definition padstack.h:76
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ TRAPEZOID
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:54
static size_t ipcPadstackHash(const PCB_VIA *aVia)
static const std::map< surfaceFinishType, wxString > surfaceFinishTypeToString
Map surfaceFinishType enum to IPC-2581 XML string values.
static bool isOppositeSideSilk(const FOOTPRINT *aFootprint, PCB_LAYER_ID aLayer)
static void mixBackdrillIntoPadstackHash(size_t &aHash, const PADSTACK &aPadstack)
static const std::map< wxString, surfaceFinishType > surfaceFinishMap
Map KiCad surface finish strings to IPC-6012 surfaceFinishType enum.
static surfaceFinishType getSurfaceFinishType(const wxString &aFinish)
see class PGM_BASE
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
std::vector< FAB_LAYER_COLOR > dummy
const std::vector< FAB_LAYER_COLOR > & GetStandardColors(BOARD_STACKUP_ITEM_TYPE aType)
#define KEY_CORE
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
LINE_STYLE
Dashed line types.
! The properties of a padstack drill. Drill position is always the pad position (origin).
Definition padstack.h:266
PCB_LAYER_ID start
Definition padstack.h:269
PCB_LAYER_ID end
Definition padstack.h:270
std::optional< PAD_DRILL_POST_MACHINING_MODE > mode
Definition padstack.h:281
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
KIBIS_PIN * pin
std::vector< std::string > header
VECTOR2I center
int radius
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:85
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:103
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:100
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:94
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:101
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:90
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:105
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:89
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:87
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition typeinfo.h:104
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:99
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:84
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:95
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:97
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:93
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:102
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
VECTOR2< double > VECTOR2D
Definition vector2d.h:686