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