KiCad PCB EDA Suite
Loading...
Searching...
No Matches
odb_entity.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 * Author: SYSUEric <[email protected]>.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21
22#include <algorithm>
23#include <array>
24#include <map>
25#include <tuple>
26#include <unordered_set>
27#include <base_units.h>
28#include <optional>
29#include <board.h>
31#include <build_version.h>
32#include <callback_gal.h>
42#include <font/font.h>
43#include <footprint.h>
44#include <hash_eda.h>
46#include <pad.h>
47#include <padstack.h>
48#include <pcb_dimension.h>
49#include <pcb_shape.h>
50#include <pcb_text.h>
51#include <pcb_textbox.h>
52#include <pcb_track.h>
53#include <zone.h>
54#include <pcbnew_settings.h>
56#include <pgm_base.h>
57#include <progress_reporter.h>
59#include <wx_fstream_progress.h>
60
65
66#include <wx/log.h>
67#include <wx/numformatter.h>
68#include <wx/mstream.h>
69#include <wx/xml/xml.h>
70
71#include "odb_attribute.h"
72#include "odb_entity.h"
73#include "odb_defines.h"
74#include "odb_feature.h"
75#include "odb_util.h"
76#include "pcb_io_odbpp.h"
77#include <trace_helpers.h>
78
79
80namespace
81{
82enum class METADATA_SECTION
83{
84 HEADER,
85 REQUIREMENTS,
86 MANUFACTURING,
88};
89
90
91struct METADATA_FIELD
92{
93 const char* m_name;
94 const char* m_display;
95 METADATA_SECTION m_section;
96 bool m_distance = false;
97};
98
99
100constexpr std::array<METADATA_FIELD, 15> METADATA_FIELDS = { {
101 { "counter_bore", "Counter Bore", METADATA_SECTION::HEADER },
102 { "countersink", "Counter Sink", METADATA_SECTION::HEADER },
103 { "edge_connectors", "Edge Connectors", METADATA_SECTION::HEADER },
104 { "layer_count", "Layer Count", METADATA_SECTION::HEADER },
105 { "board_thickness", "Board Thickness", METADATA_SECTION::REQUIREMENTS, true },
106 { "bottom_legend_color", "Bottom Legend Color", METADATA_SECTION::REQUIREMENTS },
107 { "bottom_soldermask_color", "Bottom Soldermask Color", METADATA_SECTION::REQUIREMENTS },
108 { "legend_sides", "Legend Sides", METADATA_SECTION::REQUIREMENTS },
109 { "plated_edge", "Plated Edge", METADATA_SECTION::REQUIREMENTS },
110 { "plated_slots", "Plated Slots", METADATA_SECTION::REQUIREMENTS },
111 { "soldermask_sides", "Soldermask Sides", METADATA_SECTION::REQUIREMENTS },
112 { "top_legend_color", "Top Legend Color", METADATA_SECTION::REQUIREMENTS },
113 { "top_soldermask_color", "Top Soldermask Color", METADATA_SECTION::REQUIREMENTS },
114 { "surface_finish_pads", "Surface Finish Pads", METADATA_SECTION::MANUFACTURING },
115 { "pressfit_technology", "Pressfit Technology", METADATA_SECTION::ASSEMBLY },
116} };
117
118
119const BOARD_STACKUP_ITEM* StackupItemForLayer( const FAB_STACKUP& aStackup, const ODB_LAYER_NAME& aLayer )
120{
121 return aLayer.m_layer == UNDEFINED_LAYER ? aLayer.m_stackupItem : aStackup.ItemForLayer( aLayer.m_layer );
122}
123
124
125void WriteXmlFile( ODB_TREE_WRITER& aWriter, const char* aName, wxXmlDocument& aDocument, const wxString& aError )
126{
127 wxMemoryOutputStream xml;
128
129 if( !aDocument.Save( xml ) )
130 THROW_IO_ERROR( aError );
131
132 std::string output( xml.GetSize(), '\0' );
133 xml.CopyTo( output.data(), output.size() );
134 auto file = aWriter.CreateFileProxy( aName );
135 file.GetStream().write( output.data(), output.size() );
136}
137
138
142double CopperThicknessMm( int aThicknessIu )
143{
144 return static_cast<double>( aThicknessIu ) / pcbIUScale.mmToIU( 1.0 );
145}
146
147
152double CopperWeightOz( int aThicknessIu )
153{
154 return CopperThicknessMm( aThicknessIu ) / 0.035;
155}
156
157
165double CopperWeightValue( const ODB_FORMAT& aFormat, int aThicknessIu )
166{
167 if( aFormat.m_unitsStr == "MM" )
168 return CopperThicknessMm( aThicknessIu ) * 1000.0;
169
170 return CopperWeightOz( aThicknessIu );
171}
172} // namespace
173
174
176{
177 try
178 {
179 writer.CreateEntityDirectory( writer.GetRootPath(), GetEntityName() );
180 return true;
181 }
182 catch( const std::exception& e )
183 {
184 std::cerr << e.what() << std::endl;
185 return false;
186 }
187}
188
189
191 ODB_ENTITY_BASE( aBoard, aPlugin )
192{
193 m_info = { { wxS( ODB_JOB_NAME ), wxS( "job" ) },
194 { wxS( ODB_UNITS ), m_plugin->GetFormat().m_unitsStr },
195 { wxS( "ODB_VERSION_MAJOR" ), wxS( "8" ) },
196 { wxS( "ODB_VERSION_MINOR" ), wxS( "1" ) },
197 { wxS( "ODB_SOURCE" ), wxS( "KiCad EDA" ) },
198 { wxS( "CREATION_DATE" ), wxDateTime::Now().Format( "%Y%m%d.%H%M%S" ) },
199 { wxS( "SAVE_DATE" ), wxDateTime::Now().Format( "%Y%m%d.%H%M%S" ) },
200 { wxS( "SAVE_APP" ), wxString::Format( wxS( "KiCad EDA %s" ), GetBuildVersion() ) } };
201
202 if( !m_plugin->GetFormat().m_productModelName.IsEmpty() )
203 m_info.emplace_back( wxS( "PRODUCT_MODEL_NAME" ), m_plugin->GetFormat().m_productModelName );
204}
205
206
208{
209 auto fileproxy = writer.CreateFileProxy( "info" );
210
211 ODB_TEXT_WRITER twriter( fileproxy.GetStream() );
212
213 for( auto& info : m_info )
214 {
215 twriter.WriteEquationLine( info.first, info.second );
216 }
217
218 twriter.WriteEquationLine( "MAX_UID", static_cast<int>( m_plugin->MaxUid() ) );
219
220 GenerateUserAttrFile( writer );
221
222 if( m_plugin->GetFormat().m_boardMetadata )
223 GenerateMetadataFile( writer );
224
225 if( !m_board->GetVariantNames().empty() )
226 GenerateAttrListFile( writer );
227}
228
229
231{
232 auto attrlist = writer.CreateFileProxy( "attrlist" );
233 attrlist.GetStream() << "UNITS=" << m_plugin->GetFormat().m_unitsStr << '\n';
234
235 if( m_plugin->GetFormat().m_variantNames.m_listsFit )
236 attrlist.GetStream() << ".variant_list="
237 << m_plugin->GetFormat().m_variantNames.Join( m_board->GetVariantNames() ).ToStdString()
238 << '\n';
239}
240
241
243{
244 auto userAttrs = writer.CreateFileProxy( "userattr" );
245 std::ostream& attrs = userAttrs.GetStream();
246 attrs << "UNITS=" << m_plugin->GetFormat().m_unitsStr << '\n';
247 attrs << "TEXT {\nNAME=material\nENTITY=LAYER\nMIN_LEN=0\nMAX_LEN=64\nOPTIONS=\nGROUP=Product\n}\n";
248
249 for( const char* side : { "top", "bottom" } )
250 {
251 attrs << "OPTION {\nNAME=post_machining_" << side
252 << "\nENTITY=FEATURE\nOPTIONS=countersink;counterbore\nGROUP=Fabrication\n}\n";
253
254 for( const char* dimension : { "diameter", "depth" } )
255 {
256 attrs << "FLOAT {\nNAME=post_machining_" << side << '_' << dimension
257 << "\nENTITY=FEATURE\nMIN_VAL=0.0\nMAX_VAL=100000.0"
258 << "\nUNITS=MIL_MICRON\nGROUP=Fabrication\n}\n";
259 }
260
261 attrs << "FLOAT {\nNAME=post_machining_" << side
262 << "_angle\nENTITY=FEATURE\nMIN_VAL=0.0\nMAX_VAL=180.0\nGROUP=Fabrication\n}\n";
263 }
264}
265
266
268{
269 const ODB_FORMAT& format = m_plugin->GetFormat();
270 const BOARD_STACKUP& stackup = m_plugin->GetFabStackup().Stackup();
271 const FAB_DRILL_MODEL& drillModel = m_plugin->GetFabDrillModel();
272 std::map<std::string, wxString> values;
273
274 auto yesNo = []( bool aValue )
275 {
276 return aValue ? wxS( "yes" ) : wxS( "no" );
277 };
278 auto sides = []( bool aTop, bool aBottom, const wxString& aNone ) -> wxString
279 {
280 if( aTop )
281 return aBottom ? wxS( "Both" ) : wxS( "Top" );
282
283 if( aBottom )
284 return wxS( "Bottom" );
285
286 return aNone;
287 };
288
289 const FAB_DRILL_FACTS& drillFacts = drillModel.Facts();
290
291 values["counter_bore"] = yesNo( drillFacts.m_counterbore );
292 values["countersink"] = yesNo( drillFacts.m_countersink );
293 values["edge_connectors"] = yesNo( stackup.m_EdgeConnectorConstraints != BS_EDGE_CONNECTOR_NONE );
294 values["layer_count"] = wxString::Format( "%d", m_board->GetCopperLayerCount() );
295 values["board_thickness"] = ODB::Data2String( format, m_plugin->GetFabStackup().Thickness() );
296 values["plated_edge"] = yesNo( stackup.m_EdgePlating );
297 values["plated_slots"] = yesNo( drillFacts.m_platedSlots );
298 values["pressfit_technology"] = yesNo( drillFacts.m_pressfit );
299 values["soldermask_sides"] =
300 sides( m_board->IsLayerEnabled( F_Mask ), m_board->IsLayerEnabled( B_Mask ), wxS( "None" ) );
301
302 values["legend_sides"] = sides( FabLayerHasVisibleItems( *m_board, F_SilkS ),
303 FabLayerHasVisibleItems( *m_board, B_SilkS ), wxS( "none" ) );
304
305 auto addColor = [this, &values]( const char* aName, PCB_LAYER_ID aLayer )
306 {
307 wxString color = m_plugin->GetFabStackup().NamedColor( aLayer );
308
309 if( !color.IsEmpty() )
310 values[aName] = color;
311 };
312
313 addColor( "top_soldermask_color", F_Mask );
314 addColor( "bottom_soldermask_color", B_Mask );
315 addColor( "top_legend_color", F_SilkS );
316 addColor( "bottom_legend_color", B_SilkS );
317
318 wxString finish = stackup.m_FinishType;
319
320 if( IsPrmSpecified( finish ) && finish != wxS( "User defined" ) )
321 {
322 static const std::map<wxString, wxString> finishNames = {
323 { wxS( "HAL SnPb" ), wxS( "HASL" ) },
324 { wxS( "HAL lead-free" ), wxS( "Lead-Free HASL" ) },
325 { wxS( "Immersion silver" ), wxS( "Immersion Silver" ) },
326 { wxS( "Immersion tin" ), wxS( "White Tin" ) },
327 { wxS( "OSP" ), wxS( "OSP (Entek)" ) },
328 { wxS( "None" ), wxS( "Bare Cu" ) },
329 };
330
331 if( auto it = finishNames.find( finish ); it != finishNames.end() )
332 finish = it->second;
333
334 if( std::all_of( finish.begin(), finish.end(),
335 []( wxUniChar aChar )
336 {
337 return aChar >= 32 && aChar <= 126;
338 } ) )
339 values["surface_finish_pads"] = finish;
340 }
341
342 wxXmlDocument document;
343 wxXmlNode* root = new wxXmlNode( wxXML_ELEMENT_NODE, wxS( "metadata" ) );
344 document.SetRoot( root );
345
346 auto addElement = []( wxXmlNode* aParent, const wxString& aName, const wxString& aDescription )
347 {
348 wxXmlNode* node = new wxXmlNode( wxXML_ELEMENT_NODE, aName );
349
350 if( !aDescription.IsEmpty() )
351 node->AddAttribute( wxS( "description" ), aDescription );
352
353 aParent->AddChild( node );
354 return node;
355 };
356
357 constexpr std::array<std::pair<const char*, const char*>, 4> sectionInfo = { {
358 { "Header", "General Information" },
359 { "Requirements", "Board Requirements" },
360 { "Manufacturing", "Manufacturing Process" },
361 { "Assembly", "Assembly" },
362 } };
363 std::array<wxXmlNode*, sectionInfo.size()> sections{};
364 wxXmlNode* step = nullptr;
365
366 auto sectionNode = [&]( METADATA_SECTION aSection )
367 {
368 size_t index = static_cast<size_t>( aSection );
369
370 if( !sections[index] )
371 {
372 wxXmlNode* parent = root;
373
374 if( index >= static_cast<size_t>( METADATA_SECTION::MANUFACTURING ) )
375 {
376 if( !step )
377 {
378 wxXmlNode* steps = addElement( root, wxS( "Steps" ), wxS( "Steps" ) );
379 step = addElement( steps, wxS( "Step" ), wxString() );
380 step->AddAttribute( wxS( "name" ), wxString::FromUTF8( ODB_STEP_ENTITY::STEP_NAME ) );
381 }
382
383 parent = step;
384 }
385
386 sections[index] = addElement( parent, wxString::FromUTF8( sectionInfo[index].first ),
387 wxString::FromUTF8( sectionInfo[index].second ) );
388 }
389
390 return sections[index];
391 };
392
393 for( const METADATA_FIELD& field : METADATA_FIELDS )
394 {
395 auto value = values.find( field.m_name );
396
397 if( value == values.end() )
398 continue;
399
400 wxXmlNode* node = addElement( sectionNode( field.m_section ), wxS( "node" ), wxString() );
401 node->AddAttribute( wxS( "name" ), wxString::FromUTF8( field.m_name ) );
402 node->AddAttribute( wxS( "display" ), wxString::FromUTF8( field.m_display ) );
403 node->AddAttribute( wxS( "value" ), value->second );
404
405 if( field.m_distance )
406 node->AddAttribute( wxS( "units" ), wxString::FromUTF8( format.m_unitsStr ) );
407 }
408
409 WriteXmlFile( writer, "metadata.xml", document, _( "Failed to generate ODB++ metadata." ) );
410}
411
412
413void ODB_MATRIX_ENTITY::AddStep( const wxString& aStepName )
414{
415 m_matrixSteps.emplace( ODB::GenLegalEntityName( aStepName ),
416 std::make_pair( m_col++, m_plugin->NewUid() ) );
417}
418
419
421{
422 AddStep( wxString::FromUTF8( ODB_STEP_ENTITY::STEP_NAME ) );
423
425}
426
427
429{
430 const BOARD_STACKUP& stackup = m_plugin->GetFabStackup().Stackup();
431
432 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
433 std::set<PCB_LAYER_ID> added_layers;
434
435 for( const FOOTPRINT* fp : m_board->Footprints() )
436 ( fp->IsFlipped() ? m_hasBotComp : m_hasTopComp ) = true;
437
439
440 for( int i = 0; i < stackup.GetCount(); i++ )
441 {
442 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
443
444 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
445 {
446 wxString ly_name = stackup_item->GetLayerName();
447
448 if( ly_name.IsEmpty() )
449 {
450 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
451 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
452
453 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
454 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
455 }
456
457 MATRIX_LAYER matrix( m_row++, ly_name );
458 matrix.m_info.m_stackupItem = stackup_item;
459 matrix.m_info.m_sublayer = sublayer_id;
460
461 if( stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
462 {
463 matrix.m_dielectricName = stackup_item->GetMaterial( sublayer_id );
464
465 if( stackup_item->GetTypeName() == KEY_CORE )
466 matrix.m_diType.emplace( ODB_DIELECTRIC_TYPE::CORE );
467 else
468 matrix.m_diType.emplace( ODB_DIELECTRIC_TYPE::PREPREG );
469
473 m_matrixLayers.push_back( matrix );
474
475 continue;
476 }
477 else
478 {
479 added_layers.insert( stackup_item->GetBrdLayerId() );
480 AddMatrixLayerField( matrix, stackup_item->GetBrdLayerId() );
481 }
482 }
483 }
484
486
488
489 for( PCB_LAYER_ID layer : m_board->GetEnabledLayers().Seq() )
490 {
491 if( added_layers.find( layer ) != added_layers.end() )
492 continue;
493
494 MATRIX_LAYER matrix( m_row++, m_board->GetLayerName( layer ) );
495 added_layers.insert( layer );
496 AddMatrixLayerField( matrix, layer );
497 }
498
500
502
503 const ODB_FORMAT& format = m_plugin->GetFormat();
504 bool filtered = format.m_sections.has_value() || !format.m_layerOverrides.empty();
505 auto isCopper = []( const MATRIX_LAYER& aLayer )
506 {
507 return aLayer.m_type == ODB_TYPE::SIGNAL || aLayer.m_type == ODB_TYPE::POWER_GROUND
508 || aLayer.m_type == ODB_TYPE::MIXED;
509 };
510 std::map<const BOARD_STACKUP_ITEM*, std::pair<PCB_LAYER_ID, PCB_LAYER_ID>> adjacentCopper;
511
512 if( filtered )
513 {
514 for( size_t index = 0; index < m_matrixLayers.size(); ++index )
515 {
516 const MATRIX_LAYER& layer = m_matrixLayers[index];
517
519 continue;
520
521 auto& [top, bottom] = adjacentCopper[layer.m_info.m_stackupItem];
523 bottom = UNDEFINED_LAYER;
524
525 for( size_t probe = index; probe-- > 0; )
526 {
527 const MATRIX_LAYER& neighbor = m_matrixLayers[probe];
528
529 if( neighbor.m_info.m_stackupItem == layer.m_info.m_stackupItem )
530 continue;
531
532 if( isCopper( neighbor ) )
533 top = neighbor.m_info.m_layer;
534
535 break;
536 }
537
538 for( size_t probe = index + 1; probe < m_matrixLayers.size(); ++probe )
539 {
540 const MATRIX_LAYER& neighbor = m_matrixLayers[probe];
541
542 if( neighbor.m_info.m_stackupItem == layer.m_info.m_stackupItem )
543 continue;
544
545 if( isCopper( neighbor ) )
546 bottom = neighbor.m_info.m_layer;
547
548 break;
549 }
550 }
551 }
552
553 if( filtered )
554 {
555 auto sectionFor = []( const MATRIX_LAYER& aLayer )
556 {
557 using SECTION = FAB::SECTION;
558
559 if( aLayer.m_info.m_role == ODB_LAYER_ROLE::COMPONENT )
560 return SECTION::COMPONENTS;
561
562 if( aLayer.m_info.m_role == ODB_LAYER_ROLE::DRILL || aLayer.m_info.m_role == ODB_LAYER_ROLE::BACKDRILL
563 || aLayer.m_info.m_role == ODB_LAYER_ROLE::ROUT )
564 {
565 return SECTION::DRILL_ROUT;
566 }
567
568 if( aLayer.m_info.m_role == ODB_LAYER_ROLE::VIA_PROTECTION )
569 return SECTION::MISC_FAB;
570
571 switch( aLayer.m_type )
572 {
573 case ODB_TYPE::SIGNAL:
575 case ODB_TYPE::MIXED:
576 return IsExternalCopperLayer( aLayer.m_info.m_layer ) ? SECTION::OUTER_COPPER : SECTION::INNER_COPPER;
577 case ODB_TYPE::DIELECTRIC: return SECTION::DIELECTRIC;
578 case ODB_TYPE::SOLDER_MASK: return SECTION::SOLDERMASK;
579 case ODB_TYPE::SOLDER_PASTE: return SECTION::SOLDERPASTE;
580 case ODB_TYPE::SILK_SCREEN: return SECTION::SILKSCREEN;
581 default: return SECTION::DOCUMENTATION;
582 }
583 };
584
585 std::set<PCB_LAYER_ID> excluded;
586
587 for( const ODB_LAYER_OVERRIDE& override : format.m_layerOverrides )
588 {
589 if( !override.m_include )
590 excluded.insert( override.m_layer );
591 }
592
593 std::erase_if( m_matrixLayers,
594 [&]( const MATRIX_LAYER& aLayer )
595 {
596 return !format.Includes( sectionFor( aLayer ) )
597 || excluded.contains( aLayer.m_info.m_layer );
598 } );
599
600 // Overrides are checked against the final automatic names
602
603 std::set<wxString> usedNames;
604
605 for( const MATRIX_LAYER& layer : m_matrixLayers )
606 usedNames.insert( layer.m_layerName );
607
608 for( MATRIX_LAYER& layer : m_matrixLayers )
609 {
610 for( const ODB_LAYER_OVERRIDE& override : format.m_layerOverrides )
611 {
612 if( layer.m_info.m_role != ODB_LAYER_ROLE::BOARD_LAYER || layer.m_info.m_layer != override.m_layer )
613 {
614 continue;
615 }
616
617 if( !override.m_odbName.IsEmpty() )
618 {
619 wxString name = ODB::GenLegalEntityName( override.m_odbName );
620
621 if( name.IsEmpty() || ( name != layer.m_layerName && usedNames.contains( name ) ) )
622 {
623 m_plugin->Report( _( "ODB++ layer name override is empty or collides; using automatic name." ),
625 }
626 else
627 {
628 usedNames.erase( layer.m_layerName );
629 layer.m_layerName = name;
630 usedNames.insert( name );
631 }
632 }
633
634 if( !override.m_odbType.IsEmpty() )
635 {
636 std::string type = override.m_odbType.ToStdString();
637 std::vector<ODB_TYPE> allowed = ODB::OverridableLayerTypes( layer.m_info.m_layer );
638 auto it = std::find_if( allowed.begin(), allowed.end(),
639 [&]( ODB_TYPE aType )
640 {
641 return ODB::Enum2String( aType ) == type;
642 } );
643
644 if( it != allowed.end() )
645 layer.m_type = *it;
646 else if( ODB::Enum2String( layer.m_type ) != type )
647 m_plugin->Report( _( "Invalid ODB++ layer type override; using automatic type." ),
649 }
650 }
651 }
652 }
653
655
656 std::map<PCB_LAYER_ID, wxString> namesByLayer;
657
658 for( const MATRIX_LAYER& layer : m_matrixLayers )
659 {
660 if( layer.m_info.m_role == ODB_LAYER_ROLE::BOARD_LAYER && layer.m_info.m_layer != UNDEFINED_LAYER )
661 {
662 namesByLayer[layer.m_info.m_layer] = layer.m_layerName;
663 }
664 }
665
666 std::erase_if( m_matrixLayers,
667 [&]( MATRIX_LAYER& aLayer )
668 {
669 if( !aLayer.m_span )
670 return false;
671
672 PCB_LAYER_ID start = F_Cu;
674
675 if( aLayer.m_info.m_drillSpan )
676 {
677 start = aLayer.m_info.m_drillSpan->TopLayer();
678 end = aLayer.m_info.m_drillSpan->BottomLayer();
679 }
680 else if( aLayer.m_info.m_auxKey )
681 {
682 start = std::get<1>( *aLayer.m_info.m_auxKey );
683 end = std::get<2>( *aLayer.m_info.m_auxKey );
684 }
685
686 if( namesByLayer.contains( start ) && namesByLayer.contains( end ) )
687 {
688 aLayer.m_span = { namesByLayer.at( start ), namesByLayer.at( end ) };
689 return false;
690 }
691
692 wxCHECK_MSG( filtered, false, wxS( "ODB++ span layer has no matrix row" ) );
693
694 bool through =
696 || ( aLayer.m_info.m_role == ODB_LAYER_ROLE::DRILL && start == F_Cu && end == B_Cu );
697
698 if( through )
699 {
700 aLayer.m_span = { wxEmptyString, wxEmptyString };
701 return false;
702 }
703
704 m_plugin->Report( _( "ODB++ partial drill layer omitted because a span layer is excluded." ),
706 return true;
707 } );
708
709 if( filtered )
710 {
711 for( size_t index = 0; index < m_matrixLayers.size(); ++index )
712 m_matrixLayers[index].m_rowNumber = static_cast<uint32_t>( index + 1 );
713 }
714
715 std::map<PCB_LAYER_ID, uint32_t> copperIds;
716
717 for( MATRIX_LAYER& layer : m_matrixLayers )
718 {
719 layer.m_uid = m_plugin->NewUid();
720
721 if( isCopper( layer ) )
722 {
723 copperIds[layer.m_info.m_layer] = layer.m_uid;
724 }
725 }
726
727 for( size_t i = 0; i < m_matrixLayers.size(); ++i )
728 {
729 MATRIX_LAYER& layer = m_matrixLayers[i];
730 PCB_LAYER_ID brdLayer = layer.m_info.m_layer;
731
732 // A misc layer retyped as mask, paste or silk has no physical side to reference
733 if( layer.m_context == ODB_CONTEXT::BOARD
735 || layer.m_type == ODB_TYPE::SILK_SCREEN ) )
736 {
737 PCB_LAYER_ID copper = m_board->IsFrontLayer( brdLayer ) ? F_Cu : B_Cu;
738
739 if( auto it = copperIds.find( copper ); it != copperIds.end() )
740 layer.m_ref = it->second;
741 }
742
744 continue;
745
746 if( filtered )
747 {
748 auto [top, bottom] = adjacentCopper.at( layer.m_info.m_stackupItem );
749
750 if( auto it = copperIds.find( top ); it != copperIds.end() )
751 layer.m_cuTop = it->second;
752
753 if( auto it = copperIds.find( bottom ); it != copperIds.end() )
754 layer.m_cuBottom = it->second;
755
756 continue;
757 }
758
759 for( size_t top = i; top-- > 0; )
760 {
761 if( isCopper( m_matrixLayers[top] ) )
762 {
763 layer.m_cuTop = m_matrixLayers[top].m_uid;
764 break;
765 }
766 }
767
768 for( size_t bottom = i + 1; bottom < m_matrixLayers.size(); ++bottom )
769 {
770 if( isCopper( m_matrixLayers[bottom] ) )
771 {
772 layer.m_cuBottom = m_matrixLayers[bottom].m_uid;
773 break;
774 }
775 }
776 }
777
778 std::vector<ODB_LAYER_NAME>& names = m_plugin->GetLayerNameList();
779 names.clear();
780 names.reserve( m_matrixLayers.size() );
781
782 for( const MATRIX_LAYER& layer : m_matrixLayers )
783 {
784 names.push_back( layer.m_info );
785 names.back().m_name = layer.m_layerName;
786 }
787}
788
789
791{
792 aMLayer.m_info.m_layer = aLayer;
795 switch( aLayer )
796 {
797 case F_Paste:
798 case B_Paste:
800 break;
801
802 case F_SilkS:
803 case B_SilkS:
805 break;
806
807 case F_Mask:
808 case B_Mask:
810 break;
811
812 case B_CrtYd:
813 case F_CrtYd:
814 case B_Fab:
815 case F_Fab:
816 case F_Adhes:
817 case B_Adhes:
818 case Dwgs_User:
819 case Cmts_User:
820 case Eco1_User:
821 case Eco2_User:
822 case Margin:
823 case User_1:
824 case User_2:
825 case User_3:
826 case User_4:
827 case User_5:
828 case User_6:
829 case User_7:
830 case User_8:
831 case User_9:
832 case User_10:
833 case User_11:
834 case User_12:
835 case User_13:
836 case User_14:
837 case User_15:
838 case User_16:
839 case User_17:
840 case User_18:
841 case User_19:
842 case User_20:
843 case User_21:
844 case User_22:
845 case User_23:
846 case User_24:
847 case User_25:
848 case User_26:
849 case User_27:
850 case User_28:
851 case User_29:
852 case User_30:
853 case User_31:
854 case User_32:
855 case User_33:
856 case User_34:
857 case User_35:
858 case User_36:
859 case User_37:
860 case User_38:
861 case User_39:
862 case User_40:
863 case User_41:
864 case User_42:
865 case User_43:
866 case User_44:
867 case User_45:
869 aMLayer.m_type = ODB_TYPE::DOCUMENT;
870 break;
871
872 default:
873 if( IsCopperLayer( aLayer ) )
874 {
875 switch( m_board->GetLayerType( aLayer ) )
876 {
877 case LT_POWER: aMLayer.m_type = ODB_TYPE::POWER_GROUND; break;
878 case LT_MIXED: aMLayer.m_type = ODB_TYPE::MIXED; break;
879 default: aMLayer.m_type = ODB_TYPE::SIGNAL; break;
880 }
881 }
882 else
883 {
884 // Do not handle other layers :
885 aMLayer.m_type = ODB_TYPE::UNDEFINED;
886 m_row--;
887 }
888
889 break;
890 }
891
892 if( aMLayer.m_type != ODB_TYPE::UNDEFINED )
893 {
894 m_matrixLayers.push_back( aMLayer );
895 }
896}
897
898
900{
901 std::map<DRILL_SPAN, std::vector<BOARD_ITEM*>>& drill_layers = m_plugin->GetDrillLayerItemsMap();
902
903 std::map<std::pair<PCB_LAYER_ID, PCB_LAYER_ID>, std::vector<BOARD_ITEM*>>& slot_holes =
904 m_plugin->GetSlotHolesMap();
905
906 drill_layers.clear();
907 slot_holes.clear();
908
909 bool has_pth_layer = false;
910 bool has_npth_layer = false;
911
912 for( FOOTPRINT* fp : m_board->Footprints() )
913 {
914 for( PAD* pad : fp->Pads() )
915 {
916 has_pth_layer |= pad->GetAttribute() == PAD_ATTRIB::PTH;
917 has_npth_layer |= pad->GetAttribute() == PAD_ATTRIB::NPTH;
918 }
919 }
920
921 const FAB_DRILL_MODEL& model = m_plugin->GetFabDrillModel();
922 std::map<DRILL_SPAN, std::unordered_set<BOARD_ITEM*>> seen;
923
924 for( const FAB_DRILL_LAYER& layer : model.Layers() )
925 {
926 auto addOperation = [&]( const DRILL_OPERATION& aOperation, bool aSlot )
927 {
928 BOARD_ITEM* item = aOperation.m_SourceItem;
929 // Start is the drilling side, so blind vias reaching only B.Cu start there
930 bool fromBack = !layer.m_Span.m_IsBackdrill && item->Type() == PCB_VIA_T
931 && aOperation.m_BottomLayer == B_Cu && aOperation.m_TopLayer != F_Cu;
932 DRILL_SPAN span( fromBack ? layer.m_Span.BottomLayer() : layer.m_Span.DrillStartLayer(),
933 fromBack ? layer.m_Span.TopLayer() : layer.m_Span.DrillEndLayer(),
934 layer.m_Span.m_IsBackdrill, aOperation.m_NotPlated );
935
936 if( aSlot && !span.m_IsBackdrill )
937 {
938 slot_holes[span.Pair()].push_back( item );
939 return;
940 }
941
942 if( seen[span].insert( item ).second )
943 drill_layers[span].push_back( item );
944 };
945
946 for( const DRILL_OPERATION& op : layer.m_Holes )
947 {
948 has_pth_layer |= !op.m_NotPlated && !layer.m_Span.m_IsBackdrill;
949 addOperation( op, false );
950 }
951
952 for( const DRILL_OPERATION& op : layer.m_Slots )
953 addOperation( op, true );
954 }
955
956 if( has_npth_layer )
957 {
958 DRILL_SPAN npthSpan( F_Cu, B_Cu, false, true );
959 drill_layers[npthSpan];
960 }
961
962 if( has_pth_layer )
963 {
964 DRILL_SPAN platedSpan( F_Cu, B_Cu, false, false );
965 drill_layers[platedSpan];
966 }
967
968 int backdrillIndex = 1;
969
970 auto assignName = [&]( const DRILL_SPAN& aSpan )
971 {
972 wxString name;
973
974 if( aSpan.m_IsBackdrill )
975 {
976 name.Printf( wxT( "drill%d" ), backdrillIndex++ );
977 }
978 else
979 {
980 wxString platedLabel = aSpan.m_IsNonPlatedFile ? wxT( "non-plated" ) : wxT( "plated" );
981 name.Printf( wxT( "drill_%s_%s-%s" ), platedLabel, m_board->GetLayerName( aSpan.TopLayer() ),
982 m_board->GetLayerName( aSpan.BottomLayer() ) );
983 }
984
986 };
987
988 auto InitDrillMatrix = [&]( const DRILL_SPAN& aSpan )
989 {
990 wxString dLayerName = assignName( aSpan );
991 MATRIX_LAYER matrix( m_row++, dLayerName );
992
993 matrix.m_type = ODB_TYPE::DRILL;
994 matrix.m_info.m_role = aSpan.m_IsBackdrill ? ODB_LAYER_ROLE::BACKDRILL : ODB_LAYER_ROLE::DRILL;
995 matrix.m_info.m_drillSpan = aSpan;
998 matrix.m_span.emplace(
999 std::make_pair( ODB::GenLegalEntityName( m_board->GetLayerName( aSpan.TopLayer() ) ),
1000 ODB::GenLegalEntityName( m_board->GetLayerName( aSpan.BottomLayer() ) ) ) );
1001
1002 if( aSpan.m_IsBackdrill )
1003 matrix.m_addType.emplace( ODB_SUBTYPE::BACKDRILL );
1004
1005 m_matrixLayers.push_back( matrix );
1006 };
1007
1008 for( const auto& entry : drill_layers )
1009 InitDrillMatrix( entry.first );
1010}
1011
1012
1014{
1015 MATRIX_LAYER matrix( m_row++, wxS( "rout" ) );
1016 matrix.m_type = ODB_TYPE::ROUT;
1018 matrix.m_span.emplace( ODB::GenLegalEntityName( m_board->GetLayerName( F_Cu ) ),
1019 ODB::GenLegalEntityName( m_board->GetLayerName( B_Cu ) ) );
1020 m_matrixLayers.push_back( matrix );
1021}
1022
1023
1025{
1026 // A component layer must have a components file, so a side with no components has no layer
1027 if( aCompSide == F_Cu ? !m_hasTopComp : !m_hasBotComp )
1028 return;
1029
1030 MATRIX_LAYER matrix( m_row++, aCompSide == F_Cu ? "COMP_+_TOP" : "COMP_+_BOT" );
1031 matrix.m_type = ODB_TYPE::COMPONENT;
1033 matrix.m_info.m_componentSide = aCompSide;
1035 m_matrixLayers.push_back( matrix );
1036}
1037
1039{
1040 auto& auxilliary_layers = m_plugin->GetAuxilliaryLayerItemsMap();
1041 auxilliary_layers.clear();
1042
1043 const FAB_DRILL_MODEL& model = m_plugin->GetFabDrillModel();
1044
1045 for( BOARD_ITEM* item : m_board->Tracks() )
1046 {
1047 if( item->Type() == PCB_VIA_T )
1048 {
1049 PCB_VIA* via = static_cast<PCB_VIA*>( item );
1050 const DRILL_OPERATION* op = model.Find(
1051 DRILL_SPAN( via->TopLayer(), via->BottomLayer(), false, false ), via );
1052
1053 if( !op )
1054 continue;
1055
1056 if( op->m_Filled )
1057 {
1058 auxilliary_layers[std::make_tuple( ODB_AUX_LAYER_TYPE::FILLING, via->TopLayer(),
1059 via->BottomLayer() )]
1060 .push_back( via );
1061 }
1062
1063 if( op->m_Capped )
1064 {
1065 auxilliary_layers[std::make_tuple( ODB_AUX_LAYER_TYPE::CAPPING, via->TopLayer(),
1066 via->BottomLayer() )]
1067 .push_back( via );
1068 }
1069
1070 for( PCB_LAYER_ID layer : { via->TopLayer(), via->BottomLayer() } )
1071 {
1072 bool top = layer == op->m_TopLayer;
1073
1074 if( top ? op->m_TopPlugged : op->m_BottomPlugged )
1075 {
1076 auxilliary_layers[std::make_tuple( ODB_AUX_LAYER_TYPE::PLUGGING, layer,
1078 .push_back( via );
1079 }
1080
1081 if( top ? op->m_TopCovered : op->m_BottomCovered )
1082 {
1083 auxilliary_layers[std::make_tuple( ODB_AUX_LAYER_TYPE::COVERING, layer,
1085 .push_back( via );
1086 }
1087
1088 if( top ? op->m_TopTented : op->m_BottomTented )
1089 {
1090 auxilliary_layers[std::make_tuple( ODB_AUX_LAYER_TYPE::TENTING, layer,
1092 .push_back( via );
1093 }
1094 }
1095 }
1096 }
1097
1098 auto InitAuxMatrix = [&]( ODB_AUX_LAYER_KEY aLayerPair )
1099 {
1100 wxString featureName = "";
1101 switch( std::get<0>( aLayerPair ) )
1102 {
1103 case ODB_AUX_LAYER_TYPE::TENTING: featureName = "tenting"; break;
1104 case ODB_AUX_LAYER_TYPE::COVERING: featureName = "covering"; break;
1105 case ODB_AUX_LAYER_TYPE::PLUGGING: featureName = "plugging"; break;
1106 case ODB_AUX_LAYER_TYPE::FILLING: featureName = "filling"; break;
1107 case ODB_AUX_LAYER_TYPE::CAPPING: featureName = "capping"; break;
1108 default: return;
1109 }
1110
1111 wxString dLayerName;
1112
1113 if( std::get<2>( aLayerPair ) != PCB_LAYER_ID::UNDEFINED_LAYER )
1114 {
1115 dLayerName = wxString::Format( "%s_%s-%s", featureName,
1116 m_board->GetLayerName( std::get<1>( aLayerPair ) ),
1117 m_board->GetLayerName( std::get<2>( aLayerPair ) ) );
1118 }
1119 else
1120 {
1121 if( m_board->IsFrontLayer( std::get<1>( aLayerPair ) ) )
1122 dLayerName = wxString::Format( "%s_front", featureName );
1123 else if( m_board->IsBackLayer( std::get<1>( aLayerPair ) ) )
1124 dLayerName = wxString::Format( "%s_back", featureName );
1125 else
1126 return;
1127 }
1128 MATRIX_LAYER matrix( m_row++, dLayerName );
1129
1130 matrix.m_type = ODB_TYPE::DOCUMENT;
1132 matrix.m_info.m_auxType = std::get<0>( aLayerPair );
1133 matrix.m_info.m_auxKey = aLayerPair;
1134 matrix.m_info.m_layer = std::get<2>( aLayerPair ) == PCB_LAYER_ID::UNDEFINED_LAYER
1135 ? std::get<1>( aLayerPair )
1139
1140 if( std::get<2>( aLayerPair ) != PCB_LAYER_ID::UNDEFINED_LAYER )
1141 {
1142 matrix.m_span.emplace( std::make_pair(
1143 ODB::GenLegalEntityName( m_board->GetLayerName( std::get<1>( aLayerPair ) ) ),
1145 m_board->GetLayerName( std::get<2>( aLayerPair ) ) ) ) );
1146 }
1147
1148 m_matrixLayers.push_back( matrix );
1149 };
1150
1151 for( const auto& [layer_pair, vec] : auxilliary_layers )
1152 {
1153 InitAuxMatrix( layer_pair );
1154 }
1155}
1156
1157
1159{
1160 std::vector<wxString> bases;
1161 bases.reserve( m_matrixLayers.size() );
1162
1163 for( const MATRIX_LAYER& layer : m_matrixLayers )
1164 bases.push_back( layer.m_layerName );
1165
1166 std::vector<wxString> names = ODB::UniqueNames( bases, {}, 64 );
1167
1168 for( size_t index = 0; index < names.size(); ++index )
1169 m_matrixLayers[index].m_layerName = std::move( names[index] );
1170}
1171
1172
1174{
1175 auto fileproxy = writer.CreateFileProxy( "matrix" );
1176
1177 ODB_TEXT_WRITER twriter( fileproxy.GetStream() );
1178
1179 for( const auto& [step_name, entry] : m_matrixSteps )
1180 {
1181 const auto array_proxy = twriter.MakeArrayProxy( "STEP" );
1182 twriter.WriteEquationLine( "COL", entry.first );
1183 twriter.WriteEquationLine( "ID", static_cast<int>( entry.second ) );
1184 twriter.WriteEquationLine( "NAME", step_name );
1185 }
1186
1187 for( const MATRIX_LAYER& layer : m_matrixLayers )
1188 {
1189 const auto array_proxy = twriter.MakeArrayProxy( "LAYER" );
1190 twriter.WriteEquationLine( "ROW", layer.m_rowNumber );
1191 twriter.WriteEquationLine( "ID", static_cast<int>( layer.m_uid ) );
1192 twriter.write_line_enum( "CONTEXT", layer.m_context );
1193 twriter.write_line_enum( "TYPE", layer.m_type );
1194
1195 if( layer.m_addType.has_value() )
1196 {
1197 twriter.write_line_enum( "ADD_TYPE", layer.m_addType.value() );
1198 }
1199
1200 twriter.WriteEquationLine( "NAME", layer.m_layerName );
1201 twriter.WriteEquationLine( "OLD_NAME", wxEmptyString );
1202 twriter.write_line_enum( "POLARITY", layer.m_polarity );
1203
1204 if( layer.m_diType.has_value() )
1205 {
1206 twriter.write_line_enum( "DIELECTRIC_TYPE", layer.m_diType.value() );
1207
1208 if( !layer.m_dielectricName.IsEmpty() )
1209 twriter.WriteEquationLine( "DIELECTRIC_NAME", layer.m_dielectricName );
1210
1211 if( layer.m_cuTop )
1212 twriter.WriteEquationLine( "CU_TOP", static_cast<int>( *layer.m_cuTop ) );
1213
1214 if( layer.m_cuBottom )
1215 twriter.WriteEquationLine( "CU_BOTTOM", static_cast<int>( *layer.m_cuBottom ) );
1216 }
1217
1218 if( layer.m_ref )
1219 twriter.WriteEquationLine( "REF", static_cast<int>( *layer.m_ref ) );
1220
1221 if( layer.m_span.has_value() )
1222 {
1223 twriter.WriteEquationLine( "START_NAME", layer.m_span->first );
1224 twriter.WriteEquationLine( "END_NAME", layer.m_span->second );
1225 }
1226
1227 twriter.WriteEquationLine( "COLOR", "0" );
1228 }
1229
1230 if( m_plugin->GetFormat().Includes( FAB::SECTION::STACKUP ) )
1231 GenerateStackupFile( writer );
1232}
1233
1234
1236{
1237 const ODB_FORMAT& format = m_plugin->GetFormat();
1238 const FAB_STACKUP& stackup = m_plugin->GetFabStackup();
1239 wxString units = wxString::FromUTF8( format.m_unitsStr.c_str() );
1240 wxString stepName = wxString::FromUTF8( ODB_STEP_ENTITY::STEP_NAME );
1241 wxXmlDocument document;
1242
1243 auto add = []( wxXmlNode* aParent, const wxString& aName )
1244 {
1245 wxXmlNode* node = new wxXmlNode( wxXML_ELEMENT_NODE, aName );
1246
1247 if( aParent )
1248 aParent->AddChild( node );
1249
1250 return node;
1251 };
1252
1253 wxXmlNode* root = add( nullptr, wxS( "StackupFile" ) );
1254 document.SetRoot( root );
1255 root->AddAttribute( wxS( "Version" ), wxS( "8.1" ) );
1256 root->AddAttribute( wxS( "DefaultUnits" ), units );
1257
1258 wxXmlNode* eda = add( root, wxS( "EdaData" ) );
1259 eda->AddAttribute( wxS( "CompanyName" ), m_board->GetTitleBlock().GetCompany() );
1260 wxXmlNode* specs = add( eda, wxS( "Specs" ) );
1261 wxXmlNode* spec = add( specs, wxS( "Spec" ) );
1262 spec->AddAttribute( wxS( "SpecName" ), stepName );
1263 wxXmlNode* stackupNode = add( eda, wxS( "Stackup" ) );
1264 stackupNode->AddAttribute( wxS( "StackupName" ), stepName );
1265 stackupNode->AddAttribute( wxS( "StackupThickness" ), ODB::Data2String( format, stackup.Thickness() ) );
1266 stackupNode->AddAttribute( wxS( "Units" ), units );
1267 bool maskThickness = false;
1268
1269 for( PCB_LAYER_ID layer : { F_Mask, B_Mask } )
1270 {
1271 const BOARD_STACKUP_ITEM* item = stackup.ItemForLayer( layer );
1272 maskThickness |= item && item->IsEnabled() && item->GetThickness() > 0;
1273 }
1274
1275 stackupNode->AddAttribute( wxS( "WhereMeasured" ), maskThickness ? wxS( "MASK" ) : wxS( "METAL" ) );
1276 wxXmlNode* group = add( stackupNode, wxS( "Group" ) );
1277 group->AddAttribute( wxS( "GroupName" ), stepName );
1278
1279 for( const MATRIX_LAYER& row : m_matrixLayers )
1280 {
1281 if( row.m_context != ODB_CONTEXT::BOARD )
1282 continue;
1283
1284 wxXmlNode* layer = add( group, wxS( "Layer" ) );
1285 layer->AddAttribute( wxS( "LayerName" ), row.m_layerName );
1286 layer->AddAttribute( wxS( "LayerType" ),
1287 wxString::FromUTF8( ODB::EnumStringMap<ODB_TYPE>::GetMap().at( row.m_type ).c_str() ) );
1288
1289 if( row.m_type == ODB_TYPE::DIELECTRIC )
1290 layer->AddAttribute( wxS( "Side" ), wxS( "INNER" ) );
1291 else if( m_board->IsFrontLayer( row.m_info.m_layer ) )
1292 layer->AddAttribute( wxS( "Side" ), wxS( "TOP" ) );
1293 else if( m_board->IsBackLayer( row.m_info.m_layer ) )
1294 layer->AddAttribute( wxS( "Side" ), wxS( "BOTTOM" ) );
1295 else if( IsCopperLayer( row.m_info.m_layer ) )
1296 layer->AddAttribute( wxS( "Side" ), wxS( "INNER" ) );
1297
1298 if( row.m_span && ( row.m_type == ODB_TYPE::DRILL || row.m_type == ODB_TYPE::ROUT ) )
1299 {
1300 if( !row.m_span->first.IsEmpty() )
1301 layer->AddAttribute( wxS( "MechStartLayerName" ), row.m_span->first );
1302
1303 if( !row.m_span->second.IsEmpty() )
1304 layer->AddAttribute( wxS( "MechEndLayerName" ), row.m_span->second );
1305 }
1306
1307 const BOARD_STACKUP_ITEM* item = StackupItemForLayer( stackup, row.m_info );
1308 int sublayer = row.m_info.m_sublayer;
1309
1310 if( !item || !item->IsThicknessEditable() || item->GetThickness( sublayer ) <= 0 )
1311 continue;
1312
1313 wxXmlNode* material = add( spec, wxS( "Material" ) );
1314 material->AddAttribute( wxS( "MaterialName" ), row.m_layerName );
1315
1316 if( item->GetType() == BS_ITEM_TYPE_COPPER )
1317 {
1318 wxXmlNode* conductor = add( material, wxS( "Conductor" ) );
1319 conductor->AddAttribute( wxS( "ConductorType" ), wxS( "COPPER" ) );
1320 conductor->AddAttribute( wxS( "CopperWeight_oz_ft2" ),
1321 ODB::Double2String( format, CopperWeightOz( item->GetThickness( sublayer ) ) ) );
1322 }
1323 else
1324 {
1325 wxXmlNode* dielectric = add( material, wxS( "Dielectric" ) );
1326
1327 if( item->GetType() == BS_ITEM_TYPE_SOLDERMASK )
1328 {
1329 dielectric->AddAttribute( wxS( "DielectricType" ), wxS( "OTHER" ) );
1330 dielectric->AddAttribute( wxS( "OtherSubType" ), wxS( "SOLDER_MASK" ) );
1331 }
1332 else
1333 {
1334 dielectric->AddAttribute( wxS( "DielectricType" ),
1335 row.m_diType == ODB_DIELECTRIC_TYPE::CORE ? wxS( "CORE" )
1336 : row.m_diType == ODB_DIELECTRIC_TYPE::PREPREG ? wxS( "PREPREG" )
1337 : wxS( "UNDEFINED" ) );
1338 }
1339
1340 wxString reference = item->GetMaterial( sublayer );
1341
1342 if( IsPrmSpecified( reference ) )
1343 dielectric->AddAttribute( wxS( "MaterialReference" ), reference );
1344
1345 bool hasDk = item->HasEpsilonRValue() && item->GetEpsilonR( sublayer ) > 0.0;
1346 bool hasDf = item->HasLossTangentValue() && item->GetLossTangent( sublayer ) > 0.0;
1347
1348 if( hasDk || hasDf )
1349 {
1350 wxXmlNode* properties = add( dielectric, wxS( "Properties" ) );
1351 properties->AddAttribute( wxS( "PropertyName" ), wxS( "default" ) );
1352 wxXmlNode* property = add( properties, wxS( "Property" ) );
1353 double frequency = item->GetSpecFreq( sublayer );
1354
1355 if( frequency > 0.0 )
1356 {
1357 property->AddAttribute( wxS( "FrequencyVal" ), ODB::Double2String( format, frequency / 1e9 ) );
1358 property->AddAttribute( wxS( "Units" ), wxS( "GHz" ) );
1359 }
1360
1361 if( hasDk )
1362 {
1363 property->AddAttribute( wxS( "DielectricConstant_Dk" ),
1364 ODB::Double2String( format, item->GetEpsilonR( sublayer ) ) );
1365 }
1366
1367 if( hasDf )
1368 {
1369 property->AddAttribute( wxS( "LossTangent_Df" ),
1370 ODB::Double2String( format, item->GetLossTangent( sublayer ) ) );
1371 }
1372 }
1373 }
1374
1375 wxXmlNode* thickness = add( material, wxS( "Default_Thickness" ) );
1376 thickness->AddAttribute( wxS( "Thickness" ), ODB::Data2String( format, item->GetThickness( sublayer ) ) );
1377 thickness->AddAttribute( wxS( "Units" ), units );
1378
1379 wxXmlNode* ref = add( layer, wxS( "SpecRef" ) );
1380 ref->AddAttribute( wxS( "MaterialSpecName" ), stepName );
1381 wxXmlNode* named = add( ref, wxS( "Material" ) );
1382 named->AddAttribute( wxS( "MaterialName" ), row.m_layerName );
1383 }
1384
1385 WriteXmlFile( writer, "stackup.xml", document, _( "Failed to generate ODB++ stackup." ) );
1386}
1387
1388
1390 const std::map<int, std::vector<BOARD_ITEM*>>& aMap,
1391 const ODB_LAYER_NAME& aLayer ) :
1392 ODB_ENTITY_BASE( aBoard, aPlugin ),
1393 m_layerItems( aMap ),
1394 m_layerID( aLayer.m_layer ),
1395 m_layer( aLayer )
1396{
1397 m_featuresMgr = std::make_unique<FEATURES_MANAGER>( aBoard, aPlugin, aLayer.m_name, aLayer.m_role, aLayer.m_auxType,
1398 aLayer.m_drillSpan );
1399}
1400
1401
1403{
1404 if( m_layer.m_role == ODB_LAYER_ROLE::ROUT )
1405 {
1406 InitRoutData();
1407 return;
1408 }
1409
1411 {
1412 InitDrillData();
1414 return;
1415 }
1416
1418 {
1421 return;
1422 }
1423
1424 if( m_layer.m_layer != PCB_LAYER_ID::UNDEFINED_LAYER )
1425 {
1427 }
1428}
1429
1431{
1432 if( m_layerItems.empty() )
1433 return;
1434
1435 const NETINFO_LIST& nets = m_board->GetNetInfo();
1436
1437 for( const NETINFO_ITEM* net : nets )
1438 {
1439 std::vector<BOARD_ITEM*>& vec = m_layerItems[net->GetNetCode()];
1440
1441 if( vec.empty() )
1442 continue;
1443
1444 std::stable_sort( vec.begin(), vec.end(), FabItemLess );
1445
1446 m_featuresMgr->InitFeatureList( m_layer.m_layer, vec );
1447 }
1448}
1449
1450
1452{
1453 SHAPE_POLY_SET outlines;
1454 bool hasOutline = m_board->GetBoardPolygonOutlines( outlines, false, nullptr, true );
1455
1456 if( !hasOutline || outlines.OutlineCount() == 0 )
1457 {
1458 m_plugin->Report( _( "Board outline is not closed; ODB++ rout layer has no board contour." ),
1460 }
1461
1462 int chain = 1;
1463 bool edgePlated = m_plugin->GetFabStackup().Stackup().m_EdgePlating;
1464
1465 for( int island = 0; hasOutline && island < outlines.OutlineCount(); ++island )
1466 {
1467 m_featuresMgr->AddRoutContour( outlines.COutline( island ), chain++, edgePlated );
1468
1469 for( int hole = 0; hole < outlines.HoleCount( island ); ++hole )
1470 m_featuresMgr->AddRoutContour( outlines.CHole( island, hole ), chain++, edgePlated );
1471 }
1472
1473 for( const auto& [span, items] : m_plugin->GetSlotHolesMap() )
1474 {
1475 for( BOARD_ITEM* item : items )
1476 {
1477 if( item->Type() == PCB_PAD_T )
1478 m_featuresMgr->AddRoutSlot( *static_cast<PAD*>( item ), chain++ );
1479 }
1480 }
1481}
1482
1483
1485 const EDA_DATA::PACKAGE& aPkg )
1486{
1487 if( !m_components.has_value() )
1488 m_components.emplace( m_plugin );
1489
1490 return m_components->AddComponent( aFp, aPkg );
1491}
1492
1493
1495{
1496 std::map<DRILL_SPAN, std::vector<BOARD_ITEM*>>& drill_layers = m_plugin->GetDrillLayerItemsMap();
1497
1498 std::map<std::pair<PCB_LAYER_ID, PCB_LAYER_ID>, std::vector<BOARD_ITEM*>>& slot_holes =
1499 m_plugin->GetSlotHolesMap();
1500
1501 wxCHECK_RET( m_layer.m_drillSpan.has_value(), "Drill matrix row has no span" );
1502 const DRILL_SPAN& matchedSpan = *m_layer.m_drillSpan;
1503 const FAB_DRILL_MODEL& model = m_plugin->GetFabDrillModel();
1504
1505 if( !m_layerItems.empty() )
1506 {
1507 m_layerItems.clear();
1508 }
1509
1510 m_tools.emplace( m_plugin->GetFormat().m_unitsStr );
1511
1512 bool isBackdrillLayer = matchedSpan.m_IsBackdrill;
1513 bool isNonPlatedLayer = matchedSpan.m_IsNonPlatedFile;
1514 bool isNPTHLayer = matchedSpan.m_IsNonPlatedFile && !matchedSpan.m_IsBackdrill;
1515
1516 if( !isBackdrillLayer )
1517 {
1518 // Slotted (oval) holes are routed to a separate map; emit them on the matching
1519 // plated or non-plated drill layer. Plated slots belong on the plated layer,
1520 // non-plated slots on the non-plated layer.
1521 auto slotIt = slot_holes.find( matchedSpan.Pair() );
1522
1523 if( slotIt != slot_holes.end() )
1524 {
1525 for( BOARD_ITEM* item : slotIt->second )
1526 {
1527 if( item->Type() != PCB_PAD_T )
1528 continue;
1529
1530 PAD* pad = static_cast<PAD*>( item );
1531
1532 bool padIsNPTH = pad->GetAttribute() == PAD_ATTRIB::NPTH;
1533
1534 if( isNPTHLayer != padIsNPTH )
1535 continue;
1536
1537 m_tools.value().AddDrillTool( m_plugin->GetFormat(),
1538 padIsNPTH ? wxT( "NON_PLATED" ) : wxT( "PLATED" ),
1539 std::min( pad->GetDrillSize().x, pad->GetDrillSize().y ),
1540 !padIsNPTH && GetFabPadRole( *pad ) == FAB_PAD_ROLE::PRESSFIT
1541 ? wxT( "PRESS_FIT" ) : wxT( "STANDARD" ) );
1542
1543 m_layerItems[pad->GetNetCode()].push_back( item );
1544 }
1545 }
1546 }
1547
1548 auto drillIt = drill_layers.find( matchedSpan );
1549
1550 if( drillIt != drill_layers.end() )
1551 {
1552 for( BOARD_ITEM* item : drillIt->second )
1553 {
1554 if( item->Type() == PCB_VIA_T )
1555 {
1556 PCB_VIA* via = static_cast<PCB_VIA*>( item );
1557 const DRILL_OPERATION* operation = model.Find( matchedSpan, via );
1558
1559 if( !operation )
1560 continue;
1561
1562 if( isBackdrillLayer )
1563 {
1564 m_tools.value().AddDrillTool( m_plugin->GetFormat(), wxT( "NON_PLATED" ),
1565 operation->m_Diameter,
1566 wxT( "BLIND" ) );
1567 }
1568 else if( isNonPlatedLayer )
1569 {
1570 m_tools.value().AddDrillTool( m_plugin->GetFormat(), wxT( "NON_PLATED" ),
1571 operation->m_Diameter );
1572 }
1573 else
1574 {
1575 m_tools.value().AddDrillTool( m_plugin->GetFormat(), wxT( "VIA" ),
1576 operation->m_Diameter,
1577 via->GetViaType() == VIATYPE::MICROVIA ? wxT( "LASER" )
1578 : wxT( "STANDARD" ) );
1579 }
1580
1581 m_layerItems[via->GetNetCode()].push_back( item );
1582 }
1583 else if( item->Type() == PCB_PAD_T )
1584 {
1585 PAD* pad = static_cast<PAD*>( item );
1586 const DRILL_OPERATION* operation = model.Find( matchedSpan, pad );
1587
1588 if( !operation )
1589 continue;
1590
1591 bool padIsNPTH = pad->GetAttribute() == PAD_ATTRIB::NPTH;
1592
1593 if( isNPTHLayer && !padIsNPTH )
1594 continue;
1595
1596 if( !isNonPlatedLayer && padIsNPTH )
1597 continue;
1598
1599 int drillSize = operation->m_Diameter;
1600
1601 wxString typeLabel = ( padIsNPTH || isNonPlatedLayer ) ? wxT( "NON_PLATED" ) : wxT( "PLATED" );
1602 wxString type2 = isBackdrillLayer ? wxT( "BLIND" ) : wxT( "STANDARD" );
1603
1604 if( typeLabel == wxT( "PLATED" ) && GetFabPadRole( *pad ) == FAB_PAD_ROLE::PRESSFIT )
1605 type2 = wxT( "PRESS_FIT" );
1606
1607 m_tools.value().AddDrillTool( m_plugin->GetFormat(), typeLabel, drillSize, type2 );
1608
1609 m_layerItems[pad->GetNetCode()].push_back( item );
1610 }
1611 }
1612 }
1613}
1614
1616{
1617 auto& auxilliary_layers = m_plugin->GetAuxilliaryLayerItemsMap();
1618
1619 wxCHECK_RET( m_layer.m_auxKey.has_value(), "Auxiliary matrix row has no key" );
1620
1621 if( !m_layerItems.empty() )
1622 {
1623 m_layerItems.clear();
1624 }
1625
1626 auto auxIt = auxilliary_layers.find( *m_layer.m_auxKey );
1627
1628 if( auxIt == auxilliary_layers.end() )
1629 return;
1630
1631 for( BOARD_ITEM* item : auxIt->second )
1632 {
1633 if( item->Type() == PCB_VIA_T )
1634 {
1635 PCB_VIA* via = static_cast<PCB_VIA*>( item );
1636
1637 m_layerItems[via->GetNetCode()].push_back( item );
1638 }
1639 }
1640}
1641
1643{
1645
1646 InitEdaData();
1647
1648 // Init Layer Entity Data
1649 for( const auto& [layerName, layer_entity_ptr] : m_layerEntityMap )
1650 {
1651 layer_entity_ptr->InitEntityData();
1652 }
1653}
1654
1655
1657{
1658 GenAttrList( writer );
1659
1660 GenFeatures( writer );
1661
1662 if( m_components.has_value() )
1663 GenComponents( writer );
1664
1665 if( m_tools.has_value() )
1666 {
1667 GenTools( writer );
1668 }
1669}
1670
1671
1673{
1674 auto fileproxy = writer.CreateFileProxy( "components" );
1675
1676 m_components->Write( fileproxy.GetStream() );
1677}
1678
1679
1681{
1682 auto fileproxy = writer.CreateFileProxy( "features" );
1683
1684 m_featuresMgr->GenerateFeatureFile( fileproxy.GetStream() );
1685}
1686
1687
1689{
1690 auto fileproxy = writer.CreateFileProxy( "attrlist" );
1691
1692 std::ostream& ost = fileproxy.GetStream();
1693 ost << "UNITS=" << m_plugin->GetFormat().m_unitsStr << '\n';
1694
1695 if( m_layer.m_drillSpan
1696 && IsCopperLayerLowerThan( m_layer.m_drillSpan->m_StartLayer, m_layer.m_drillSpan->m_EndLayer ) )
1697 {
1698 ost << ".drill_layer_direction=bottom2top" << std::endl;
1699 }
1700
1701 if( m_layer.m_role == ODB_LAYER_ROLE::BACKDRILL && m_layer.m_drillSpan )
1702 {
1703 const DRILL_SPAN& span = *m_layer.m_drillSpan;
1704 PCB_LAYER_ID stop = FabBackdrillMustNotCut( m_board->GetEnabledLayers().CuStack(),
1705 span.m_StartLayer, span.m_EndLayer );
1706
1707 if( stop != UNDEFINED_LAYER )
1708 {
1709 const std::vector<ODB_LAYER_NAME>& layers = m_plugin->GetLayerNameList();
1710 auto row = std::find_if( layers.begin(), layers.end(),
1711 [stop]( const ODB_LAYER_NAME& aLayer )
1712 {
1713 return aLayer.m_role == ODB_LAYER_ROLE::BOARD_LAYER
1714 && aLayer.m_layer == stop;
1715 } );
1716
1717 if( row != layers.end() )
1718 ost << ".backdrill_penetrate_stop_layer=" << row->m_name.ToStdString() << std::endl;
1719 }
1720 }
1721
1722 const BOARD_STACKUP_ITEM* stackupItem = StackupItemForLayer( m_plugin->GetFabStackup(), m_layer );
1723
1724 if( stackupItem )
1725 {
1726 int sublayer = m_layer.m_sublayer;
1727 int thickness = stackupItem->GetThickness( sublayer );
1728
1729 if( thickness > 0 )
1730 {
1731 ost << ".layer_dielectric=" << ODB::Data2String( m_plugin->GetFormat(), thickness ) << std::endl;
1732 }
1733
1734 if( stackupItem->GetType() == BS_ITEM_TYPE_COPPER )
1735 {
1736 const double copperWeight = CopperWeightValue( m_plugin->GetFormat(), thickness );
1737
1738 ost << ".copper_weight="
1739 << ODB::Double2String( m_plugin->GetFormat(), copperWeight )
1740 << std::endl;
1741 }
1742
1743 if( stackupItem->HasEpsilonRValue() )
1744 {
1745 double epsilonR = stackupItem->GetEpsilonR( sublayer );
1746
1747 if( epsilonR > 0.0 )
1748 {
1749 ost << ".dielectric_constant=" << ODB::Double2String( m_plugin->GetFormat(), epsilonR ) << std::endl;
1750 }
1751 }
1752
1753 if( stackupItem->HasLossTangentValue() )
1754 {
1755 double lossTangent = stackupItem->GetLossTangent( sublayer );
1756
1757 if( lossTangent > 0.0 )
1758 {
1759 ost << ".loss_tangent=" << ODB::Double2String( m_plugin->GetFormat(), lossTangent ) << std::endl;
1760 }
1761 }
1762
1763 wxString material = stackupItem->GetMaterial( sublayer );
1764
1765 if( !material.IsEmpty() )
1766 {
1767 ost << "material=" << ODB::GenLegalEntityName( material ).ToStdString() << std::endl;
1768 }
1769 }
1770}
1771
1772
1774{
1775 auto fileproxy = writer.CreateFileProxy( "tools" );
1776
1777 m_tools.value().GenerateFile( fileproxy.GetStream() );
1778}
1779
1780
1782{
1783 //InitPackage
1784 for( const FOOTPRINT* fp : m_board->Footprints() )
1785 {
1786 m_edaData.AddPackage( fp, m_plugin->GetFormat() );
1787 }
1788
1789 // for NET
1790 const NETINFO_LIST& nets = m_board->GetNetInfo();
1791
1792 for( const NETINFO_ITEM* net : nets )
1793 {
1794 m_edaData.AddNET( net, m_plugin->GetLegalNetName( net->GetNetCode() ) );
1795 }
1796
1797 // for CMP
1798 size_t j = 0;
1799 bool writeComponents = m_plugin->GetFormat().Includes( FAB::SECTION::COMPONENTS );
1800 std::array<wxString, 2> componentNames;
1801
1802 for( const ODB_LAYER_NAME& layer : m_plugin->GetLayerNameList() )
1803 {
1804 if( layer.m_role == ODB_LAYER_ROLE::COMPONENT )
1805 componentNames[layer.m_componentSide == B_Cu ? 1 : 0] = layer.m_name;
1806 }
1807
1808 for( const FOOTPRINT* fp : m_board->Footprints() )
1809 {
1810 // ODBPP only need unique PACKAGE in PKG record in eda/data file.
1811 // the PKG index can repeat to be ref in CMP record in component file.
1812 std::shared_ptr<FOOTPRINT> fp_pkg = m_edaData.GetEdaFootprints().at( j );
1813 ++j;
1814
1815 const EDA_DATA::PACKAGE& eda_pkg =
1816 m_edaData.GetPackage( hash_fp_item( fp_pkg.get(), HASH_POS | REL_COORD ) );
1817
1818 bool hasCourtyard = std::any_of( fp->GraphicalItems().begin(), fp->GraphicalItems().end(),
1819 []( const BOARD_ITEM* item )
1820 {
1821 return item->IsOnLayer( F_CrtYd ) || item->IsOnLayer( B_CrtYd );
1822 } );
1823
1824 if( fp->Pads().empty() && !hasCourtyard && !ODB::HasShownModel( fp ) )
1825 continue;
1826
1827 ODB_COMPONENT* comp = nullptr;
1828
1829 if( writeComponents )
1830 {
1831 auto iter = m_layerEntityMap.find( componentNames[fp->IsFlipped() ? 1 : 0] );
1832
1833 if( iter != m_layerEntityMap.end() )
1834 comp = &iter->second->InitComponentData( fp, eda_pkg );
1835 }
1836
1837 for( int i = 0; i < (int) fp->Pads().size(); ++i )
1838 {
1839 PAD* pad = fp->Pads()[i];
1840 EDA_DATA::NET& eda_net = m_edaData.GetNet( pad->GetNetCode() );
1841
1843 &m_edaData,
1845 comp ? comp->m_index : 0, comp ? comp->m_toeprints.size() : i );
1846
1847 m_plugin->GetPadSubnetMap().emplace( pad, &subnet );
1848
1849 if( !comp )
1850 continue;
1851
1852 const std::shared_ptr<EDA_DATA::PIN> pin = eda_pkg.GetEdaPkgPin( i );
1853 const EDA_DATA::PIN& pin_ref = *pin;
1854 auto& toep = comp->m_toeprints.emplace_back( pin_ref );
1855
1856 toep.m_net_num = eda_net.m_index;
1857 toep.m_subnet_num = subnet.m_index;
1858
1859 toep.m_center = ODB::AddXY( m_plugin->GetFormat(), pad->GetPosition() );
1860
1861 toep.m_rot = ODB::Double2String( m_plugin->GetFormat(),
1862 ( ANGLE_360 - pad->GetOrientation() ).Normalize().AsDegrees() );
1863
1864 if( pad->IsFlipped() )
1865 toep.m_mirror = wxT( "M" );
1866 else
1867 toep.m_mirror = wxT( "N" );
1868 }
1869 }
1870
1871 for( PCB_TRACK* track : m_board->Tracks() )
1872 {
1873 EDA_DATA::NET& eda_net = m_edaData.GetNet( track->GetNetCode() );
1874 EDA_DATA::SUB_NET* subnet = nullptr;
1875
1876 if( track->Type() == PCB_VIA_T )
1877 subnet = &( eda_net.AddSubnet<EDA_DATA::SUB_NET_VIA>( &m_edaData ) );
1878 else
1879 subnet = &( eda_net.AddSubnet<EDA_DATA::SUB_NET_TRACE>( &m_edaData ) );
1880
1881 m_plugin->GetViaTraceSubnetMap().emplace( track, subnet );
1882 }
1883
1884 for( ZONE* zone : m_board->Zones() )
1885 {
1886 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
1887 {
1888 if( !IsCopperLayer( layer ) )
1889 continue;
1890
1891 EDA_DATA::NET& eda_net = m_edaData.GetNet( zone->GetNetCode() );
1893 &m_edaData,
1896 0 );
1897 m_plugin->GetPlaneSubnetMap().emplace( std::piecewise_construct,
1898 std::forward_as_tuple( layer, zone ),
1899 std::forward_as_tuple( &subnet ) );
1900 }
1901 }
1902}
1903
1904
1906{
1907 wxString step_root = writer.GetCurrentPath();
1908
1909 writer.CreateEntityDirectory( step_root, "layers" );
1910 GenerateLayerFiles( writer );
1911
1912 writer.CreateEntityDirectory( step_root, "eda" );
1913 GenerateEdaFiles( writer );
1914
1915 if( m_plugin->GetFormat().Includes( FAB::SECTION::PHYSICAL_NET ) )
1916 {
1917 writer.CreateEntityDirectory( step_root, "netlists/cadnet" );
1918 GenerateNetlistsFiles( writer );
1919 }
1920
1921 writer.SetCurrentPath( step_root );
1922 GenerateProfileFile( writer );
1923
1924 GenerateStepHeaderFile( writer );
1925 GenerateAttrListFile( writer );
1926}
1927
1928
1930{
1931 auto attrlist = writer.CreateFileProxy( "attrlist" );
1932 int thickness = m_plugin->GetFabStackup().Thickness();
1933 const ODB_FORMAT& format = m_plugin->GetFormat();
1934 attrlist.GetStream() << "UNITS=" << format.m_unitsStr << '\n';
1935 attrlist.GetStream() << ".board_thickness="
1936 << ODB::Double2String( format, format.m_scale * thickness * 1000.0 ) << '\n';
1937
1938 wxString selected = format.m_variantNames.SelectedName( format.m_variantName );
1939
1940 if( !selected.IsEmpty() )
1941 {
1942 attrlist.GetStream() << ".current_variant=" << selected.ToStdString() << '\n';
1943 }
1944}
1945
1946
1948{
1949 auto fileproxy = writer.CreateFileProxy( "profile" );
1950
1951 m_profile = std::make_unique<FEATURES_MANAGER>( m_board, m_plugin, wxEmptyString, ODB_LAYER_ROLE::BOARD_LAYER,
1952 std::nullopt );
1953
1954 SHAPE_POLY_SET board_outline;
1955
1956 if( !m_board->GetBoardPolygonOutlines( board_outline, true ) )
1957 wxLogTrace( traceOdbppIo, "Failed to get board outline" );
1958
1959 int count = board_outline.OutlineCount();
1960 int largest = 0;
1961 double largestArea = count > 0 ? board_outline.Outline( 0 ).Area() : 0.0;
1962
1963 for( int ii = 1; ii < count; ++ii )
1964 {
1965 double area = board_outline.Outline( ii ).Area();
1966
1967 if( area > largestArea )
1968 {
1969 largest = ii;
1970 largestArea = area;
1971 }
1972 }
1973
1974 if( !m_profile->AddContour( board_outline, largest ) )
1975 wxLogTrace( traceOdbppIo, "Failed to add polygon to profile" );
1976
1977 m_profile->GenerateProfileFeatures( fileproxy.GetStream() );
1978}
1979
1980
1982{
1983 auto fileproxy = writer.CreateFileProxy( "stephdr" );
1984
1985 m_stephdr = {
1986 { ODB_UNITS, m_plugin->GetFormat().m_unitsStr },
1987 { "X_DATUM", "0" },
1988 { "Y_DATUM", "0" },
1989 { "X_ORIGIN", "0" },
1990 { "Y_ORIGIN", "0" },
1991 { "TOP_ACTIVE", "0" },
1992 { "BOTTOM_ACTIVE", "0" },
1993 { "RIGHT_ACTIVE", "0" },
1994 { "LEFT_ACTIVE", "0" },
1995 { "AFFECTING_BOM", "" },
1996 { "AFFECTING_BOM_CHANGED", "0" },
1997 };
1998
1999 ODB_TEXT_WRITER twriter( fileproxy.GetStream() );
2000
2001 for( const auto& [key, value] : m_stephdr )
2002 {
2003 twriter.WriteEquationLine( key, value );
2004 }
2005}
2006
2007
2009{
2010 wxString layers_root = writer.GetCurrentPath();
2011
2012 for( auto& [layerName, layerEntity] : m_layerEntityMap )
2013 {
2014 writer.CreateEntityDirectory( layers_root, layerName );
2015
2016 layerEntity->GenerateFiles( writer );
2017 }
2018}
2019
2020
2021namespace
2022{
2023struct SHORT_SHAPE
2024{
2025 std::set<int> m_nets;
2026 wxString m_layer;
2027 size_t m_featureIndex;
2028};
2029
2030
2031std::vector<SHORT_SHAPE> CollectNetTieShorts( BOARD* aBoard, PCB_IO_ODBPP* aPlugin )
2032{
2033 std::vector<SHORT_SHAPE> shortShapes;
2034
2035 for( const auto& [shapeLayer, feature] : aPlugin->GetNetTieFeatures() )
2036 {
2037 const auto* shape = static_cast<const PCB_SHAPE*>( shapeLayer.first );
2038 PCB_LAYER_ID layer = shapeLayer.second;
2039 const FOOTPRINT* footprint = shape->GetParentFootprint();
2040 std::map<wxString, int> groups = footprint->MapPadNumbersToNetTieGroups();
2041 auto copper = shape->GetEffectiveShape( layer );
2042 std::set<int> netCodes;
2043 int group = -1;
2044 bool invalidGroup = false;
2045
2046 // The DRC net tie cache lists permitted nets, not physical pad contact
2047 for( const PAD* pad : footprint->Pads() )
2048 {
2049 if( pad->GetNetCode() <= 0 || !pad->GetLayerSet()[layer]
2050 || !pad->GetEffectiveShape( layer )->Collide( copper.get() ) )
2051 {
2052 continue;
2053 }
2054
2055 auto padGroup = groups.find( pad->GetNumber() );
2056 int currentGroup = padGroup == groups.end() ? -1 : padGroup->second;
2057
2058 if( currentGroup < 0 || ( group >= 0 && group != currentGroup ) )
2059 invalidGroup = true;
2060
2061 group = currentGroup;
2062 netCodes.insert( pad->GetNetCode() );
2063 }
2064
2065 if( invalidGroup )
2066 {
2067 wxLogTrace( traceOdbppIo, wxT( "Net tie shape touches pads outside one group" ) );
2068 continue;
2069 }
2070
2071 if( netCodes.size() >= 2 )
2072 shortShapes.push_back( { std::move( netCodes ), feature.first, feature.second } );
2073 }
2074
2075 for( const FOOTPRINT* footprint : aBoard->Footprints() )
2076 {
2077 if( !footprint->IsNetTie() )
2078 continue;
2079
2080 bool hasCopperShape = std::any_of( footprint->GraphicalItems().begin(),
2081 footprint->GraphicalItems().end(),
2082 []( const BOARD_ITEM* item )
2083 {
2084 return item->Type() == PCB_SHAPE_T
2085 && ( item->GetLayerSet() & LSET::AllCuMask() ).any();
2086 } );
2087
2088 if( !hasCopperShape )
2089 wxLogTrace( traceOdbppIo, wxT( "Net tie has no copper shape for shortf" ) );
2090 }
2091
2092 std::sort( shortShapes.begin(), shortShapes.end(), []( const SHORT_SHAPE& left, const SHORT_SHAPE& right )
2093 { return std::tie( left.m_layer, left.m_featureIndex )
2094 < std::tie( right.m_layer, right.m_featureIndex ); } );
2095
2096 return shortShapes;
2097}
2098}
2099
2100
2102{
2103 std::vector<SHORT_SHAPE> shortShapes;
2104
2105 if( m_plugin->GetFormat().m_writeEdaNets )
2106 shortShapes = CollectNetTieShorts( m_board, m_plugin );
2107
2108 if( !shortShapes.empty() )
2109 {
2110 m_edaData.AssignNetUids( *m_plugin );
2111
2112 for( const SHORT_SHAPE& shape : shortShapes )
2113 m_edaData.AddShort( shape.m_nets, shape.m_layer, shape.m_featureIndex, m_plugin->NewUid() );
2114 }
2115
2116 auto fileproxy = writer.CreateFileProxy( "data" );
2117
2118 m_edaData.Write( fileproxy.GetStream(), m_plugin->GetFormat() );
2119
2120 if( m_edaData.HasShorts() )
2121 {
2122 auto shorts = writer.CreateFileProxy( "shortf" );
2123 m_edaData.WriteShortf( shorts.GetStream(), m_plugin->GetFormat() );
2124 }
2125}
2126
2127
2129{
2130 auto fileproxy = writer.CreateFileProxy( "netlist" );
2131
2132 m_netlist.Write( fileproxy.GetStream(), m_plugin->GetFormat() );
2133}
2134
2135
2137{
2138 try
2139 {
2140 writer.CreateEntityDirectory( writer.GetRootPath(), "steps" );
2142 return true;
2143 }
2144 catch( const std::exception& e )
2145 {
2146 std::cerr << e.what() << std::endl;
2147 return false;
2148 }
2149}
2150
2151
2153{
2155
2156 for( const ODB_LAYER_NAME& layer : m_plugin->GetLayerNameList() )
2157 {
2158 std::shared_ptr<ODB_LAYER_ENTITY> layerEntity =
2159 std::make_shared<ODB_LAYER_ENTITY>( m_board, m_plugin, index.Items( layer.m_layer ), layer );
2160
2161 m_layerEntityMap.emplace( layer.m_name, layerEntity );
2162 }
2163}
int index
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
@ LT_POWER
Definition board.h:246
@ LT_MIXED
Definition board.h:247
bool IsPrmSpecified(const wxString &aPrmValue)
@ BS_EDGE_CONNECTOR_NONE
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_DIELECTRIC
@ BS_ITEM_TYPE_SOLDERMASK
wxString GetBuildVersion()
Get the full KiCad version string.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
FOOTPRINT * GetParentFootprint() const
Manage one layer needed to make a physical board.
wxString GetTypeName() const
int GetSublayersCount() const
double GetEpsilonR(int aDielectricSubLayer=0) const
bool HasEpsilonRValue() const
wxString GetLayerName() const
bool HasLossTangentValue() const
PCB_LAYER_ID GetBrdLayerId() const
bool IsThicknessEditable() const
int GetThickness(int aDielectricSubLayer=0) const
bool IsEnabled() const
BOARD_STACKUP_ITEM_TYPE GetType() const
wxString GetMaterial(int aDielectricSubLayer=0) const
double GetSpecFreq(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 m_EdgePlating
True if the edge board is plated.
BS_EDGE_CONNECTOR_CONSTRAINTS m_EdgeConnectorConstraints
If the board has edge connector cards, some constrains can be specified in job file: BS_EDGE_CONNECTO...
wxString m_FinishType
The name of external copper finish.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
const FOOTPRINTS & Footprints() const
Definition board.h:465
T & AddSubnet(Args &&... args)
const size_t m_index
const std::shared_ptr< PIN > GetEdaPkgPin(size_t aPadIndex) const
const size_t m_index
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
const FAB_DRILL_FACTS & Facts() const
Board items grouped by authored layer and net, sorted by FabItemLess.
Board stackup snapshot synchronized without changing the source board.
Definition fab_stackup.h:29
int Thickness() const
Definition fab_stackup.h:42
const BOARD_STACKUP_ITEM * ItemForLayer(PCB_LAYER_ID aLayer) const
std::map< wxString, int > MapPadNumbersToNetTieGroups() const
std::deque< PAD * > & Pads()
Definition footprint.h:355
bool IsNetTie() const
Definition footprint.h:525
DRAWINGS & GraphicalItems()
Definition footprint.h:358
Handle the data for a net.
Definition netinfo.h:50
Container for NETINFO_ITEM elements, which are the nets.
Definition netinfo.h:232
static std::map< T, std::string > & GetMap()
Definition odb_util.h:203
virtual std::string GetEntityName()=0
virtual bool CreateDirectoryTree(ODB_TREE_WRITER &writer)
BOARD * m_board
Definition odb_entity.h:62
PCB_IO_ODBPP * m_plugin
Definition odb_entity.h:64
ODB_ENTITY_BASE(BOARD *aBoard, PCB_IO_ODBPP *aPlugin)
Definition odb_entity.h:48
std::ostream & GetStream()
Definition odb_util.h:328
void GenFeatures(ODB_TREE_WRITER &writer)
PCB_LAYER_ID m_layerID
Definition odb_entity.h:243
void GenComponents(ODB_TREE_WRITER &writer)
virtual void GenerateFiles(ODB_TREE_WRITER &writer) override
ODB_LAYER_NAME m_layer
Definition odb_entity.h:244
std::optional< COMPONENTS_MANAGER > m_components
Definition odb_entity.h:247
std::optional< ODB_DRILL_TOOLS > m_tools
Definition odb_entity.h:246
void GenTools(ODB_TREE_WRITER &writer)
std::map< int, std::vector< BOARD_ITEM * > > m_layerItems
Definition odb_entity.h:242
ODB_LAYER_ENTITY(BOARD *aBoard, PCB_IO_ODBPP *aPlugin, const std::map< int, std::vector< BOARD_ITEM * > > &aMap, const ODB_LAYER_NAME &aLayer)
ODB_COMPONENT & InitComponentData(const FOOTPRINT *aFp, const EDA_DATA::PACKAGE &aPkg)
virtual void InitEntityData() override
void GenAttrList(ODB_TREE_WRITER &writer)
std::unique_ptr< FEATURES_MANAGER > m_featuresMgr
Definition odb_entity.h:248
void GenerateStackupFile(ODB_TREE_WRITER &writer)
void AddStep(const wxString &aStepName)
unsigned int m_col
Definition odb_entity.h:142
std::vector< MATRIX_LAYER > m_matrixLayers
Definition odb_entity.h:140
void AddMatrixLayerField(MATRIX_LAYER &aMLayer, PCB_LAYER_ID aLayer)
virtual void GenerateFiles(ODB_TREE_WRITER &writer) override
unsigned int m_row
Definition odb_entity.h:141
std::map< wxString, std::pair< unsigned int, uint32_t > > m_matrixSteps
Definition odb_entity.h:139
void AddAuxilliaryMatrixLayer()
void EnsureUniqueLayerNames()
virtual void InitEntityData() override
void AddCOMPMatrixLayer(PCB_LAYER_ID aCompSide)
void GenerateAttrListFile(ODB_TREE_WRITER &writer)
void GenerateUserAttrFile(ODB_TREE_WRITER &writer)
ODB_MISC_ENTITY(BOARD *aBoard, PCB_IO_ODBPP *aPlugin)
std::vector< std::pair< wxString, wxString > > m_info
Definition odb_entity.h:162
virtual void GenerateFiles(ODB_TREE_WRITER &writer) override
void GenerateMetadataFile(ODB_TREE_WRITER &writer)
virtual std::string GetEntityName() override
Definition odb_entity.h:179
void GenerateProfileFile(ODB_TREE_WRITER &writer)
EDA_DATA m_edaData
Definition odb_entity.h:206
void GenerateStepHeaderFile(ODB_TREE_WRITER &writer)
void GenerateEdaFiles(ODB_TREE_WRITER &writer)
virtual void InitEntityData() override
static constexpr const char * STEP_NAME
Definition odb_entity.h:170
void GenerateAttrListFile(ODB_TREE_WRITER &writer)
void GenerateLayerFiles(ODB_TREE_WRITER &writer)
std::map< wxString, std::shared_ptr< ODB_LAYER_ENTITY > > m_layerEntityMap
Definition odb_entity.h:203
ODB_NET_LIST m_netlist
Definition odb_entity.h:208
std::unique_ptr< FEATURES_MANAGER > m_profile
Definition odb_entity.h:204
std::vector< std::pair< wxString, wxString > > m_stephdr
Definition odb_entity.h:207
virtual bool CreateDirectoryTree(ODB_TREE_WRITER &writer) override
void GenerateNetlistsFiles(ODB_TREE_WRITER &writer)
virtual void GenerateFiles(ODB_TREE_WRITER &writer) override
void WriteEquationLine(const std::string &var, int value)
Definition odb_util.cpp:538
ARRAY_PROXY MakeArrayProxy(const std::string &aStr)
Definition odb_util.h:406
void write_line_enum(const std::string &var, const T &value)
Definition odb_util.h:380
const wxString GetRootPath() const
Definition odb_util.h:361
void CreateEntityDirectory(const wxString &aPareDir, const wxString &aSubDir=wxEmptyString)
Definition odb_util.cpp:456
ODB_FILE_WRITER CreateFileProxy(const wxString &aFileName)
Definition odb_util.h:348
void SetCurrentPath(const wxString &aDir)
Definition odb_util.h:357
const wxString GetCurrentPath() const
Definition odb_util.h:355
Definition pad.h:61
const std::map< std::pair< const BOARD_ITEM *, PCB_LAYER_ID >, std::pair< wxString, size_t > > & GetNetTieFeatures() const
double Area(bool aAbsolute=true) const
Return the area of this chain.
Represent a set of closed polygons.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
const SHAPE_LINE_CHAIN & CHole(int aOutline, int aHole) const
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
Handle a list of polygons defining a copper zone.
Definition zone.h:70
#define _(s)
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:454
PCB_LAYER_ID FabBackdrillMustNotCut(const LSEQ &aCopperStack, PCB_LAYER_ID aStart, PCB_LAYER_ID aMustCut)
aMustCut is DRILL_PROPS.end; returns the next enabled copper past it away from aStart,...
Definition fab_drill.cpp:26
bool FabItemLess(const BOARD_ITEM *aFirst, const BOARD_ITEM *aSecond)
Order by footprint UUID so feature output is independent of allocation order.
bool FabLayerHasVisibleItems(const BOARD &aBoard, PCB_LAYER_ID aLayer)
True if the board draws visible text, graphics or zones on aLayer.
FAB_PAD_ROLE GetFabPadRole(const PAD &aPad)
Explicit fabrication properties take precedence over the unnumbered NPTH fallback.
Definition fab_pin.cpp:26
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
const wxChar *const traceOdbppIo
size_t hash_fp_item(const EDA_ITEM *aItem, int aFlags)
Calculate hash of an EDA_ITEM.
Definition hash_eda.cpp:54
Hashing functions for EDA_ITEMs.
@ HASH_POS
Definition hash_eda.h:43
@ REL_COORD
Use coordinates relative to the parent object.
Definition hash_eda.h:46
bool IsCopperLayerLowerThan(PCB_LAYER_ID aLayerA, PCB_LAYER_ID aLayerB)
Return true if copper aLayerA is placed lower than aLayerB, false otherwise.
Definition layer_ids.h:888
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:741
bool IsExternalCopperLayer(int aLayerId)
Test whether a layer is an external (F_Cu or B_Cu) copper layer.
Definition layer_ids.h:752
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ User_16
Definition layer_ids.h:135
@ User_29
Definition layer_ids.h:148
@ User_40
Definition layer_ids.h:159
@ User_15
Definition layer_ids.h:134
@ User_8
Definition layer_ids.h:127
@ F_CrtYd
Definition layer_ids.h:112
@ User_11
Definition layer_ids.h:130
@ User_25
Definition layer_ids.h:144
@ User_34
Definition layer_ids.h:153
@ User_45
Definition layer_ids.h:164
@ B_Adhes
Definition layer_ids.h:99
@ User_36
Definition layer_ids.h:155
@ Dwgs_User
Definition layer_ids.h:103
@ F_Paste
Definition layer_ids.h:100
@ Cmts_User
Definition layer_ids.h:104
@ User_6
Definition layer_ids.h:125
@ User_7
Definition layer_ids.h:126
@ User_19
Definition layer_ids.h:138
@ User_23
Definition layer_ids.h:142
@ F_Adhes
Definition layer_ids.h:98
@ User_41
Definition layer_ids.h:160
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ User_14
Definition layer_ids.h:133
@ User_39
Definition layer_ids.h:158
@ User_5
Definition layer_ids.h:124
@ User_20
Definition layer_ids.h:139
@ Eco1_User
Definition layer_ids.h:105
@ F_Mask
Definition layer_ids.h:93
@ User_42
Definition layer_ids.h:161
@ User_43
Definition layer_ids.h:162
@ B_Paste
Definition layer_ids.h:101
@ User_10
Definition layer_ids.h:129
@ User_9
Definition layer_ids.h:128
@ User_27
Definition layer_ids.h:146
@ User_28
Definition layer_ids.h:147
@ F_Fab
Definition layer_ids.h:115
@ Margin
Definition layer_ids.h:109
@ F_SilkS
Definition layer_ids.h:96
@ B_CrtYd
Definition layer_ids.h:111
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ Eco2_User
Definition layer_ids.h:106
@ User_35
Definition layer_ids.h:154
@ User_31
Definition layer_ids.h:150
@ User_3
Definition layer_ids.h:122
@ User_1
Definition layer_ids.h:120
@ User_12
Definition layer_ids.h:131
@ B_SilkS
Definition layer_ids.h:97
@ User_30
Definition layer_ids.h:149
@ User_37
Definition layer_ids.h:156
@ User_22
Definition layer_ids.h:141
@ User_38
Definition layer_ids.h:157
@ User_4
Definition layer_ids.h:123
@ User_21
Definition layer_ids.h:140
@ User_24
Definition layer_ids.h:143
@ User_13
Definition layer_ids.h:132
@ User_2
Definition layer_ids.h:121
@ User_17
Definition layer_ids.h:136
@ User_33
Definition layer_ids.h:152
@ User_26
Definition layer_ids.h:145
@ User_32
Definition layer_ids.h:151
@ User_18
Definition layer_ids.h:137
@ User_44
Definition layer_ids.h:163
@ F_Cu
Definition layer_ids.h:60
@ B_Fab
Definition layer_ids.h:114
bool IsValidLayer(int aLayerId)
Test whether a given integer is a valid layer index, i.e.
Definition layer_ids.h:719
SECTION
Schema sections of the IPC-2581C function mode table, Table 4 p 80.
std::string Enum2String(T value)
Definition odb_util.h:279
std::vector< ODB_TYPE > OverridableLayerTypes(PCB_LAYER_ID aLayer)
Matrix types a layer override may assign to aLayer.
Definition odb_util.cpp:110
std::pair< wxString, wxString > AddXY(const ODB_FORMAT &aFormat, const VECTOR2I &aVec)
Definition odb_util.cpp:412
std::vector< wxString > UniqueNames(const std::vector< wxString > &aBases, const std::set< wxString > &aReserved, size_t aMaxLength)
Definition odb_util.cpp:252
wxString Data2String(const ODB_FORMAT &aFormat, double aVal)
Definition odb_util.cpp:406
wxString GenLegalEntityName(const wxString &aStr)
Definition odb_util.cpp:212
bool HasShownModel(const FOOTPRINT *aFp)
wxString Double2String(const ODB_FORMAT &aFormat, double aVal)
Definition odb_util.cpp:382
#define ODB_JOB_NAME
Definition odb_defines.h:27
#define ODB_UNITS
Definition odb_defines.h:28
std::tuple< ODB_AUX_LAYER_TYPE, PCB_LAYER_ID, PCB_LAYER_ID > ODB_AUX_LAYER_KEY
Definition odb_util.h:153
ODB_TYPE
Definition odb_util.h:105
@ UNDEFINED
Definition odb_util.h:106
@ POWER_GROUND
Definition odb_util.h:109
@ COMPONENT
Definition odb_util.h:118
@ SOLDER_PASTE
Definition odb_util.h:113
@ SILK_SCREEN
Definition odb_util.h:114
@ SOLDER_MASK
Definition odb_util.h:112
@ DIELECTRIC
Definition odb_util.h:110
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
@ PTH
Plated through hole pad.
Definition padstack.h:97
see class PGM_BASE
static bool isCopper(const PNS::ITEM *aItem)
@ RPT_SEVERITY_WARNING
#define KEY_CORE
One machining action, which is also one NC hit and one tool assignment.
PCB_LAYER_ID m_TopLayer
PCB_LAYER_ID m_BottomLayer
BOARD_ITEM * m_SourceItem
non-owning, valid only for this enumeration
DRILL_LAYER_PAIR Pair() const
Definition drill_span.h:93
PCB_LAYER_ID m_EndLayer
Definition drill_span.h:129
PCB_LAYER_ID m_StartLayer
Definition drill_span.h:128
bool m_IsNonPlatedFile
Definition drill_span.h:131
bool m_IsBackdrill
Definition drill_span.h:130
Board-wide hole facts that a fabrication product states outright.
wxString SelectedName(const wxString &aName) const
Definition odb_util.cpp:343
bool Includes(FAB::SECTION aSection) const
Definition odb_util.h:85
ODB::VARIANT_NAMES m_variantNames
Definition odb_util.h:83
std::vector< ODB_LAYER_OVERRIDE > m_layerOverrides
Definition odb_util.h:82
std::optional< FAB::SECTION_SET > m_sections
Definition odb_util.h:81
double m_scale
Definition odb_util.h:68
std::string m_unitsStr
Definition odb_util.h:73
wxString m_variantName
Definition odb_util.h:74
PCB_LAYER_ID m_componentSide
Definition odb_entity.h:83
PCB_LAYER_ID m_layer
Definition odb_entity.h:74
wxString m_name
Definition odb_entity.h:75
ODB_LAYER_ROLE m_role
Definition odb_entity.h:76
std::optional< DRILL_SPAN > m_drillSpan
Definition odb_entity.h:81
const BOARD_STACKUP_ITEM * m_stackupItem
Definition odb_entity.h:79
std::optional< ODB_AUX_LAYER_TYPE > m_auxType
Definition odb_entity.h:77
std::optional< ODB_AUX_LAYER_KEY > m_auxKey
Definition odb_entity.h:78
std::optional< ODB_SUBTYPE > m_addType
Definition odb_entity.h:100
std::optional< ODB_DIELECTRIC_TYPE > m_diType
Definition odb_entity.h:101
std::optional< uint32_t > m_cuBottom
Definition odb_entity.h:104
std::optional< uint32_t > m_ref
Definition odb_entity.h:102
std::optional< uint32_t > m_cuTop
Definition odb_entity.h:103
std::optional< std::pair< wxString, wxString > > m_span
Definition odb_entity.h:99
KIBIS top(path, &reporter)
KIBIS_MODEL * model
KIBIS_COMPONENT * comp
KIBIS_PIN * pin
const SHAPE_LINE_CHAIN chain
VECTOR2I end
wxLogTrace helper definitions.
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79