KiCad PCB EDA Suite
Loading...
Searching...
No Matches
allegro_builder.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 Quilter
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU 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, you may find one here:
19 * http://www.gnu.org/licenses/gpl-3.0.html
20 * or you may search the http://www.gnu.org website for the version 3 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include "allegro_builder.h"
26
27#include <chrono>
28#include <cmath>
29#include <limits>
30#include <set>
31#include <tuple>
32#include <unordered_set>
33
35
36#include <wx/log.h>
37
38#include <core/profile.h>
39
42#include <footprint.h>
43#include <netclass.h>
44#include <pad.h>
45#include <pcb_text.h>
46#include <pcb_shape.h>
47#include <pcb_track.h>
48#include <zone.h>
50
51
52using namespace ALLEGRO;
53
54
62static const wxChar* const traceAllegroBuilder = wxT( "KICAD_ALLEGRO_BUILDER" );
63static const wxChar* const traceAllegroPerf = wxT( "KICAD_ALLEGRO_PERF" );
64
65
66#define BLK_FIELD( BLK_T, FIELD ) static_cast<const BLOCK<BLK_T>&>( aBlock ).GetData().FIELD
67
68
79static uint32_t GetPrimaryNext( const BLOCK_BASE& aBlock )
80{
81 const uint8_t type = aBlock.GetBlockType();
82
83 switch( type )
84 {
85 case 0x01: return BLK_FIELD( BLK_0x01_ARC, m_Next );
86 case 0x04: return BLK_FIELD( BLK_0x04_NET_ASSIGNMENT, m_Next );
87 case 0x05: return BLK_FIELD( BLK_0x05_TRACK, m_Next );
88 case 0x0E: return BLK_FIELD( BLK_0x0E_SHAPE_SEG, m_Next );
89 case 0x14: return BLK_FIELD( BLK_0x14_GRAPHIC, m_Next );
90 case 0x15:
91 case 0x16:
92 case 0x17: return BLK_FIELD( BLK_0x15_16_17_SEGMENT, m_Next );
93 case 0x1B: return BLK_FIELD( BLK_0x1B_NET, m_Next );
94 case 0x1D: return BLK_FIELD( BLK_0x1D_CONSTRAINT_SET, m_Next );
95 case 0x1E: return BLK_FIELD( BLK_0x1E_SI_MODEL, m_Next );
96 case 0x1F: return BLK_FIELD( BLK_0x1F_PADSTACK_DIM, m_Next );
97 case 0x2B: return BLK_FIELD( BLK_0x2B_FOOTPRINT_DEF, m_Next );
98 case 0x2D: return BLK_FIELD( BLK_0x2D_FOOTPRINT_INST, m_Next );
99 case 0x2E: return BLK_FIELD( BLK_0x2E_CONNECTION, m_Next );
100 case 0x30: return BLK_FIELD( BLK_0x30_STR_WRAPPER, m_Next );
101 case 0x31: return 0; // Doesn't exist
102 case 0x32: return BLK_FIELD( BLK_0x32_PLACED_PAD, m_Next );
103 case 0x24: return BLK_FIELD( BLK_0x24_RECT, m_Next );
104 case 0x28: return BLK_FIELD( BLK_0x28_SHAPE, m_Next );
105 case 0x33: return BLK_FIELD( BLK_0x33_VIA, m_Next );
106 case 0x36: return BLK_FIELD( BLK_0x36_DEF_TABLE, m_Next );
107 default: return 0;
108 }
109}
110
111
115static uint32_t PadGetNextInFootprint( const BLOCK_BASE& aBlock )
116{
117 const uint8_t type = aBlock.GetBlockType();
118
119 if( type != 0x32 )
120 {
122 wxString::Format( "Unexpected next item in 0x32 pad list: block type %#04x, offset %#lx, key %#010x",
123 type, aBlock.GetOffset(), aBlock.GetKey() ) );
124 }
125
126 // When iterating in a footprint use this field, not m_Next.
127 return BLK_FIELD( BLK_0x32_PLACED_PAD, m_NextInFp );
128}
129
130
132{
133public:
134
135 using NEXT_FUNC_T = std::function<uint32_t( const BLOCK_BASE& )>;
136
138 {
139 public:
140 iterator( uint32_t aCurrent, uint32_t aTail, const BRD_DB& aBoard, NEXT_FUNC_T aNextFunc ) :
141 m_current( aCurrent ), m_tail( aTail ), m_board( aBoard ), m_NextFunc( aNextFunc )
142 {
143 m_currBlock = m_board.GetObjectByKey( m_current );
144
145 if( !m_currBlock )
146 m_current = 0;
147 }
148
149 const BLOCK_BASE* operator*() const { return m_currBlock; }
150
152 {
153 if( m_current == m_tail || !m_currBlock )
154 {
155 m_current = 0;
156 }
157 else
158 {
160
161 if( m_current == m_tail || m_board.IsSentinel( m_current ) )
162 {
163 m_current = 0;
164 }
165 else
166 {
167 m_currBlock = m_board.GetObjectByKey( m_current );
168
169 if( m_currBlock == nullptr )
170 {
171 m_current = 0;
172 }
173 }
174 }
175 return *this;
176 }
177
178 bool operator!=( const iterator& other ) const { return m_current != other.m_current; }
179
180 private:
181 uint32_t m_current;
183 uint32_t m_tail;
186 };
187
188 LL_WALKER( uint32_t aHead, uint32_t aTail, const BRD_DB& aBoard ) :
189 m_head( aHead ), m_tail( aTail ), m_board( aBoard )
190 {
191 // The default next function
193 }
194
195 LL_WALKER( const FILE_HEADER::LINKED_LIST& aList, const BRD_DB& aBoard ) :
196 LL_WALKER( aList.m_Head, aList.m_Tail, aBoard )
197 {
198 }
199
201 iterator end() const { return iterator( 0, m_tail, m_board, m_nextFunction ); }
202
203 void SetNextFunc( NEXT_FUNC_T aNextFunc ) { m_nextFunction = aNextFunc; }
204
205private:
206 uint32_t m_head;
207 uint32_t m_tail;
209
210 // This is the function that can get the next item in a list. By default
212};
213
214
215template <>
216struct std::hash<LAYER_INFO>
217{
218 size_t operator()( const LAYER_INFO& aLayerInfo ) const noexcept
219 {
220 return ( aLayerInfo.m_Class << 8 ) + aLayerInfo.m_Subclass;
221 }
222};
223
224
232// clang-format off
233static const std::unordered_map<LAYER_INFO, PCB_LAYER_ID> s_LayerKiMap = {
234
237
240
247
252
255};
256
264static const std::unordered_map<LAYER_INFO, wxString> s_OptionalFixedMappings = {
271
276
281
286
291};
292
293// clang-format on
294
295
302static wxString layerInfoDisplayName( const LAYER_INFO& aLayerInfo )
303{
304 // clang-format off
305 static const std::unordered_map<uint8_t, wxString> s_ClassNames = {
306 { LAYER_INFO::CLASS::BOARD_GEOMETRY, wxS( "Board Geometry" ) },
307 { LAYER_INFO::CLASS::COMPONENT_VALUE, wxS( "Component Value" ) },
308 { LAYER_INFO::CLASS::DEVICE_TYPE, wxS( "Device Type" ) },
309 { LAYER_INFO::CLASS::DRAWING_FORMAT, wxS( "Drawing Format" ) },
310 { LAYER_INFO::CLASS::ETCH, wxS( "Etch" ) },
311 { LAYER_INFO::CLASS::MANUFACTURING, wxS( "Manufacturing" ) },
312 { LAYER_INFO::CLASS::PACKAGE_GEOMETRY, wxS( "Package Geometry" ) },
313 { LAYER_INFO::CLASS::PACKAGE_KEEPIN, wxS( "Package Keepin" ) },
314 { LAYER_INFO::CLASS::PACKAGE_KEEPOUT, wxS( "Package Keepout" ) },
315 { LAYER_INFO::CLASS::PIN, wxS( "Pin" ) },
316 { LAYER_INFO::CLASS::REF_DES, wxS( "Ref Des" ) },
317 { LAYER_INFO::CLASS::ROUTE_KEEPIN, wxS( "Route Keepin" ) },
318 { LAYER_INFO::CLASS::ROUTE_KEEPOUT, wxS( "Route Keepout" ) },
319 { LAYER_INFO::CLASS::TOLERANCE, wxS( "Tolerance" ) },
320 { LAYER_INFO::CLASS::USER_PART_NUMBER, wxS( "User Part Number" ) },
321 { LAYER_INFO::CLASS::VIA_CLASS, wxS( "Via Class" ) },
322 { LAYER_INFO::CLASS::VIA_KEEPOUT, wxS( "Via Keepout" ) },
323 { LAYER_INFO::CLASS::ANTI_ETCH, wxS( "Anti Etch" ) },
324 { LAYER_INFO::CLASS::BOUNDARY, wxS( "Boundary" ) },
325 };
326
327 static const std::unordered_map<uint8_t, wxString> s_BoardGeomSubclassNames = {
328 { LAYER_INFO::SUBCLASS::BGEOM_CONSTRAINT_AREA, wxS( "Constraint Area" ) },
329 { LAYER_INFO::SUBCLASS::BGEOM_OFF_GRID_AREA, wxS( "Off Grid Area" ) },
330 { LAYER_INFO::SUBCLASS::BGEOM_SOLDERMASK_BOTTOM, wxS( "Soldermask Bottom" ) },
331 { LAYER_INFO::SUBCLASS::BGEOM_SOLDERMASK_TOP, wxS( "Soldermask Top" ) },
332 { LAYER_INFO::SUBCLASS::BGEOM_ASSEMBLY_DETAIL, wxS( "Assembly Detail" ) },
333 { LAYER_INFO::SUBCLASS::BGEOM_SILKSCREEN_BOTTOM, wxS( "Silkscreen Bottom" ) },
334 { LAYER_INFO::SUBCLASS::BGEOM_SILKSCREEN_TOP, wxS( "Silkscreen Top" ) },
335 { LAYER_INFO::SUBCLASS::BGEOM_SWITCH_AREA_BOTTOM, wxS( "Switch Area Bottom" ) },
336 { LAYER_INFO::SUBCLASS::BGEOM_SWITCH_AREA_TOP, wxS( "Switch Area Top" ) },
337 { LAYER_INFO::SUBCLASS::BGEOM_BOTH_ROOMS, wxS( "Both Rooms" ) },
338 { LAYER_INFO::SUBCLASS::BGEOM_BOTTOM_ROOM, wxS( "Bottom Room" ) },
339 { LAYER_INFO::SUBCLASS::BGEOM_TOP_ROOM, wxS( "Top Room" ) },
340 { LAYER_INFO::SUBCLASS::BGEOM_PLACE_GRID_BOTTOM, wxS( "Place Grid Bottom" ) },
341 { LAYER_INFO::SUBCLASS::BGEOM_PLACE_GRID_TOP, wxS( "Place Grid Top" ) },
342 { LAYER_INFO::SUBCLASS::BGEOM_DIMENSION, wxS( "Dimension" ) },
343 { LAYER_INFO::SUBCLASS::BGEOM_TOOLING_CORNERS, wxS( "Tooling Corners" ) },
344 { LAYER_INFO::SUBCLASS::BGEOM_ASSEMBLY_NOTES, wxS( "Assembly Notes" ) },
345 { LAYER_INFO::SUBCLASS::BGEOM_PLATING_BAR, wxS( "Plating Bar" ) },
346 { LAYER_INFO::SUBCLASS::BGEOM_OUTLINE, wxS( "Outline" ) },
347
348 };
349
350 static const std::unordered_map<uint8_t, wxString> s_ComponentValueSubclassNames = {
351 { LAYER_INFO::SUBCLASS::DISPLAY_BOTTOM, wxS( "Display Bottom" ) },
352 { LAYER_INFO::SUBCLASS::DISPLAY_TOP, wxS( "Display Top" ) },
353 { LAYER_INFO::SUBCLASS::SILKSCREEN_BOTTOM, wxS( "Silkscreen Bottom" ) },
354 { LAYER_INFO::SUBCLASS::SILKSCREEN_TOP, wxS( "Silkscreen Top" ) },
355 { LAYER_INFO::SUBCLASS::ASSEMBLY_BOTTOM, wxS( "Assembly Bottom" ) },
356 { LAYER_INFO::SUBCLASS::ASSEMBLY_TOP, wxS( "Assembly Top" ) },
357 };
358
359 static const std::unordered_map<uint8_t, wxString> s_DrawingFormatSubclassNames = {
360 { LAYER_INFO::SUBCLASS::DFMT_REVISION_DATA, wxS( "Revision Data" ) },
361 { LAYER_INFO::SUBCLASS::DFMT_REVISION_BLOCK, wxS( "Revision Block" ) },
362 { LAYER_INFO::SUBCLASS::DFMT_TITLE_DATA, wxS( "Title Data" ) },
363 { LAYER_INFO::SUBCLASS::DFMT_TITLE_BLOCK, wxS( "Title Block" ) },
364 { LAYER_INFO::SUBCLASS::DFMT_OUTLINE, wxS( "Outline" ) },
365 };
366
367 static const std::unordered_map<uint8_t, wxString> s_PackageGeometrySubclassNames = {
368 { LAYER_INFO::SUBCLASS::DFA_BOUND_BOTTOM, wxS( "DFA Bound Bottom" ) },
369 { LAYER_INFO::SUBCLASS::DFA_BOUND_TOP, wxS( "DFA Bound Top" ) },
370 { LAYER_INFO::SUBCLASS::PGEOM_DISPLAY_BOTTOM, wxS( "Display Bottom" ) },
371 { LAYER_INFO::SUBCLASS::PGEOM_DISPLAY_TOP, wxS( "Display Top" ) },
372 { LAYER_INFO::SUBCLASS::PGEOM_SOLDERMASK_BOTTOM, wxS( "Soldermask Bottom" ) },
373 { LAYER_INFO::SUBCLASS::PGEOM_SOLDERMASK_TOP, wxS( "Soldermask Top" ) },
374 { LAYER_INFO::SUBCLASS::PGEOM_BODY_CENTER, wxS( "Body Center" ) },
375 { LAYER_INFO::SUBCLASS::PGEOM_SILKSCREEN_BOTTOM, wxS( "Silkscreen Bottom" ) },
376 { LAYER_INFO::SUBCLASS::PGEOM_SILKSCREEN_TOP, wxS( "Silkscreen Top" ) },
377 { LAYER_INFO::SUBCLASS::PGEOM_PAD_STACK_NAME, wxS( "Pad Stack Name" ) },
378 { LAYER_INFO::SUBCLASS::PGEOM_PIN_NUMBER, wxS( "Pin Number" ) },
379 { LAYER_INFO::SUBCLASS::PGEOM_PLACE_BOUND_BOTTOM, wxS( "Place Bound Bottom" ) },
380 { LAYER_INFO::SUBCLASS::PGEOM_PLACE_BOUND_TOP, wxS( "Place Bound Top" ) },
381 { LAYER_INFO::SUBCLASS::PGEOM_ASSEMBLY_BOTTOM, wxS( "Assembly Bottom" ) },
382 { LAYER_INFO::SUBCLASS::PGEOM_ASSEMBLY_TOP, wxS( "Assembly Top" ) },
383 };
384
385 static const std::unordered_map<uint8_t, wxString> s_ManufacturingSubclassNames = {
386 { LAYER_INFO::SUBCLASS::MFR_NO_PROBE_BOTTOM, wxS( "No Probe Bottom" ) },
387 { LAYER_INFO::SUBCLASS::MFR_NO_PROBE_TOP, wxS( "No Probe Top" ) },
388 { LAYER_INFO::SUBCLASS::MFR_AUTOSILK_BOTTOM, wxS( "AutoSilk Bottom" ) },
389 { LAYER_INFO::SUBCLASS::MFR_AUTOSILK_TOP, wxS( "AutoSilk Top" ) },
390 { LAYER_INFO::SUBCLASS::MFR_PROBE_BOTTOM, wxS( "Probe Bottom" ) },
391 { LAYER_INFO::SUBCLASS::MFR_PROBE_TOP, wxS( "Probe Top" ) },
392 { LAYER_INFO::SUBCLASS::MFR_NCDRILL_FIGURE, wxS( "NC Drill Figure" ) },
393 { LAYER_INFO::SUBCLASS::MFR_NCDRILL_LEGEND, wxS( "NC Drill Legend" ) },
394 { LAYER_INFO::SUBCLASS::MFR_NO_GLOSS_INTERNAL, wxS( "No Gloss Internal" ) },
395 { LAYER_INFO::SUBCLASS::MFR_NO_GLOSS_BOTTOM, wxS( "No Gloss Bottom" ) },
396 { LAYER_INFO::SUBCLASS::MFR_NO_GLOSS_TOP, wxS( "No Gloss Top" ) },
397 { LAYER_INFO::SUBCLASS::MFR_NO_GLOSS_ALL, wxS( "No Gloss All" ) },
398 { LAYER_INFO::SUBCLASS::MFR_PHOTOPLOT_OUTLINE, wxS( "Photoplot Outline" ) },
399 };
400
401 static const std::unordered_map<uint8_t, wxString> s_AnalysisSubclassNames = {
402 { LAYER_INFO::SUBCLASS::ANALYSIS_PCB_TEMPERATURE, wxS( "PCB Temperature" ) },
403 { LAYER_INFO::SUBCLASS::ANALYSIS_HIGH_ISOCONTOUR, wxS( "High IsoContour" ) },
404 { LAYER_INFO::SUBCLASS::ANALYSIS_MEDIUM3_ISOCONTOUR, wxS( "Medium3 IsoContour" ) },
405 { LAYER_INFO::SUBCLASS::ANALYSIS_MEDIUM2_ISOCONTOUR, wxS( "Medium2 IsoContour" ) },
406 { LAYER_INFO::SUBCLASS::ANALYSIS_MEDIUM1_ISOCONTOUR, wxS( "Medium1 IsoContour" ) },
407 { LAYER_INFO::SUBCLASS::ANALYSIS_LOW_ISOCONTOUR, wxS( "Low IsoContour" ) },
408 };
409
410 static const std::unordered_map<uint8_t, const std::unordered_map<uint8_t, wxString>&> s_SubclassNameMaps = {
411 { LAYER_INFO::CLASS::BOARD_GEOMETRY, s_BoardGeomSubclassNames },
412
413 // These classes all share the same subclass names
414 { LAYER_INFO::CLASS::COMPONENT_VALUE, s_ComponentValueSubclassNames },
415 { LAYER_INFO::CLASS::DEVICE_TYPE, s_ComponentValueSubclassNames },
416 { LAYER_INFO::CLASS::REF_DES, s_ComponentValueSubclassNames },
417 { LAYER_INFO::CLASS::TOLERANCE, s_ComponentValueSubclassNames },
418 { LAYER_INFO::CLASS::USER_PART_NUMBER, s_ComponentValueSubclassNames },
419
420 { LAYER_INFO::CLASS::DRAWING_FORMAT, s_DrawingFormatSubclassNames },
421 { LAYER_INFO::CLASS::PACKAGE_GEOMETRY, s_PackageGeometrySubclassNames },
422 { LAYER_INFO::CLASS::MANUFACTURING, s_ManufacturingSubclassNames },
423 { LAYER_INFO::CLASS::ANALYSIS, s_AnalysisSubclassNames },
424 };
425 // clang-format on
426
427 wxString className;
428 const auto classIt = s_ClassNames.find( aLayerInfo.m_Class );
429
430 if( classIt != s_ClassNames.end() )
431 className = classIt->second;
432 else
433 className = wxString::Format( wxS( "Class_%02X" ), aLayerInfo.m_Class );
434
435 wxString subclassName;
436
437 // Find the right subclass name map for this class
438 auto classMapIt = s_SubclassNameMaps.find( aLayerInfo.m_Class );
439
440 if( classMapIt != s_SubclassNameMaps.end() )
441 {
442 const std::unordered_map<uint8_t, wxString>& subclassMap = classMapIt->second;
443
444 const auto subIt = subclassMap.find( aLayerInfo.m_Subclass );
445
446 if( subIt != subclassMap.end() )
447 subclassName = subIt->second;
448 else
449 {
450 // This subclass seems not to have a known name
451 subclassName = wxString::Format( wxS( "Subclass_%02X" ), aLayerInfo.m_Subclass );
452 }
453 }
454 else
455 {
456 // Don't have a specific map for this class, just do a generic one.
457 subclassName = wxString::Format( wxS( "Subclass_%02X" ), aLayerInfo.m_Subclass );
458 }
459
460 return className + wxS( "/" ) + subclassName;
461}
462
463
468{
475 {
476 wxString m_Name;
477 // LAYER_ARTWORK: POSITIVE/NEGATIVE
478 // LAYER_USE: empty, EMBEDDED_PLANE, ...?
479 // bool m_IsConductor;
480 };
481
482public:
483 LAYER_MAPPER( const BRD_DB& aRawBoard, BOARD& aBoard,
484 const LAYER_MAPPING_HANDLER& aLayerMappingHandler ) :
485 m_brdDb( aRawBoard ), m_board( aBoard ), m_layerMappingHandler( aLayerMappingHandler )
486 {}
487
488 void ProcessLayerList( uint8_t aClass, const BLK_0x2A_LAYER_LIST& aList )
489 {
490 // If we haven't seen this list yet, create and store the CUSTOM_LAYER list
491 if( m_Lists.count( &aList ) == 0 )
492 {
493 std::vector<CUSTOM_LAYER>& classLayers = m_Lists[&aList];
494
495 if( aList.m_RefEntries.has_value() )
496 {
497 for( const BLK_0x2A_LAYER_LIST::REF_ENTRY& entry : aList.m_RefEntries.value() )
498 {
499 const wxString& layerName = m_brdDb.GetString( entry.mLayerNameId );
500
501 classLayers.emplace_back( CUSTOM_LAYER( layerName ) );
502 }
503 }
504 else if( aList.m_NonRefEntries.has_value() )
505 {
506 for( const BLK_0x2A_LAYER_LIST::NONREF_ENTRY& entry : aList.m_NonRefEntries.value() )
507 {
508 classLayers.emplace_back( CUSTOM_LAYER( entry.m_Name ) );
509 }
510 }
511 else
512 {
513 // Presumably a parsing error.
514 THROW_IO_ERROR( "No ETCH layer list found." );
515 }
516
517 wxLogTrace( traceAllegroBuilder, "Added %zu layers for class %#04x, from 0x2A key %#010x",
518 classLayers.size(), aClass, aList.m_Key );
519 }
520
521 // Store the class ID -> 0x2A mapping
522 m_ClassCustomLayerLists[aClass] = &m_Lists[&aList];
523 }
524
531 {
533
534 if( it == m_ClassCustomLayerLists.end() || !it->second )
535 {
536 wxLogTrace( traceAllegroBuilder, "No ETCH layer class found; cannot finalize layers" );
537 return;
538 }
539
540 const std::vector<CUSTOM_LAYER>& etchLayers = *it->second;
541 const size_t numCuLayers = etchLayers.size();
542
543 m_board.GetDesignSettings().SetCopperLayerCount( numCuLayers );
544
545 std::vector<INPUT_LAYER_DESC> inputLayers;
546
547 for( size_t li = 0; li < numCuLayers; ++li )
548 {
549 INPUT_LAYER_DESC desc;
550 desc.Name = etchLayers[li].m_Name;
551 desc.AutoMapLayer = getNthCopperLayer( li, numCuLayers );
553 desc.Required = true;
554 inputLayers.push_back( desc );
555 }
556
557 for( const auto& [layerInfo, kiLayer] : s_LayerKiMap )
558 {
559 INPUT_LAYER_DESC desc;
560 desc.Name = layerInfoDisplayName( layerInfo );
561 desc.AutoMapLayer = kiLayer;
563 desc.Required = false;
564 inputLayers.push_back( desc );
565 }
566
567 // Add non-ETCH custom layers so they appear in the layer mapping dialog
568 const std::vector<CUSTOM_LAYER>* etchList = m_ClassCustomLayerLists[LAYER_INFO::CLASS::ETCH];
569 int nextAutoUser = 0;
570
571 for( const auto& [classId, layerList] : m_ClassCustomLayerLists )
572 {
573 if( classId == LAYER_INFO::CLASS::ETCH || layerList == etchList )
574 continue;
575
576 for( size_t si = 0; si < layerList->size(); ++si )
577 {
578 const LAYER_INFO li{ classId, static_cast<uint8_t>( si ) };
579
580 // Skip entries already covered by s_LayerKiMap
581 if( s_LayerKiMap.count( li ) )
582 continue;
583
584 INPUT_LAYER_DESC desc;
585 desc.Name = layerInfoDisplayName( li );
586
587 if( layerList->at( si ).m_Name.length() > 0 )
588 desc.Name = layerList->at( si ).m_Name;
589
590 desc.AutoMapLayer = getNthUserLayer( nextAutoUser++ );
592 desc.Required = false;
593 inputLayers.push_back( desc );
594
596 }
597 }
598
599 std::map<wxString, PCB_LAYER_ID> resolvedMapping = m_layerMappingHandler( inputLayers );
600
601 // Apply copper layer mapping
602 for( size_t li = 0; li < numCuLayers; ++li )
603 {
604 const LAYER_INFO layerInfo{ LAYER_INFO::CLASS::ETCH, static_cast<uint8_t>( li ) };
605 const wxString& layerName = etchLayers[li].m_Name;
606
607 auto it = resolvedMapping.find( layerName );
608 PCB_LAYER_ID lId = ( it != resolvedMapping.end() ) ? it->second
609 : getNthCopperLayer( li, numCuLayers );
610
611 m_customLayerToKiMap[layerInfo] = lId;
612 m_board.SetLayerName( lId, layerName );
613 }
614
615 // Apply non-copper static layer mapping from the handler result
616 for( const auto& [layerInfo, defaultKiLayer] : s_LayerKiMap )
617 {
618 const wxString displayName = layerInfoDisplayName( layerInfo );
619
620 auto it = resolvedMapping.find( displayName );
621
622 if( it != resolvedMapping.end() && it->second != PCB_LAYER_ID::UNDEFINED_LAYER )
623 {
624 m_staticLayerOverrides[layerInfo] = it->second;
625 }
626 }
627
628 // Apply custom layer mapping from the handler result
629 for( const auto& [layerInfo, dialogName] : m_customLayerDialogNames )
630 {
631 auto it = resolvedMapping.find( dialogName );
632
633 if( it != resolvedMapping.end() && it->second != PCB_LAYER_ID::UNDEFINED_LAYER )
634 {
635 m_customLayerToKiMap[layerInfo] = it->second;
636 m_board.SetLayerName( it->second, dialogName );
637 }
638 }
639 }
640
641 PCB_LAYER_ID GetLayer( const LAYER_INFO& aLayerInfo )
642 {
643 // We already mapped and created the layer
644 if( m_customLayerToKiMap.count( aLayerInfo ) )
645 return m_customLayerToKiMap.at( aLayerInfo );
646
647 // Check for user-remapped static layers first, then the defaults
648 if( m_staticLayerOverrides.count( aLayerInfo ) )
649 return m_staticLayerOverrides.at( aLayerInfo );
650
651 if( s_LayerKiMap.count( aLayerInfo ) )
652 return s_LayerKiMap.at( aLayerInfo );
653
654 // Next, have a look and see if the class:subclass was recorded as a custom layer
655 if( m_ClassCustomLayerLists.count( aLayerInfo.m_Class ) )
656 {
657 const std::vector<CUSTOM_LAYER>* cLayerList = m_ClassCustomLayerLists.at( aLayerInfo.m_Class );
658
659 // if it is using the copper layer list, return that
660 if( cLayerList == m_ClassCustomLayerLists.at( LAYER_INFO::CLASS::ETCH ) )
661 {
662 const PCB_LAYER_ID cuLayer = getNthCopperLayer( aLayerInfo.m_Subclass, cLayerList->size() );
663 // Remember this mapping
664 m_customLayerToKiMap[aLayerInfo] = cuLayer;
665 return cuLayer;
666 }
667
668 if( aLayerInfo.m_Subclass < cLayerList->size() )
669 {
670 // This subclass maps to a custom layer in this class
671 const CUSTOM_LAYER& cLayer = cLayerList->at( aLayerInfo.m_Subclass );
672 return mapCustomLayer( aLayerInfo, cLayer.m_Name );
673 }
674 }
675
676 // Now, there may be layers that map to custom layers in KiCad, but are fixed in Allegro
677 // (perhaps, DFA_BOUND_TOP), which means we won't find them in the layer lists.
678 // We add them if we encounter them, with the names defined.
679 if( s_OptionalFixedMappings.count( aLayerInfo ) )
680 {
681 const wxString& layerName = s_OptionalFixedMappings.at( aLayerInfo );
682 return mapCustomLayer( aLayerInfo, layerName );
683 }
684
685 // Keep a record of what we failed to map
686 if( m_unknownLayers.count( aLayerInfo ) == 0 )
687 {
688 wxLogTrace( traceAllegroBuilder, "Failed to map class:subclass to layer: %#04x:%#04x", aLayerInfo.m_Class,
689 aLayerInfo.m_Subclass );
690 m_unknownLayers[aLayerInfo] = 1;
691 }
692 m_unknownLayers[aLayerInfo]++;
693
694 // Dump everything else here
695 return m_unmappedLayer;
696 }
697
705 {
706 const wxString name = aTop ? "PLACE_BOUND_TOP" : "PLACE_BOUND_BOTTOM";
707 return mapCustomLayerByName( name );
708 }
709
714 bool IsOutlineLayer( const LAYER_INFO& aLayerInfo ) const
715 {
718 {
719 return false;
720 }
721
724 }
725
726private:
727 static PCB_LAYER_ID getNthCopperLayer( int aNum, int aTotal )
728 {
729 if( aNum == 0 )
730 return F_Cu;
731 if( aNum == aTotal - 1 )
732 return B_Cu;
733 return ToLAYER_ID( 2 * ( aNum + 1 ) );
734 }
735
736 static PCB_LAYER_ID getNthUserLayer( int aNum )
737 {
738 aNum = std::min( aNum, MAX_USER_DEFINED_LAYERS - 1 );
739 return ToLAYER_ID( static_cast<int>( User_1 ) + 2 * aNum );
740 }
741
742 PCB_LAYER_ID mapCustomLayer( const LAYER_INFO& aLayerInfo, const wxString& aLayerName )
743 {
744 // See if we have mapped this layer name under a different class:subclass
745 if( m_MappedOptionalLayers.count( aLayerName ) )
746 {
747 const PCB_LAYER_ID existingLId = m_MappedOptionalLayers.at( aLayerName );
748 // Record the reuse
749 m_customLayerToKiMap[aLayerInfo] = existingLId;
750 return existingLId;
751 }
752
753 // First time we needed this name:
754 // Add as a user layer and store for next time
755 const PCB_LAYER_ID lId = addUserLayer( aLayerName );
756 m_customLayerToKiMap[aLayerInfo] = lId;
757 m_MappedOptionalLayers[aLayerName] = lId;
758
759 wxLogTrace( traceAllegroBuilder, "Adding mapping for %#04x:%#04x to %s", aLayerInfo.m_Class,
760 aLayerInfo.m_Subclass, aLayerName );
761 return lId;
762 }
763
770 PCB_LAYER_ID mapCustomLayerByName( const wxString& aLayerName )
771 {
772 // If it's been added already, use it
773 if( m_MappedOptionalLayers.count( aLayerName ) )
774 {
775 return m_MappedOptionalLayers.at( aLayerName );
776 }
777
778 const PCB_LAYER_ID newLId = addUserLayer( aLayerName );
779 m_MappedOptionalLayers[aLayerName] = newLId;
780 return newLId;
781 }
782
783 PCB_LAYER_ID addUserLayer( const wxString& aName )
784 {
786 m_board.GetDesignSettings().SetUserDefinedLayerCount( m_numUserLayersUsed );
787 m_board.SetLayerName( lId, aName );
788 wxLogTrace( traceAllegroBuilder, "Adding user layer %s: %s", LayerName( lId ), aName );
789 return lId;
790 }
791
792 // Map of original layer list - we use this to store the CUSTOM_LAYERs, as well
793 // as check that we only handle each one once
794 std::unordered_map<const BLK_0x2A_LAYER_LIST*, std::vector<CUSTOM_LAYER>> m_Lists;
795
796 // Which classes point to which layer lists (more than one class can point to one list.
797 std::unordered_map<uint8_t, std::vector<CUSTOM_LAYER>*> m_ClassCustomLayerLists;
798
805 std::unordered_map<LAYER_INFO, PCB_LAYER_ID> m_customLayerToKiMap;
806
812 std::unordered_map<wxString, PCB_LAYER_ID> m_MappedOptionalLayers;
813
817 std::unordered_map<LAYER_INFO, PCB_LAYER_ID> m_staticLayerOverrides;
818
823 std::unordered_map<LAYER_INFO, wxString> m_customLayerDialogNames;
824
828 std::unordered_map<LAYER_INFO, int> m_unknownLayers;
829
831
832 // The layer to use for mapping failures;
834
836
839};
840
841
842BOARD_BUILDER::BOARD_BUILDER( const BRD_DB& aRawBoard, BOARD& aBoard, REPORTER& aReporter,
843 PROGRESS_REPORTER* aProgressReporter,
844 const LAYER_MAPPING_HANDLER& aLayerMappingHandler ) :
845 m_brdDb( aRawBoard ), m_board( aBoard ), m_reporter( aReporter ), m_progressReporter( aProgressReporter ),
846 m_layerMappingHandler( aLayerMappingHandler ),
848{
849 // Internal coordinates are always stored in mils / divisor, regardless of the
850 // "board units" flag (which controls UI display only, not internal storage).
851 // 1 mil = 25400 nm (KiCad internal units are nanometers).
852 static constexpr double NM_PER_MIL = 25400.0;
853
854 if( m_brdDb.m_Header->m_UnitsDivisor == 0 )
855 THROW_IO_ERROR( "Board units divisor is 0" );
856
857 m_scale = NM_PER_MIL / m_brdDb.m_Header->m_UnitsDivisor;
858}
859
860
864
865
866static int safeScale( double aValue )
867{
868 double result = std::round( aValue );
869
870 if( result > std::numeric_limits<int>::max() )
871 return std::numeric_limits<int>::max();
872
873 if( result < std::numeric_limits<int>::min() )
874 return std::numeric_limits<int>::min();
875
876 return static_cast<int>( result );
877}
878
879
881{
882 return VECTOR2I{ safeScale( aVector.x * m_scale ), safeScale( -aVector.y * m_scale ) };
883}
884
885int BOARD_BUILDER::scale( int aValue ) const
886{
887 return safeScale( aValue * m_scale );
888}
889
890
892{
893 return VECTOR2I{ safeScale( std::abs( aSize.x ) * m_scale ),
894 safeScale( std::abs( aSize.y ) * m_scale ) };
895}
896
897
898void BOARD_BUILDER::reportMissingBlock( uint32_t aKey, uint8_t aType ) const
899{
900 m_reporter.Report( wxString::Format( "Could not find expected block with key %#010x and type %#04x", aKey, aType ),
902}
903
904
905void BOARD_BUILDER::reportUnexpectedBlockType( uint8_t aGot, uint8_t aExpected, uint32_t aKey, size_t aOffset,
906 const wxString& aName ) const
907{
908 wxString name = aName.IsEmpty() ? wxString( "Object" ) : aName;
909 wxString withKey = ( aKey == 0 ) ? wxString( "" ) : wxString::Format( ", with key %#010x ", aKey );
910 wxString withOffset = ( aOffset == 0 ) ? wxString( "" ) : wxString::Format( ", at offset %#lx ", aOffset );
911
912 wxString s = wxString::Format( "%s has unexpected type %#04x (expected %#04x)%s%s", name, aGot, aExpected, withKey,
913 withOffset );
914
915 m_reporter.Report( s, RPT_SEVERITY_WARNING );
916}
917
918
919wxString BOARD_BUILDER::get0x30StringValue( uint32_t a0x30Key ) const
920{
921 const BLK_0x30_STR_WRAPPER* blk0x30 = expectBlockByKey<BLK_0x30_STR_WRAPPER>( a0x30Key, 0x30 );
922
923 if( blk0x30 == nullptr )
924 THROW_IO_ERROR( "Failed to get 0x30 for string lookup" );
925
927
928 if( blk0x31 == nullptr )
929 THROW_IO_ERROR( "Failed to get 0x31 for string lookup" );
930
931 return blk0x31->m_Value;
932}
933
934
936{
937 LL_WALKER x36_walker{ m_brdDb.m_Header->m_LL_0x36.m_Head, m_brdDb.m_Header->m_LL_0x36.m_Tail, m_brdDb };
938
939 bool encountered = false;
940
941 for( const BLOCK_BASE* block : x36_walker )
942 {
943 if( block->GetBlockType() != 0x36 )
944 continue;
945
946 const BLK_0x36_DEF_TABLE& blk0x36 = static_cast<const BLOCK<BLK_0x36_DEF_TABLE>&>( *block ).GetData();
947
948 if( blk0x36.m_Code != 0x08 )
949 continue;
950
951 if( encountered )
952 {
953 // This would be bad, because we won't get the indexes into the list right if there
954 // it's made up of entries from more than one list of entries.
955 m_reporter.Report( "Found more than one font definition lists in the 0x36 list.", RPT_SEVERITY_WARNING );
956 break;
957 }
958
959 for( const auto& item : blk0x36.m_Items )
960 {
961 const auto& fontDef = std::get<BLK_0x36_DEF_TABLE::FontDef_X08>( item );
962 m_fontDefList.push_back( &fontDef );
963 }
964
965 encountered = true;
966 }
967}
968
969
971{
972 wxLogTrace( traceAllegroBuilder, "Creating nets from Allegro data" );
973
974 // Incrementing netcode. We could also choose to, say, use the 0x1B key if we wanted
975 int netCode = 1;
976
977 std::vector<BOARD_ITEM*> bulkAdded;
978
979 LL_WALKER netWalker{ m_brdDb.m_Header->m_LL_0x1B_Nets, m_brdDb };
980
981 for( const BLOCK_BASE* block : netWalker )
982 {
983 const uint8_t type = block->GetBlockType();
984
985 if( type != BLOCK_TYPE::x1B_NET )
986 {
987 reportUnexpectedBlockType( type, BLOCK_TYPE::x1B_NET, 0, block->GetOffset(), "Net" );
988 continue;
989 }
990
991 const auto& netBlk = static_cast<const BLOCK<BLK_0x1B_NET>&>( *block ).GetData();
992
993 wxString netName = m_brdDb.GetString( netBlk.m_NetName );
994
995 // Allegro allows unnamed nets. KiCad's NETINFO_LIST matches nets by name, and all
996 // empty-named nets would collapse to the unconnected net (code 0). Generate a unique
997 // name so each Allegro net gets its own KiCad net code.
998 if( netName.IsEmpty() )
999 netName = wxString::Format( wxS( "Net_%d" ), netCode );
1000
1001 auto kiNetInfo = std::make_unique<NETINFO_ITEM>( &m_board, netName, netCode );
1002 netCode++;
1003
1004 m_netCache[netBlk.m_Key] = kiNetInfo.get();
1005 bulkAdded.push_back( kiNetInfo.get() );
1006 m_board.Add( kiNetInfo.release(), ADD_MODE::BULK_APPEND );
1007 }
1008
1009 m_board.FinalizeBulkAdd( bulkAdded );
1010
1011 wxLogTrace( traceAllegroBuilder, "Added %zu nets", m_netCache.size() );
1012}
1013
1014
1015wxString BOARD_BUILDER::resolveConstraintSetNameFromField( uint32_t aFieldKey ) const
1016{
1017 const BLOCK_BASE* fieldBlock = m_brdDb.GetObjectByKey( aFieldKey );
1018
1019 if( !fieldBlock || fieldBlock->GetBlockType() != 0x03 )
1020 return wxEmptyString;
1021
1022 const BLK_0x03_FIELD& field = static_cast<const BLOCK<BLK_0x03_FIELD>&>( *fieldBlock ).GetData();
1023 const std::string* str = std::get_if<std::string>( &field.m_Substruct );
1024
1025 if( !str )
1026 return wxEmptyString;
1027
1028 // Extract name from schematic cross-reference format: @lib.xxx(view):\NAME\.
1029 // Find the last colon-backslash separator in the raw std::string and extract from there.
1030 size_t sep = str->find( ":\\" );
1031
1032 if( sep == std::string::npos )
1033 return wxEmptyString;
1034
1035 std::string extracted = str->substr( sep + 2 );
1036
1037 if( !extracted.empty() && extracted.back() == '\\' )
1038 extracted.pop_back();
1039
1040 return wxString( extracted );
1041}
1042
1043
1045{
1046 wxLogTrace( traceAllegroBuilder, "Importing physical constraint sets from 0x1D blocks" );
1047
1048 BOARD_DESIGN_SETTINGS& bds = m_board.GetDesignSettings();
1049 std::shared_ptr<NET_SETTINGS> netSettings = bds.m_NetSettings;
1050
1051 bool isV172Plus = ( m_brdDb.m_FmtVer >= FMT_VER::V_172 );
1052
1053 struct CS_DEF
1054 {
1055 wxString name;
1056 int lineWidth = 0;
1057 int clearance = 0;
1058 int diffPairGap = 0;
1059 };
1060
1061 // Map from constraint set name to its definition
1062 std::map<wxString, CS_DEF> constraintSets;
1063
1064 // Also map string table keys to set names for net lookup
1065 std::map<uint32_t, wxString> keyToSetName;
1066
1067 int csIndex = 0;
1068 const LL_WALKER csWalker( m_brdDb.m_Header->m_LL_0x1D_0x1E_0x1F, m_brdDb );
1069
1070 for( const BLOCK_BASE* block : csWalker )
1071 {
1072 if( block->GetBlockType() != 0x1D )
1073 continue;
1074
1075 const BLK_0x1D_CONSTRAINT_SET& csBlock = static_cast<const BLOCK<BLK_0x1D_CONSTRAINT_SET>&>( *block ).GetData();
1076
1077 wxString setName;
1078 const wxString* resolved = m_brdDb.ResolveString( csBlock.m_NameStrKey );
1079
1080 if( resolved && !resolved->IsEmpty() )
1081 {
1082 setName = *resolved;
1083 }
1084 else if( csBlock.m_FieldPtr != 0 )
1085 {
1086 // Some boards store the name in a 0x03 FIELD block as a schematic cross-reference
1087 setName = resolveConstraintSetNameFromField( csBlock.m_FieldPtr );
1088 }
1089
1090 if( setName.IsEmpty() )
1091 setName = wxString::Format( wxS( "CS_%d" ), csIndex );
1092
1093 csIndex++;
1094
1095 if( csBlock.m_DataB.empty() )
1096 {
1097 wxLogTrace( traceAllegroBuilder, "Constraint set '%s' has no DataB records, skipping", setName );
1098 continue;
1099 }
1100
1101 // Parse first DataB record (first copper layer) as 14 x int32
1102 const auto& record = csBlock.m_DataB[0];
1103 int32_t fields[14];
1104 static_assert( sizeof( fields ) == std::tuple_size_v<std::decay_t<decltype( record )>> );
1105 memcpy( fields, record.data(), sizeof( fields ) );
1106
1107 CS_DEF def;
1108 def.name = setName;
1109
1110 if( isV172Plus )
1111 {
1112 def.lineWidth = scale( fields[1] );
1113 def.clearance = scale( fields[4] );
1114 }
1115 else
1116 {
1117 // Pre-V172: f[0] is preferred line width, f[1] is line spacing (used as clearance).
1118 // f[4] is sometimes also clearance when non-zero, but f[1] is the primary source.
1119 def.lineWidth = scale( fields[0] );
1120 def.clearance = scale( fields[1] );
1121 }
1122
1123 def.diffPairGap = scale( fields[7] );
1124
1125 constraintSets[setName] = def;
1126 keyToSetName[csBlock.m_NameStrKey] = setName;
1127
1128 wxLogTrace( traceAllegroBuilder,
1129 "Constraint set '%s': line_width=%d nm, clearance=%d nm, dp_gap=%d nm",
1130 setName, def.lineWidth, def.clearance, def.diffPairGap );
1131 }
1132
1133 if( constraintSets.empty() )
1134 {
1135 wxLogTrace( traceAllegroBuilder, "No physical constraint sets found" );
1136 return;
1137 }
1138
1139 // Create a netclass for each constraint set that has nonzero values
1140 for( const auto& [name, def] : constraintSets )
1141 {
1142 wxString ncName = name;
1143
1144 if( ncName.CmpNoCase( NETCLASS::Default ) == 0 )
1145 ncName = wxS( "Allegro_Default" );
1146
1147 if( netSettings->HasNetclass( ncName ) )
1148 continue;
1149
1150 auto nc = std::make_shared<NETCLASS>( ncName );
1151
1152 if( def.lineWidth > 0 )
1153 nc->SetTrackWidth( def.lineWidth );
1154
1155 if( def.clearance > 0 )
1156 nc->SetClearance( def.clearance );
1157
1158 if( def.diffPairGap > 0 )
1159 {
1160 nc->SetDiffPairGap( def.diffPairGap );
1161
1162 // Diff pair width is the same as track width for the pair's netclass
1163 if( def.lineWidth > 0 )
1164 nc->SetDiffPairWidth( def.lineWidth );
1165 }
1166
1167 netSettings->SetNetclass( ncName, nc );
1168
1169 wxLogTrace( traceAllegroBuilder, "Created netclass '%s' from constraint set '%s'", ncName, name );
1170 }
1171
1172 // Walk all NETs and assign them to constraint set netclasses via field 0x1a0
1173 wxString defaultSetName;
1174
1175 for( const auto& [name, def] : constraintSets )
1176 {
1177 if( name.CmpNoCase( wxS( "DEFAULT" ) ) == 0 )
1178 {
1179 defaultSetName = name;
1180 break;
1181 }
1182 }
1183
1184 m_brdDb.VisitNets( [&]( const VIEW_OBJS& aView )
1185 {
1186 if( !aView.m_Net )
1187 return;
1188
1189 const NET& net = *aView.m_Net;
1190
1191 // Field 0x1a0 references the constraint set. It can be an integer (string table key
1192 // that matches 0x1D.m_NameStrKey) or a direct string (the constraint set name).
1194
1195 wxString assignedSetName;
1196
1197 if( csField.has_value() )
1198 {
1199 if( auto* intVal = std::get_if<uint32_t>( &csField.value() ) )
1200 {
1201 auto it = keyToSetName.find( *intVal );
1202
1203 if( it != keyToSetName.end() )
1204 assignedSetName = it->second;
1205 }
1206 else if( auto* strVal = std::get_if<wxString>( &csField.value() ) )
1207 {
1208 if( constraintSets.count( *strVal ) )
1209 assignedSetName = *strVal;
1210 }
1211 }
1212
1213 // Nets without field 0x1a0 use the DEFAULT constraint set
1214 if( assignedSetName.IsEmpty() && !defaultSetName.IsEmpty() )
1215 assignedSetName = defaultSetName;
1216
1217 if( assignedSetName.IsEmpty() )
1218 return;
1219
1220 wxString ncName = assignedSetName;
1221
1222 if( ncName.CmpNoCase( NETCLASS::Default ) == 0 )
1223 ncName = wxS( "Allegro_Default" );
1224
1225 if( !netSettings->HasNetclass( ncName ) )
1226 return;
1227
1228 auto netIt = m_netCache.find( net.GetKey() );
1229
1230 if( netIt == m_netCache.end() )
1231 return;
1232
1233 NETINFO_ITEM* kiNet = netIt->second;
1234 netSettings->SetNetclassPatternAssignment( kiNet->GetNetname(), ncName );
1235 kiNet->SetNetClass( netSettings->GetNetClassByName( ncName ) );
1236 } );
1237
1238 wxLogTrace( traceAllegroBuilder, "Applied %zu physical constraint sets", constraintSets.size() );
1239}
1240
1241
1243{
1244 wxLogTrace( traceAllegroBuilder, "Applying per-net trace width constraints" );
1245
1246 BOARD_DESIGN_SETTINGS& bds = m_board.GetDesignSettings();
1247 std::shared_ptr<NET_SETTINGS> netSettings = bds.m_NetSettings;
1248
1249 // Group nets by their minimum trace width to create netclasses.
1250 // Allegro stores per-net min/max trace width in FIELD blocks attached to each NET.
1251 std::map<int, std::vector<uint32_t>> widthToNetKeys;
1252
1253 m_brdDb.VisitNets( [&]( const VIEW_OBJS& aView )
1254 {
1255 if( !aView.m_Net )
1256 return;
1257
1258 std::optional<int> minWidth = aView.m_Net->GetNetMinLineWidth();
1259
1260 if( !minWidth.has_value() || minWidth.value() <= 0 )
1261 return;
1262
1263 int widthNm = scale( minWidth.value() );
1264 widthToNetKeys[widthNm].push_back( aView.m_Net->GetKey() );
1265 } );
1266
1267 if( widthToNetKeys.empty() )
1268 {
1269 wxLogTrace( traceAllegroBuilder, "No per-net trace width constraints found" );
1270 return;
1271 }
1272
1273 for( const auto& [widthNm, netKeys] : widthToNetKeys )
1274 {
1275 int widthMils = ( widthNm + 12700 ) / 25400;
1276 wxString ncName = wxString::Format( wxS( "W%dmil" ), widthMils );
1277
1278 if( netSettings->HasNetclass( ncName ) )
1279 continue;
1280
1281 auto nc = std::make_shared<NETCLASS>( ncName );
1282 nc->SetTrackWidth( widthNm );
1283 netSettings->SetNetclass( ncName, nc );
1284
1285 for( uint32_t netKey : netKeys )
1286 {
1287 auto it = m_netCache.find( netKey );
1288
1289 if( it == m_netCache.end() )
1290 continue;
1291
1292 NETINFO_ITEM* kiNet = it->second;
1293 netSettings->SetNetclassPatternAssignment( kiNet->GetNetname(), ncName );
1294 kiNet->SetNetClass( nc );
1295 }
1296
1297 wxLogTrace( traceAllegroBuilder, "Created netclass '%s' (track width %d nm) with %zu nets",
1298 ncName, widthNm, netKeys.size() );
1299 }
1300
1301 wxLogTrace( traceAllegroBuilder, "Applied trace width constraints from %zu unique width groups",
1302 widthToNetKeys.size() );
1303}
1304
1305
1307{
1308 if( aNet.m_MatchGroupPtr == 0 )
1309 return wxEmptyString;
1310
1311 const BLOCK_BASE* block = m_brdDb.GetObjectByKey( aNet.m_MatchGroupPtr );
1312
1313 if( !block )
1314 return wxEmptyString;
1315
1316 uint32_t tableKey = 0;
1317
1318 if( block->GetBlockType() == 0x26 )
1319 {
1320 // V172+ path: NET -> 0x26 -> m_GroupPtr -> 0x2C TABLE
1321 const auto& x26 = static_cast<const BLOCK<BLK_0x26_MATCH_GROUP>&>( *block ).GetData();
1322 tableKey = x26.m_GroupPtr;
1323
1324 // Some boards have chained 0x26 blocks (m_GroupPtr -> another 0x26 -> 0x2C)
1325 if( tableKey != 0 )
1326 {
1327 const BLOCK_BASE* next = m_brdDb.GetObjectByKey( tableKey );
1328
1329 if( next && next->GetBlockType() == 0x26 )
1330 {
1331 const auto& x26b = static_cast<const BLOCK<BLK_0x26_MATCH_GROUP>&>( *next ).GetData();
1332 tableKey = x26b.m_GroupPtr;
1333 }
1334 }
1335 }
1336 else if( block->GetBlockType() == 0x2C )
1337 {
1338 // Pre-V172 path: NET -> 0x2C TABLE directly
1339 tableKey = aNet.m_MatchGroupPtr;
1340 }
1341 else
1342 {
1343 return wxEmptyString;
1344 }
1345
1346 if( tableKey == 0 )
1347 return wxEmptyString;
1348
1349 // Verify the target is actually a 0x2C TABLE before calling expectBlockByKey to
1350 // avoid noisy warnings on boards with unexpected pointer chain configurations.
1351 const BLOCK_BASE* tableBlock = m_brdDb.GetObjectByKey( tableKey );
1352
1353 if( !tableBlock || tableBlock->GetBlockType() != 0x2C )
1354 return wxEmptyString;
1355
1356 const BLK_0x2C_TABLE* tbl = expectBlockByKey<BLK_0x2C_TABLE>( tableKey, 0x2C );
1357
1358 if( !tbl || tbl->m_StringPtr == 0 )
1359 return wxEmptyString;
1360
1361 const wxString& name = m_brdDb.GetString( tbl->m_StringPtr );
1362 return name;
1363}
1364
1365
1367{
1368 wxLogTrace( traceAllegroBuilder, "Applying match group / differential pair assignments" );
1369
1370 BOARD_DESIGN_SETTINGS& bds = m_board.GetDesignSettings();
1371 std::shared_ptr<NET_SETTINGS> netSettings = bds.m_NetSettings;
1372
1373 // Group NET keys by their match group name
1374 std::map<wxString, std::vector<uint32_t>> groupToNetKeys;
1375
1376 LL_WALKER netWalker{ m_brdDb.m_Header->m_LL_0x1B_Nets, m_brdDb };
1377
1378 for( const BLOCK_BASE* block : netWalker )
1379 {
1380 if( block->GetBlockType() != BLOCK_TYPE::x1B_NET )
1381 continue;
1382
1383 const auto& netBlk = static_cast<const BLOCK<BLK_0x1B_NET>&>( *block ).GetData();
1384 wxString groupName = resolveMatchGroupName( netBlk );
1385
1386 if( groupName.empty() )
1387 continue;
1388
1389 groupToNetKeys[groupName].push_back( netBlk.m_Key );
1390 }
1391
1392 if( groupToNetKeys.empty() )
1393 {
1394 wxLogTrace( traceAllegroBuilder, "No match groups found" );
1395 return;
1396 }
1397
1398 int dpCount = 0;
1399 int mgCount = 0;
1400
1401 for( const auto& [groupName, netKeys] : groupToNetKeys )
1402 {
1403 // A diff pair has exactly 2 nets. We don't check P/N naming because Allegro doesn't
1404 // require any naming convention for paired nets.
1405 bool isDiffPair = ( netKeys.size() == 2 );
1406 wxString ncPrefix = isDiffPair ? wxS( "DP_" ) : wxS( "MG_" );
1407 wxString ncName = ncPrefix + groupName;
1408
1409 if( netSettings->HasNetclass( ncName ) )
1410 continue;
1411
1412 auto nc = std::make_shared<NETCLASS>( ncName );
1413
1414 // Inherit constraint set values from the first net's current netclass so that
1415 // clearance and track width from the underlying constraint set are not lost.
1416 for( uint32_t netKey : netKeys )
1417 {
1418 auto it = m_netCache.find( netKey );
1419
1420 if( it == m_netCache.end() )
1421 continue;
1422
1423 NETCLASS* existing = it->second->GetNetClass();
1424
1425 if( existing && existing->GetName() != NETCLASS::Default )
1426 {
1427 if( existing->HasClearance() )
1428 nc->SetClearance( existing->GetClearance() );
1429
1430 if( existing->HasTrackWidth() )
1431 nc->SetTrackWidth( existing->GetTrackWidth() );
1432
1433 if( existing->HasDiffPairGap() )
1434 nc->SetDiffPairGap( existing->GetDiffPairGap() );
1435
1436 if( existing->HasDiffPairWidth() )
1437 nc->SetDiffPairWidth( existing->GetDiffPairWidth() );
1438
1439 break;
1440 }
1441 }
1442
1443 netSettings->SetNetclass( ncName, nc );
1444
1445 for( uint32_t netKey : netKeys )
1446 {
1447 auto it = m_netCache.find( netKey );
1448
1449 if( it == m_netCache.end() )
1450 continue;
1451
1452 NETINFO_ITEM* kiNet = it->second;
1453 netSettings->SetNetclassPatternAssignment( kiNet->GetNetname(), ncName );
1454 kiNet->SetNetClass( nc );
1455 }
1456
1457 if( isDiffPair )
1458 dpCount++;
1459 else
1460 mgCount++;
1461
1462 wxLogTrace( traceAllegroBuilder, "%s group '%s' -> netclass '%s' with %zu nets",
1463 isDiffPair ? wxS( "Diff pair" ) : wxS( "Match" ),
1464 groupName, ncName, netKeys.size() );
1465 }
1466
1467 wxLogTrace( traceAllegroBuilder,
1468 "Applied match groups: %d diff pairs, %d match groups (%zu total groups)",
1469 dpCount, mgCount, groupToNetKeys.size() );
1470}
1471
1472
1474{
1475 wxLogTrace( traceAllegroBuilder, "Setting up layer mapping from Allegro to KiCad" );
1476
1477 const auto& layerMap = m_brdDb.m_Header->m_LayerMap;
1478
1479 for( size_t i = 0; i < layerMap.size(); ++i )
1480 {
1481 const uint8_t classNum = static_cast<uint8_t>( i );
1482
1483 const uint32_t x2aKey = layerMap[i].m_LayerList0x2A;
1484
1485 if( x2aKey == 0 )
1486 continue;
1487
1488 const BLK_0x2A_LAYER_LIST* layerList = expectBlockByKey<BLK_0x2A_LAYER_LIST>( x2aKey, 0x2A );
1489
1490 // Probably an error
1491 if( !layerList )
1492 continue;
1493
1494 m_layerMapper->ProcessLayerList( classNum, *layerList );
1495 }
1496
1497 m_layerMapper->FinalizeLayers();
1498}
1499
1500
1502{
1503 if( aIndex == 0 || aIndex > m_fontDefList.size() )
1504 {
1505 m_reporter.Report(
1506 wxString::Format( "Font def index %u requested, have %zu entries", aIndex, m_fontDefList.size() ),
1508 return nullptr;
1509 }
1510
1511 // The index appears to be 1-indexed (maybe 0 means something special?)
1512 aIndex -= 1;
1513
1514 return m_fontDefList[aIndex];
1515}
1516
1517
1518std::unique_ptr<PCB_TEXT> BOARD_BUILDER::buildPcbText( const BLK_0x30_STR_WRAPPER& aStrWrapper,
1519 BOARD_ITEM_CONTAINER& aParent )
1520{
1521 std::unique_ptr<PCB_TEXT> text = std::make_unique<PCB_TEXT>( &aParent );
1522
1523 VECTOR2I textPos = scale( VECTOR2I{ aStrWrapper.m_CoordsX, aStrWrapper.m_CoordsY } );
1524 PCB_LAYER_ID layer = getLayer( aStrWrapper.m_Layer );
1525
1526 text->SetPosition( textPos );
1527 text->SetLayer( layer );
1528
1529 const BLK_0x31_SGRAPHIC* strGraphic = expectBlockByKey<BLK_0x31_SGRAPHIC>( aStrWrapper.m_StrGraphicPtr, 0x31 );
1530
1531 if( !strGraphic )
1532 {
1533 m_reporter.Report( wxString::Format( "Failed to find string graphic (0x31) with key %#010x "
1534 "in string wrapper (0x30) with key %#010x",
1535 aStrWrapper.m_StrGraphicPtr, aStrWrapper.m_Key ),
1537 return nullptr;
1538 }
1539
1540 const BLK_0x30_STR_WRAPPER::TEXT_PROPERTIES* props = nullptr;
1541
1542 if( aStrWrapper.m_Font.has_value() )
1543 props = &aStrWrapper.m_Font.value();
1544
1545 if( !props && aStrWrapper.m_Font16x.has_value() )
1546 props = &aStrWrapper.m_Font16x.value();
1547
1548 if( !props )
1549 {
1550 m_reporter.Report(
1551 wxString::Format( "Expected one of the font properties fields in 0x30 object (key %#010x) to be set.",
1552 aStrWrapper.m_Key ),
1554 return nullptr;
1555 }
1556
1557 const BLK_0x36_DEF_TABLE::FontDef_X08* fontDef = getFontDef( props->m_Key );
1558
1559 if( !fontDef )
1560 return nullptr;
1561
1562 text->SetText( strGraphic->m_Value );
1563 text->SetTextWidth( safeScale( m_scale * fontDef->m_CharWidth ) );
1564 text->SetTextHeight( safeScale( m_scale * fontDef->m_CharHeight ) );
1565 text->SetTextThickness( std::max( 1, safeScale( m_scale * fontDef->m_CharHeight ) / 8 ) );
1566
1567 const EDA_ANGLE textAngle{ static_cast<double>( aStrWrapper.m_Rotation ) / 1000.0, DEGREES_T };
1568 text->SetTextAngle( textAngle );
1569
1571 text->SetMirrored( true );
1572
1573 switch( props->m_Alignment )
1574 {
1576 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1577 break;
1579 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
1580 break;
1582 text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
1583 break;
1584 default:
1585 break;
1586 }
1587
1588 return text;
1589}
1590
1591
1593{
1594 return m_layerMapper->GetLayer( aLayerInfo );
1595}
1596
1597
1598std::vector<std::unique_ptr<PCB_SHAPE>> BOARD_BUILDER::buildShapes( const BLK_0x14_GRAPHIC& aGraphic,
1599 BOARD_ITEM_CONTAINER& aParent )
1600{
1601 std::vector<std::unique_ptr<PCB_SHAPE>> shapes;
1602
1603 PCB_LAYER_ID layer = getLayer( aGraphic.m_Layer );
1604
1605 // Within the graphics list, we can get various lines and arcs on PLACE_BOUND_TOP, which
1606 // aren't actually the courtyard, which is a polygon in the 0x28 list. So, if we see such items,
1607 // remap them now to a specific other layer
1608 if( layer == F_CrtYd )
1609 layer = m_layerMapper->GetPlaceBounds( true );
1610 else if( layer == B_CrtYd )
1611 layer = m_layerMapper->GetPlaceBounds( false );
1612 const LL_WALKER segWalker{ aGraphic.m_SegmentPtr, aGraphic.m_Key, m_brdDb };
1613
1614 for( const BLOCK_BASE* segBlock : segWalker )
1615 {
1616 std::unique_ptr<PCB_SHAPE>& shape = shapes.emplace_back( std::make_unique<PCB_SHAPE>( &aParent ) );
1617 shape->SetLayer( layer );
1618
1619 switch( segBlock->GetBlockType() )
1620 {
1621 case 0x01:
1622 {
1623 const auto& arc = static_cast<const BLOCK<BLK_0x01_ARC>&>( *segBlock ).GetData();
1624
1625 VECTOR2I start{ arc.m_StartX, arc.m_StartY };
1626 VECTOR2I end{ arc.m_EndX, arc.m_EndY };
1627
1628 if( layer == Cmts_User )
1629 {
1630 wxLogTrace( traceAllegroBuilder, "Unmapped Arc: %#04x %#04x %s, %s", aGraphic.m_Layer.m_Class,
1631 aGraphic.m_Layer.m_Subclass, start.Format(), end.Format() );
1632 }
1633
1634 start = scale( start );
1635 end = scale( end );
1636
1637 VECTOR2I c = scale( KiROUND( VECTOR2D{ arc.m_CenterX, arc.m_CenterY } ) );
1638
1639 int radius = safeScale( arc.m_Radius * m_scale );
1640
1641 bool clockwise = ( arc.m_SubType & 0x40 ) != 0;
1642
1643 {
1644 int arcWidth = safeScale( m_scale * arc.m_Width );
1645
1646 if( arcWidth <= 0 )
1647 arcWidth = m_board.GetDesignSettings().GetLineThickness( layer );
1648
1649 shape->SetWidth( arcWidth );
1650 }
1651
1652 if( start == end )
1653 {
1654 shape->SetShape( SHAPE_T::CIRCLE );
1655 shape->SetCenter( c );
1656 shape->SetRadius( radius );
1657 }
1658 else
1659 {
1660 shape->SetShape( SHAPE_T::ARC );
1661 EDA_ANGLE startangle( start - c );
1662 EDA_ANGLE endangle( end - c );
1663
1664 startangle.Normalize();
1665 endangle.Normalize();
1666
1667 EDA_ANGLE angle = endangle - startangle;
1668
1669 if( clockwise && angle < ANGLE_0 )
1670 angle += ANGLE_360;
1671 if( !clockwise && angle > ANGLE_0 )
1672 angle -= ANGLE_360;
1673
1674 if( start == end )
1675 angle = -ANGLE_360;
1676
1677 VECTOR2I mid = start;
1678 RotatePoint( mid, c, -angle / 2.0 );
1679
1680 shape->SetArcGeometry( start, mid, end );
1681 }
1682 break;
1683 }
1684 case 0x15:
1685 case 0x16:
1686 case 0x17:
1687 {
1688 shape->SetShape( SHAPE_T::SEGMENT );
1689
1690 const auto& seg = static_cast<const BLOCK<BLK_0x15_16_17_SEGMENT>&>( *segBlock ).GetData();
1691 VECTOR2I start = scale( { seg.m_StartX, seg.m_StartY } );
1692 VECTOR2I end = scale( { seg.m_EndX, seg.m_EndY } );
1693 const int width = static_cast<int>( seg.m_Width );
1694
1695 if( layer == Cmts_User )
1696 {
1697 wxLogTrace( traceAllegroBuilder, "Unmapped Seg: %#04x %#04x %s, %s", aGraphic.m_Layer.m_Class,
1698 aGraphic.m_Layer.m_Subclass, start.Format(), end.Format() );
1699 }
1700
1701 shape->SetStart( start );
1702 shape->SetEnd( end );
1703
1704 {
1705 int scaledWidth = safeScale( width * m_scale );
1706
1707 if( scaledWidth <= 0 )
1708 scaledWidth = m_board.GetDesignSettings().GetLineThickness( layer );
1709
1710 shape->SetWidth( scaledWidth );
1711 }
1712
1713 break;
1714 }
1715 default:
1716 {
1717 break;
1718 }
1719 }
1720 }
1721
1722 return shapes;
1723}
1724
1725
1726std::unique_ptr<PCB_SHAPE> BOARD_BUILDER::buildRect( const BLK_0x24_RECT& aRect, BOARD_ITEM_CONTAINER& aParent )
1727{
1728 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent );
1729
1730 PCB_LAYER_ID layer = getLayer( aRect.m_Layer );
1731 shape->SetLayer( layer );
1732
1733 shape->SetShape( SHAPE_T::RECTANGLE );
1734
1735 const int width = scale( aRect.m_Coords[2] - aRect.m_Coords[0] );
1736 const int height = scale( aRect.m_Coords[3] - aRect.m_Coords[1] );
1737 const VECTOR2I corner = scale( VECTOR2I{ aRect.m_Coords[0], aRect.m_Coords[1] } );
1738
1739 shape->SetStart( corner );
1740 shape->SetEnd( corner + VECTOR2I{ width, -height } );
1741
1742 const int lineWidth = 0;
1743 shape->SetWidth( lineWidth );
1744
1745 return shape;
1746}
1747
1748
1749std::unique_ptr<PCB_SHAPE> BOARD_BUILDER::buildPolygon( const BLK_0x28_SHAPE& aPolygon, BOARD_ITEM_CONTAINER& aParent )
1750{
1751 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent );
1752
1753 PCB_LAYER_ID layer = getLayer( aPolygon.m_Layer );
1754 shape->SetLayer( layer );
1755
1756 shape->SetShape( SHAPE_T::POLY );
1757
1758 SHAPE_LINE_CHAIN chain = buildOutline( aPolygon );
1759
1760 if( chain.PointCount() < 3 )
1761 {
1762 wxLogTrace( traceAllegroBuilder, "Polygon (0x28) with key %#010x has fewer than 3 points, skipping",
1763 aPolygon.m_Key );
1764 return nullptr;
1765 }
1766
1767 chain.SetClosed( true );
1768 shape->SetPolyShape( chain );
1769
1770 const int lineWidth = 0;
1771 shape->SetWidth( lineWidth );
1772
1773 return shape;
1774}
1775
1776
1778{
1779 uint32_t refKey = 0x00;
1780
1781 if( aFpInstance.m_InstRef.has_value() )
1782 refKey = aFpInstance.m_InstRef.value();
1783
1784 if( !refKey && aFpInstance.m_InstRef16x.has_value() )
1785 refKey = aFpInstance.m_InstRef16x.value();
1786
1787 // This can happen, for example for dimension "symbols".
1788 if( refKey == 0 )
1789 return nullptr;
1790
1792 return blk07;
1793}
1794
1795
1796std::vector<std::unique_ptr<BOARD_ITEM>> BOARD_BUILDER::buildPadItems( const BLK_0x1C_PADSTACK& aPadstack,
1797 FOOTPRINT& aFp, const wxString& aPadName, int aNetcode )
1798{
1799 // Not all Allegro PADSTACKS can be represented by a single KiCad pad. For example, the
1800 // paste and mask layers can have completely independent shapes in Allegro, but in KiCad that
1801 // would require a separate aperture pad.
1802 // Also if there are multiple drills, we will need to make a pad for each
1803 std::vector<std::unique_ptr<BOARD_ITEM>> padItems;
1804
1805 std::vector<std::unique_ptr<PADSTACK::COPPER_LAYER_PROPS>> copperLayers( aPadstack.m_LayerCount );
1806
1807 // Thermal relief gap from antipad/pad size difference on the first layer that has both.
1808 std::optional<int> thermalGap;
1809
1810 const wxString& padStackName = m_brdDb.GetString( aPadstack.m_PadStr );
1811
1812 wxLogTrace( traceAllegroBuilder, "Building pad '%s' with %u layers", padStackName, aPadstack.m_LayerCount );
1813
1814 // First, gather all the copper layers into a set of shape props, which we can then use to decide on the padstack mode
1815 for( size_t i = 0; i < aPadstack.m_LayerCount; ++i )
1816 {
1817 const size_t layerBaseIndex = aPadstack.m_NumFixedCompEntries + i * aPadstack.m_NumCompsPerLayer;
1818 const ALLEGRO::PADSTACK_COMPONENT& padComp = aPadstack.m_Components[layerBaseIndex + BLK_0x1C_PADSTACK::LAYER_COMP_SLOT::PAD];
1819 const ALLEGRO::PADSTACK_COMPONENT& antiPadComp = aPadstack.m_Components[layerBaseIndex + BLK_0x1C_PADSTACK::LAYER_COMP_SLOT::ANTIPAD];
1821
1822 // If this is zero just skip entirely - I don't think we can usefully make pads with just thermal relief
1823 // Flag up if that happens.
1825 {
1826 if( antiPadComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL )
1827 {
1828 m_reporter.Report(
1829 wxString::Format( "Padstack %s: Copper layer %zu has no pad component, but has antipad",
1830 padStackName, i ),
1832 }
1833 if( thermalComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL )
1834 {
1835 m_reporter.Report(
1836 wxString::Format( "Copper layer %zu has no pad component, but has thermal relief", i ),
1838 }
1839 continue;
1840 }
1841
1842 auto& layerCuProps = copperLayers[i];
1843 wxLogTrace( traceAllegroBuilder, " Adding copper layer %zu with pad type %d", i, (int) padComp.m_Type );
1844 layerCuProps = std::make_unique<PADSTACK::COPPER_LAYER_PROPS>();
1845
1846 switch( padComp.m_Type )
1847 {
1849 layerCuProps->shape.shape = PAD_SHAPE::RECTANGLE;
1850 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
1851 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
1852 break;
1854 layerCuProps->shape.shape = PAD_SHAPE::RECTANGLE;
1855 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_W } );
1856 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
1857 break;
1859 layerCuProps->shape.shape = PAD_SHAPE::CIRCLE;
1860 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
1861 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
1862 break;
1865 layerCuProps->shape.shape = PAD_SHAPE::OVAL;
1866 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
1867 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
1868 break;
1870 {
1871 layerCuProps->shape.shape = PAD_SHAPE::ROUNDRECT;
1872 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
1873 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
1874
1875 int minDim = std::min( std::abs( padComp.m_W ), std::abs( padComp.m_H ) );
1876
1877 if( padComp.m_Z1.has_value() && padComp.m_Z1.value() > 0 && minDim > 0 )
1878 layerCuProps->shape.round_rect_radius_ratio = padComp.m_Z1.value() / (double) minDim;
1879 else
1880 layerCuProps->shape.round_rect_radius_ratio = 0.25;
1881
1882 break;
1883 }
1885 {
1886 layerCuProps->shape.shape = PAD_SHAPE::CHAMFERED_RECT;
1887 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
1888 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
1889
1890 int minDim = std::min( std::abs( padComp.m_W ), std::abs( padComp.m_H ) );
1891
1892 if( padComp.m_Z1.has_value() && padComp.m_Z1.value() > 0 && minDim > 0 )
1893 layerCuProps->shape.chamfered_rect_ratio = padComp.m_Z1.value() / (double) minDim;
1894 else
1895 layerCuProps->shape.chamfered_rect_ratio = 0.25;
1896
1897 layerCuProps->shape.chamfered_rect_positions = RECT_CHAMFER_ALL;
1898 break;
1899 }
1901 {
1902 // Approximate octagon as a round rectangle with ~29.3% corner radius
1903 // (tan(22.5°) ≈ 0.414, half of that as ratio ≈ 0.207, but visually 0.293 is closer)
1904 layerCuProps->shape.shape = PAD_SHAPE::CHAMFERED_RECT;
1905 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
1906 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
1907 layerCuProps->shape.chamfered_rect_ratio = 1.0 - 1.0 / sqrt( 2.0 );
1908 layerCuProps->shape.chamfered_rect_positions = RECT_CHAMFER_ALL;
1909 break;
1910 }
1912 {
1913 // Custom shape defined by a 0x28 polygon. Walk the shape's segments and build
1914 // a polygon primitive for this pad.
1915 const BLK_0x28_SHAPE* shapeData =
1917
1918 if( !shapeData )
1919 {
1920 wxLogTrace( traceAllegroBuilder,
1921 "Padstack %s: SHAPE_SYMBOL on layer %zu has no 0x28 shape at %#010x",
1922 padStackName, i, padComp.m_StrPtr );
1923 break;
1924 }
1925
1926 SHAPE_LINE_CHAIN outline;
1927 const LL_WALKER segWalker{ shapeData->m_FirstSegmentPtr, shapeData->m_Key, m_brdDb };
1928
1929 for( const BLOCK_BASE* segBlock : segWalker )
1930 {
1931 switch( segBlock->GetBlockType() )
1932 {
1933 case 0x01:
1934 {
1935 const auto& arc =
1936 static_cast<const BLOCK<BLK_0x01_ARC>&>( *segBlock ).GetData();
1937 VECTOR2I start = scale( { arc.m_StartX, arc.m_StartY } );
1938 VECTOR2I end = scale( { arc.m_EndX, arc.m_EndY } );
1940 scale( KiROUND( VECTOR2D{ arc.m_CenterX, arc.m_CenterY } ) );
1941
1942 if( start == end )
1943 {
1944 SHAPE_ARC shapeArc( center, start, ANGLE_360 );
1945 outline.Append( shapeArc );
1946 }
1947 else
1948 {
1949 bool clockwise = ( arc.m_SubType & 0x40 ) != 0;
1950
1951 EDA_ANGLE startAngle( start - center );
1952 EDA_ANGLE endAngle( end - center );
1953 startAngle.Normalize();
1954 endAngle.Normalize();
1955 EDA_ANGLE arcAngle = endAngle - startAngle;
1956
1957 if( clockwise && arcAngle < ANGLE_0 )
1958 arcAngle += ANGLE_360;
1959
1960 if( !clockwise && arcAngle > ANGLE_0 )
1961 arcAngle -= ANGLE_360;
1962
1963 SHAPE_ARC shapeArc( center, start, arcAngle );
1964 outline.Append( shapeArc );
1965 }
1966 break;
1967 }
1968 case 0x15:
1969 case 0x16:
1970 case 0x17:
1971 {
1972 const auto& seg =
1973 static_cast<const BLOCK<BLK_0x15_16_17_SEGMENT>&>( *segBlock )
1974 .GetData();
1975 VECTOR2I start = scale( { seg.m_StartX, seg.m_StartY } );
1976
1977 if( outline.PointCount() == 0 || outline.CLastPoint() != start )
1978 outline.Append( start );
1979
1980 VECTOR2I end = scale( { seg.m_EndX, seg.m_EndY } );
1981 outline.Append( end );
1982 break;
1983 }
1984 default:
1985 break;
1986 }
1987 }
1988
1989 if( outline.PointCount() >= 3 )
1990 {
1991 outline.SetClosed( true );
1992
1993 layerCuProps->shape.shape = PAD_SHAPE::CUSTOM;
1994 layerCuProps->shape.anchor_shape = PAD_SHAPE::CIRCLE;
1995
1996 // Anchor size based on the shape's bounding box center
1997 BOX2I bbox = outline.BBox();
1998 int anchorSize = static_cast<int>(
1999 std::min( bbox.GetWidth(), bbox.GetHeight() ) / 4 );
2000
2001 if( anchorSize < 1 )
2002 anchorSize = 1;
2003
2004 layerCuProps->shape.size = VECTOR2I( anchorSize, anchorSize );
2005
2006 auto poly = std::make_shared<PCB_SHAPE>( nullptr, SHAPE_T::POLY );
2007 poly->SetPolyShape( SHAPE_POLY_SET( outline ) );
2008 poly->SetFilled( true );
2009 poly->SetWidth( 0 );
2010 layerCuProps->custom_shapes.push_back( poly );
2011 }
2012 else
2013 {
2014 wxLogTrace( traceAllegroBuilder,
2015 "Padstack %s: SHAPE_SYMBOL on layer %zu produced only %d points",
2016 padStackName, i, outline.PointCount() );
2017 }
2018
2019 break;
2020 }
2022 {
2023 layerCuProps->shape.shape = PAD_SHAPE::CUSTOM;
2024 layerCuProps->shape.anchor_shape = PAD_SHAPE::CIRCLE;
2025 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
2026
2027 const int w = std::max( padComp.m_W, 300 );
2028 const int h = std::max( padComp.m_H, 220 );
2029
2030 SHAPE_LINE_CHAIN outline;
2031 auto S = [&]( int x, int y )
2032 {
2033 return scale( VECTOR2I{ x, y } );
2034 };
2035
2036 // Regular pentagon with flat bottom edge
2037 outline.Append( S( 0, -h / 2 ) );
2038 outline.Append( S( w / 2, -h / 6 ) );
2039 outline.Append( S( w / 3, h / 2 ) );
2040 outline.Append( S( -w / 3, h / 2 ) );
2041 outline.Append( S( -w / 2, -h / 6 ) );
2042 outline.SetClosed( true );
2043
2044 BOX2I bbox = outline.BBox();
2045 int anchorSize = static_cast<int>(
2046 std::min( bbox.GetWidth(), bbox.GetHeight() ) / 7 );
2047
2048 if( anchorSize < 1 )
2049 anchorSize = 1;
2050
2051 layerCuProps->shape.size = VECTOR2I( anchorSize, anchorSize );
2052
2053 auto poly = std::make_shared<PCB_SHAPE>( nullptr, SHAPE_T::POLY );
2054 poly->SetPolyShape( SHAPE_POLY_SET( outline ) );
2055 poly->SetFilled( true );
2056 poly->SetWidth( 0 );
2057 layerCuProps->custom_shapes.push_back( poly );
2058 break;
2059 }
2060 default:
2061 m_reporter.Report(
2062 wxString::Format( "Padstack %s: unhandled copper pad shape type %d on layer %zu",
2063 padStackName, static_cast<int>( padComp.m_Type ), i ),
2065 break;
2066 }
2067
2068 if( antiPadComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL)
2069 {
2070 if( antiPadComp.m_Type != padComp.m_Type )
2071 {
2072 wxLogTrace( traceAllegroBuilder, "Padstack %s: copper layer %zu antipad shape %d "
2073 "differs from pad shape %d",
2074 padStackName, i, antiPadComp.m_Type, padComp.m_Type );
2075 }
2076
2077 int clearanceX = scale( ( antiPadComp.m_W - padComp.m_W ) / 2 );
2078 int clearanceY = scale( ( antiPadComp.m_H - padComp.m_H ) / 2 );
2079
2080 if( clearanceX && clearanceX != clearanceY )
2081 {
2082 wxLogTrace( traceAllegroBuilder, "Padstack %s: copper layer %zu unequal antipad "
2083 "clearance X=%d Y=%d",
2084 padStackName, i, clearanceX, clearanceY );
2085 }
2086
2087 if( antiPadComp.m_X3 != 0 || antiPadComp.m_X4 != 0 )
2088 {
2089 wxLogTrace( traceAllegroBuilder, "Padstack %s: copper layer %zu antipad offset "
2090 "%d, %d",
2091 padStackName, i, antiPadComp.m_X3, antiPadComp.m_X4 );
2092 }
2093
2094 layerCuProps->clearance = clearanceX;
2095 }
2096
2097 if( thermalComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL && !thermalGap.has_value() )
2098 {
2099 // The thermal gap is the clearance between the pad copper and the surrounding zone.
2100 // We derive it from the antipad-to-pad size difference.
2101 if( antiPadComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL && padComp.m_W > 0 )
2102 {
2103 int gap = scale( ( antiPadComp.m_W - padComp.m_W ) / 2 );
2104
2105 if( gap > 0 )
2106 thermalGap = gap;
2107 }
2108
2109 wxLogTrace( traceAllegroBuilder,
2110 "Padstack %s: thermal relief type=%d, gap=%snm",
2111 padStackName, thermalComp.m_Type,
2112 thermalGap.has_value() ? wxString::Format( "%d", thermalGap.value() )
2113 : wxString( "N/A" ) );
2114 }
2115
2116 // Padstack-level keepouts (antipad relief geometry) are handled via the
2117 // antipad slot in copperLayers above. Board-level keepouts use BLK_0x34.
2118 }
2119
2120 // We have now constructed a list of copper props. We can determine the PADSTACK mode now and assign the shapes
2121 PADSTACK padStack( &aFp );
2122
2123 if( copperLayers.size() == 0 )
2124 {
2125 // SMD aperture PAD or something?
2126 }
2127 else
2128 {
2129 const auto layersEqual = [&](size_t aFrom, size_t aTo) -> bool
2130 {
2131 bool eq = true;
2132 for( size_t i = aFrom + 1; i < aTo; ++i )
2133 {
2134 if( !copperLayers[i - 1] || !copperLayers[i] || *copperLayers[i - 1] != *copperLayers[i] )
2135 {
2136 eq = false;
2137 break;
2138 }
2139 }
2140 return eq;
2141 };
2142
2143 for(size_t i = 0; i < copperLayers.size(); ++i )
2144 {
2145 wxLogTrace( traceAllegroBuilder, " Layer %zu: %s", i, copperLayers[i] ? "present" : "null" );
2146 }
2147
2148 padStack.SetLayerSet( PAD::PTHMask() );
2149
2150 if( copperLayers.front() && copperLayers.back() && layersEqual( 0, copperLayers.size() ) )
2151 {
2152 wxLogTrace( traceAllegroBuilder, " Using NORMAL padstack mode (all layers identical)" );
2153 padStack.SetMode( PADSTACK::MODE::NORMAL );
2154 PADSTACK::COPPER_LAYER_PROPS& layerProps = padStack.CopperLayer( F_Cu );
2155 layerProps = *copperLayers.front();
2156 }
2157 else if( copperLayers.front() && copperLayers.back()
2158 && layersEqual( 1, copperLayers.size() - 1 ) )
2159 {
2160 wxLogTrace( traceAllegroBuilder, " Using FRONT_INNER_BACK padstack mode (inner layers identical)" );
2162 padStack.CopperLayer( F_Cu ) = *copperLayers.front();
2163 padStack.CopperLayer( B_Cu ) = *copperLayers.back();
2164
2165 // May be B_Cu if layers = 2, but that's OK
2166 if( copperLayers.size() > 2 && copperLayers[1] )
2167 padStack.CopperLayer( In1_Cu ) = *copperLayers[1];
2168 }
2169 else
2170 {
2171 wxLogTrace( traceAllegroBuilder, " Using CUSTOM padstack mode (layers differ)" );
2172 padStack.SetMode( PADSTACK::MODE::CUSTOM );
2173
2174 for( size_t i = 0; i < copperLayers.size(); ++i )
2175 {
2176 if( !copperLayers[i] )
2177 continue;
2178
2179 PCB_LAYER_ID layer = F_Cu;
2180
2181 if( i == 0 )
2182 layer = F_Cu;
2183 else if( i == copperLayers.size() - 1 )
2184 layer = B_Cu;
2185 else
2186 layer = ToLAYER_ID( In1_Cu + static_cast<int>( i - 1 ) * 2 );
2187
2188 padStack.CopperLayer( layer ) = *copperLayers[i];
2189 }
2190 }
2191 }
2192
2193 // The drill/slot dimensions are extracted in priority order:
2194 //
2195 // 1. V172+ m_SlotAndUnknownArr[0] and [3] hold the true slot outline dimensions (X and Y).
2196 // For routed slots (round drill bit + routing path), m_DrillArr only has the bit diameter,
2197 // while m_SlotAndUnknownArr has the full slot envelope.
2198 //
2199 // 2. V172+ m_DrillArr[4] (width) and m_DrillArr[7] (height, 0 for round).
2200 // These match m_SlotAndUnknownArr for punched oblong drills but only have the bit
2201 // diameter for routed slots.
2202 //
2203 // 3. Pre-V172 m_Drill field (drill diameter, always round).
2204 int drillW = 0;
2205 int drillH = 0;
2206
2207 if( m_brdDb.m_FmtVer >= FMT_VER::V_172 )
2208 {
2209 if( aPadstack.m_SlotAndUnknownArr.has_value() )
2210 {
2211 const auto& slotArr = aPadstack.m_SlotAndUnknownArr.value();
2212 int slotX = scale( static_cast<int>( slotArr[0] ) );
2213 int slotY = scale( static_cast<int>( slotArr[3] ) );
2214
2215 if( slotX > 0 && slotY > 0 )
2216 {
2217 drillW = slotX;
2218 drillH = slotY;
2219 }
2220 }
2221
2222 if( drillW == 0 )
2223 {
2224 drillW = scale( static_cast<int>( aPadstack.m_DrillArr[4] ) );
2225 drillH = scale( static_cast<int>( aPadstack.m_DrillArr[7] ) );
2226 }
2227 }
2228 else
2229 {
2230 drillW = scale( static_cast<int>( aPadstack.m_Drill ) );
2231 }
2232
2233 if( drillH == 0 )
2234 drillH = drillW;
2235
2236 // Allegro stores slot dimensions as (primary, secondary) regardless of orientation,
2237 // not as (X, Y). Compare the first copper layer pad's aspect ratio to determine if
2238 // the drill needs to be rotated 90 degrees.
2239 if( drillW != drillH && aPadstack.m_LayerCount > 0 )
2240 {
2241 size_t firstCopperIdx = aPadstack.m_NumFixedCompEntries;
2242 const ALLEGRO::PADSTACK_COMPONENT& firstPadComp =
2243 aPadstack.m_Components[firstCopperIdx + BLK_0x1C_PADSTACK::LAYER_COMP_SLOT::PAD];
2244
2245 bool padIsTaller = ( std::abs( firstPadComp.m_H ) > std::abs( firstPadComp.m_W ) );
2246 bool drillIsTaller = ( drillH > drillW );
2247
2248 if( padIsTaller != drillIsTaller )
2249 std::swap( drillW, drillH );
2250 }
2251
2252 bool isSmd = ( drillW == 0 ) || ( aPadstack.m_LayerCount == 1 );
2253
2254 if( isSmd )
2255 {
2256 padStack.Drill().size = VECTOR2I( 0, 0 );
2257 }
2258 else
2259 {
2260 padStack.Drill().size = VECTOR2I( drillW, drillH );
2261
2262 if( drillW != drillH )
2263 padStack.Drill().shape = PAD_DRILL_SHAPE::OBLONG;
2264 else
2265 padStack.Drill().shape = PAD_DRILL_SHAPE::CIRCLE;
2266 }
2267
2268 std::unique_ptr<PAD> pad = std::make_unique<PAD>( &aFp );
2269 pad->SetPadstack( padStack );
2270 pad->SetNumber( aPadName );
2271 pad->SetNetCode( aNetcode );
2272
2273 if( isSmd )
2274 {
2275 pad->SetAttribute( PAD_ATTRIB::SMD );
2276 pad->SetLayerSet( PAD::SMDMask() );
2277 }
2278 else
2279 {
2280 pad->SetAttribute( PAD_ATTRIB::PTH );
2281 pad->SetLayerSet( PAD::PTHMask() );
2282 }
2283
2284 if( thermalGap.has_value() )
2285 pad->SetThermalGap( thermalGap.value() );
2286
2287 padItems.push_back( std::move( pad ) );
2288
2289 // Now, for each technical layer, we see if we can include it into the existing padstack, or if we need to add
2290 // it as a standalone pad
2291 for( size_t i = 0; i < aPadstack.m_NumFixedCompEntries; ++i )
2292 {
2293 const ALLEGRO::PADSTACK_COMPONENT& psComp = aPadstack.m_Components[i];
2294
2297 continue;
2298
2299 // All fixed slots are technical layers (solder mask, paste mask, film mask,
2300 // assembly variant, etc). Custom mask expansion extraction is not yet implemented;
2301 // KiCad's default pad-matches-mask behavior applies.
2302 wxLogTrace( traceAllegroBuilder,
2303 "Fixed padstack slot %zu: type=%d, W=%d, H=%d",
2304 i, static_cast<int>( psComp.m_Type ), psComp.m_W, psComp.m_H );
2305 }
2306
2307 return padItems;
2308}
2309
2310
2311std::unique_ptr<FOOTPRINT> BOARD_BUILDER::buildFootprint( const BLK_0x2D_FOOTPRINT_INST& aFpInstance )
2312{
2313 auto fp = std::make_unique<FOOTPRINT>( &m_board );
2314
2315 const BLK_0x07_COMPONENT_INST* fpInstData = getFpInstRef( aFpInstance );
2316
2317 wxLogTrace( traceAllegroBuilder, "Building footprint from 0x2D block key %#010x", aFpInstance.m_Key );
2318
2319 wxString refDesStr;
2320 if( fpInstData )
2321 {
2322 refDesStr = m_brdDb.GetString( fpInstData->m_RefDesStrPtr );
2323
2324 if( refDesStr.IsEmpty() )
2325 {
2326 // Does this happen even when there's an 0x07 block?
2327 m_reporter.Report( wxString::Format( "Empty ref des for 0x2D key %#010x", aFpInstance.m_Key ),
2329 }
2330 }
2331
2332 // We may update the PCB_FIELD layer if it's specified explicitly (e.g. with font size and so on),
2333 // but if not, set the refdes at least, but make it invisible
2334 fp->SetReference( refDesStr );
2335 fp->GetField( FIELD_T::REFERENCE )->SetVisible( false );
2336
2337 wxLogTrace( traceAllegroBuilder, " Footprint reference: '%s'", refDesStr );
2338
2339 const VECTOR2I fpPos = scale( VECTOR2I{ aFpInstance.m_CoordX, aFpInstance.m_CoordY } );
2340 const EDA_ANGLE rotation{ aFpInstance.m_Rotation / 1000., DEGREES_T };
2341
2342 fp->SetPosition( fpPos );
2343 fp->SetOrientation( rotation );
2344
2345 // Allegro stores placed instance data in board-absolute form: bottom-side
2346 // components already have shapes on bottom layers with bottom-side positions.
2347 // Allegro stores placed footprints in board-absolute form with final layers.
2348 // KiCad stores footprints in canonical front-side form and uses Flip() to
2349 // mirror both positions and layers to the back side.
2350 //
2351 // Move back-layer items to their front-side counterpart so that fp->Flip()
2352 // consistently mirrors positions AND layers for all children. Without this,
2353 // bottom-side footprints would have their back-layer graphics double-flipped
2354 // to the front.
2355 const auto canonicalizeLayer = []( BOARD_ITEM* aItem )
2356 {
2357 if( IsBackLayer( aItem->GetLayer() ) )
2358 aItem->SetLayer( FlipLayer( aItem->GetLayer() ) );
2359 };
2360
2361 const LL_WALKER graphicsWalker{ aFpInstance.m_GraphicPtr, aFpInstance.m_Key, m_brdDb };
2362
2363 for( const BLOCK_BASE* graphicsBlock : graphicsWalker )
2364 {
2365 const uint8_t type = graphicsBlock->GetBlockType();
2366
2367 if( type == 0x14 )
2368 {
2369 const auto& graphics = static_cast<const BLOCK<BLK_0x14_GRAPHIC>&>( *graphicsBlock ).GetData();
2370
2371 std::vector<std::unique_ptr<PCB_SHAPE>> shapes = buildShapes( graphics, *fp );
2372
2373 for( std::unique_ptr<PCB_SHAPE>& shape : shapes )
2374 {
2375 canonicalizeLayer( shape.get() );
2376 fp->Add( shape.release() );
2377 }
2378 }
2379 else
2380 {
2381 m_reporter.Report( wxString::Format( "Unexpected type in graphics list: %#04x", type ),
2383 }
2384 }
2385
2386 bool valueFieldSet = false;
2387
2388 const LL_WALKER textWalker{ aFpInstance.m_TextPtr, aFpInstance.m_Key, m_brdDb };
2389
2390 for( const BLOCK_BASE* textBlock : textWalker )
2391 {
2392 const uint8_t type = textBlock->GetBlockType();
2393
2394 if( type != 0x30 )
2395 continue;
2396
2397 const auto& strWrapper = static_cast<const BLOCK<BLK_0x30_STR_WRAPPER>&>( *textBlock ).GetData();
2398
2399 std::unique_ptr<PCB_TEXT> text = buildPcbText( strWrapper, *fp );
2400
2401 if( !text )
2402 continue;
2403
2404 canonicalizeLayer( text.get() );
2405
2406 const uint8_t textClass = strWrapper.m_Layer.m_Class;
2407 const uint8_t textSubclass = strWrapper.m_Layer.m_Subclass;
2408
2409 bool isSilk = ( textSubclass == LAYER_INFO::SUBCLASS::SILKSCREEN_TOP
2410 || textSubclass == LAYER_INFO::SUBCLASS::SILKSCREEN_BOTTOM );
2411 bool isAssembly = ( textSubclass == LAYER_INFO::SUBCLASS::ASSEMBLY_TOP
2412 || textSubclass == LAYER_INFO::SUBCLASS::ASSEMBLY_BOTTOM );
2413
2414 if( textClass == LAYER_INFO::CLASS::REF_DES && isSilk )
2415 {
2416 // Visible silkscreen refdes updates the built-in REFERENCE field.
2417 PCB_FIELD* const refDes = fp->GetField( FIELD_T::REFERENCE );
2418
2419 // KiCad netlisting requires non-digit + digit annotation.
2420 if( !text->GetText().IsEmpty() && !wxIsalpha( text->GetText()[0] ) )
2421 text->SetText( wxString( "UNK" ) + text->GetText() );
2422
2423 *refDes = PCB_FIELD( *text, FIELD_T::REFERENCE );
2424 }
2425 else if( textClass == LAYER_INFO::CLASS::REF_DES && isAssembly )
2426 {
2427 // Assembly refdes becomes a user field with the KiCad reference variable
2428 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "Reference" ) );
2429 field->SetText( wxS( "${REFERENCE}" ) );
2430 field->SetVisible( false );
2431 fp->Add( field, ADD_MODE::APPEND );
2432 }
2433 else if( textClass == LAYER_INFO::CLASS::COMPONENT_VALUE && isAssembly )
2434 {
2435 if( !valueFieldSet )
2436 {
2437 // First COMPONENT_VALUE on assembly updates the built-in VALUE field
2438 PCB_FIELD* valField = fp->GetField( FIELD_T::VALUE );
2439 *valField = PCB_FIELD( *text, FIELD_T::VALUE );
2440 valField->SetVisible( false );
2441 valueFieldSet = true;
2442 }
2443 else
2444 {
2445 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "Component Value" ) );
2446 field->SetVisible( false );
2447 fp->Add( field, ADD_MODE::APPEND );
2448 }
2449 }
2450 else if( textClass == LAYER_INFO::CLASS::DEVICE_TYPE )
2451 {
2452 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "Device Type" ) );
2453 field->SetVisible( !isAssembly );
2454 fp->Add( field, ADD_MODE::APPEND );
2455 }
2456 else if( textClass == LAYER_INFO::CLASS::TOLERANCE )
2457 {
2458 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "Tolerance" ) );
2459 field->SetVisible( isSilk );
2460 fp->Add( field, ADD_MODE::APPEND );
2461 }
2462 else if( textClass == LAYER_INFO::CLASS::USER_PART_NUMBER )
2463 {
2464 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "User Part Number" ) );
2465 field->SetVisible( isSilk );
2466 fp->Add( field, ADD_MODE::APPEND );
2467 }
2468 else if( textClass == LAYER_INFO::CLASS::COMPONENT_VALUE && isSilk )
2469 {
2470 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "Component Value" ) );
2471 field->SetVisible( true );
2472 fp->Add( field, ADD_MODE::APPEND );
2473 }
2474 else
2475 {
2476 fp->Add( text.release() );
2477 }
2478 }
2479
2480 // Assembly drawing
2481 LL_WALKER assemblyWalker{ aFpInstance.m_AssemblyPtr, aFpInstance.m_Key, m_brdDb };
2482
2483 for( const BLOCK_BASE* assemblyBlock : assemblyWalker )
2484 {
2485 const uint8_t type = assemblyBlock->GetBlockType();
2486 std::unique_ptr<BOARD_ITEM> item;
2487
2488 switch( type )
2489 {
2490 case 0x24:
2491 {
2492 const auto& rect = static_cast<const BLOCK<BLK_0x24_RECT>&>( *assemblyBlock ).GetData();
2493 std::unique_ptr<PCB_SHAPE> shape = buildRect( rect, *fp );
2494
2495 shape->Rotate( shape->GetStart(), fp->GetOrientation() );
2496 item = std::move( shape );
2497 break;
2498 }
2499 case 0x28:
2500 {
2501 const auto& polygon = static_cast<const BLOCK<BLK_0x28_SHAPE>&>( *assemblyBlock ).GetData();
2502 item = buildPolygon( polygon, *fp );
2503 break;
2504 }
2505 default:
2506 break;
2507 }
2508
2509 if( item )
2510 {
2511 canonicalizeLayer( item.get() );
2512 fp->Add( item.release() );
2513 }
2514 }
2515
2516 // Areas (courtyards, etc)
2517 LL_WALKER areaWalker{ aFpInstance.m_AreasPtr, aFpInstance.m_Key, m_brdDb };
2518 for( const BLOCK_BASE* areaBlock : areaWalker )
2519 {
2520 const uint8_t type = areaBlock->GetBlockType();
2521 std::unique_ptr<BOARD_ITEM> item;
2522
2523 switch( type )
2524 {
2525 case 0x0E:
2526 {
2527 // These exist sometimes in this list, but not too clear how they work yet.
2528 const auto& shapeSeg = static_cast<const BLOCK<BLK_0x0E_SHAPE_SEG>&>( *areaBlock ).GetData();
2529
2530 wxLogTrace( traceAllegroBuilder, "Footprint area with 0x0E shape segment in %s: layer=%s",
2531 fp->Reference().GetText(), layerInfoDisplayName( shapeSeg.m_Layer ) );
2532 break;
2533 }
2534 case 0x28:
2535 {
2536 const auto& polygon = static_cast<const BLOCK<BLK_0x28_SHAPE>&>( *areaBlock ).GetData();
2537 item = buildPolygon( polygon, *fp );
2538 break;
2539 }
2540 default:
2541 break;
2542 }
2543
2544 if( item )
2545 {
2546 canonicalizeLayer( item.get() );
2547 fp->Add( item.release() );
2548 }
2549 }
2550
2551 // Find the pads
2552 LL_WALKER padWalker{ aFpInstance.m_FirstPadPtr, aFpInstance.m_Key, m_brdDb };
2554 for( const BLOCK_BASE* padBlock : padWalker )
2555 {
2556 const auto& placedPadInfo = static_cast<const BLOCK<BLK_0x32_PLACED_PAD>&>( *padBlock ).GetData();
2557
2558 const BLK_0x04_NET_ASSIGNMENT* netAssignment =
2559 expectBlockByKey<BLK_0x04_NET_ASSIGNMENT>( placedPadInfo.m_NetPtr, 0x04 );
2560 const BLK_0x0D_PAD* padInfo = expectBlockByKey<BLK_0x0D_PAD>( placedPadInfo.m_PadPtr, 0x0D );
2561
2562 if( !netAssignment || !padInfo )
2563 continue;
2564
2565 const BLK_0x1C_PADSTACK* padStack = expectBlockByKey<BLK_0x1C_PADSTACK>( padInfo->m_PadStack, 0x1C );
2566
2567 if( !padStack )
2568 continue;
2569
2570 auto netIt = m_netCache.find( netAssignment->m_Net );
2571 const int netCode = ( netIt != m_netCache.end() ) ? netIt->second->GetNetCode()
2573 const wxString padName = m_brdDb.GetString( padInfo->m_NameStrId );
2574
2575 // 0x0D coordinates and rotation are in the footprint's local (unrotated) space.
2576 // Use SetFPRelativePosition/Orientation to let KiCad handle the transform to
2577 // board-absolute coordinates (rotating by FP orientation and adding FP position).
2578 const VECTOR2I padLocalPos = scale( VECTOR2I{ padInfo->m_CoordsX, padInfo->m_CoordsY } );
2579 const EDA_ANGLE padLocalRot{ static_cast<double>( padInfo->m_Rotation ) / 1000.0, DEGREES_T };
2580
2581 std::vector<std::unique_ptr<BOARD_ITEM>> padItems = buildPadItems( *padStack, *fp, padName, netCode );
2582
2583 for( std::unique_ptr<BOARD_ITEM>& item : padItems )
2584 {
2585 if( item->Type() == PCB_PAD_T )
2586 {
2587 PAD* pad = static_cast<PAD*>( item.get() );
2588 pad->SetFPRelativeOrientation( padLocalRot );
2589 }
2590
2591 item->SetFPRelativePosition( padLocalPos );
2592 fp->Add( item.release() );
2593 }
2594 }
2595
2596 // Flip AFTER adding all children so that graphics, text, and pads all get
2597 // their layers and positions mirrored correctly for bottom-layer footprints.
2598 //
2599 // Allegro mirrors bottom-side components via X-mirror (flip around Y axis),
2600 // then rotates by R. KiCad's Flip(TOP_BOTTOM) is a Y-mirror which negates
2601 // the orientation. Since X-mirror = Y-mirror + Rotate(180), we need the
2602 // final orientation to be R+180. Flip negates what we set before it, so
2603 // set -(R+180) to get R+180 after negation.
2604 if( aFpInstance.m_Layer != 0 )
2605 {
2606 fp->SetOrientation( -rotation - ANGLE_180 );
2607 fp->Flip( fpPos, FLIP_DIRECTION::TOP_BOTTOM );
2608 }
2609
2610 return fp;
2611}
2612
2613std::vector<std::unique_ptr<BOARD_ITEM>> BOARD_BUILDER::buildTrack( const BLK_0x05_TRACK& aTrackBlock, int aNetCode )
2614{
2615 std::vector<std::unique_ptr<BOARD_ITEM>> items;
2616
2617 // Anti-etch tracks are thermal relief patterns generated by Allegro.
2618 // These are handled by pad-level thermal relief properties instead.
2619 if( aTrackBlock.m_Layer.m_Class == LAYER_INFO::CLASS::ANTI_ETCH )
2620 {
2621 wxLogTrace( traceAllegroBuilder, "Skipping ANTI_ETCH track (class=%#04x, subclass=%#04x)",
2622 aTrackBlock.m_Layer.m_Class, aTrackBlock.m_Layer.m_Subclass );
2623 return items;
2624 }
2625
2626 const PCB_LAYER_ID layer = getLayer( aTrackBlock.m_Layer );
2627
2628 LL_WALKER segWalker{ aTrackBlock.m_FirstSegPtr, aTrackBlock.m_Key, m_brdDb };
2629 for( const BLOCK_BASE* block : segWalker )
2630 {
2631 const uint8_t segType = block->GetBlockType();
2632
2633 switch( segType )
2634 {
2635 case 0x15:
2636 case 0x16:
2637 case 0x17:
2638 {
2639 const BLK_0x15_16_17_SEGMENT& segInfo =
2640 static_cast<const BLOCK<BLK_0x15_16_17_SEGMENT>&>( *block ).GetData();
2641
2642 VECTOR2I start{ segInfo.m_StartX, segInfo.m_StartY };
2643 VECTOR2I end{ segInfo.m_EndX, segInfo.m_EndY };
2644 int width = static_cast<int>( segInfo.m_Width );
2645
2646 std::unique_ptr<PCB_TRACK> seg = std::make_unique<PCB_TRACK>( &m_board );
2647
2648 seg->SetNetCode( aNetCode );
2649 seg->SetLayer( layer );
2650
2651 seg->SetStart( scale( start ) );
2652 seg->SetEnd( scale( end ) );
2653 seg->SetWidth( scale( width ) );
2654
2655 items.push_back( std::move( seg ) );
2656 break;
2657 }
2658 case 0x01:
2659 {
2660 const BLK_0x01_ARC& arcInfo = static_cast<const BLOCK<BLK_0x01_ARC>&>( *block ).GetData();
2661
2662 VECTOR2I start = scale( { arcInfo.m_StartX, arcInfo.m_StartY } );
2663 VECTOR2I end = scale( { arcInfo.m_EndX, arcInfo.m_EndY } );
2664 VECTOR2I c = scale( KiROUND( VECTOR2D{ arcInfo.m_CenterX, arcInfo.m_CenterY } ) );
2665 int width = scale( static_cast<int>( arcInfo.m_Width ) );
2666
2667 bool clockwise = ( arcInfo.m_SubType & 0x40 ) != 0;
2668
2669 EDA_ANGLE startAngle( start - c );
2670 EDA_ANGLE endAngle( end - c );
2671 startAngle.Normalize();
2672 endAngle.Normalize();
2673
2674 EDA_ANGLE angle = endAngle - startAngle;
2675
2676 if( clockwise && angle < ANGLE_0 )
2677 angle += ANGLE_360;
2678
2679 if( !clockwise && angle > ANGLE_0 )
2680 angle -= ANGLE_360;
2681
2682 VECTOR2I mid = start;
2683 RotatePoint( mid, c, -angle / 2.0 );
2684
2685 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( &m_board );
2686
2687 arc->SetNetCode( aNetCode );
2688 arc->SetLayer( layer );
2689
2690 arc->SetStart( start );
2691 arc->SetMid( mid );
2692 arc->SetEnd( end );
2693 arc->SetWidth( width );
2694
2695 items.push_back( std::move( arc ) );
2696 break;
2697 }
2698 default:
2699 wxLogTrace( traceAllegroBuilder, "Unhandled segment type in track: %#04x", segType );
2700 break;
2701 }
2702 }
2703 return items;
2704}
2705
2706
2707std::unique_ptr<BOARD_ITEM> BOARD_BUILDER::buildVia( const BLK_0x33_VIA& aViaData, int aNetCode )
2708{
2709 VECTOR2I viaPos{ aViaData.m_CoordsX, aViaData.m_CoordsY };
2710
2711 const BLK_0x1C_PADSTACK* viaPadstack = expectBlockByKey<BLK_0x1C_PADSTACK>( aViaData.m_Padstack, 0x1C );
2712
2713 if( !viaPadstack )
2714 return nullptr;
2715
2716 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( &m_board );
2717 via->SetPosition( scale( viaPos ) );
2718 via->SetNetCode( aNetCode );
2719
2720 via->SetTopLayer( F_Cu );
2721 via->SetBottomLayer( B_Cu );
2722
2723 // Extract via size from the first copper layer's pad component
2724 int viaWidth = 0;
2725
2726 if( viaPadstack->m_LayerCount > 0 )
2727 {
2728 const size_t layerBaseIndex = viaPadstack->m_NumFixedCompEntries;
2729 const ALLEGRO::PADSTACK_COMPONENT& padComp =
2730 viaPadstack->m_Components[layerBaseIndex + BLK_0x1C_PADSTACK::LAYER_COMP_SLOT::PAD];
2731
2732 if( padComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL )
2733 {
2734 viaWidth = scale( padComp.m_W );
2735 }
2736 }
2737
2738 int viaDrill = 0;
2739
2740 if( m_brdDb.m_FmtVer >= FMT_VER::V_172 )
2741 viaDrill = scale( static_cast<int>( viaPadstack->m_DrillArr[4] ) );
2742 else
2743 viaDrill = scale( static_cast<int>( viaPadstack->m_Drill ) );
2744
2745 if( viaDrill == 0 )
2746 {
2747 viaDrill = viaWidth / 2;
2748 wxLogTrace( traceAllegroBuilder, "Via at (%d, %d): no drill in padstack, using fallback %d",
2749 aViaData.m_CoordsX, aViaData.m_CoordsY, viaDrill );
2750 }
2751
2752 if( viaWidth <= 0 )
2753 {
2754 wxLogTrace( traceAllegroBuilder, "Via at (%d, %d) has no valid pad component, using drill-based fallback",
2755 aViaData.m_CoordsX, aViaData.m_CoordsY );
2756 viaWidth = viaDrill * 2;
2757 }
2758
2759 via->SetWidth( F_Cu, viaWidth );
2760 via->SetDrill( viaDrill );
2761
2762 return via;
2763}
2764
2765
2767{
2768 wxLogTrace( traceAllegroBuilder, "Creating tracks, vias, and other routed items" );
2769
2770 std::vector<BOARD_ITEM*> newItems;
2771
2772 // We need to walk this list again - we could do this all in createNets, but this seems tidier.
2773 LL_WALKER netWalker{ m_brdDb.m_Header->m_LL_0x1B_Nets, m_brdDb };
2774 for( const BLOCK_BASE* block : netWalker )
2775 {
2776 const uint8_t type = block->GetBlockType();
2777 if( type != BLOCK_TYPE::x1B_NET )
2778 {
2779 reportUnexpectedBlockType( type, BLOCK_TYPE::x1B_NET, 0, block->GetOffset(), "Net" );
2780 continue;
2781 }
2782
2783 const auto& net = static_cast<const BLOCK<BLK_0x1B_NET>&>( *block ).GetData();
2784
2785 auto netIt = m_netCache.find( net.m_Key );
2786
2787 if( netIt == m_netCache.end() )
2788 continue;
2789
2790 const int netCode = netIt->second->GetNetCode();
2791
2792 LL_WALKER assignmentWalker{ net.m_Assignment, net.m_Key, m_brdDb };
2793 for( const BLOCK_BASE* assignBlock : assignmentWalker )
2794 {
2795 if( assignBlock->GetBlockType() != 0x04 )
2796 {
2797 reportUnexpectedBlockType( assignBlock->GetBlockType(), 0x04, 0, block->GetOffset(), "Net assignment" );
2798 continue;
2799 }
2800
2801 const auto& assign = static_cast<const BLOCK<BLK_0x04_NET_ASSIGNMENT>&>( *assignBlock ).GetData();
2802
2803 // Walk the 0x05/0x32/... list
2804 LL_WALKER connWalker{ assign.m_ConnItem, assign.m_Key, m_brdDb };
2805 for( const BLOCK_BASE* connItemBlock : connWalker )
2806 {
2807 const uint8_t connType = connItemBlock->GetBlockType();
2808
2809 // One connected item can be multiple KiCad objects, e.g.
2810 // 0x05 track -> list of segments/arcs
2811 std::vector<std::unique_ptr<BOARD_ITEM>> newItemList;
2812
2813 switch( connType )
2814 {
2815 // Track
2816 case 0x05:
2817 {
2818 const BLK_0x05_TRACK& trackData =
2819 static_cast<const BLOCK<BLK_0x05_TRACK>&>( *connItemBlock ).GetData();
2820 newItemList = buildTrack( trackData, netCode );
2821 break;
2822 }
2823 case 0x33:
2824 {
2825 const BLK_0x33_VIA& viaData = static_cast<const BLOCK<BLK_0x33_VIA>&>( *connItemBlock ).GetData();
2826 newItemList.push_back( buildVia( viaData, netCode ) );
2827 break;
2828 }
2829 case 0x32:
2830 {
2831 // This is a pad in a footprint - we don't need to handle this here, as we do all the footprint
2832 // pads, connected or not, in the footprint step.
2833 break;
2834 }
2835 case 0x28:
2836 {
2837 // 0x28 shapes on the net chain are computed copper fills.
2838 // Collect them for zone net fallback and fill polygon import.
2839 const BLK_0x28_SHAPE& fillShape =
2840 static_cast<const BLOCK<BLK_0x28_SHAPE>&>( *connItemBlock ).GetData();
2841
2842 PCB_LAYER_ID fillLayer = getLayer( fillShape.m_Layer );
2843
2844 if( fillLayer != UNDEFINED_LAYER )
2845 m_zoneFillShapes.push_back( { &fillShape, netCode, fillLayer } );
2846
2847 break;
2848 }
2849 case 0x2E:
2850 default:
2851 {
2852 wxLogTrace( traceAllegroBuilder, " Unhandled connected item code: %#04x",
2853 (int) connType );
2854 }
2855 }
2856
2857 for( std::unique_ptr<BOARD_ITEM>& newItem : newItemList )
2858 {
2859 newItems.push_back( newItem.get() );
2860 m_board.Add( newItem.release(), ADD_MODE::BULK_APPEND );
2861 }
2862 }
2863 }
2864 }
2865
2866 m_board.FinalizeBulkAdd( newItems );
2867
2868 wxLogTrace( traceAllegroBuilder, "Finished creating %zu track/via items", newItems.size() );
2869}
2870
2871
2873{
2874 wxLogTrace( traceAllegroBuilder, "Creating board outline" );
2875
2876 std::vector<BOARD_ITEM*> outlineItems;
2877
2878 // Track unique line segments to avoid duplicates. Some Allegro files store the board
2879 // outline on both BOARD_GEOMETRY:OUTLINE and DRAWING_FORMAT:OUTLINE layers, which
2880 // produces duplicate (sometimes direction-reversed) edges.
2881 std::set<std::tuple<int, int, int, int>> uniqueSegments;
2882
2883 auto isNewSegment = [&]( const VECTOR2I& a, const VECTOR2I& b ) -> bool
2884 {
2885 VECTOR2I p1 = a, p2 = b;
2886
2887 if( p1.x > p2.x || ( p1.x == p2.x && p1.y > p2.y ) )
2888 std::swap( p1, p2 );
2889
2890 return uniqueSegments.emplace( p1.x, p1.y, p2.x, p2.y ).second;
2891 };
2892
2893 // Walk through LL_0x24_0x28 which contains rectangles (0x24) and shapes (0x28)
2894 const LL_WALKER shapeWalker( m_brdDb.m_Header->m_LL_0x24_0x28, m_brdDb );
2895 int shapeCount = 0;
2896
2897 for( const BLOCK_BASE* block : shapeWalker )
2898 {
2899 if( block->GetBlockType() == 0x24 )
2900 {
2901 const BLK_0x24_RECT& rectData = static_cast<const BLOCK<BLK_0x24_RECT>&>( *block ).GetData();
2902
2903 if( !m_layerMapper->IsOutlineLayer( rectData.m_Layer ) )
2904 continue;
2905
2906 shapeCount++;
2907
2908 // Create 4 segments from the rectangle coordinates
2909 // Coords are: [0]=left, [1]=bottom, [2]=right, [3]=top
2910 VECTOR2I bl = scale( { rectData.m_Coords[0], rectData.m_Coords[1] } );
2911 VECTOR2I tr = scale( { rectData.m_Coords[2], rectData.m_Coords[3] } );
2912 VECTOR2I br = scale( { rectData.m_Coords[2], rectData.m_Coords[1] } );
2913 VECTOR2I tl = scale( { rectData.m_Coords[0], rectData.m_Coords[3] } );
2914 int width = m_board.GetDesignSettings().GetLineThickness( Edge_Cuts );
2915
2916 auto makeSegment = [&]( const VECTOR2I& start, const VECTOR2I& end )
2917 {
2918 if( !isNewSegment( start, end ) )
2919 return;
2920
2921 auto shape = std::make_unique<PCB_SHAPE>( &m_board );
2922 shape->SetLayer( Edge_Cuts );
2923 shape->SetShape( SHAPE_T::SEGMENT );
2924 shape->SetStart( start );
2925 shape->SetEnd( end );
2926 shape->SetWidth( width );
2927 outlineItems.push_back( shape.get() );
2928 m_board.Add( shape.release(), ADD_MODE::BULK_APPEND );
2929 };
2930
2931 makeSegment( bl, br );
2932 makeSegment( br, tr );
2933 makeSegment( tr, tl );
2934 makeSegment( tl, bl );
2935 continue;
2936 }
2937
2938 if( block->GetBlockType() != 0x28 )
2939 continue;
2940
2941 const BLK_0x28_SHAPE& shapeData = static_cast<const BLOCK<BLK_0x28_SHAPE>&>( *block ).GetData();
2942
2943 if( !m_layerMapper->IsOutlineLayer( shapeData.m_Layer ) )
2944 continue;
2945
2946 shapeCount++;
2947
2948 // Walk the segments in this shape and create PCB_SHAPE objects on Edge_Cuts
2949 const LL_WALKER segWalker{ shapeData.m_FirstSegmentPtr, shapeData.m_Key, m_brdDb };
2950
2951 for( const BLOCK_BASE* segBlock : segWalker )
2952 {
2953 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &m_board );
2954 shape->SetLayer( Edge_Cuts );
2955
2956 switch( segBlock->GetBlockType() )
2957 {
2958 case 0x01:
2959 {
2960 const auto& arc = static_cast<const BLOCK<BLK_0x01_ARC>&>( *segBlock ).GetData();
2961
2962 VECTOR2I start = scale( { arc.m_StartX, arc.m_StartY } );
2963 VECTOR2I end = scale( { arc.m_EndX, arc.m_EndY } );
2964 VECTOR2I c = scale( KiROUND( VECTOR2D{ arc.m_CenterX, arc.m_CenterY } ) );
2965
2966 int radius = safeScale( arc.m_Radius * m_scale );
2967
2968 shape->SetWidth( m_board.GetDesignSettings().GetLineThickness( Edge_Cuts ) );
2969
2970 if( start == end )
2971 {
2972 shape->SetShape( SHAPE_T::CIRCLE );
2973 shape->SetCenter( c );
2974 shape->SetRadius( radius );
2975 }
2976 else
2977 {
2978 shape->SetShape( SHAPE_T::ARC );
2979
2980 bool clockwise = ( arc.m_SubType & 0x40 ) != 0;
2981
2982 EDA_ANGLE startangle( start - c );
2983 EDA_ANGLE endangle( end - c );
2984
2985 startangle.Normalize();
2986 endangle.Normalize();
2987
2988 EDA_ANGLE angle = endangle - startangle;
2989
2990 if( clockwise && angle < ANGLE_0 )
2991 angle += ANGLE_360;
2992
2993 if( !clockwise && angle > ANGLE_0 )
2994 angle -= ANGLE_360;
2995
2996 VECTOR2I mid = start;
2997 RotatePoint( mid, c, -angle / 2.0 );
2998
2999 shape->SetArcGeometry( start, mid, end );
3000 }
3001 break;
3002 }
3003 case 0x15:
3004 case 0x16:
3005 case 0x17:
3006 {
3007 const auto& seg = static_cast<const BLOCK<BLK_0x15_16_17_SEGMENT>&>( *segBlock ).GetData();
3008 VECTOR2I start = scale( { seg.m_StartX, seg.m_StartY } );
3009 VECTOR2I end = scale( { seg.m_EndX, seg.m_EndY } );
3010
3011 if( !isNewSegment( start, end ) )
3012 continue;
3013
3014 shape->SetShape( SHAPE_T::SEGMENT );
3015 shape->SetStart( start );
3016 shape->SetEnd( end );
3017 shape->SetWidth( m_board.GetDesignSettings().GetLineThickness( Edge_Cuts ) );
3018 break;
3019 }
3020 default:
3021 wxLogTrace( traceAllegroBuilder, " Unhandled segment type in outline: %#04x", segBlock->GetBlockType() );
3022 continue;
3023 }
3024
3025 outlineItems.push_back( shape.get() );
3026 m_board.Add( shape.release(), ADD_MODE::BULK_APPEND );
3027 }
3028 }
3029
3030 if( !outlineItems.empty() )
3031 {
3032 m_board.FinalizeBulkAdd( outlineItems );
3033 }
3034
3035 wxLogTrace( traceAllegroBuilder, "Found %d outline items, created %zu board outline segments",
3036 shapeCount, outlineItems.size() );
3037}
3038
3039
3040const SHAPE_LINE_CHAIN& BOARD_BUILDER::buildSegmentChain( uint32_t aStartKey ) const
3041{
3042 auto cacheIt = m_segChainCache.find( aStartKey );
3043
3044 if( cacheIt != m_segChainCache.end() )
3045 return cacheIt->second;
3046
3047 SHAPE_LINE_CHAIN& outline = m_segChainCache[aStartKey];
3048 uint32_t currentKey = aStartKey;
3049
3050 // Safety limit to prevent infinite loops on corrupt data
3051 static constexpr int MAX_CHAIN_LENGTH = 50000;
3052 int visited = 0;
3053
3054 while( currentKey != 0 && visited < MAX_CHAIN_LENGTH )
3055 {
3056 const BLOCK_BASE* block = m_brdDb.GetObjectByKey( currentKey );
3057
3058 if( !block )
3059 break;
3060
3061 visited++;
3062
3063 switch( block->GetBlockType() )
3064 {
3065 case 0x01:
3066 {
3067 const auto& arc = static_cast<const BLOCK<BLK_0x01_ARC>&>( *block ).GetData();
3068 VECTOR2I start = scale( { arc.m_StartX, arc.m_StartY } );
3069 VECTOR2I end = scale( { arc.m_EndX, arc.m_EndY } );
3070 VECTOR2I center = scale( KiROUND( VECTOR2D{ arc.m_CenterX, arc.m_CenterY } ) );
3071
3072 if( start == end )
3073 {
3074 SHAPE_ARC shapeArc( center, start, ANGLE_360 );
3075 outline.Append( shapeArc );
3076 }
3077 else
3078 {
3079 bool clockwise = ( arc.m_SubType & 0x40 ) != 0;
3080
3081 EDA_ANGLE startAngle( start - center );
3082 EDA_ANGLE endAngle( end - center );
3083 startAngle.Normalize();
3084 endAngle.Normalize();
3085 EDA_ANGLE arcAngle = endAngle - startAngle;
3086
3087 if( clockwise && arcAngle < ANGLE_0 )
3088 arcAngle += ANGLE_360;
3089
3090 if( !clockwise && arcAngle > ANGLE_0 )
3091 arcAngle -= ANGLE_360;
3092
3093 SHAPE_ARC shapeArc( center, start, arcAngle );
3094 outline.Append( shapeArc );
3095 }
3096
3097 currentKey = arc.m_Next;
3098 break;
3099 }
3100 case 0x15:
3101 case 0x16:
3102 case 0x17:
3103 {
3104 const auto& seg =
3105 static_cast<const BLOCK<BLK_0x15_16_17_SEGMENT>&>( *block ).GetData();
3106 VECTOR2I start = scale( { seg.m_StartX, seg.m_StartY } );
3107
3108 if( outline.PointCount() == 0 || outline.CLastPoint() != start )
3109 outline.Append( start );
3110
3111 VECTOR2I end = scale( { seg.m_EndX, seg.m_EndY } );
3112 outline.Append( end );
3113 currentKey = seg.m_Next;
3114 break;
3115 }
3116 default:
3117 currentKey = 0;
3118 break;
3119 }
3120 }
3121
3122 return outline;
3123}
3124
3125
3127{
3128 auto it = m_outlineCache.find( aShape.m_Key );
3129
3130 if( it != m_outlineCache.end() )
3131 return it->second;
3132
3133 SHAPE_LINE_CHAIN& outline = m_outlineCache[aShape.m_Key];
3134 const LL_WALKER segWalker{ aShape.m_FirstSegmentPtr, aShape.m_Key, m_brdDb };
3135
3136 for( const BLOCK_BASE* segBlock : segWalker )
3137 {
3138 switch( segBlock->GetBlockType() )
3139 {
3140 case 0x01:
3141 {
3142 const auto& arc = static_cast<const BLOCK<BLK_0x01_ARC>&>( *segBlock ).GetData();
3143 VECTOR2I start = scale( { arc.m_StartX, arc.m_StartY } );
3144 VECTOR2I end = scale( { arc.m_EndX, arc.m_EndY } );
3145 VECTOR2I center = scale( KiROUND( VECTOR2D{ arc.m_CenterX, arc.m_CenterY } ) );
3146
3147 if( start == end )
3148 {
3149 SHAPE_ARC shapeArc( center, start, ANGLE_360 );
3150 outline.Append( shapeArc );
3151 }
3152 else
3153 {
3154 bool clockwise = ( arc.m_SubType & 0x40 ) != 0;
3155
3156 EDA_ANGLE startAngle( start - center );
3157 EDA_ANGLE endAngle( end - center );
3158 startAngle.Normalize();
3159 endAngle.Normalize();
3160 EDA_ANGLE arcAngle = endAngle - startAngle;
3161
3162 if( clockwise && arcAngle < ANGLE_0 )
3163 arcAngle += ANGLE_360;
3164
3165 if( !clockwise && arcAngle > ANGLE_0 )
3166 arcAngle -= ANGLE_360;
3167
3168 SHAPE_ARC shapeArc( center, start, arcAngle );
3169 outline.Append( shapeArc );
3170 }
3171
3172 break;
3173 }
3174 case 0x15:
3175 case 0x16:
3176 case 0x17:
3177 {
3178 const auto& seg = static_cast<const BLOCK<BLK_0x15_16_17_SEGMENT>&>( *segBlock ).GetData();
3179 VECTOR2I start = scale( { seg.m_StartX, seg.m_StartY } );
3180
3181 if( outline.PointCount() == 0 || outline.CLastPoint() != start )
3182 outline.Append( start );
3183
3184 VECTOR2I end = scale( { seg.m_EndX, seg.m_EndY } );
3185 outline.Append( end );
3186 break;
3187 }
3188 default:
3189 wxLogTrace( traceAllegroBuilder, " Unhandled segment type in shape outline: %#04x",
3190 segBlock->GetBlockType() );
3191 break;
3192 }
3193 }
3194
3195 return outline;
3196}
3197
3198
3199std::unique_ptr<ZONE> BOARD_BUILDER::buildZone( const BLK_0x28_SHAPE& aShape, int aNetcode )
3200{
3201 bool isCopperZone = ( aShape.m_Layer.m_Class == LAYER_INFO::CLASS::ETCH
3203 bool isRouteKeepout = ( aShape.m_Layer.m_Class == LAYER_INFO::CLASS::ROUTE_KEEPOUT );
3204 bool isViaKeepout = ( aShape.m_Layer.m_Class == LAYER_INFO::CLASS::VIA_KEEPOUT );
3205
3207
3208 if( isCopperZone )
3209 {
3210 // BOUNDARY shares the ETCH layer list, so resolve subclass via ETCH class
3212 {
3213 LAYER_INFO etchLayer{};
3214 etchLayer.m_Class = LAYER_INFO::CLASS::ETCH;
3215 etchLayer.m_Subclass = aShape.m_Layer.m_Subclass;
3216 layer = getLayer( etchLayer );
3217 }
3218 else
3219 {
3220 layer = getLayer( aShape.m_Layer );
3221 }
3222 }
3223 else
3224 {
3225 layer = F_Cu;
3226 }
3227
3228 if( isCopperZone && layer == UNDEFINED_LAYER )
3229 {
3230 wxLogTrace( traceAllegroBuilder, " Skipping shape %#010x - unmapped copper layer class=%#02x subclass=%#02x",
3231 aShape.m_Key, aShape.m_Layer.m_Class, aShape.m_Layer.m_Subclass );
3232 return nullptr;
3233 }
3234
3235 SHAPE_LINE_CHAIN outline = buildOutline( aShape );
3236
3237 if( outline.PointCount() < 3 )
3238 {
3239 wxLogTrace( traceAllegroBuilder, " Skipping shape %#010x - not enough points for polygon (%d)",
3240 aShape.m_Key, outline.PointCount() );
3241 return nullptr;
3242 }
3243
3244 outline.SetClosed( true );
3245
3246 auto zone = std::make_unique<ZONE>( &m_board );
3247 zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH );
3248
3249 if( isCopperZone )
3250 {
3251 zone->SetLayer( layer );
3252 zone->SetFillMode( ZONE_FILL_MODE::POLYGONS );
3253 }
3254 else
3255 {
3256 zone->SetIsRuleArea( true );
3257 zone->SetLayerSet( LSET::AllCuMask() );
3258 zone->SetDoNotAllowTracks( isRouteKeepout );
3259 zone->SetDoNotAllowVias( isViaKeepout );
3260 zone->SetDoNotAllowZoneFills( isRouteKeepout || isViaKeepout );
3261 zone->SetDoNotAllowPads( false );
3262 zone->SetDoNotAllowFootprints( false );
3263 }
3264
3265 // Set net code AFTER layer assignment. SetNetCode checks IsOnCopperLayer() and
3266 // forces net=0 if the zone isn't on a copper layer yet.
3267 zone->SetNetCode( aNetcode );
3268
3269 zone->AddPolygon( outline );
3270 return zone;
3271}
3272
3273
3275{
3276 // Follow pointer chain: BOUNDARY.Ptr7 -> 0x2C TABLE -> Ptr1 -> 0x37 -> m_Ptrs[0] -> 0x1B NET
3277 uint32_t ptr7Key = 0;
3278
3279 if( aShape.m_Ptr7.has_value() )
3280 ptr7Key = aShape.m_Ptr7.value();
3281 else if( aShape.m_Ptr7_16x.has_value() )
3282 ptr7Key = aShape.m_Ptr7_16x.value();
3283
3284 if( ptr7Key == 0 )
3286
3287 const BLK_0x2C_TABLE* tbl = expectBlockByKey<BLK_0x2C_TABLE>( ptr7Key, 0x2C );
3288
3289 if( !tbl )
3291
3292 const BLK_0x37_PTR_ARRAY* ptrArray = expectBlockByKey<BLK_0x37_PTR_ARRAY>( tbl->m_Ptr1, 0x37 );
3293
3294 if( !ptrArray || ptrArray->m_Count == 0 )
3296
3297 uint32_t netKey = ptrArray->m_Ptrs[0];
3298 auto it = m_netCache.find( netKey );
3299
3300 if( it != m_netCache.end() )
3301 {
3302 wxLogTrace( traceAllegroBuilder, " Resolved BOUNDARY %#010x -> net '%s' (code %d)",
3303 aShape.m_Key, it->second->GetNetname(), it->second->GetNetCode() );
3304 return it->second->GetNetCode();
3305 }
3306
3307 wxLogTrace( traceAllegroBuilder, " BOUNDARY %#010x: net key %#010x not in cache", aShape.m_Key, netKey );
3309}
3310
3311
3313{
3314 // Collect all 0x30 keys that appear in footprint text chains so we can skip them.
3315 // Board-level text is identified by exclusion: any 0x30 block NOT referenced from a
3316 // footprint is a board-level text object.
3317 std::unordered_set<uint32_t> footprintTextKeys;
3318
3319 const LL_WALKER fpDefWalker( m_brdDb.m_Header->m_LL_0x2B, m_brdDb );
3320
3321 for( const BLOCK_BASE* fpContainer : fpDefWalker )
3322 {
3323 if( fpContainer->GetBlockType() != 0x2B )
3324 continue;
3325
3326 const auto& fpBlock =
3327 static_cast<const BLOCK<BLK_0x2B_FOOTPRINT_DEF>&>( *fpContainer ).GetData();
3328
3329 const LL_WALKER instWalker( fpBlock.m_FirstInstPtr, fpBlock.m_Key, m_brdDb );
3330
3331 for( const BLOCK_BASE* instBlock : instWalker )
3332 {
3333 if( instBlock->GetBlockType() != 0x2D )
3334 continue;
3335
3336 const auto& fpInst =
3337 static_cast<const BLOCK<BLK_0x2D_FOOTPRINT_INST>&>( *instBlock ).GetData();
3338
3339 const LL_WALKER textWalker( fpInst.m_TextPtr, fpInst.m_Key, m_brdDb );
3340
3341 for( const BLOCK_BASE* textBlock : textWalker )
3342 {
3343 if( textBlock->GetBlockType() == 0x30 )
3344 footprintTextKeys.insert( textBlock->GetKey() );
3345 }
3346 }
3347 }
3348
3349 int textCount = 0;
3350
3351 for( const auto& block : m_brdDb.m_Blocks )
3352 {
3353 if( block->GetBlockType() != 0x30 )
3354 continue;
3355
3356 if( footprintTextKeys.count( block->GetKey() ) )
3357 continue;
3358
3359 const auto& strWrapper =
3360 static_cast<const BLOCK<BLK_0x30_STR_WRAPPER>&>( *block ).GetData();
3361
3362 std::unique_ptr<PCB_TEXT> text = buildPcbText( strWrapper, m_board );
3363
3364 if( !text )
3365 continue;
3366
3367 wxLogTrace( traceAllegroBuilder, " Board text '%s' on layer %s at (%d, %d)",
3368 text->GetText(), m_board.GetLayerName( text->GetLayer() ),
3369 text->GetPosition().x, text->GetPosition().y );
3370
3371 m_board.Add( text.release(), ADD_MODE::APPEND );
3372 textCount++;
3373 }
3374
3375 wxLogTrace( traceAllegroBuilder, "Created %d board-level text objects", textCount );
3376}
3377
3378
3380{
3381 wxLogTrace( traceAllegroBuilder, "Creating zones from m_LL_Shapes and m_LL_0x24_0x28" );
3382
3383 int boundaryCount = 0;
3384 int mergedCount = 0;
3385 int keepoutCount = 0;
3386
3387 std::vector<std::unique_ptr<ZONE>> boundaryZones;
3388
3389 // Walk m_LL_Shapes to find BOUNDARY shapes (zone outlines).
3390 // BOUNDARY shapes use class 0x15 with copper layer subclass indices.
3391 const LL_WALKER shapeWalker( m_brdDb.m_Header->m_LL_Shapes, m_brdDb );
3392
3393 for( const BLOCK_BASE* block : shapeWalker )
3394 {
3395 if( block->GetBlockType() != 0x28 )
3396 continue;
3397
3398 const BLK_0x28_SHAPE& shapeData =
3399 static_cast<const BLOCK<BLK_0x28_SHAPE>&>( *block ).GetData();
3400
3402 continue;
3403
3404 int netCode = resolveShapeNet( shapeData );
3405
3406 // Fallback: if the pointer chain didn't resolve a net, look for a computed
3407 // copper fill on the same layer whose bounding box contains this zone's bbox.
3408 if( netCode == NETINFO_LIST::UNCONNECTED && !m_zoneFillShapes.empty() )
3409 {
3410 LAYER_INFO etchLayer{};
3411 etchLayer.m_Class = LAYER_INFO::CLASS::ETCH;
3412 etchLayer.m_Subclass = shapeData.m_Layer.m_Subclass;
3413 PCB_LAYER_ID zoneLayer = getLayer( etchLayer );
3414
3415 if( zoneLayer != UNDEFINED_LAYER )
3416 {
3417 const SHAPE_LINE_CHAIN& zoneOutline = buildOutline( shapeData );
3418 BOX2I zoneBbox = zoneOutline.BBox();
3419
3420 for( const ZoneFillEntry& fill : m_zoneFillShapes )
3421 {
3422 if( fill.layer != zoneLayer || fill.netCode == NETINFO_LIST::UNCONNECTED )
3423 continue;
3424
3425 const SHAPE_LINE_CHAIN& fillOutline = buildOutline( *fill.shape );
3426 BOX2I fillBbox = fillOutline.BBox();
3427
3428 if( zoneBbox.Contains( fillBbox ) || fillBbox.Contains( zoneBbox ) )
3429 {
3430 netCode = fill.netCode;
3431
3432 wxLogTrace( traceAllegroBuilder,
3433 " BOUNDARY %#010x: resolved net via fill fallback -> code %d",
3434 shapeData.m_Key, netCode );
3435 break;
3436 }
3437 }
3438 }
3439 }
3440
3441 std::unique_ptr<ZONE> zone = buildZone( shapeData, netCode );
3442
3443 if( zone )
3444 {
3445 wxLogTrace( traceAllegroBuilder, " Zone %#010x net=%d layer=%s (subclass=%#04x)",
3446 shapeData.m_Key, netCode,
3447 m_board.GetLayerName( zone->GetFirstLayer() ),
3448 shapeData.m_Layer.m_Subclass );
3449
3450 zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::NEVER );
3451 boundaryZones.push_back( std::move( zone ) );
3452 boundaryCount++;
3453 }
3454 }
3455
3456 // Merge zones with identical outlines and same net into multi-layer zones.
3457 // Allegro often defines the same zone outline on multiple copper layers (e.g.
3458 // a ground pour spanning all layers). KiCad represents this as a single zone
3459 // with multiple fill layers.
3460 std::vector<bool> merged( boundaryZones.size(), false );
3461
3462 for( size_t i = 0; i < boundaryZones.size(); i++ )
3463 {
3464 if( merged[i] )
3465 continue;
3466
3467 ZONE* primary = boundaryZones[i].get();
3468 const SHAPE_LINE_CHAIN& primaryOutline = primary->Outline()->COutline( 0 );
3469 LSET layers = primary->GetLayerSet();
3470
3471 for( size_t j = i + 1; j < boundaryZones.size(); j++ )
3472 {
3473 if( merged[j] )
3474 continue;
3475
3476 ZONE* candidate = boundaryZones[j].get();
3477
3478 if( candidate->GetNetCode() != primary->GetNetCode() )
3479 continue;
3480
3481 const SHAPE_LINE_CHAIN& candidateOutline = candidate->Outline()->COutline( 0 );
3482
3483 if( primaryOutline.PointCount() != candidateOutline.PointCount() )
3484 continue;
3485
3486 if( primaryOutline.BBox() != candidateOutline.BBox() )
3487 continue;
3488
3489 if( primaryOutline.CompareGeometry( candidateOutline ) )
3490 {
3491 layers |= candidate->GetLayerSet();
3492 merged[j] = true;
3493 mergedCount++;
3494
3495 wxLogTrace( traceAllegroBuilder, " Merging zone on %s into zone on %s (net %d)",
3496 m_board.GetLayerName( candidate->GetFirstLayer() ),
3497 m_board.GetLayerName( primary->GetFirstLayer() ),
3498 primary->GetNetCode() );
3499 }
3500 }
3501
3502 if( layers != primary->GetLayerSet() )
3503 primary->SetLayerSet( layers );
3504
3505 m_board.Add( boundaryZones[i].release(), ADD_MODE::APPEND );
3506 }
3507
3508 if( mergedCount > 0 )
3509 {
3510 wxLogTrace( traceAllegroBuilder,
3511 " Merged %d zones into multi-layer zones (%d zones remain from %d)",
3512 mergedCount, boundaryCount - mergedCount, boundaryCount );
3513 }
3514
3515 // Walk m_LL_0x24_0x28 for keepout shapes
3516 const LL_WALKER keepoutWalker( m_brdDb.m_Header->m_LL_0x24_0x28, m_brdDb );
3517
3518 for( const BLOCK_BASE* block : keepoutWalker )
3519 {
3520 if( block->GetBlockType() != 0x28 )
3521 continue;
3522
3523 const BLK_0x28_SHAPE& shapeData =
3524 static_cast<const BLOCK<BLK_0x28_SHAPE>&>( *block ).GetData();
3525
3526 bool isRouteKeepout = ( shapeData.m_Layer.m_Class == LAYER_INFO::CLASS::ROUTE_KEEPOUT );
3527 bool isViaKeepout = ( shapeData.m_Layer.m_Class == LAYER_INFO::CLASS::VIA_KEEPOUT );
3528
3529 if( !isRouteKeepout && !isViaKeepout )
3530 continue;
3531
3532 wxLogTrace( traceAllegroBuilder, " Processing %s shape %#010x",
3533 isRouteKeepout ? wxS( "ROUTE_KEEPOUT" ) : wxS( "VIA_KEEPOUT" ),
3534 shapeData.m_Key );
3535
3536 std::unique_ptr<ZONE> zone = buildZone( shapeData, NETINFO_LIST::UNCONNECTED );
3537
3538 if( zone )
3539 {
3540 m_board.Add( zone.release(), ADD_MODE::APPEND );
3541 keepoutCount++;
3542 }
3543 }
3544
3545 wxLogTrace( traceAllegroBuilder,
3546 "Created %d zone outlines (%d merged), %d keepout areas",
3547 boundaryCount - mergedCount, mergedCount, keepoutCount );
3548}
3549
3550
3552{
3553 if( m_zoneFillShapes.empty() )
3554 return;
3555
3556 PROF_TIMER fillTimer;
3557
3558 wxLogTrace( traceAllegroBuilder, "Applying zone fill polygons from %zu collected fills",
3559 m_zoneFillShapes.size() );
3560
3561 int fillCount = 0;
3562 int totalHoles = 0;
3563 std::vector<bool> matched( m_zoneFillShapes.size(), false );
3564
3565 // Index fills by (layer, netCode) to avoid scanning all fills for each zone
3566 std::unordered_map<uint64_t, std::vector<size_t>> fillIndex;
3567
3568 for( size_t i = 0; i < m_zoneFillShapes.size(); i++ )
3569 {
3570 const ZoneFillEntry& fill = m_zoneFillShapes[i];
3571 uint64_t key = ( static_cast<uint64_t>( fill.layer ) << 32 )
3572 | static_cast<uint32_t>( fill.netCode );
3573 fillIndex[key].push_back( i );
3574 }
3575
3576 for( ZONE* zone : m_board.Zones() )
3577 {
3578 if( zone->GetIsRuleArea() || zone->GetNetCode() == NETINFO_LIST::UNCONNECTED )
3579 continue;
3580
3581 bool hasFill = false;
3582
3583 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
3584 {
3585 if( !IsCopperLayer( layer ) )
3586 continue;
3587
3588 uint64_t key = ( static_cast<uint64_t>( layer ) << 32 )
3589 | static_cast<uint32_t>( zone->GetNetCode() );
3590 auto indexIt = fillIndex.find( key );
3591
3592 if( indexIt == fillIndex.end() )
3593 continue;
3594
3595 SHAPE_POLY_SET combinedFill;
3596
3597 for( size_t i : indexIt->second )
3598 {
3599 const ZoneFillEntry& fill = m_zoneFillShapes[i];
3600 const SHAPE_LINE_CHAIN& cachedOutline = buildOutline( *fill.shape );
3601
3602 if( cachedOutline.PointCount() < 3 )
3603 continue;
3604
3605 SHAPE_LINE_CHAIN fillOutline = cachedOutline;
3606 fillOutline.SetClosed( true );
3607 fillOutline.ClearArcs();
3608
3609 BOX2I fillBbox = fillOutline.BBox();
3610 BOX2I zoneBbox = zone->GetBoundingBox();
3611
3612 if( !fillBbox.Intersects( zoneBbox ) )
3613 continue;
3614
3615 combinedFill.AddOutline( fillOutline );
3616 int outlineIdx = combinedFill.OutlineCount() - 1;
3617 matched[i] = true;
3618
3619 // Walk 0x34 KEEPOUT chain from m_Ptr4 for clearance holes
3620 uint32_t holeKey = fill.shape->m_Ptr4;
3621
3622 while( holeKey != 0 )
3623 {
3624 const BLOCK_BASE* holeBlock = m_brdDb.GetObjectByKey( holeKey );
3625
3626 if( !holeBlock || holeBlock->GetBlockType() != 0x34 )
3627 break;
3628
3629 const auto& keepout =
3630 static_cast<const BLOCK<BLK_0x34_KEEPOUT>&>( *holeBlock ).GetData();
3631
3632 SHAPE_LINE_CHAIN holeOutline = buildSegmentChain( keepout.m_Ptr2 );
3633
3634 if( holeOutline.PointCount() >= 3 )
3635 {
3636 holeOutline.SetClosed( true );
3637 holeOutline.ClearArcs();
3638 combinedFill.AddHole( holeOutline, outlineIdx );
3639 totalHoles++;
3640 }
3641
3642 holeKey = keepout.m_Next;
3643 }
3644 }
3645
3646 if( combinedFill.OutlineCount() > 0 )
3647 {
3648 // Allegro fill data is well-formed, skip Clipper2 Simplify
3649 if( combinedFill.HasHoles() )
3650 combinedFill.Fracture( /* aSimplify */ false );
3651
3652 zone->SetFilledPolysList( layer, combinedFill );
3653 hasFill = true;
3654 fillCount++;
3655 }
3656 }
3657
3658 if( hasFill )
3659 {
3660 zone->SetIsFilled( true );
3661 zone->SetNeedRefill( false );
3662 }
3663 }
3664
3665 wxLogTrace( traceAllegroPerf, wxT( " applyZoneFills matched loop: %.3f ms (%d fills, %d holes)" ), //format:allow
3666 fillTimer.msecs( true ), fillCount, totalHoles );
3667
3668 // Unmatched ETCH shapes are either standalone copper polygons or dynamic copper
3669 // (teardrops/fillets). On V172+ boards, m_Unknown2 bit 12 (0x1000) marks auto-generated
3670 // dynamic copper that maps to KiCad teardrop zones. Shapes without this flag are genuine
3671 // standalone copper imported as filled PCB_SHAPE.
3672 int copperShapeCount = 0;
3673 int teardropCount = 0;
3674
3675 for( size_t i = 0; i < m_zoneFillShapes.size(); i++ )
3676 {
3677 if( matched[i] )
3678 continue;
3679
3680 const ZoneFillEntry& fill = m_zoneFillShapes[i];
3681
3682 SHAPE_LINE_CHAIN outline = buildOutline( *fill.shape );
3683
3684 if( outline.PointCount() < 3 )
3685 continue;
3686
3687 outline.SetClosed( true );
3688 outline.ClearArcs();
3689
3690 SHAPE_POLY_SET polySet;
3691 polySet.AddOutline( outline );
3692
3693 // Walk 0x34 KEEPOUT chain for clearance holes
3694 uint32_t holeKey = fill.shape->m_Ptr4;
3695
3696 while( holeKey != 0 )
3697 {
3698 const BLOCK_BASE* holeBlock = m_brdDb.GetObjectByKey( holeKey );
3699
3700 if( !holeBlock || holeBlock->GetBlockType() != 0x34 )
3701 break;
3702
3703 const auto& keepout =
3704 static_cast<const BLOCK<BLK_0x34_KEEPOUT>&>( *holeBlock ).GetData();
3705
3706 SHAPE_LINE_CHAIN holeOutline = buildSegmentChain( keepout.m_Ptr2 );
3707
3708 if( holeOutline.PointCount() >= 3 )
3709 {
3710 holeOutline.SetClosed( true );
3711 holeOutline.ClearArcs();
3712 polySet.AddHole( holeOutline );
3713 }
3714
3715 holeKey = keepout.m_Next;
3716 }
3717
3718 if( polySet.HasHoles() )
3719 polySet.Fracture( /* aSimplify */ false );
3720
3721 const bool isDynCopperShape = ( fill.shape->m_Unknown2.value_or( 0 ) & 0x1000 ) != 0;
3722
3723 if( isDynCopperShape )
3724 {
3725 auto zone = std::make_unique<ZONE>( &m_board );
3726
3727 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_VIAPAD );
3728 zone->SetLayer( fill.layer );
3729 zone->SetNetCode( fill.netCode );
3730 zone->SetLocalClearance( 0 );
3731 zone->SetPadConnection( ZONE_CONNECTION::FULL );
3732 zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::NEVER );
3733 zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::INVISIBLE_BORDER );
3734
3735 zone->AddPolygon( outline );
3736 zone->SetFilledPolysList( fill.layer, polySet );
3737 zone->SetIsFilled( true );
3738 zone->SetNeedRefill( false );
3739 zone->CalculateFilledArea();
3740
3741 m_board.Add( zone.release(), ADD_MODE::APPEND );
3742 teardropCount++;
3743 }
3744 else
3745 {
3746 auto shape = std::make_unique<PCB_SHAPE>( &m_board, SHAPE_T::POLY );
3747 shape->SetPolyShape( polySet );
3748 shape->SetFilled( true );
3749 shape->SetLayer( fill.layer );
3750 shape->SetNetCode( fill.netCode );
3751 shape->SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
3752
3753 m_board.Add( shape.release(), ADD_MODE::APPEND );
3754 copperShapeCount++;
3755 }
3756 }
3757
3758 wxLogTrace( traceAllegroPerf, wxT( " applyZoneFills unmatched loop: %.3f ms (%d shapes, %d teardrops)" ), //format:allow
3759 fillTimer.msecs( true ), copperShapeCount, teardropCount );
3760
3761 wxLogTrace( traceAllegroBuilder,
3762 "Applied fills to %d zone/layer pairs (%d clearance holes), "
3763 "created %d standalone copper shapes, %d teardrop zones",
3764 fillCount, totalHoles, copperShapeCount, teardropCount );
3765}
3766
3767
3769{
3770 std::unordered_map<int, std::vector<ZONE*>> teardropsByNet;
3771
3772 for( ZONE* zone : m_board.Zones() )
3773 {
3774 if( zone->IsTeardropArea() )
3775 teardropsByNet[zone->GetNetCode()].push_back( zone );
3776 }
3777
3778 if( teardropsByNet.empty() )
3779 return;
3780
3781 int padCount = 0;
3782 int viaCount = 0;
3783
3784 for( FOOTPRINT* fp : m_board.Footprints() )
3785 {
3786 for( PAD* pad : fp->Pads() )
3787 {
3788 auto it = teardropsByNet.find( pad->GetNetCode() );
3789
3790 if( it == teardropsByNet.end() )
3791 continue;
3792
3793 for( ZONE* tdZone : it->second )
3794 {
3795 if( !pad->IsOnLayer( tdZone->GetLayer() ) )
3796 continue;
3797
3798 if( tdZone->Outline()->Contains( pad->GetPosition() ) )
3799 {
3800 pad->SetTeardropsEnabled( true );
3801 padCount++;
3802 break;
3803 }
3804 }
3805 }
3806 }
3807
3808 for( PCB_TRACK* track : m_board.Tracks() )
3809 {
3810 if( track->Type() != PCB_VIA_T )
3811 continue;
3812
3813 PCB_VIA* via = static_cast<PCB_VIA*>( track );
3814 auto it = teardropsByNet.find( via->GetNetCode() );
3815
3816 if( it == teardropsByNet.end() )
3817 continue;
3818
3819 for( ZONE* tdZone : it->second )
3820 {
3821 if( !via->IsOnLayer( tdZone->GetLayer() ) )
3822 continue;
3823
3824 if( tdZone->Outline()->Contains( via->GetPosition() ) )
3825 {
3826 via->SetTeardropsEnabled( true );
3827 viaCount++;
3828 break;
3829 }
3830 }
3831 }
3832
3833 wxLogTrace( traceAllegroBuilder, "Enabled teardrops on %d pads and %d vias", padCount, viaCount );
3834}
3835
3836
3838{
3839 wxLogTrace( traceAllegroBuilder, "Starting BuildBoard() - Phase 2 of Allegro import" );
3840 wxLogTrace( traceAllegroBuilder, " Format version: %d (V172+ = %s)",
3841 static_cast<int>( m_brdDb.m_FmtVer ),
3842 ( m_brdDb.m_FmtVer >= FMT_VER::V_172 ) ? "yes" : "no" );
3843 wxLogTrace( traceAllegroBuilder, " Allegro version string: %.60s",
3844 m_brdDb.m_Header->m_AllegroVersion.data() );
3845
3846 if( m_progressReporter )
3847 {
3848 m_progressReporter->AddPhases( 4 );
3849 m_progressReporter->AdvancePhase( _( "Constructing caches" ) );
3850 m_progressReporter->KeepRefreshing();
3851 }
3852
3853 PROF_TIMER buildTimer;
3854
3855 wxLogTrace( traceAllegroBuilder, "Caching font definitions and setting up layers" );
3856 cacheFontDefs();
3857 wxLogTrace( traceAllegroPerf, wxT( " cacheFontDefs: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3858
3859 setupLayers();
3860 wxLogTrace( traceAllegroPerf, wxT( " setupLayers: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3861
3862 if( m_progressReporter )
3863 {
3864 m_progressReporter->AdvancePhase( _( "Creating nets" ) );
3865 m_progressReporter->KeepRefreshing();
3866 }
3867
3868 createNets();
3869 wxLogTrace( traceAllegroPerf, wxT( " createNets: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3870
3871 if( m_progressReporter )
3872 {
3873 m_progressReporter->AdvancePhase( _( "Creating tracks" ) );
3874 m_progressReporter->KeepRefreshing();
3875 }
3876
3877 createTracks();
3878 wxLogTrace( traceAllegroPerf, wxT( " createTracks: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3879
3880 if( m_progressReporter )
3881 m_progressReporter->KeepRefreshing();
3882
3884 wxLogTrace( traceAllegroPerf, wxT( " createBoardOutline: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3885
3887 wxLogTrace( traceAllegroPerf, wxT( " createBoardText: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3888
3889 createZones();
3890 wxLogTrace( traceAllegroPerf, wxT( " createZones: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3891
3892 if( m_progressReporter )
3893 m_progressReporter->KeepRefreshing();
3894
3896 wxLogTrace( traceAllegroPerf, wxT( " applyZoneFills: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3897
3899 wxLogTrace( traceAllegroPerf, wxT( " applyConstraintSets: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3900
3902 wxLogTrace( traceAllegroPerf, wxT( " applyNetConstraints: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3903
3905 wxLogTrace( traceAllegroPerf, wxT( " applyMatchGroups: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3906
3907 if( m_progressReporter )
3908 {
3909 m_progressReporter->AdvancePhase( _( "Converting footprints" ) );
3910 m_progressReporter->KeepRefreshing();
3911 }
3912
3913 const LL_WALKER fpWalker( m_brdDb.m_Header->m_LL_0x2B, m_brdDb );
3914 std::vector<BOARD_ITEM*> bulkAddedItems;
3915
3916 auto lastRefresh = std::chrono::steady_clock::now();
3917
3918 for( const BLOCK_BASE* fpContainer : fpWalker )
3919 {
3920 if( fpContainer->GetBlockType() == 0x2B )
3921 {
3922 const BLK_0x2B_FOOTPRINT_DEF& fpBlock =
3923 static_cast<const BLOCK<BLK_0x2B_FOOTPRINT_DEF>&>( *fpContainer ).GetData();
3924
3925 const LL_WALKER instWalker( fpBlock.m_FirstInstPtr, fpBlock.m_Key, m_brdDb );
3926
3927 for( const BLOCK_BASE* instBlock : instWalker )
3928 {
3929 if( instBlock->GetBlockType() != 0x2D )
3930 {
3931 m_reporter.Report(
3932 wxString::Format( "Unexpected object of type %#04x found in footprint %#010x",
3933 instBlock->GetBlockType(), fpBlock.m_Key ),
3935 }
3936 else
3937 {
3938 const auto& inst =
3939 static_cast<const BLOCK<BLK_0x2D_FOOTPRINT_INST>&>( *instBlock ).GetData();
3940
3941 std::unique_ptr<FOOTPRINT> fp = buildFootprint( inst );
3942
3943 if( fp )
3944 {
3945 bulkAddedItems.push_back( fp.get() );
3946 m_board.Add( fp.release(), ADD_MODE::BULK_APPEND, true );
3947 }
3948 else
3949 {
3950 m_reporter.Report(
3951 wxString::Format( "Failed to construct footprint for 0x2D key %#010x",
3952 inst.m_Key ),
3954 }
3955 }
3956
3957 if( m_progressReporter )
3958 {
3959 auto now = std::chrono::steady_clock::now();
3960
3961 if( now - lastRefresh >= std::chrono::milliseconds( 100 ) )
3962 {
3963 m_progressReporter->KeepRefreshing();
3964 lastRefresh = now;
3965 }
3966 }
3967 }
3968 }
3969 }
3970
3971 wxLogTrace( traceAllegroPerf, wxT( " convertFootprints (%zu footprints): %.3f ms" ), //format:allow
3972 bulkAddedItems.size(), buildTimer.msecs( true ) );
3973
3974 if( !bulkAddedItems.empty() )
3975 m_board.FinalizeBulkAdd( bulkAddedItems );
3976
3977 wxLogTrace( traceAllegroPerf, wxT( " FinalizeBulkAdd: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3978 wxLogTrace( traceAllegroBuilder, "Converted %zu footprints", bulkAddedItems.size() );
3979
3981 wxLogTrace( traceAllegroPerf, wxT( " enablePadTeardrops: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
3982 wxLogTrace( traceAllegroPerf, wxT( " Phase 2 total: %.3f ms" ), buildTimer.msecs() ); //format:allow
3983
3984 wxLogTrace( traceAllegroBuilder, "Board construction completed successfully" );
3985 return true;
3986}
const char * name
static uint32_t GetPrimaryNext(const BLOCK_BASE &aBlock)
Gets the next block in the linked list.
static wxString layerInfoDisplayName(const LAYER_INFO &aLayerInfo)
Build a unique display name for a LAYER_INFO entry from the static maps above, suitable for presentat...
#define BLK_FIELD(BLK_T, FIELD)
static const std::unordered_map< LAYER_INFO, PCB_LAYER_ID > s_LayerKiMap
Map of the pre-set class:subclass pairs to standard layers.
static const std::unordered_map< LAYER_INFO, wxString > s_OptionalFixedMappings
Names for custom KiCad layers that correspond to pre-defined Allegro layers.
static uint32_t PadGetNextInFootprint(const BLOCK_BASE &aBlock)
"Get Next" function for the pad list in a footprint's 0x32 list.
static int safeScale(double aValue)
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
The base class for all blocks in the main body of an Allegro file.
uint32_t GetKey() const
uint8_t GetBlockType() const
const T & GetData() const
VECTOR2I scale(const VECTOR2I &aVector) const
wxString resolveMatchGroupName(const BLK_0x1B_NET &aNet) const
Follow m_MatchGroupPtr through the 0x26/0x2C pointer chain to get the match group name for a NET.
const SHAPE_LINE_CHAIN & buildSegmentChain(uint32_t aStartKey) const
Walk a geometry chain (0x01 arcs and 0x15-17 segments) starting from the given key,...
LAYER_MAPPING_HANDLER m_layerMappingHandler
PROGRESS_REPORTER * m_progressReporter
void reportUnexpectedBlockType(uint8_t aGot, uint8_t aExpected, uint32_t aKey=0, size_t aOffset=0, const wxString &aName=wxEmptyString) const
const BLK_0x07_COMPONENT_INST * getFpInstRef(const BLK_0x2D_FOOTPRINT_INST &aFpInstance) const
Look up 0x07 FP instance data (0x07) for a given 0x2D FP instance.
VECTOR2I scaleSize(const VECTOR2I &aSize) const
PCB_LAYER_ID getLayer(const LAYER_INFO &aLayerInfo) const
std::unordered_map< uint32_t, SHAPE_LINE_CHAIN > m_segChainCache
std::vector< std::unique_ptr< PCB_SHAPE > > buildShapes(const BLK_0x14_GRAPHIC &aGraphicList, BOARD_ITEM_CONTAINER &aParent)
Build the shapes from an 0x14 shape list.
int resolveShapeNet(const BLK_0x28_SHAPE &aShape) const
Resolve the net code for a BOUNDARY shape by following the pointer chain: BOUNDARY....
BOARD_BUILDER(const BRD_DB &aBrdDb, BOARD &aBoard, REPORTER &aReporter, PROGRESS_REPORTER *aProgressReporter, const LAYER_MAPPING_HANDLER &aLayerMappingHandler)
std::unique_ptr< LAYER_MAPPER > m_layerMapper
std::unique_ptr< PCB_SHAPE > buildRect(const BLK_0x24_RECT &aRect, BOARD_ITEM_CONTAINER &aParent)
Build a rectangular shape from a 0x24 RECT block.
const SHAPE_LINE_CHAIN & buildOutline(const BLK_0x28_SHAPE &aShape) const
const T * expectBlockByKey(uint32_t aKey, uint8_t aType) const
std::unique_ptr< PCB_TEXT > buildPcbText(const BLK_0x30_STR_WRAPPER &aStrWrapper, BOARD_ITEM_CONTAINER &aParent)
std::unique_ptr< FOOTPRINT > buildFootprint(const BLK_0x2D_FOOTPRINT_INST &aFpInstance)
void reportMissingBlock(uint32_t aKey, uint8_t aType) const
std::unordered_map< uint32_t, NETINFO_ITEM * > m_netCache
std::vector< ZoneFillEntry > m_zoneFillShapes
std::vector< std::unique_ptr< BOARD_ITEM > > buildTrack(const BLK_0x05_TRACK &aBlock, int aNetcode)
std::vector< const BLK_0x36_DEF_TABLE::FontDef_X08 * > m_fontDefList
std::unordered_map< uint32_t, SHAPE_LINE_CHAIN > m_outlineCache
wxString get0x30StringValue(uint32_t a0x30Key) const
Get just the string value from a 0x31 STRING WRAPPER -> 0x30 STRING GRAPHIC pair.
const BLK_0x36_DEF_TABLE::FontDef_X08 * getFontDef(unsigned aIndex) const
Get the font definition for a given index in a 0x30, etc.
std::unique_ptr< BOARD_ITEM > buildVia(const BLK_0x33_VIA &aBlock, int aNetcode)
std::unique_ptr< ZONE > buildZone(const BLK_0x28_SHAPE &aShape, int aNetcode)
std::unique_ptr< PCB_SHAPE > buildPolygon(const BLK_0x28_SHAPE &aShape, BOARD_ITEM_CONTAINER &aParent)
Build a graphic polygon from a 0x28 SHAPE block.
wxString resolveConstraintSetNameFromField(uint32_t aFieldKey) const
Extract constraint set name from a 0x03 FIELD block pointer.
std::vector< std::unique_ptr< BOARD_ITEM > > buildPadItems(const BLK_0x1C_PADSTACK &aPadstack, FOOTPRINT &aFp, const wxString &aPadName, int aNetcode)
Construct "pad" items for a given 0x1C PADSTACK block.
An Allegro database that represents a .brd file (amd presumably .dra)
Definition allegro_db.h:849
std::optional< std::variant< wxString, uint32_t > > GetOptField(uint16_t aFieldCode) const
Get the raw variant value of the field with the given code, if present.
Class to handle the mapping for Allegro CLASS/SUBCLASS idiom to KiCad layers.
PCB_LAYER_ID GetPlaceBounds(bool aTop)
Allegro puts more graphics than just the polygon on PBT/B, but we don't want to always make a static ...
void FinalizeLayers()
Called after all the custom layers are loaded.
void ProcessLayerList(uint8_t aClass, const BLK_0x2A_LAYER_LIST &aList)
LAYER_MAPPER(const BRD_DB &aRawBoard, BOARD &aBoard, const LAYER_MAPPING_HANDLER &aLayerMappingHandler)
PCB_LAYER_ID addUserLayer(const wxString &aName)
std::unordered_map< LAYER_INFO, wxString > m_customLayerDialogNames
Names used in the layer mapping dialog for custom (non-ETCH, non-static) layers.
static PCB_LAYER_ID getNthUserLayer(int aNum)
std::unordered_map< const BLK_0x2A_LAYER_LIST *, std::vector< CUSTOM_LAYER > > m_Lists
PCB_LAYER_ID mapCustomLayerByName(const wxString &aLayerName)
Create or find a mapped layer with a given name, but not specifically bound to a specific class:subcl...
std::unordered_map< LAYER_INFO, PCB_LAYER_ID > m_staticLayerOverrides
Overrides for the static s_LayerKiMap entries, populated by the layer mapping handler.
std::unordered_map< wxString, PCB_LAYER_ID > m_MappedOptionalLayers
This is a map of optional, Allegro layers that we have mapped to KiCad layers with given names.
PCB_LAYER_ID GetLayer(const LAYER_INFO &aLayerInfo)
bool IsOutlineLayer(const LAYER_INFO &aLayerInfo) const
Resolve the subclass name for a given class:subclass pair using the per-class custom layer list.
PCB_LAYER_ID mapCustomLayer(const LAYER_INFO &aLayerInfo, const wxString &aLayerName)
std::unordered_map< LAYER_INFO, int > m_unknownLayers
A record of what we failed to map.
std::unordered_map< uint8_t, std::vector< CUSTOM_LAYER > * > m_ClassCustomLayerLists
static PCB_LAYER_ID getNthCopperLayer(int aNum, int aTotal)
std::unordered_map< LAYER_INFO, PCB_LAYER_ID > m_customLayerToKiMap
The main map from CLASS:SUBCLASS custom mappings to KiCadLayers.
const LAYER_MAPPING_HANDLER & m_layerMappingHandler
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
Abstract interface for BOARD_ITEMs capable of storing other items inside.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr size_type GetHeight() const
Definition box2.h:215
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:168
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:311
EDA_ANGLE Normalize()
Definition eda_angle.h:229
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:400
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:284
iterator(uint32_t aCurrent, uint32_t aTail, const BRD_DB &aBoard, NEXT_FUNC_T aNextFunc)
bool operator!=(const iterator &other) const
const BLOCK_BASE * m_currBlock
const BLOCK_BASE * operator*() const
void SetNextFunc(NEXT_FUNC_T aNextFunc)
const BRD_DB & m_board
iterator begin() const
iterator end() const
std::function< uint32_t(const BLOCK_BASE &)> NEXT_FUNC_T
NEXT_FUNC_T m_nextFunction
LL_WALKER(uint32_t aHead, uint32_t aTail, const BRD_DB &aBoard)
LL_WALKER(const FILE_HEADER::LINKED_LIST &aList, const BRD_DB &aBoard)
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:599
static const LSET & AllLayersMask()
Definition lset.cpp:641
static LSET AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:608
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:45
static const char Default[]
the name of the default NETCLASS
Definition netclass.h:47
int GetDiffPairGap() const
Definition netclass.h:173
bool HasDiffPairWidth() const
Definition netclass.h:164
const wxString GetName() const
Gets the name of this (maybe aggregate) netclass in a format for internal usage or for export to exte...
Definition netclass.cpp:328
bool HasTrackWidth() const
Definition netclass.h:124
int GetDiffPairWidth() const
Definition netclass.h:165
bool HasDiffPairGap() const
Definition netclass.h:172
int GetTrackWidth() const
Definition netclass.h:125
int GetClearance() const
Definition netclass.h:117
bool HasClearance() const
Definition netclass.h:116
Handle the data for a net.
Definition netinfo.h:54
const wxString & GetNetname() const
Definition netinfo.h:112
void SetNetClass(const std::shared_ptr< NETCLASS > &aNetClass)
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:247
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:157
void SetMode(MODE aMode)
COPPER_LAYER_PROPS & CopperLayer(PCB_LAYER_ID aLayer)
DRILL_PROPS & Drill()
Definition padstack.h:348
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:171
@ CUSTOM
Shapes can be defined on arbitrary layers.
Definition padstack.h:173
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:172
void SetLayerSet(const LSET &aSet)
Definition padstack.h:324
Definition pad.h:55
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:369
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:376
A small class to help profiling.
Definition profile.h:49
double msecs(bool aSinceLast=false)
Definition profile.h:149
A progress reporter interface for use in multi-threaded environments.
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
void ClearArcs()
Remove all arc references in the line chain, resulting in a chain formed only of straight segments.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
bool CompareGeometry(const SHAPE_LINE_CHAIN &aOther, bool aCyclicalCompare=false, int aEpsilon=0) const
Compare this line chain with another one.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Represent a set of closed polygons.
bool HasHoles() const
Return true if the polygon set has any holes.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
Simple container to manage line stroke parameters.
const std::string Format() const
Return the vector formatted as a string.
Definition vector2d.h:423
Handle a list of polygons defining a copper zone.
Definition zone.h:73
virtual PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition zone.cpp:501
SHAPE_POLY_SET * Outline()
Definition zone.h:340
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:556
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:136
PCB_LAYER_ID GetFirstLayer() const
Definition zone.cpp:523
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:417
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:415
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
static const wxChar *const traceAllegroBuilder
Flag to enable debug output of Allegro board construction.
@ S
Solder (HASL/SMOBC)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayerId, int aCopperLayersCount)
Definition layer_id.cpp:172
wxString LayerName(int aLayer)
Returns the default display name for a given layer.
Definition layer_id.cpp:31
#define MAX_USER_DEFINED_LAYERS
Definition layer_ids.h:177
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:803
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:677
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ F_CrtYd
Definition layer_ids.h:116
@ Edge_Cuts
Definition layer_ids.h:112
@ Cmts_User
Definition layer_ids.h:108
@ B_Cu
Definition layer_ids.h:65
@ F_Fab
Definition layer_ids.h:119
@ F_SilkS
Definition layer_ids.h:100
@ B_CrtYd
Definition layer_ids.h:115
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ In1_Cu
Definition layer_ids.h:66
@ User_1
Definition layer_ids.h:124
@ B_SilkS
Definition layer_ids.h:101
@ F_Cu
Definition layer_ids.h:64
@ B_Fab
Definition layer_ids.h:118
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition lset.cpp:754
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:29
@ PHYS_CONSTRAINT_SET
Physical Constraint Set assignment.
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:99
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ RECTANGLE
Definition padstack.h:54
static const wxChar *const traceAllegroPerf
std::function< std::map< wxString, PCB_LAYER_ID >(const std::vector< INPUT_LAYER_DESC > &)> LAYER_MAPPING_HANDLER
Pointer to a function that takes a map of source and KiCad layers and returns a re-mapped version.
CITER next(CITER it)
Definition ptree.cpp:124
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
const int scale
Arc segment used in tracks, zone outlines, and shape boundaries.
uint8_t m_SubType
Bit 6 (0x40) = clockwise direction.
Field/property references with variable-typed substructs.
std::variant< uint32_t, std::array< uint32_t, 2 >, std::string, SUB_0x6C, SUB_0x70_0x74, SUB_0xF6 > m_Substruct
Net assignment linking a net (0x1B) to its member objects.
Track segment container.
Component instance reference data.
Pad geometry and placement in board-absolute coordinates.
uint32_t m_Rotation
Board-absolute millidegrees. Subtract footprint rotation for FP-local orientation.
int32_t m_CoordsX
Board coordinates. Use SetFPRelativePosition() for KiCad FP-local space.
int32_t m_CoordsY
Board coordinates. Use SetFPRelativePosition() for KiCad FP-local space.
Shape/fill segment linking a copper shape to its parent footprint.
Graphics container holding a chain of line segments and arcs.
0x15 , 0x16, 0x17 are segments:
0x1B objects are nets.
uint32_t m_MatchGroupPtr
Diff pair / match group pointer (0x26 or 0x2C)
Padstack definition containing drill dimensions and a table of per-layer pad/antipad/thermal componen...
std::array< uint32_t, 8 > m_DrillArr
In >= V172, elements [4] and [7] hold drill dimensions: [4] = drill diameter (or width for oblong dri...
uint32_t m_Drill
In < V172, this is the drill diameter in internal coordinates.
COND_GE< FMT_VER::V_172, std::array< uint32_t, 28 > > m_SlotAndUnknownArr
In V172+, elements [0] and [3] hold the true slot outline dimensions (X, Y) in internal coordinate un...
size_t m_NumFixedCompEntries
How many of the entries are fixed roles (after this is n*layers)
std::vector< PADSTACK_COMPONENT > m_Components
Collection of components that make up the padstack.
Physical constraint sets containing trace width, clearance, and routing rules.
uint32_t m_FieldPtr
Pointer to 0x03 FIELD with CS name (fallback when m_NameStrKey fails)
uint32_t m_NameStrKey
String table key for constraint set name.
std::vector< std::array< uint8_t, 56 > > m_DataB
Per-copper-layer dimension values, 14 x int32 per record.
Signal integrity and simulation model data (IBIS netlists).
Per-padstack dimension records with name and value.
Rectangle defined by four coordinates.
std::array< int32_t, 4 > m_Coords
Polygon shape defined by a linked list of segments starting at m_FirstSegmentPtr (0x15/0x16/0x17 line...
COND_LT< FMT_VER::V_172, uint32_t > m_Ptr7_16x
COND_GE< FMT_VER::V_172, uint32_t > m_Ptr7
COND_GE< FMT_VER::V_172, uint32_t > m_Unknown2
Represents a list of layers.
COND_GE< FMT_VER::V_165, std::vector< REF_ENTRY > > m_RefEntries
COND_LT< FMT_VER::V_165, std::vector< NONREF_ENTRY > > m_NonRefEntries
Footprint definition (template) shared by multiple placed instances.
Lookup table used for named associations.
Placed footprint instance on the board.
uint32_t m_Rotation
Millidegrees (divide by 1000 for degrees)
COND_LT< FMT_VER::V_172, uint32_t > m_InstRef16x
COND_GE< FMT_VER::V_172, uint32_t > m_InstRef
Connection point at a track junction or pad-to-track transition.
Text object with position, rotation, layer, font properties, and alignment.
COND_GE< FMT_VER::V_172, TEXT_PROPERTIES > m_Font
COND_LT< FMT_VER::V_172, TEXT_PROPERTIES > m_Font16x
String graphic content holding the actual text value and its display layer category.
Placed pad instance linking a pad definition (0x0D via m_PadPtr) to its parent footprint (m_ParentFp)...
Via instance with board position, padstack reference (m_Padstack for drill/annular ring definitions),...
Heterogeneous definition table containing font metrics (FontDef_X08), layer name definitions (X03),...
std::vector< SubstructVariant > m_Items
Fixed-capacity pointer array (100 entries).
std::array< uint32_t, 100 > m_Ptrs
const T & value_or(const T &aDefault) const
uint32_t GetKey() const
Definition allegro_db.h:234
This is apparently some kind of linked list that chains though subsets objects in the file.
Represents the information found in a single entry of a layer list.
0x1B NET objects
Definition allegro_db.h:633
std::optional< int > GetNetMinLineWidth() const
FIELD_LIST m_Fields
Definition allegro_db.h:651
Substruct in a padstack object.
uint32_t m_StrPtr
Seems to point to various things:
COND_GE< FMT_VER::V_172, int16_t > m_Z1
When processing a view, some objects are available and some are not.
Definition allegro_db.h:815
const NET * m_Net
Definition allegro_db.h:838
Describes an imported layer and how it could be mapped to KiCad Layers.
PCB_LAYER_ID AutoMapLayer
Best guess as to what the equivalent KiCad layer might be.
bool Required
Should we require the layer to be assigned?
LSET PermittedLayers
KiCad layers that the imported layer can be mapped onto.
wxString Name
Imported layer name as displayed in original application.
The features of a padstack that can vary between copper layers All parameters are optional; leaving t...
Definition padstack.h:227
VECTOR2I size
Drill diameter (x == y) or slot dimensions (x != y)
Definition padstack.h:267
PAD_DRILL_SHAPE shape
Definition padstack.h:268
size_t operator()(const LAYER_INFO &aLayerInfo) const noexcept
@ USER
The field ID hasn't been set yet; field is invalid.
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
VECTOR2I center
const SHAPE_LINE_CHAIN chain
int radius
VECTOR2I end
int clearance
wxString result
Test unit parsing edge cases and error handling.
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694
@ FULL
pads are covered by copper
Definition zones.h:51