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 <cmath>
28#include <limits>
29#include <set>
30#include <tuple>
31#include <unordered_set>
32
34
35#include <wx/log.h>
36
37#include <core/profile.h>
38#include <core/throttle.h>
39
40#include <base_units.h>
45#include <footprint.h>
46#include <netclass.h>
47#include <pad.h>
48#include <pcb_group.h>
49#include <pcb_text.h>
50#include <pcb_shape.h>
51#include <pcb_track.h>
53#include <zone.h>
54#include <zone_utils.h>
55
56
57using namespace ALLEGRO;
58
59
67static const wxChar* const traceAllegroBuilder = wxT( "KICAD_ALLEGRO_BUILDER" );
68static const wxChar* const traceAllegroPerf = wxT( "KICAD_ALLEGRO_PERF" );
69
70
71template <typename BLK_T>
72const BLK_T& BlockDataAs( const BLOCK_BASE& aBlock )
73{
74 return static_cast<const BLOCK<BLK_T>&>( aBlock ).GetData();
75}
76
77
78#define BLK_FIELD( BLK_T, FIELD ) BlockDataAs<BLK_T>( aBlock ).FIELD
79
80
91static uint32_t GetPrimaryNext( const BLOCK_BASE& aBlock )
92{
93 const uint8_t type = aBlock.GetBlockType();
94
95 switch( type )
96 {
97 case 0x01: return BLK_FIELD( BLK_0x01_ARC, m_Next );
98 case 0x03: return BLK_FIELD( BLK_0x03_FIELD, m_Next );
99 case 0x04: return BLK_FIELD( BLK_0x04_NET_ASSIGNMENT, m_Next );
100 case 0x05: return BLK_FIELD( BLK_0x05_TRACK, m_Next );
101 case 0x0E: return BLK_FIELD( BLK_0x0E_RECT, m_Next );
102 case 0x14: return BLK_FIELD( BLK_0x14_GRAPHIC, m_Next );
103 case 0x15:
104 case 0x16:
105 case 0x17: return BLK_FIELD( BLK_0x15_16_17_SEGMENT, m_Next );
106 case 0x1B: return BLK_FIELD( BLK_0x1B_NET, m_Next );
107 case 0x1D: return BLK_FIELD( BLK_0x1D_CONSTRAINT_SET, m_Next );
108 case 0x1E: return BLK_FIELD( BLK_0x1E_SI_MODEL, m_Next );
109 case 0x1F: return BLK_FIELD( BLK_0x1F_PADSTACK_DIM, m_Next );
110 case 0x2B: return BLK_FIELD( BLK_0x2B_FOOTPRINT_DEF, m_Next );
111 case 0x2D: return BLK_FIELD( BLK_0x2D_FOOTPRINT_INST, m_Next );
112 case 0x2E: return BLK_FIELD( BLK_0x2E_CONNECTION, m_Next );
113 case 0x30: return BLK_FIELD( BLK_0x30_STR_WRAPPER, m_Next );
114 case 0x31: return 0; // Doesn't exist
115 case 0x32: return BLK_FIELD( BLK_0x32_PLACED_PAD, m_Next );
116 case 0x24: return BLK_FIELD( BLK_0x24_RECT, m_Next );
117 case 0x28: return BLK_FIELD( BLK_0x28_SHAPE, m_Next );
118 case 0x2C: return BLK_FIELD( BLK_0x2C_TABLE, m_Next );
119 case 0x33: return BLK_FIELD( BLK_0x33_VIA, m_Next );
120 case 0x36: return BLK_FIELD( BLK_0x36_DEF_TABLE, m_Next );
121 case 0x37: return BLK_FIELD( BLK_0x37_PTR_ARRAY, m_Next );
122 default: return 0;
123 }
124}
125
126
130static uint32_t PadGetNextInFootprint( const BLOCK_BASE& aBlock )
131{
132 const uint8_t type = aBlock.GetBlockType();
133
134 if( type != 0x32 )
135 {
137 wxString::Format( "Unexpected next item in 0x32 pad list: block type %#04x, offset %#lx, key %#010x",
138 type, aBlock.GetOffset(), aBlock.GetKey() ) );
139 }
140
141 // When iterating in a footprint use this field, not m_Next.
142 return BLK_FIELD( BLK_0x32_PLACED_PAD, m_NextInFp );
143}
144
145
147{
148public:
149
150 using NEXT_FUNC_T = std::function<uint32_t( const BLOCK_BASE& )>;
151
153 {
154 public:
155 iterator( uint32_t aCurrent, uint32_t aTail, const BRD_DB& aBoard, NEXT_FUNC_T aNextFunc ) :
156 m_current( aCurrent ), m_tail( aTail ), m_board( aBoard ), m_NextFunc( aNextFunc )
157 {
158 m_currBlock = m_board.GetObjectByKey( m_current );
159
160 if( !m_currBlock )
161 m_current = 0;
162 }
163
164 const BLOCK_BASE* operator*() const { return m_currBlock; }
165
167 {
168 if( m_current == m_tail || !m_currBlock )
169 {
170 m_current = 0;
171 }
172 else
173 {
175
176 if( m_current == m_tail || m_board.IsSentinel( m_current ) )
177 {
178 m_current = 0;
179 }
180 else
181 {
182 m_currBlock = m_board.GetObjectByKey( m_current );
183
184 if( m_currBlock == nullptr )
185 {
186 m_current = 0;
187 }
188 }
189 }
190 return *this;
191 }
192
193 bool operator!=( const iterator& other ) const { return m_current != other.m_current; }
194
195 private:
196 uint32_t m_current;
198 uint32_t m_tail;
201 };
202
203 LL_WALKER( uint32_t aHead, uint32_t aTail, const BRD_DB& aBoard ) :
204 m_head( aHead ), m_tail( aTail ), m_board( aBoard )
205 {
206 // The default next function
208 }
209
210 LL_WALKER( const FILE_HEADER::LINKED_LIST& aList, const BRD_DB& aBoard ) :
211 LL_WALKER( aList.m_Head, aList.m_Tail, aBoard )
212 {
213 }
214
216 iterator end() const { return iterator( 0, m_tail, m_board, m_nextFunction ); }
217
218 void SetNextFunc( NEXT_FUNC_T aNextFunc ) { m_nextFunction = aNextFunc; }
219
220private:
221 uint32_t m_head;
222 uint32_t m_tail;
224
225 // This is the function that can get the next item in a list. By default
227};
228
229
230template <>
231struct std::hash<LAYER_INFO>
232{
233 size_t operator()( const LAYER_INFO& aLayerInfo ) const noexcept
234 {
235 return ( aLayerInfo.m_Class << 8 ) + aLayerInfo.m_Subclass;
236 }
237};
238
239
247// clang-format off
248static const std::unordered_map<LAYER_INFO, PCB_LAYER_ID> s_LayerKiMap = {
249
256
259
262
269
274
277};
278
286static const std::unordered_map<LAYER_INFO, wxString> s_OptionalFixedMappings = {
291
293
295
300
305
310
315
317};
318
319// clang-format on
320
321
328static wxString layerInfoDisplayName( const LAYER_INFO& aLayerInfo )
329{
330 // clang-format off
331 static const std::unordered_map<uint8_t, wxString> s_ClassNames = {
332 { LAYER_INFO::CLASS::BOARD_GEOMETRY, wxS( "Board Geometry" ) },
333 { LAYER_INFO::CLASS::COMPONENT_VALUE, wxS( "Component Value" ) },
334 { LAYER_INFO::CLASS::DEVICE_TYPE, wxS( "Device Type" ) },
335 { LAYER_INFO::CLASS::DRAWING_FORMAT, wxS( "Drawing Format" ) },
336 { LAYER_INFO::CLASS::ETCH, wxS( "Etch" ) },
337 { LAYER_INFO::CLASS::MANUFACTURING, wxS( "Manufacturing" ) },
338 { LAYER_INFO::CLASS::PACKAGE_GEOMETRY, wxS( "Package Geometry" ) },
339 { LAYER_INFO::CLASS::PACKAGE_KEEPIN, wxS( "Package Keepin" ) },
340 { LAYER_INFO::CLASS::PACKAGE_KEEPOUT, wxS( "Package Keepout" ) },
341 { LAYER_INFO::CLASS::PIN, wxS( "Pin" ) },
342 { LAYER_INFO::CLASS::REF_DES, wxS( "Ref Des" ) },
343 { LAYER_INFO::CLASS::ROUTE_KEEPIN, wxS( "Route Keepin" ) },
344 { LAYER_INFO::CLASS::ROUTE_KEEPOUT, wxS( "Route Keepout" ) },
345 { LAYER_INFO::CLASS::TOLERANCE, wxS( "Tolerance" ) },
346 { LAYER_INFO::CLASS::USER_PART_NUMBER, wxS( "User Part Number" ) },
347 { LAYER_INFO::CLASS::VIA_CLASS, wxS( "Via Class" ) },
348 { LAYER_INFO::CLASS::VIA_KEEPOUT, wxS( "Via Keepout" ) },
349 { LAYER_INFO::CLASS::ANTI_ETCH, wxS( "Anti Etch" ) },
350 { LAYER_INFO::CLASS::BOUNDARY, wxS( "Boundary" ) },
351 { LAYER_INFO::CLASS::CONSTRAINTS_REGION, wxS( "Constraints Region" ) },
352 };
353
354 static const std::unordered_map<uint8_t, wxString> s_BoardGeomSubclassNames = {
355 { LAYER_INFO::SUBCLASS::BGEOM_OUTLINE, wxS( "Outline" ) },
356 { LAYER_INFO::SUBCLASS::BGEOM_CONSTRAINT_AREA, wxS( "Constraint Area" ) },
357 { LAYER_INFO::SUBCLASS::BGEOM_OFF_GRID_AREA, wxS( "Off Grid Area" ) },
358 { LAYER_INFO::SUBCLASS::BGEOM_SOLDERMASK_BOTTOM, wxS( "Soldermask Bottom" ) },
359 { LAYER_INFO::SUBCLASS::BGEOM_SOLDERMASK_TOP, wxS( "Soldermask Top" ) },
360 { LAYER_INFO::SUBCLASS::BGEOM_ASSEMBLY_DETAIL, wxS( "Assembly Detail" ) },
361 { LAYER_INFO::SUBCLASS::BGEOM_SILKSCREEN_BOTTOM, wxS( "Silkscreen Bottom" ) },
362 { LAYER_INFO::SUBCLASS::BGEOM_SILKSCREEN_TOP, wxS( "Silkscreen Top" ) },
363 { LAYER_INFO::SUBCLASS::BGEOM_SWITCH_AREA_BOTTOM, wxS( "Switch Area Bottom" ) },
364 { LAYER_INFO::SUBCLASS::BGEOM_SWITCH_AREA_TOP, wxS( "Switch Area Top" ) },
365 { LAYER_INFO::SUBCLASS::BGEOM_BOTH_ROOMS, wxS( "Both Rooms" ) },
366 { LAYER_INFO::SUBCLASS::BGEOM_BOTTOM_ROOM, wxS( "Bottom Room" ) },
367 { LAYER_INFO::SUBCLASS::BGEOM_TOP_ROOM, wxS( "Top Room" ) },
368 { LAYER_INFO::SUBCLASS::BGEOM_PLACE_GRID_BOTTOM, wxS( "Place Grid Bottom" ) },
369 { LAYER_INFO::SUBCLASS::BGEOM_PLACE_GRID_TOP, wxS( "Place Grid Top" ) },
370 { LAYER_INFO::SUBCLASS::BGEOM_DIMENSION, wxS( "Dimension" ) },
371 { LAYER_INFO::SUBCLASS::BGEOM_TOOLING_CORNERS, wxS( "Tooling Corners" ) },
372 { LAYER_INFO::SUBCLASS::BGEOM_ASSEMBLY_NOTES, wxS( "Assembly Notes" ) },
373 { LAYER_INFO::SUBCLASS::BGEOM_PLATING_BAR, wxS( "Plating Bar" ) },
374 { LAYER_INFO::SUBCLASS::BGEOM_DESIGN_OUTLINE, wxS( "Design Outline" ) },
375
376 };
377
378 static const std::unordered_map<uint8_t, wxString> s_ComponentValueSubclassNames = {
379 { LAYER_INFO::SUBCLASS::DISPLAY_BOTTOM, wxS( "Display Bottom" ) },
380 { LAYER_INFO::SUBCLASS::DISPLAY_TOP, wxS( "Display Top" ) },
381 { LAYER_INFO::SUBCLASS::SILKSCREEN_BOTTOM, wxS( "Silkscreen Bottom" ) },
382 { LAYER_INFO::SUBCLASS::SILKSCREEN_TOP, wxS( "Silkscreen Top" ) },
383 { LAYER_INFO::SUBCLASS::ASSEMBLY_BOTTOM, wxS( "Assembly Bottom" ) },
384 { LAYER_INFO::SUBCLASS::ASSEMBLY_TOP, wxS( "Assembly Top" ) },
385 };
386
387 static const std::unordered_map<uint8_t, wxString> s_DrawingFormatSubclassNames = {
388 { LAYER_INFO::SUBCLASS::DFMT_REVISION_DATA, wxS( "Revision Data" ) },
389 { LAYER_INFO::SUBCLASS::DFMT_REVISION_BLOCK, wxS( "Revision Block" ) },
390 { LAYER_INFO::SUBCLASS::DFMT_TITLE_DATA, wxS( "Title Data" ) },
391 { LAYER_INFO::SUBCLASS::DFMT_TITLE_BLOCK, wxS( "Title Block" ) },
392 { LAYER_INFO::SUBCLASS::DFMT_OUTLINE, wxS( "Outline" ) },
393 };
394
395 static const std::unordered_map<uint8_t, wxString> s_PackageGeometrySubclassNames = {
396 { LAYER_INFO::SUBCLASS::DFA_BOUND_BOTTOM, wxS( "DFA Bound Bottom" ) },
397 { LAYER_INFO::SUBCLASS::DFA_BOUND_TOP, wxS( "DFA Bound Top" ) },
398 { LAYER_INFO::SUBCLASS::PGEOM_DISPLAY_BOTTOM, wxS( "Display Bottom" ) },
399 { LAYER_INFO::SUBCLASS::PGEOM_DISPLAY_TOP, wxS( "Display Top" ) },
400 { LAYER_INFO::SUBCLASS::PGEOM_SOLDERMASK_BOTTOM, wxS( "Soldermask Bottom" ) },
401 { LAYER_INFO::SUBCLASS::PGEOM_SOLDERMASK_TOP, wxS( "Soldermask Top" ) },
402 { LAYER_INFO::SUBCLASS::PGEOM_BODY_CENTER, wxS( "Body Center" ) },
403 { LAYER_INFO::SUBCLASS::PGEOM_SILKSCREEN_BOTTOM, wxS( "Silkscreen Bottom" ) },
404 { LAYER_INFO::SUBCLASS::PGEOM_SILKSCREEN_TOP, wxS( "Silkscreen Top" ) },
405 { LAYER_INFO::SUBCLASS::PGEOM_PAD_STACK_NAME, wxS( "Pad Stack Name" ) },
406 { LAYER_INFO::SUBCLASS::PGEOM_PIN_NUMBER, wxS( "Pin Number" ) },
407 { LAYER_INFO::SUBCLASS::PGEOM_PLACE_BOUND_BOTTOM, wxS( "Place Bound Bottom" ) },
408 { LAYER_INFO::SUBCLASS::PGEOM_PLACE_BOUND_TOP, wxS( "Place Bound Top" ) },
409 { LAYER_INFO::SUBCLASS::PGEOM_ASSEMBLY_BOTTOM, wxS( "Assembly Bottom" ) },
410 { LAYER_INFO::SUBCLASS::PGEOM_ASSEMBLY_TOP, wxS( "Assembly Top" ) },
411 };
412
413 static const std::unordered_map<uint8_t, wxString> s_ManufacturingSubclassNames = {
414 { LAYER_INFO::SUBCLASS::MFR_XSECTION_CHART, wxS( "X-Section Chart" ) },
415 { LAYER_INFO::SUBCLASS::MFR_NO_PROBE_BOTTOM, wxS( "No Probe Bottom" ) },
416 { LAYER_INFO::SUBCLASS::MFR_NO_PROBE_TOP, wxS( "No Probe Top" ) },
417 { LAYER_INFO::SUBCLASS::MFR_AUTOSILK_BOTTOM, wxS( "AutoSilk Bottom" ) },
418 { LAYER_INFO::SUBCLASS::MFR_AUTOSILK_TOP, wxS( "AutoSilk Top" ) },
419 { LAYER_INFO::SUBCLASS::MFR_PROBE_BOTTOM, wxS( "Probe Bottom" ) },
420 { LAYER_INFO::SUBCLASS::MFR_PROBE_TOP, wxS( "Probe Top" ) },
421 { LAYER_INFO::SUBCLASS::MFR_NCDRILL_FIGURE, wxS( "NC Drill Figure" ) },
422 { LAYER_INFO::SUBCLASS::MFR_NCDRILL_LEGEND, wxS( "NC Drill Legend" ) },
423 { LAYER_INFO::SUBCLASS::MFR_NO_GLOSS_INTERNAL, wxS( "No Gloss Internal" ) },
424 { LAYER_INFO::SUBCLASS::MFR_NO_GLOSS_BOTTOM, wxS( "No Gloss Bottom" ) },
425 { LAYER_INFO::SUBCLASS::MFR_NO_GLOSS_TOP, wxS( "No Gloss Top" ) },
426 { LAYER_INFO::SUBCLASS::MFR_NO_GLOSS_ALL, wxS( "No Gloss All" ) },
427 { LAYER_INFO::SUBCLASS::MFR_PHOTOPLOT_OUTLINE, wxS( "Photoplot Outline" ) },
428 };
429
430 static const std::unordered_map<uint8_t, wxString> s_AnalysisSubclassNames = {
431 { LAYER_INFO::SUBCLASS::ANALYSIS_PCB_TEMPERATURE, wxS( "PCB Temperature" ) },
432 { LAYER_INFO::SUBCLASS::ANALYSIS_HIGH_ISOCONTOUR, wxS( "High IsoContour" ) },
433 { LAYER_INFO::SUBCLASS::ANALYSIS_MEDIUM3_ISOCONTOUR, wxS( "Medium3 IsoContour" ) },
434 { LAYER_INFO::SUBCLASS::ANALYSIS_MEDIUM2_ISOCONTOUR, wxS( "Medium2 IsoContour" ) },
435 { LAYER_INFO::SUBCLASS::ANALYSIS_MEDIUM1_ISOCONTOUR, wxS( "Medium1 IsoContour" ) },
436 { LAYER_INFO::SUBCLASS::ANALYSIS_LOW_ISOCONTOUR, wxS( "Low IsoContour" ) },
437 };
438
439 static const std::unordered_map<uint8_t, wxString> s_ConstraintSubclassNames = {
440 { LAYER_INFO::SUBCLASS::CREG_ALL, wxS( "All" ) },
441 };
442
443 static const std::unordered_map<uint8_t, wxString> s_KeepinSubclassNames = {
444 { LAYER_INFO::SUBCLASS::KEEPIN_ALL, wxS( "All" ) },
445 };
446
447 static const std::unordered_map<uint8_t, wxString> s_KeepoutSubclassNames = {
448 { LAYER_INFO::SUBCLASS::KEEPOUT_ALL, wxS( "All" ) },
449 { LAYER_INFO::SUBCLASS::KEEPOUT_TOP, wxS( "Top" ) },
450 { LAYER_INFO::SUBCLASS::KEEPOUT_BOTTOM, wxS( "Bottom" ) },
451 };
452
453 static const std::unordered_map<uint8_t, const std::unordered_map<uint8_t, wxString>&> s_SubclassNameMaps = {
454 { LAYER_INFO::CLASS::BOARD_GEOMETRY, s_BoardGeomSubclassNames },
455
456 // These classes all share the same subclass names
457 { LAYER_INFO::CLASS::COMPONENT_VALUE, s_ComponentValueSubclassNames },
458 { LAYER_INFO::CLASS::DEVICE_TYPE, s_ComponentValueSubclassNames },
459 { LAYER_INFO::CLASS::REF_DES, s_ComponentValueSubclassNames },
460 { LAYER_INFO::CLASS::TOLERANCE, s_ComponentValueSubclassNames },
461 { LAYER_INFO::CLASS::USER_PART_NUMBER, s_ComponentValueSubclassNames },
462
463 { LAYER_INFO::CLASS::DRAWING_FORMAT, s_DrawingFormatSubclassNames },
464 { LAYER_INFO::CLASS::PACKAGE_GEOMETRY, s_PackageGeometrySubclassNames },
465 { LAYER_INFO::CLASS::MANUFACTURING, s_ManufacturingSubclassNames },
466 { LAYER_INFO::CLASS::ANALYSIS, s_AnalysisSubclassNames },
467 { LAYER_INFO::CLASS::CONSTRAINTS_REGION, s_ConstraintSubclassNames },
468 { LAYER_INFO::CLASS::PACKAGE_KEEPIN, s_KeepinSubclassNames },
469 { LAYER_INFO::CLASS::PACKAGE_KEEPOUT, s_KeepoutSubclassNames },
470 { LAYER_INFO::CLASS::ROUTE_KEEPIN, s_KeepinSubclassNames },
471 { LAYER_INFO::CLASS::ROUTE_KEEPOUT, s_KeepoutSubclassNames },
472 { LAYER_INFO::CLASS::VIA_KEEPOUT, s_KeepoutSubclassNames },
473 };
474 // clang-format on
475
476 wxString className;
477 const auto classIt = s_ClassNames.find( aLayerInfo.m_Class );
478
479 if( classIt != s_ClassNames.end() )
480 className = classIt->second;
481 else
482 className = wxString::Format( wxS( "Class_%02X" ), aLayerInfo.m_Class );
483
484 wxString subclassName;
485
486 // Find the right subclass name map for this class
487 auto classMapIt = s_SubclassNameMaps.find( aLayerInfo.m_Class );
488
489 if( classMapIt != s_SubclassNameMaps.end() )
490 {
491 const std::unordered_map<uint8_t, wxString>& subclassMap = classMapIt->second;
492
493 const auto subIt = subclassMap.find( aLayerInfo.m_Subclass );
494
495 if( subIt != subclassMap.end() )
496 subclassName = subIt->second;
497 else
498 {
499 // This subclass seems not to have a known name
500 subclassName = wxString::Format( wxS( "Subclass_%02X" ), aLayerInfo.m_Subclass );
501 }
502 }
503 else
504 {
505 // Don't have a specific map for this class, just do a generic one.
506 subclassName = wxString::Format( wxS( "Subclass_%02X" ), aLayerInfo.m_Subclass );
507 }
508
509 return className + wxS( "/" ) + subclassName;
510}
511
512
522static bool layerIsZone( const LAYER_INFO& aLayerInfo )
523{
525 aLayerInfo.m_Class == LAYER_INFO::CLASS::BOUNDARY ||
531 return true;
532
533 return false;
534}
535
536
540static std::optional<LAYER_INFO> tryLayerFromBlock( const BLOCK_BASE& aBlock )
541{
542 switch( aBlock.GetBlockType() )
543 {
544 case 0x0e:
545 {
546 const auto& net = BlockDataAs<BLK_0x0E_RECT>( aBlock );
547 return net.m_Layer;
548 }
549 case 0x14:
550 {
551 const auto& trace = BlockDataAs<BLK_0x14_GRAPHIC>( aBlock );
552 return trace.m_Layer;
553 }
554 case 0x24:
555 {
556 const auto& rect = BlockDataAs<BLK_0x24_RECT>( aBlock );
557 return rect.m_Layer;
558 }
559 case 0x28:
560 {
561 const auto& shape = BlockDataAs<BLK_0x28_SHAPE>( aBlock );
562 return shape.m_Layer;
563 }
564 }
565
566 return std::nullopt;
567}
568
569
576{
577 std::optional<LAYER_INFO> layerInfo = tryLayerFromBlock( aBlock );
578
579 // Programming error - should only call this function if we're sure the block has layer info
580 wxCHECK( layerInfo.has_value(), LAYER_INFO() );
581
582 return layerInfo.value();
583}
584
585
590{
597 {
598 wxString m_Name;
599 // LAYER_ARTWORK: POSITIVE/NEGATIVE
600 // LAYER_USE: empty, EMBEDDED_PLANE, ...?
601 // bool m_IsConductor;
602 };
603
604public:
605 LAYER_MAPPER( const BRD_DB& aRawBoard, BOARD& aBoard, const LAYER_MAPPING_HANDLER& aLayerMappingHandler ) :
606 m_layerMappingHandler( aLayerMappingHandler ),
607 m_brdDb( aRawBoard ),
608 m_board( aBoard )
609 {}
610
611 void ProcessLayerList( uint8_t aClass, const BLK_0x2A_LAYER_LIST& aList )
612 {
613 // If we haven't seen this list yet, create and store the CUSTOM_LAYER list
614 if( m_Lists.count( &aList ) == 0 )
615 {
616 std::vector<CUSTOM_LAYER>& classLayers = m_Lists[&aList];
617
618 if( aList.m_RefEntries.has_value() )
619 {
620 for( const BLK_0x2A_LAYER_LIST::REF_ENTRY& entry : aList.m_RefEntries.value() )
621 {
622 const wxString& layerName = m_brdDb.GetString( entry.mLayerNameId );
623
624 classLayers.emplace_back( CUSTOM_LAYER( layerName ) );
625 }
626 }
627 else if( aList.m_NonRefEntries.has_value() )
628 {
629 for( const BLK_0x2A_LAYER_LIST::NONREF_ENTRY& entry : aList.m_NonRefEntries.value() )
630 {
631 classLayers.emplace_back( CUSTOM_LAYER( entry.m_Name ) );
632 }
633 }
634 else
635 {
636 // Presumably a parsing error.
637 THROW_IO_ERROR( "No ETCH layer list found." );
638 }
639
640 wxLogTrace( traceAllegroBuilder, "Added %zu layers for class %#04x, from 0x2A key %#010x",
641 classLayers.size(), aClass, aList.m_Key );
642 }
643
644 // Store the class ID -> 0x2A mapping
645 m_ClassCustomLayerLists[aClass] = &m_Lists[&aList];
646 }
647
654 {
655 auto customLayerIt = m_ClassCustomLayerLists.find( LAYER_INFO::CLASS::ETCH );
656
657 if( customLayerIt == m_ClassCustomLayerLists.end() || !customLayerIt->second )
658 {
659 wxLogTrace( traceAllegroBuilder, "No ETCH layer class found; cannot finalize layers" );
660 return;
661 }
662
663 const std::vector<CUSTOM_LAYER>& etchLayers = *customLayerIt->second;
664 const size_t numCuLayers = etchLayers.size();
665
666 m_board.GetDesignSettings().SetCopperLayerCount( numCuLayers );
667
668 std::vector<INPUT_LAYER_DESC> inputLayers;
669
670 for( size_t li = 0; li < numCuLayers; ++li )
671 {
672 INPUT_LAYER_DESC desc;
673 desc.Name = etchLayers[li].m_Name;
674 desc.AutoMapLayer = getNthCopperLayer( li, numCuLayers );
676 desc.Required = true;
677 inputLayers.push_back( desc );
678 }
679
680 // Add non-ETCH custom layers so they appear in the layer mapping dialog
681 const std::vector<CUSTOM_LAYER>* etchList = m_ClassCustomLayerLists[LAYER_INFO::CLASS::ETCH];
682 int nextAutoUser = 0;
683
684 for( const auto& [classId, layerList] : m_ClassCustomLayerLists )
685 {
686 if( classId == LAYER_INFO::CLASS::ETCH || layerList == etchList )
687 continue;
688
689 for( size_t si = 0; si < layerList->size(); ++si )
690 {
691 const LAYER_INFO li{ classId, static_cast<uint8_t>( si ) };
692
693 // Skip entries already covered by s_LayerKiMap
694 if( s_LayerKiMap.count( li ) )
695 continue;
696
697 INPUT_LAYER_DESC desc;
698 desc.Name = layerInfoDisplayName( li );
699
700 if( layerList->at( si ).m_Name.length() > 0 )
701 desc.Name = layerList->at( si ).m_Name;
702
703 desc.AutoMapLayer = getNthUserLayer( nextAutoUser++ );
705 desc.Required = false;
706 inputLayers.push_back( desc );
707
709 }
710 }
711
712 // The layers that maybe lump together multiple Allegro class:subclasses
713 // into a single, named, KiCad layer
714 for( const auto& [layerName, kiLayer] : m_MappedOptionalLayers )
715 {
716 INPUT_LAYER_DESC desc;
717 desc.Name = layerName;
718 desc.AutoMapLayer = kiLayer;
720 desc.Required = false;
721 inputLayers.push_back( desc );
722 }
723
724 for( const auto& [layerInfo, kiLayer] : s_LayerKiMap )
725 {
726 INPUT_LAYER_DESC desc;
727 desc.Name = layerInfoDisplayName( layerInfo );
728 desc.AutoMapLayer = kiLayer;
730 desc.Required = false;
731 inputLayers.push_back( desc );
732 }
733
734 std::map<wxString, PCB_LAYER_ID> resolvedMapping = m_layerMappingHandler( inputLayers );
735
736 // Apply copper layer mapping
737 for( size_t li = 0; li < numCuLayers; ++li )
738 {
739 const LAYER_INFO layerInfo{ LAYER_INFO::CLASS::ETCH, static_cast<uint8_t>( li ) };
740 const wxString& layerName = etchLayers[li].m_Name;
741
742 auto it = resolvedMapping.find( layerName );
743 PCB_LAYER_ID lId = ( it != resolvedMapping.end() ) ? it->second
744 : getNthCopperLayer( li, numCuLayers );
745
746 m_customLayerToKiMap[layerInfo] = lId;
747 m_board.SetLayerName( lId, layerName );
748 }
749
750 // Apply non-copper static layer mapping from the handler result
751 for( const auto& [layerInfo, defaultKiLayer] : s_LayerKiMap )
752 {
753 const wxString displayName = layerInfoDisplayName( layerInfo );
754
755 auto rmIt = resolvedMapping.find( displayName );
756
757 if( rmIt != resolvedMapping.end() && rmIt->second != PCB_LAYER_ID::UNDEFINED_LAYER )
758 {
759 m_staticLayerOverrides[layerInfo] = rmIt->second;
760 }
761 }
762
763 // Apply custom layer mapping from the handler result
764 for( const auto& [layerInfo, dialogName] : m_customLayerDialogNames )
765 {
766 auto rmIt = resolvedMapping.find( dialogName );
767
768 if( rmIt != resolvedMapping.end() && rmIt->second != PCB_LAYER_ID::UNDEFINED_LAYER )
769 {
770 m_customLayerToKiMap[layerInfo] = rmIt->second;
771 m_board.SetLayerName( rmIt->second, dialogName );
772 }
773 }
774
775 // Enable all the layers we ended up mapping to
776 LSET enabledLayersMask = m_board.GetEnabledLayers();
777 int userLayers = 0;
778 for( const auto& [name, layerId] : resolvedMapping )
779 {
780 if( layerId != PCB_LAYER_ID::UNDEFINED_LAYER )
781 enabledLayersMask |= LSET{ layerId };
782
783 if( IsUserLayer( layerId ) )
784 userLayers++;
785
786 wxLogTrace( traceAllegroBuilder, "Mapping Allegro layer '%s' to KiCad layer '%s' (%d)", name,
787 m_board.GetLayerName( layerId ), layerId );
788
789 m_board.SetLayerName( layerId, name );
790 }
791 m_board.SetEnabledLayers( enabledLayersMask );
792 wxLogTrace( traceAllegroBuilder, "After mapping, there are %d user layers", userLayers );
793 m_board.GetDesignSettings().SetUserDefinedLayerCount( userLayers );
794 }
795
796 PCB_LAYER_ID GetLayer( const LAYER_INFO& aLayerInfo )
797 {
798 // We already mapped and created the layer
799 if( m_customLayerToKiMap.count( aLayerInfo ) )
800 return m_customLayerToKiMap.at( aLayerInfo );
801
802 // Check for user-remapped static layers first
803 if( m_staticLayerOverrides.count( aLayerInfo ) )
804 return m_staticLayerOverrides.at( aLayerInfo );
805
806 // Next, have a look and see if the class:subclass was recorded as a custom layer
807 if( m_ClassCustomLayerLists.count( aLayerInfo.m_Class ) )
808 {
809 const std::vector<CUSTOM_LAYER>* cLayerList = m_ClassCustomLayerLists.at( aLayerInfo.m_Class );
810
811 // If it is using the copper layer list and the subclass is within the
812 // copper layer range, return the mapped copper layer. Non-ETCH classes
813 // can share the same layer list pointer, but their subclass values may
814 // exceed the copper layer count and must fall through to the custom
815 // layer mapping below.
816 const auto etchIt = m_ClassCustomLayerLists.find( LAYER_INFO::CLASS::ETCH );
817 if( etchIt != m_ClassCustomLayerLists.end()
818 && cLayerList == etchIt->second
819 && aLayerInfo.m_Subclass < cLayerList->size() )
820 {
821 const PCB_LAYER_ID cuLayer = getNthCopperLayer( aLayerInfo.m_Subclass, cLayerList->size() );
822 // Remember this mapping
823 m_customLayerToKiMap[aLayerInfo] = cuLayer;
824 return cuLayer;
825 }
826
827 if( aLayerInfo.m_Subclass < cLayerList->size() )
828 {
829 // This subclass maps to a custom layer in this class
830 const CUSTOM_LAYER& cLayer = cLayerList->at( aLayerInfo.m_Subclass );
831 return MapCustomLayer( aLayerInfo, cLayer.m_Name );
832 }
833 }
834
835 // Now, there may be layers that map to custom layers in KiCad, but are fixed in Allegro
836 // (perhaps, DFA_BOUND_TOP), which means we won't find them in the layer lists.
837 // We add them if we encounter them, with the names defined.
838 if( s_OptionalFixedMappings.count( aLayerInfo ) )
839 {
840 const wxString& layerName = s_OptionalFixedMappings.at( aLayerInfo );
841 return MapCustomLayer( aLayerInfo, layerName );
842 }
843
844 // Finally, fallback to the static mapping for any layers we haven't got a custom map for
845 // We do this last so that it can be overridden for example if we want to remap
846 // OUTLINE and DESIGN_OUTLINE to different layers.
847 if( s_LayerKiMap.count( aLayerInfo ) )
848 return s_LayerKiMap.at( aLayerInfo );
849
850 // Keep a record of what we failed to map
851 if( m_unknownLayers.count( aLayerInfo ) == 0 )
852 {
853 wxLogTrace( traceAllegroBuilder, "Failed to map class:subclass to layer: %#04x:%#04x", aLayerInfo.m_Class,
854 aLayerInfo.m_Subclass );
855 m_unknownLayers[aLayerInfo] = 1;
856 }
857 m_unknownLayers[aLayerInfo]++;
858
859 // Dump everything else here
860 return m_unmappedLayer;
861 }
862
866 bool IsLayerMapped( PCB_LAYER_ID aLayerId ) const
867 {
868 return aLayerId != m_unmappedLayer;
869 }
870
878 {
879 const wxString name = aTop ? "PLACE_BOUND_TOP" : "PLACE_BOUND_BOTTOM";
880 return mapCustomLayerByName( name );
881 }
882
887 bool IsOutlineLayer( const LAYER_INFO& aLayerInfo ) const
888 {
891 {
892 return false;
893 }
894
897 }
898
906 PCB_LAYER_ID MapCustomLayer( const LAYER_INFO& aLayerInfo, const wxString& aLayerName )
907 {
908 // See if we have mapped this layer name under a different class:subclass
909 if( m_MappedOptionalLayers.count( aLayerName ) )
910 {
911 const PCB_LAYER_ID existingLId = m_MappedOptionalLayers.at( aLayerName );
912 // Record the reuse
913 m_customLayerToKiMap[aLayerInfo] = existingLId;
914 return existingLId;
915 }
916
917 // First time we needed this name:
918 // Add as a user layer and store for next time
919 const PCB_LAYER_ID lId = addUserLayer( aLayerName );
920 m_customLayerToKiMap[aLayerInfo] = lId;
921 m_MappedOptionalLayers[aLayerName] = lId;
922
923 wxLogTrace( traceAllegroBuilder, "Adding mapping for %#04x:%#04x to %s", aLayerInfo.m_Class,
924 aLayerInfo.m_Subclass, aLayerName );
925 return lId;
926 }
927
928
929 LSET GetRuleAreaLayers( const LAYER_INFO& aLayerInfo )
930 {
931 LSET layerSet{};
932
933 switch( aLayerInfo.m_Class )
934 {
938 {
939 switch( aLayerInfo.m_Subclass )
940 {
942 layerSet = LSET::AllCuMask();
943 break;
945 layerSet = LSET{ F_Cu };
946 break;
948 layerSet = LSET{ B_Cu };
949 break;
950 default:
951 layerSet = LSET{ GetLayer( aLayerInfo ) };
952 break;
953 }
954 break;
955 }
958 {
959 // This can be ALL, but can it be anything else?
961 layerSet = LSET::AllCuMask();
962 else
963 layerSet = LSET{ GetLayer( aLayerInfo ) };
964 break;
965 }
966 default:
967 wxLogTrace( traceAllegroBuilder, " Unhandled non-copper zone layer class %#02x, using default layers",
968 aLayerInfo.m_Class );
969 layerSet = LSET{ GetLayer( aLayerInfo ) };
970 break;
971 }
972
973 return layerSet;
974 }
975
976private:
977 static PCB_LAYER_ID getNthCopperLayer( int aNum, int aTotal )
978 {
979 if( aNum == 0 )
980 return F_Cu;
981 if( aNum == aTotal - 1 )
982 return B_Cu;
983 return ToLAYER_ID( 2 * ( aNum + 1 ) );
984 }
985
986 static PCB_LAYER_ID getNthUserLayer( int aNum )
987 {
988 aNum = std::min( aNum, MAX_USER_DEFINED_LAYERS - 1 );
989 return ToLAYER_ID( static_cast<int>( User_1 ) + 2 * aNum );
990 }
991
998 PCB_LAYER_ID mapCustomLayerByName( const wxString& aLayerName )
999 {
1000 // If it's been added already, use it
1001 if( m_MappedOptionalLayers.count( aLayerName ) )
1002 {
1003 return m_MappedOptionalLayers.at( aLayerName );
1004 }
1005
1006 const PCB_LAYER_ID newLId = addUserLayer( aLayerName );
1007 m_MappedOptionalLayers[aLayerName] = newLId;
1008 return newLId;
1009 }
1010
1011 PCB_LAYER_ID addUserLayer( const wxString& aName )
1012 {
1014 m_board.GetDesignSettings().SetUserDefinedLayerCount( m_board.GetDesignSettings().GetUserDefinedLayerCount() + 1 );
1015 m_board.SetLayerName( lId, aName );
1016 wxLogTrace( traceAllegroBuilder, "Adding user layer %s: %s", LayerName( lId ), aName );
1017 return lId;
1018 }
1019
1020 // Map of original layer list - we use this to store the CUSTOM_LAYERs, as well
1021 // as check that we only handle each one once
1022 std::unordered_map<const BLK_0x2A_LAYER_LIST*, std::vector<CUSTOM_LAYER>> m_Lists;
1023
1024 // Which classes point to which layer lists (more than one class can point to one list.
1025 std::unordered_map<uint8_t, std::vector<CUSTOM_LAYER>*> m_ClassCustomLayerLists;
1026
1033 std::unordered_map<LAYER_INFO, PCB_LAYER_ID> m_customLayerToKiMap;
1034
1040 std::unordered_map<wxString, PCB_LAYER_ID> m_MappedOptionalLayers;
1041
1045 std::unordered_map<LAYER_INFO, PCB_LAYER_ID> m_staticLayerOverrides;
1046
1051 std::unordered_map<LAYER_INFO, wxString> m_customLayerDialogNames;
1052
1056 std::unordered_map<LAYER_INFO, int> m_unknownLayers;
1057
1059
1060 // The layer to use for mapping failures;
1062
1064
1067};
1068
1069
1070BOARD_BUILDER::BOARD_BUILDER( const BRD_DB& aRawBoard, BOARD& aBoard, REPORTER& aReporter,
1071 PROGRESS_REPORTER* aProgressReporter,
1072 const LAYER_MAPPING_HANDLER& aLayerMappingHandler ) :
1073 m_brdDb( aRawBoard ), m_board( aBoard ), m_reporter( aReporter ), m_progressReporter( aProgressReporter ),
1074 m_layerMappingHandler( aLayerMappingHandler ),
1076{
1077 // Internal coordinates are stored in <base> / <divisor> units.
1078
1079 const std::map<BOARD_UNITS, int> c_baseScales = { { BOARD_UNITS::MILS, pcbIUScale.MilsToIU( 1 ) },
1080 { BOARD_UNITS::INCHES, pcbIUScale.MilsToIU( 1000 ) },
1081 { BOARD_UNITS::MILLIMETERS, pcbIUScale.mmToIU( 1 ) },
1082 { BOARD_UNITS::CENTIMETERS, pcbIUScale.mmToIU( 10 ) },
1083 { BOARD_UNITS::MICROMETERS, pcbIUScale.mmToIU( 0.001 ) } };
1084
1085 if( m_brdDb.m_Header->m_UnitsDivisor == 0 )
1086 THROW_IO_ERROR( "Board units divisor is 0" );
1087
1088 if( !c_baseScales.contains( m_brdDb.m_Header->m_BoardUnits ) )
1089 THROW_IO_ERROR( "Unknown board units" );
1090
1091 double baseScale( c_baseScales.at( m_brdDb.m_Header->m_BoardUnits ) );
1092
1093 m_scale = baseScale / m_brdDb.m_Header->m_UnitsDivisor;
1094}
1095
1096
1111{
1112public:
1123
1133 class COMPLEX_FIRST_FILL_TASK : public PRIORITY_THREAD_POOL_TASK<std::vector<FILL_INFO>>
1134 {
1135 public:
1136 COMPLEX_FIRST_FILL_TASK( bool aSimplify ) : m_simplify( aSimplify ) {}
1137
1138 private:
1139 int computePriorityKey( const FILL_INFO& a ) const override
1140 {
1141 return static_cast<int>( a.m_CombinedFill.TotalVertices() );
1142 }
1143
1144 size_t task( FILL_INFO& fillInfo ) override
1145 {
1146 SHAPE_POLY_SET finalFillPolys = *fillInfo.m_Zone->Outline();
1147
1148 finalFillPolys.ClearArcs();
1149 fillInfo.m_CombinedFill.ClearArcs();
1150
1151 // Intersect the zone outline with the combined fill that was assembled
1152 // from all the related objects.
1153 finalFillPolys.BooleanIntersection( fillInfo.m_CombinedFill );
1154 finalFillPolys.Fracture( m_simplify );
1155
1156 // This is already mutex-ed, so this is safe
1157 fillInfo.m_Zone->SetFilledPolysList( fillInfo.m_Layer, finalFillPolys );
1158 return 1;
1159 }
1160
1162 };
1163
1167 void ProcessPolygons( bool aSimplify )
1168 {
1169 PROF_TIMER timer( "Zone fill processing" );
1170
1171 COMPLEX_FIRST_FILL_TASK fillTask( aSimplify );
1172 fillTask.Execute( m_FillInfos );
1173
1174 wxLogTrace( traceAllegroPerf, wxT( " Intersected and fractured zone fills in %.3f ms" ), // format:allow
1175 timer.msecs() );
1176 }
1177
1178 void QueuePolygonForZone( ZONE& aZone, SHAPE_POLY_SET aFilledArea, PCB_LAYER_ID aLayer )
1179 {
1180 // Rule areas don't need filling
1181 if( aZone.GetIsRuleArea() )
1182 return;
1183
1184 m_FillInfos.emplace_back( &aZone, aLayer, std::move( aFilledArea ) );
1185 }
1186
1187private:
1188 std::vector<FILL_INFO> m_FillInfos;
1189};
1190
1191
1195
1196
1197static int clampForScale( double aValue )
1198{
1199 double result = std::round( aValue );
1200
1201 if( result > std::numeric_limits<int>::max() )
1202 return std::numeric_limits<int>::max();
1203
1204 if( result < std::numeric_limits<int>::min() )
1205 return std::numeric_limits<int>::min();
1206
1207 return static_cast<int>( result );
1208}
1209
1210
1212{
1213 return VECTOR2I{
1214 clampForScale( aVector.x * m_scale ),
1215 clampForScale( -aVector.y * m_scale ),
1216 };
1217}
1218
1219int BOARD_BUILDER::scale( int aValue ) const
1220{
1221 return clampForScale( aValue * m_scale );
1222}
1223
1224
1226{
1227 return VECTOR2I{
1228 clampForScale( std::abs( aSize.x ) * m_scale ),
1229 clampForScale( std::abs( aSize.y ) * m_scale ),
1230 };
1231}
1232
1233
1234static EDA_ANGLE fromMillidegrees( uint32_t aMilliDegrees )
1235{
1236 return EDA_ANGLE{ static_cast<double>( aMilliDegrees ) / 1000.0, DEGREES_T };
1237}
1238
1239
1240void BOARD_BUILDER::reportMissingBlock( uint32_t aKey, uint8_t aType ) const
1241{
1242 m_reporter.Report( wxString::Format( "Could not find expected block with key %#010x and type %#04x", aKey, aType ),
1244}
1245
1246
1247void BOARD_BUILDER::reportUnexpectedBlockType( uint8_t aGot, uint8_t aExpected, uint32_t aKey, size_t aOffset,
1248 const wxString& aName ) const
1249{
1250 wxString name = aName.IsEmpty() ? wxString( "Object" ) : aName;
1251 wxString withKey = ( aKey == 0 ) ? wxString( "" ) : wxString::Format( ", with key %#010x ", aKey );
1252 wxString withOffset = ( aOffset == 0 ) ? wxString( "" ) : wxString::Format( ", at offset %#lx ", aOffset );
1253
1254 wxString s = wxString::Format( "%s has unexpected type %#04x (expected %#04x)%s%s", name, aGot, aExpected, withKey,
1255 withOffset );
1256
1257 m_reporter.Report( s, RPT_SEVERITY_WARNING );
1258}
1259
1260
1261wxString BOARD_BUILDER::get0x30StringValue( uint32_t a0x30Key ) const
1262{
1263 const BLK_0x30_STR_WRAPPER* blk0x30 = expectBlockByKey<BLK_0x30_STR_WRAPPER>( a0x30Key, 0x30 );
1264
1265 if( blk0x30 == nullptr )
1266 THROW_IO_ERROR( "Failed to get 0x30 for string lookup" );
1267
1269
1270 if( blk0x31 == nullptr )
1271 THROW_IO_ERROR( "Failed to get 0x31 for string lookup" );
1272
1273 return blk0x31->m_Value;
1274}
1275
1276
1278{
1279 LL_WALKER x36_walker{ m_brdDb.m_Header->m_LL_0x36.m_Head, m_brdDb.m_Header->m_LL_0x36.m_Tail, m_brdDb };
1280
1281 bool encountered = false;
1282
1283 for( const BLOCK_BASE* block : x36_walker )
1284 {
1285 if( block->GetBlockType() != 0x36 )
1286 continue;
1287
1288 const BLK_0x36_DEF_TABLE& blk0x36 = static_cast<const BLOCK<BLK_0x36_DEF_TABLE>&>( *block ).GetData();
1289
1290 if( blk0x36.m_Code != 0x08 )
1291 continue;
1292
1293 if( encountered )
1294 {
1295 // This would be bad, because we won't get the indexes into the list right if there
1296 // it's made up of entries from more than one list of entries.
1297 m_reporter.Report( "Found more than one font definition lists in the 0x36 list.", RPT_SEVERITY_WARNING );
1298 break;
1299 }
1300
1301 for( const auto& item : blk0x36.m_Items )
1302 {
1303 const auto& fontDef = std::get<BLK_0x36_DEF_TABLE::FontDef_X08>( item );
1304 m_fontDefList.push_back( &fontDef );
1305 }
1306
1307 encountered = true;
1308 }
1309}
1310
1311
1313{
1314 wxLogTrace( traceAllegroBuilder, "Creating nets from Allegro data" );
1315
1316 // Incrementing netcode. We could also choose to, say, use the 0x1B key if we wanted
1317 int netCode = 1;
1318
1319 std::vector<BOARD_ITEM*> bulkAdded;
1320
1321 LL_WALKER netWalker{ m_brdDb.m_Header->m_LL_0x1B_Nets, m_brdDb };
1322
1323 for( const BLOCK_BASE* block : netWalker )
1324 {
1325 const uint8_t type = block->GetBlockType();
1326
1327 if( type != BLOCK_TYPE::x1B_NET )
1328 {
1329 reportUnexpectedBlockType( type, BLOCK_TYPE::x1B_NET, 0, block->GetOffset(), "Net" );
1330 continue;
1331 }
1332
1333 const auto& netBlk = static_cast<const BLOCK<BLK_0x1B_NET>&>( *block ).GetData();
1334
1335 wxString netName = m_brdDb.GetString( netBlk.m_NetName );
1336
1337 // Allegro allows unnamed nets. KiCad's NETINFO_LIST matches nets by name, and all
1338 // empty-named nets would collapse to the unconnected net (code 0). Generate a unique
1339 // name so each Allegro net gets its own KiCad net code.
1340 if( netName.IsEmpty() )
1341 netName = wxString::Format( wxS( "Net_%d" ), netCode );
1342
1343 auto kiNetInfo = std::make_unique<NETINFO_ITEM>( &m_board, netName, netCode );
1344 netCode++;
1345
1346 m_netCache[netBlk.m_Key] = kiNetInfo.get();
1347 bulkAdded.push_back( kiNetInfo.get() );
1348 m_board.Add( kiNetInfo.release(), ADD_MODE::BULK_APPEND );
1349 }
1350
1351 m_board.FinalizeBulkAdd( bulkAdded );
1352
1353 wxLogTrace( traceAllegroBuilder, "Added %zu nets", m_netCache.size() );
1354}
1355
1356
1357wxString BOARD_BUILDER::resolveConstraintSetNameFromField( uint32_t aFieldKey ) const
1358{
1359 const BLOCK_BASE* fieldBlock = m_brdDb.GetObjectByKey( aFieldKey );
1360
1361 if( !fieldBlock || fieldBlock->GetBlockType() != 0x03 )
1362 return wxEmptyString;
1363
1364 const BLK_0x03_FIELD& field = static_cast<const BLOCK<BLK_0x03_FIELD>&>( *fieldBlock ).GetData();
1365 const std::string* str = std::get_if<std::string>( &field.m_Substruct );
1366
1367 if( !str )
1368 return wxEmptyString;
1369
1370 // Extract name from schematic cross-reference format: @lib.xxx(view):\NAME\.
1371 // Find the last colon-backslash separator in the raw std::string and extract from there.
1372 size_t sep = str->find( ":\\" );
1373
1374 if( sep == std::string::npos )
1375 return wxEmptyString;
1376
1377 std::string extracted = str->substr( sep + 2 );
1378
1379 if( !extracted.empty() && extracted.back() == '\\' )
1380 extracted.pop_back();
1381
1382 return wxString( extracted );
1383}
1384
1385
1387{
1388 wxLogTrace( traceAllegroBuilder, "Importing physical constraint sets from 0x1D blocks" );
1389
1390 BOARD_DESIGN_SETTINGS& bds = m_board.GetDesignSettings();
1391 std::shared_ptr<NET_SETTINGS> netSettings = bds.m_NetSettings;
1392
1393 bool isV172Plus = ( m_brdDb.m_FmtVer >= FMT_VER::V_172 );
1394
1395 struct CS_DEF
1396 {
1397 wxString name;
1398 int lineWidth = 0;
1399 int clearance = 0;
1400 int diffPairGap = 0;
1401 };
1402
1403 // Map from constraint set name to its definition
1404 std::map<wxString, CS_DEF> constraintSets;
1405
1406 // Also map string table keys to set names for net lookup
1407 std::map<uint32_t, wxString> keyToSetName;
1408
1409 int csIndex = 0;
1410 const LL_WALKER csWalker( m_brdDb.m_Header->m_LL_0x1D_0x1E_0x1F, m_brdDb );
1411
1412 for( const BLOCK_BASE* block : csWalker )
1413 {
1414 if( block->GetBlockType() != 0x1D )
1415 continue;
1416
1417 const BLK_0x1D_CONSTRAINT_SET& csBlock = static_cast<const BLOCK<BLK_0x1D_CONSTRAINT_SET>&>( *block ).GetData();
1418
1419 wxString setName;
1420 const wxString* resolved = m_brdDb.ResolveString( csBlock.m_NameStrKey );
1421
1422 if( resolved && !resolved->IsEmpty() )
1423 {
1424 setName = *resolved;
1425 }
1426 else if( csBlock.m_FieldPtr != 0 )
1427 {
1428 // Some boards store the name in a 0x03 FIELD block as a schematic cross-reference
1429 setName = resolveConstraintSetNameFromField( csBlock.m_FieldPtr );
1430 }
1431
1432 if( setName.IsEmpty() )
1433 setName = wxString::Format( wxS( "CS_%d" ), csIndex );
1434
1435 csIndex++;
1436
1437 if( csBlock.m_DataB.empty() )
1438 {
1439 wxLogTrace( traceAllegroBuilder, "Constraint set '%s' has no DataB records, skipping", setName );
1440 continue;
1441 }
1442
1443 // Parse first DataB record (first copper layer) as 14 x int32
1444 const auto& record = csBlock.m_DataB[0];
1445 int32_t fields[14];
1446 static_assert( sizeof( fields ) == std::tuple_size_v<std::decay_t<decltype( record )>> );
1447 memcpy( fields, record.data(), sizeof( fields ) );
1448
1449 CS_DEF def;
1450 def.name = setName;
1451
1452 if( isV172Plus )
1453 {
1454 def.lineWidth = scale( fields[1] );
1455 def.clearance = scale( fields[4] );
1456 }
1457 else
1458 {
1459 // Pre-V172: f[0] is preferred line width, f[1] is line spacing (used as clearance).
1460 // f[4] is sometimes also clearance when non-zero, but f[1] is the primary source.
1461 def.lineWidth = scale( fields[0] );
1462 def.clearance = scale( fields[1] );
1463 }
1464
1465 def.diffPairGap = scale( fields[7] );
1466
1467 constraintSets[setName] = def;
1468 keyToSetName[csBlock.m_NameStrKey] = setName;
1469
1470 wxLogTrace( traceAllegroBuilder,
1471 "Constraint set '%s': line_width=%d nm, clearance=%d nm, dp_gap=%d nm",
1472 setName, def.lineWidth, def.clearance, def.diffPairGap );
1473 }
1474
1475 if( constraintSets.empty() )
1476 {
1477 wxLogTrace( traceAllegroBuilder, "No physical constraint sets found" );
1478 return;
1479 }
1480
1481 // Create a netclass for each constraint set that has nonzero values
1482 for( const auto& [name, def] : constraintSets )
1483 {
1484 wxString ncName = name;
1485
1486 if( ncName.CmpNoCase( NETCLASS::Default ) == 0 )
1487 ncName = wxS( "Allegro_Default" );
1488
1489 if( netSettings->HasNetclass( ncName ) )
1490 continue;
1491
1492 auto nc = std::make_shared<NETCLASS>( ncName );
1493
1494 if( def.lineWidth > 0 )
1495 nc->SetTrackWidth( def.lineWidth );
1496
1497 if( def.clearance > 0 )
1498 nc->SetClearance( def.clearance );
1499
1500 if( def.diffPairGap > 0 )
1501 {
1502 nc->SetDiffPairGap( def.diffPairGap );
1503
1504 // Diff pair width is the same as track width for the pair's netclass
1505 if( def.lineWidth > 0 )
1506 nc->SetDiffPairWidth( def.lineWidth );
1507 }
1508
1509 netSettings->SetNetclass( ncName, nc );
1510
1511 wxLogTrace( traceAllegroBuilder, "Created netclass '%s' from constraint set '%s'", ncName, name );
1512 }
1513
1514 // Walk all NETs and assign them to constraint set netclasses via field 0x1a0
1515 wxString defaultSetName;
1516
1517 for( const auto& [name, def] : constraintSets )
1518 {
1519 if( name.CmpNoCase( wxS( "DEFAULT" ) ) == 0 )
1520 {
1521 defaultSetName = name;
1522 break;
1523 }
1524 }
1525
1526 m_brdDb.VisitNets( [&]( const VIEW_OBJS& aView )
1527 {
1528 if( !aView.m_Net )
1529 return;
1530
1531 const NET& net = *aView.m_Net;
1532
1533 // Field 0x1a0 references the constraint set. It can be an integer (string table key
1534 // that matches 0x1D.m_NameStrKey) or a direct string (the constraint set name).
1536
1537 wxString assignedSetName;
1538
1539 if( csField.has_value() )
1540 {
1541 if( auto* intVal = std::get_if<uint32_t>( &csField.value() ) )
1542 {
1543 auto it = keyToSetName.find( *intVal );
1544
1545 if( it != keyToSetName.end() )
1546 assignedSetName = it->second;
1547 }
1548 else if( auto* strVal = std::get_if<wxString>( &csField.value() ) )
1549 {
1550 if( constraintSets.count( *strVal ) )
1551 assignedSetName = *strVal;
1552 }
1553 }
1554
1555 // Nets without field 0x1a0 use the DEFAULT constraint set
1556 if( assignedSetName.IsEmpty() && !defaultSetName.IsEmpty() )
1557 assignedSetName = defaultSetName;
1558
1559 if( assignedSetName.IsEmpty() )
1560 return;
1561
1562 wxString ncName = assignedSetName;
1563
1564 if( ncName.CmpNoCase( NETCLASS::Default ) == 0 )
1565 ncName = wxS( "Allegro_Default" );
1566
1567 if( !netSettings->HasNetclass( ncName ) )
1568 return;
1569
1570 auto netIt = m_netCache.find( net.GetKey() );
1571
1572 if( netIt == m_netCache.end() )
1573 return;
1574
1575 NETINFO_ITEM* kiNet = netIt->second;
1576 netSettings->SetNetclassPatternAssignment( kiNet->GetNetname(), ncName );
1577 kiNet->SetNetClass( netSettings->GetNetClassByName( ncName ) );
1578 } );
1579
1580 wxLogTrace( traceAllegroBuilder, "Applied %zu physical constraint sets", constraintSets.size() );
1581}
1582
1583
1585{
1586 wxLogTrace( traceAllegroBuilder, "Applying per-net trace width constraints" );
1587
1588 BOARD_DESIGN_SETTINGS& bds = m_board.GetDesignSettings();
1589 std::shared_ptr<NET_SETTINGS> netSettings = bds.m_NetSettings;
1590
1591 // Group nets by their minimum trace width to create netclasses.
1592 // Allegro stores per-net min/max trace width in FIELD blocks attached to each NET.
1593 std::map<int, std::vector<uint32_t>> widthToNetKeys;
1594
1595 m_brdDb.VisitNets( [&]( const VIEW_OBJS& aView )
1596 {
1597 if( !aView.m_Net )
1598 return;
1599
1600 std::optional<int> minWidth = aView.m_Net->GetNetMinLineWidth();
1601
1602 if( !minWidth.has_value() || minWidth.value() <= 0 )
1603 return;
1604
1605 int widthNm = scale( minWidth.value() );
1606 widthToNetKeys[widthNm].push_back( aView.m_Net->GetKey() );
1607 } );
1608
1609 if( widthToNetKeys.empty() )
1610 {
1611 wxLogTrace( traceAllegroBuilder, "No per-net trace width constraints found" );
1612 return;
1613 }
1614
1615 for( const auto& [widthNm, netKeys] : widthToNetKeys )
1616 {
1617 int widthMils = ( widthNm + 12700 ) / 25400;
1618 wxString ncName = wxString::Format( wxS( "W%dmil" ), widthMils );
1619
1620 if( netSettings->HasNetclass( ncName ) )
1621 continue;
1622
1623 auto nc = std::make_shared<NETCLASS>( ncName );
1624 nc->SetTrackWidth( widthNm );
1625 netSettings->SetNetclass( ncName, nc );
1626
1627 for( uint32_t netKey : netKeys )
1628 {
1629 auto it = m_netCache.find( netKey );
1630
1631 if( it == m_netCache.end() )
1632 continue;
1633
1634 NETINFO_ITEM* kiNet = it->second;
1635 netSettings->SetNetclassPatternAssignment( kiNet->GetNetname(), ncName );
1636 kiNet->SetNetClass( nc );
1637 }
1638
1639 wxLogTrace( traceAllegroBuilder, "Created netclass '%s' (track width %d nm) with %zu nets",
1640 ncName, widthNm, netKeys.size() );
1641 }
1642
1643 wxLogTrace( traceAllegroBuilder, "Applied trace width constraints from %zu unique width groups",
1644 widthToNetKeys.size() );
1645}
1646
1647
1649{
1650 if( aNet.m_MatchGroupPtr == 0 )
1651 return wxEmptyString;
1652
1653 const BLOCK_BASE* block = m_brdDb.GetObjectByKey( aNet.m_MatchGroupPtr );
1654
1655 if( !block )
1656 return wxEmptyString;
1657
1658 uint32_t tableKey = 0;
1659
1660 if( block->GetBlockType() == 0x26 )
1661 {
1662 // V172+ path: NET -> 0x26 -> m_GroupPtr -> 0x2C TABLE
1663 const auto& x26 = static_cast<const BLOCK<BLK_0x26_MATCH_GROUP>&>( *block ).GetData();
1664 tableKey = x26.m_GroupPtr;
1665
1666 // Some boards have chained 0x26 blocks (m_GroupPtr -> another 0x26 -> 0x2C)
1667 if( tableKey != 0 )
1668 {
1669 const BLOCK_BASE* next = m_brdDb.GetObjectByKey( tableKey );
1670
1671 if( next && next->GetBlockType() == 0x26 )
1672 {
1673 const auto& x26b = static_cast<const BLOCK<BLK_0x26_MATCH_GROUP>&>( *next ).GetData();
1674 tableKey = x26b.m_GroupPtr;
1675 }
1676 }
1677 }
1678 else if( block->GetBlockType() == 0x2C )
1679 {
1680 // Pre-V172 path: NET -> 0x2C TABLE directly
1681 tableKey = aNet.m_MatchGroupPtr;
1682 }
1683 else
1684 {
1685 return wxEmptyString;
1686 }
1687
1688 if( tableKey == 0 )
1689 return wxEmptyString;
1690
1691 // Verify the target is actually a 0x2C TABLE before calling expectBlockByKey to
1692 // avoid noisy warnings on boards with unexpected pointer chain configurations.
1693 const BLOCK_BASE* tableBlock = m_brdDb.GetObjectByKey( tableKey );
1694
1695 if( !tableBlock || tableBlock->GetBlockType() != 0x2C )
1696 return wxEmptyString;
1697
1698 const BLK_0x2C_TABLE* tbl = expectBlockByKey<BLK_0x2C_TABLE>( tableKey, 0x2C );
1699
1700 if( !tbl || tbl->m_StringPtr == 0 )
1701 return wxEmptyString;
1702
1703 const wxString& name = m_brdDb.GetString( tbl->m_StringPtr );
1704
1705 wxLogTrace( traceAllegroBuilder, "Resolving match group name for NET '%s': found table at key %#010x, subtype %#x, name '%s'",
1706 m_brdDb.GetString( aNet.m_NetName ), tableKey, tbl->m_SubType, name );
1707
1708 return name;
1709}
1710
1711
1713{
1714 wxLogTrace( traceAllegroBuilder, "Applying match group / differential pair assignments" );
1715
1716 BOARD_DESIGN_SETTINGS& bds = m_board.GetDesignSettings();
1717 std::shared_ptr<NET_SETTINGS> netSettings = bds.m_NetSettings;
1718
1719 // Group NET keys by their match group name
1720 std::map<wxString, std::vector<uint32_t>> groupToNetKeys;
1721
1722 LL_WALKER netWalker{ m_brdDb.m_Header->m_LL_0x1B_Nets, m_brdDb };
1723
1724 for( const BLOCK_BASE* block : netWalker )
1725 {
1726 if( block->GetBlockType() != BLOCK_TYPE::x1B_NET )
1727 continue;
1728
1729 const auto& netBlk = static_cast<const BLOCK<BLK_0x1B_NET>&>( *block ).GetData();
1730 wxString groupName = resolveMatchGroupName( netBlk );
1731
1732 if( groupName.empty() )
1733 continue;
1734
1735 groupToNetKeys[groupName].push_back( netBlk.m_Key );
1736 }
1737
1738 if( groupToNetKeys.empty() )
1739 {
1740 wxLogTrace( traceAllegroBuilder, "No match groups found" );
1741 return;
1742 }
1743
1744 int dpCount = 0;
1745 int mgCount = 0;
1746
1747 for( const auto& [groupName, netKeys] : groupToNetKeys )
1748 {
1749 // A diff pair has exactly 2 nets. We don't check P/N naming because Allegro doesn't
1750 // require any naming convention for paired nets.
1751 bool isDiffPair = ( netKeys.size() == 2 );
1752 wxString ncPrefix = isDiffPair ? wxS( "DP_" ) : wxS( "MG_" );
1753 wxString ncName = ncPrefix + groupName;
1754
1755 if( netSettings->HasNetclass( ncName ) )
1756 continue;
1757
1758 auto nc = std::make_shared<NETCLASS>( ncName );
1759
1760 // Inherit constraint set values from the first net's current netclass so that
1761 // clearance and track width from the underlying constraint set are not lost.
1762 for( uint32_t netKey : netKeys )
1763 {
1764 auto it = m_netCache.find( netKey );
1765
1766 if( it == m_netCache.end() )
1767 continue;
1768
1769 NETCLASS* existing = it->second->GetNetClass();
1770
1771 if( existing && existing->GetName() != NETCLASS::Default )
1772 {
1773 if( existing->HasClearance() )
1774 nc->SetClearance( existing->GetClearance() );
1775
1776 if( existing->HasTrackWidth() )
1777 nc->SetTrackWidth( existing->GetTrackWidth() );
1778
1779 if( existing->HasDiffPairGap() )
1780 nc->SetDiffPairGap( existing->GetDiffPairGap() );
1781
1782 if( existing->HasDiffPairWidth() )
1783 nc->SetDiffPairWidth( existing->GetDiffPairWidth() );
1784
1785 break;
1786 }
1787 }
1788
1789 netSettings->SetNetclass( ncName, nc );
1790
1791 for( uint32_t netKey : netKeys )
1792 {
1793 auto it = m_netCache.find( netKey );
1794
1795 if( it == m_netCache.end() )
1796 continue;
1797
1798 NETINFO_ITEM* kiNet = it->second;
1799 netSettings->SetNetclassPatternAssignment( kiNet->GetNetname(), ncName );
1800 kiNet->SetNetClass( nc );
1801 }
1802
1803 if( isDiffPair )
1804 dpCount++;
1805 else
1806 mgCount++;
1807
1808 wxLogTrace( traceAllegroBuilder, "%s group '%s' -> netclass '%s' with %zu nets",
1809 isDiffPair ? wxS( "Diff pair" ) : wxS( "Match" ),
1810 groupName, ncName, netKeys.size() );
1811 }
1812
1813 wxLogTrace( traceAllegroBuilder,
1814 "Applied match groups: %d diff pairs, %d match groups (%zu total groups)",
1815 dpCount, mgCount, groupToNetKeys.size() );
1816}
1817
1818
1832static std::unordered_set<LAYER_INFO> ScanForLayers( const BRD_DB& aDb )
1833{
1834 std::unordered_set<LAYER_INFO> layersFound;
1835
1836 const auto& addLayer = [&]( std::optional<LAYER_INFO>& info )
1837 {
1838 if( info.has_value() )
1839 {
1840 layersFound.insert( std::move( info.value() ) );
1841 }
1842 };
1843
1844 const auto& simpleWalker = [&]( const FILE_HEADER::LINKED_LIST& aLL )
1845 {
1846 LL_WALKER walker{ aLL, aDb };
1847 for( const BLOCK_BASE* block : walker )
1848 {
1849 std::optional<LAYER_INFO> info = tryLayerFromBlock( *block );
1850 addLayer( info );
1851 }
1852 };
1853
1854 simpleWalker( aDb.m_Header->m_LL_Shapes );
1855 simpleWalker( aDb.m_Header->m_LL_0x24_0x28 );
1856 simpleWalker( aDb.m_Header->m_LL_0x14 );
1857
1858 return layersFound;
1859}
1860
1861
1863{
1864 wxLogTrace( traceAllegroBuilder, "Setting up layer mapping from Allegro to KiCad" );
1865
1866 const auto& layerMap = m_brdDb.m_Header->m_LayerMap;
1867
1868 for( size_t i = 0; i < layerMap.size(); ++i )
1869 {
1870 const uint8_t classNum = static_cast<uint8_t>( i );
1871
1872 const uint32_t x2aKey = layerMap[i].m_LayerList0x2A;
1873
1874 if( x2aKey == 0 )
1875 continue;
1876
1877 const BLK_0x2A_LAYER_LIST* layerList = expectBlockByKey<BLK_0x2A_LAYER_LIST>( x2aKey, 0x2A );
1878
1879 // Probably an error
1880 if( !layerList )
1881 continue;
1882
1883 m_layerMapper->ProcessLayerList( classNum, *layerList );
1884 }
1885
1886 std::unordered_set<LAYER_INFO> layersFound = ScanForLayers( m_brdDb );
1887
1888 wxLogTrace( traceAllegroBuilder, "Scanned %zu layers", layersFound.size() );
1889 for( const LAYER_INFO& info : layersFound )
1890 {
1891 wxLogTrace( traceAllegroBuilder, " - %#02x:%#02x (%s)", info.m_Class, info.m_Subclass,
1893 }
1894
1895 // The outline is sometimes on OUTLINE and sometimes on DESIGN_OUTLINE, and sometimes
1896 // on both. In the first two cases, whichever it is goes to Edge.Cuts, but in the both case,
1897 // we send one to a User layer
1900
1901 if( layersFound.count( outlineInfo ) && layersFound.count( designOutlineInfo ) )
1902 {
1903 // Both layers found, remap DESIGN_OUTLINE to a user layer
1904 wxLogTrace( traceAllegroBuilder,
1905 "Both OUTLINE and DESIGN_OUTLINE layers found, remapping DESIGN_OUTLINE to a user layer" );
1906 m_layerMapper->MapCustomLayer( designOutlineInfo, layerInfoDisplayName( designOutlineInfo ) );
1907 }
1908
1909 m_layerMapper->FinalizeLayers();
1910}
1911
1912
1914{
1915 if( aIndex == 0 || aIndex > m_fontDefList.size() )
1916 {
1917 m_reporter.Report(
1918 wxString::Format( "Font def index %u requested, have %zu entries", aIndex, m_fontDefList.size() ),
1920 return nullptr;
1921 }
1922
1923 // The index appears to be 1-indexed (maybe 0 means something special?)
1924 aIndex -= 1;
1925
1926 return m_fontDefList[aIndex];
1927}
1928
1929
1930std::unique_ptr<PCB_SHAPE> BOARD_BUILDER::buildLineSegment( const BLK_0x15_16_17_SEGMENT& aSegment,
1931 const LAYER_INFO& aLayerInfo, PCB_LAYER_ID aLayer,
1932 BOARD_ITEM_CONTAINER& aParent )
1933{
1934 VECTOR2I start = scale( { aSegment.m_StartX, aSegment.m_StartY } );
1935 VECTOR2I end = scale( { aSegment.m_EndX, aSegment.m_EndY } );
1936 const int width = scale( aSegment.m_Width );
1937
1938 if( !m_layerMapper->IsLayerMapped( aLayer ) )
1939 {
1940 wxLogTrace( traceAllegroBuilder, "Unmapped Seg: %#04x %#04x %s, %s", aLayerInfo.m_Class, aLayerInfo.m_Subclass,
1941 start.Format(), end.Format() );
1942 }
1943
1944 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent, SHAPE_T::SEGMENT );
1945 shape->SetLayer( aLayer );
1946 shape->SetStart( start );
1947 shape->SetEnd( end );
1948
1949 {
1950 int adjustedWidth = width;
1951
1952 if( adjustedWidth <= 0 )
1953 adjustedWidth = m_board.GetDesignSettings().GetLineThickness( aLayer );
1954
1955 shape->SetWidth( adjustedWidth );
1956 }
1957
1958 return shape;
1959}
1960
1961
1962std::unique_ptr<PCB_SHAPE> BOARD_BUILDER::buildArc( const BLK_0x01_ARC& aArc, const LAYER_INFO& aLayerInfo,
1963 PCB_LAYER_ID aLayer, BOARD_ITEM_CONTAINER& aParent )
1964{
1965 VECTOR2I start{ aArc.m_StartX, aArc.m_StartY };
1966 VECTOR2I end{ aArc.m_EndX, aArc.m_EndY };
1967
1968 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent, SHAPE_T::ARC );
1969
1970 shape->SetLayer( aLayer );
1971
1972 if( !m_layerMapper->IsLayerMapped( aLayer ) )
1973 {
1974 wxLogTrace( traceAllegroBuilder, "Unmapped Arc: %#04x %#04x %s, %s", aLayerInfo.m_Class, aLayerInfo.m_Subclass,
1975 start.Format(), end.Format() );
1976 }
1977
1978 start = scale( start );
1979 end = scale( end );
1980
1981 VECTOR2I c = scale( KiROUND( VECTOR2D{ aArc.m_CenterX, aArc.m_CenterY } ) );
1982
1983 int radius = scale( KiROUND( aArc.m_Radius ) );
1984
1985 bool clockwise = ( aArc.m_SubType & 0x40 ) != 0;
1986
1987 {
1988 int arcWidth = scale( aArc.m_Width );
1989
1990 if( arcWidth <= 0 )
1991 arcWidth = m_board.GetDesignSettings().GetLineThickness( aLayer );
1992
1993 shape->SetWidth( arcWidth );
1994 }
1995
1996 if( start == end )
1997 {
1998 shape->SetShape( SHAPE_T::CIRCLE );
1999 shape->SetCenter( c );
2000 shape->SetRadius( radius );
2001 }
2002 else
2003 {
2004 shape->SetShape( SHAPE_T::ARC );
2005 EDA_ANGLE startangle( start - c );
2006 EDA_ANGLE endangle( end - c );
2007
2008 startangle.Normalize();
2009 endangle.Normalize();
2010
2011 EDA_ANGLE angle = endangle - startangle;
2012
2013 if( clockwise && angle < ANGLE_0 )
2014 angle += ANGLE_360;
2015 if( !clockwise && angle > ANGLE_0 )
2016 angle -= ANGLE_360;
2017
2018 if( start == end )
2019 angle = -ANGLE_360;
2020
2021 VECTOR2I mid = start;
2022 RotatePoint( mid, c, -angle / 2.0 );
2023
2024 shape->SetArcGeometry( start, mid, end );
2025 }
2026
2027 return shape;
2028}
2029
2030
2031std::unique_ptr<PCB_TEXT> BOARD_BUILDER::buildPcbText( const BLK_0x30_STR_WRAPPER& aStrWrapper,
2032 BOARD_ITEM_CONTAINER& aParent )
2033{
2034 std::unique_ptr<PCB_TEXT> text = std::make_unique<PCB_TEXT>( &aParent );
2035
2036 PCB_LAYER_ID layer = getLayer( aStrWrapper.m_Layer );
2037 text->SetLayer( layer );
2038
2039 const BLK_0x31_SGRAPHIC* strGraphic = expectBlockByKey<BLK_0x31_SGRAPHIC>( aStrWrapper.m_StrGraphicPtr, 0x31 );
2040
2041 if( !strGraphic )
2042 {
2043 m_reporter.Report( wxString::Format( "Failed to find string graphic (0x31) with key %#010x "
2044 "in string wrapper (0x30) with key %#010x",
2045 aStrWrapper.m_StrGraphicPtr, aStrWrapper.m_Key ),
2047 return nullptr;
2048 }
2049
2050 const BLK_0x30_STR_WRAPPER::TEXT_PROPERTIES* props = nullptr;
2051
2052 if( aStrWrapper.m_Font.has_value() )
2053 props = &aStrWrapper.m_Font.value();
2054
2055 if( !props && aStrWrapper.m_Font16x.has_value() )
2056 props = &aStrWrapper.m_Font16x.value();
2057
2058 if( !props )
2059 {
2060 m_reporter.Report(
2061 wxString::Format( "Expected one of the font properties fields in 0x30 object (key %#010x) to be set.",
2062 aStrWrapper.m_Key ),
2064 return nullptr;
2065 }
2066
2067 const BLK_0x36_DEF_TABLE::FontDef_X08* fontDef = getFontDef( props->m_Key );
2068
2069 if( !fontDef )
2070 return nullptr;
2071
2072 text->SetText( strGraphic->m_Value );
2073 text->SetTextWidth( scale( fontDef->m_CharWidth ) );
2074 text->SetTextHeight( scale( fontDef->m_CharHeight ) );
2075 text->SetTextThickness( std::max( 1, scale( fontDef->m_StrokeWidth ) ) );
2076
2077 const EDA_ANGLE textAngle = fromMillidegrees( aStrWrapper.m_Rotation );
2078 text->SetTextAngle( textAngle );
2079
2080 VECTOR2I textPos = scale( VECTOR2I{ aStrWrapper.m_CoordsX, aStrWrapper.m_CoordsY } );
2081
2082 // KiCad's stroke font has a different baseline than Allegro's, so apply a vertical offset to compensate.
2083 // The exact offset is a bit of guesswork based on visually matching Allegro and KiCad text, but the
2084 // stoke font itself isn't the same anyway, so we can't be 100% here.
2085 VECTOR2I textFontOffset = VECTOR2I{ 0, -( scale( fontDef->m_CharHeight ) * 45 ) / 100 };
2086 RotatePoint( textFontOffset, textAngle );
2087 text->SetPosition( textPos + textFontOffset );
2088
2090 text->SetMirrored( true );
2091
2092 switch( props->m_Alignment )
2093 {
2095 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
2096 break;
2098 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
2099 break;
2101 text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
2102 break;
2103 default:
2104 break;
2105 }
2106
2107 return text;
2108}
2109
2110
2111std::vector<std::unique_ptr<BOARD_ITEM>> BOARD_BUILDER::buildDrillMarker( const BLK_0x0C_PIN_DEF& aPinDef,
2112 BOARD_ITEM_CONTAINER& aParent )
2113{
2115 std::vector<std::unique_ptr<PCB_SHAPE>> shapes;
2116
2117 const uint32_t markerShape = aPinDef.GetShape();
2118
2119 PCB_LAYER_ID layer = getLayer( aPinDef.m_Layer );
2120 const VECTOR2I center = scale( VECTOR2I{ aPinDef.m_Coords[0], aPinDef.m_Coords[1] } );
2121 const VECTOR2I size = scaleSize( VECTOR2I{ aPinDef.m_Size[0], aPinDef.m_Size[1] } );
2122
2123 const auto addLine = [&]( const SEG& aSeg )
2124 {
2125 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent, SHAPE_T::SEGMENT );
2126 shape->SetStart( aSeg.A );
2127 shape->SetEnd( aSeg.B );
2128 shapes.push_back( std::move( shape ) );
2129 };
2130
2131 const auto addPolyPts = [&]( const std::vector<VECTOR2I>& aPts )
2132 {
2133 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent, SHAPE_T::POLY );
2134 shape->SetPolyPoints( aPts );
2135 shapes.push_back( std::move( shape ) );
2136 };
2137
2138 switch( markerShape )
2139 {
2140 case MS::CIRCLE:
2141 {
2142 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent, SHAPE_T::CIRCLE );
2143 shape->SetCenter( center );
2144 shape->SetRadius( size.x / 2 );
2145 shapes.push_back( std::move( shape ) );
2146 break;
2147 }
2148 case MS::SQUARE:
2149 case MS::RECTANGLE:
2150 {
2151 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent, SHAPE_T::RECTANGLE );
2152 shape->SetStart( center - size / 2 );
2153 shape->SetEnd( center + size / 2 );
2154 shapes.push_back( std::move( shape ) );
2155 break;
2156 }
2157 case MS::CROSS:
2158 {
2159 std::unique_ptr<PCB_SHAPE> shape;
2160
2161 std::vector<SEG> segs = KIGEOM::MakeCrossSegments( center, size, ANGLE_0 );
2162
2163 for( const SEG& seg : segs )
2164 {
2165 addLine( seg );
2166 }
2167 break;
2168 }
2169 case MS::OBLONG_X:
2170 case MS::OBLONG_Y:
2171 {
2172 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent, SHAPE_T::RECTANGLE );
2173 shape->SetStart( center - size / 2 );
2174 shape->SetEnd( center + size / 2 );
2175
2176 int minSize = std::min( size.x, size.y );
2177 shape->SetCornerRadius( minSize / 2 );
2178 shapes.push_back( std::move( shape ) );
2179 break;
2180 }
2181 case MS::TRIANGLE:
2182 {
2183 // This triangle is point-up
2184 // Size follows fabmaster - the circumscribed circle of the triangle
2185 std::vector<VECTOR2I> pts = KIGEOM::MakeRegularPolygonPoints( center, 3, size.x / 2, true, ANGLE_90 );
2186 addPolyPts( pts );
2187 break;
2188 }
2189 case MS::DIAMOND:
2190 {
2191 std::vector<VECTOR2I> pts = KIGEOM::MakeRegularPolygonPoints( center, 4, size.x / 2, true, ANGLE_90 );
2192 addPolyPts( pts );
2193 break;
2194 }
2195 case MS::PENTAGON:
2196 {
2197 // Not 100% sure which way this should point
2198 std::vector<VECTOR2I> pts = KIGEOM::MakeRegularPolygonPoints( center, 5, size.x / 2, true, ANGLE_90 );
2199 addPolyPts( pts );
2200 break;
2201 }
2202 case MS::HEXAGON_X:
2203 case MS::HEXAGON_Y:
2204 {
2205 EDA_ANGLE startAngle = ( markerShape == MS::HEXAGON_X ) ? ANGLE_0 : ANGLE_90;
2206 std::vector<VECTOR2I> pts = KIGEOM::MakeRegularPolygonPoints( center, 6, size.x / 2, true, startAngle );
2207 addPolyPts( pts );
2208 break;
2209 }
2210 case MS::OCTAGON:
2211 {
2212 EDA_ANGLE startAngle = FULL_CIRCLE / 16; // Start at 22.5 degrees to align flat sides with axes
2213 // Octagons are measured across flats
2214 std::vector<VECTOR2I> pts = KIGEOM::MakeRegularPolygonPoints( center, 8, size.x / 2, false, startAngle );
2215 addPolyPts( pts );
2216 break;
2217 }
2218 default:
2219 {
2220 wxLogTrace( traceAllegroBuilder, "Unsupported drill marker shape type %#04x for pin definition with key %#010x",
2221 markerShape, aPinDef.m_Key );
2222 break;
2223 }
2224 }
2225
2226 std::vector<std::unique_ptr<BOARD_ITEM>> items;
2227 for( std::unique_ptr<PCB_SHAPE>& shape : shapes )
2228 {
2229 shape->SetLayer( layer );
2230 shape->SetWidth( 0 );
2231
2232 items.push_back( std::move( shape ) );
2233 }
2234
2235 return items;
2236}
2237
2238
2239std::vector<std::unique_ptr<BOARD_ITEM>> BOARD_BUILDER::buildGraphicItems( const BLOCK_BASE& aBlock,
2240 BOARD_ITEM_CONTAINER& aParent )
2241{
2242 std::vector<std::unique_ptr<BOARD_ITEM>> newItems;
2243
2244 switch( aBlock.GetBlockType() )
2245 {
2246 case 0x0c:
2247 {
2248 const auto& pinDef = static_cast<const BLOCK<BLK_0x0C_PIN_DEF>&>( aBlock ).GetData();
2249 newItems = buildDrillMarker( pinDef, aParent );
2250 break;
2251 }
2252 case 0x0e:
2253 {
2254 const auto& rect = static_cast<const BLOCK<BLK_0x0E_RECT>&>( aBlock ).GetData();
2255 std::unique_ptr<PCB_SHAPE> shape = buildRect( rect, aParent );
2256 if( shape )
2257 newItems.push_back( std::move( shape ) );
2258 break;
2259 }
2260 case 0x14:
2261 {
2262 const auto& graphicContainer = static_cast<const BLOCK<BLK_0x14_GRAPHIC>&>( aBlock ).GetData();
2263 std::vector<std::unique_ptr<PCB_SHAPE>> shapes = buildShapes( graphicContainer, aParent );
2264 for( std::unique_ptr<PCB_SHAPE>& shape : shapes )
2265 newItems.push_back( std::move( shape ) );
2266 break;
2267 }
2268 case 0x24:
2269 {
2270 const auto& rect = static_cast<const BLOCK<BLK_0x24_RECT>&>( aBlock ).GetData();
2271 std::unique_ptr<PCB_SHAPE> shape = buildRect( rect, aParent );
2272 if( shape )
2273 newItems.push_back( std::move( shape ) );
2274 break;
2275 }
2276 case 0x28:
2277 {
2278 const auto& shapeData = static_cast<const BLOCK<BLK_0x28_SHAPE>&>( aBlock ).GetData();
2279 std::unique_ptr<PCB_SHAPE> shape = buildPolygon( shapeData, aParent );
2280 if( shape )
2281 newItems.push_back( std::move( shape ) );
2282 break;
2283 }
2284 case 0x30:
2285 {
2286 const auto& strWrapper = static_cast<const BLOCK<BLK_0x30_STR_WRAPPER>&>( aBlock ).GetData();
2287
2288 std::unique_ptr<BOARD_ITEM> newItem = buildPcbText( strWrapper, aParent );
2289 if( newItem )
2290 newItems.push_back( std::move( newItem ) );
2291 break;
2292 }
2293 default:
2294 {
2295 wxLogTrace( traceAllegroBuilder, " Unhandled block type for buildItems: %#04x", aBlock.GetBlockType() );
2296 break;
2297 }
2298 }
2299
2300 return newItems;
2301};
2302
2303
2305{
2306 return m_layerMapper->GetLayer( aLayerInfo );
2307}
2308
2309
2310std::vector<std::unique_ptr<PCB_SHAPE>> BOARD_BUILDER::buildShapes( const BLK_0x14_GRAPHIC& aGraphic,
2311 BOARD_ITEM_CONTAINER& aParent )
2312{
2313 std::vector<std::unique_ptr<PCB_SHAPE>> shapes;
2314
2315 PCB_LAYER_ID layer = getLayer( aGraphic.m_Layer );
2316
2317 // Within the graphics list, we can get various lines and arcs on PLACE_BOUND_TOP, which
2318 // aren't actually the courtyard, which is a polygon in the 0x28 list. So, if we see such items,
2319 // remap them now to a specific other layer
2320 if( layer == F_CrtYd )
2321 layer = m_layerMapper->GetPlaceBounds( true );
2322 else if( layer == B_CrtYd )
2323 layer = m_layerMapper->GetPlaceBounds( false );
2324
2325 const LL_WALKER segWalker{ aGraphic.m_SegmentPtr, aGraphic.m_Key, m_brdDb };
2326
2327 for( const BLOCK_BASE* segBlock : segWalker )
2328 {
2329 std::unique_ptr<PCB_SHAPE> shape;
2330
2331 switch( segBlock->GetBlockType() )
2332 {
2333 case 0x01:
2334 {
2335 const auto& arc = static_cast<const BLOCK<BLK_0x01_ARC>&>( *segBlock ).GetData();
2336 shape = buildArc( arc, aGraphic.m_Layer, layer, aParent );
2337 break;
2338 }
2339 case 0x15:
2340 case 0x16:
2341 case 0x17:
2342 {
2343 const auto& seg = static_cast<const BLOCK<BLK_0x15_16_17_SEGMENT>&>( *segBlock ).GetData();
2344 shape = buildLineSegment( seg, aGraphic.m_Layer, layer, aParent );
2345 break;
2346 }
2347 default:
2348 {
2349 wxLogTrace( traceAllegroBuilder, " Unhandled block type in BLK_0x14_GRAPHIC: %#04x",
2350 segBlock->GetBlockType() );
2351 break;
2352 }
2353 }
2354
2355 if( shape )
2356 shapes.push_back( std::move( shape ) );
2357 }
2358
2359 return shapes;
2360}
2361
2362
2363std::unique_ptr<PCB_SHAPE> BOARD_BUILDER::buildRect( const BLK_0x24_RECT& aRect, BOARD_ITEM_CONTAINER& aParent )
2364{
2365 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent );
2366
2367 PCB_LAYER_ID layer = getLayer( aRect.m_Layer );
2368 shape->SetLayer( layer );
2369
2370 shape->SetShape( SHAPE_T::RECTANGLE );
2371
2372 const VECTOR2I cornerA = scale( VECTOR2I{ aRect.m_Coords[0], aRect.m_Coords[1] } );
2373 const VECTOR2I cornerB = scale( VECTOR2I{ aRect.m_Coords[2], aRect.m_Coords[3] } );
2374
2375 shape->SetStart( cornerA );
2376 shape->SetEnd( cornerB );
2377
2378 const EDA_ANGLE angle = fromMillidegrees( aRect.m_Rotation );
2379 shape->Rotate( cornerA, angle );
2380
2381 const int lineWidth = 0;
2382 shape->SetWidth( lineWidth );
2383
2384 return shape;
2385}
2386
2387
2388std::unique_ptr<PCB_SHAPE> BOARD_BUILDER::buildRect( const BLK_0x0E_RECT& aRect, BOARD_ITEM_CONTAINER& aParent )
2389{
2390 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent );
2391
2392 PCB_LAYER_ID layer = getLayer( aRect.m_Layer );
2393 shape->SetLayer( layer );
2394
2395 shape->SetShape( SHAPE_T::RECTANGLE );
2396
2397 const VECTOR2I cornerA = scale( VECTOR2I{ aRect.m_Coords[0], aRect.m_Coords[1] } );
2398 const VECTOR2I cornerB = scale( VECTOR2I{ aRect.m_Coords[2], aRect.m_Coords[3] } );
2399
2400 shape->SetStart( cornerA );
2401 shape->SetEnd( cornerB );
2402
2403 const EDA_ANGLE angle = fromMillidegrees( aRect.m_Rotation );
2404 shape->Rotate( cornerA, angle );
2405
2406 const int lineWidth = 0;
2407 shape->SetWidth( lineWidth );
2408
2409 return shape;
2410}
2411
2412
2413std::unique_ptr<PCB_SHAPE> BOARD_BUILDER::buildPolygon( const BLK_0x28_SHAPE& aPolygon, BOARD_ITEM_CONTAINER& aParent )
2414{
2415 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &aParent );
2416
2417 PCB_LAYER_ID layer = getLayer( aPolygon.m_Layer );
2418 shape->SetLayer( layer );
2419
2420 shape->SetShape( SHAPE_T::POLY );
2421
2422 SHAPE_LINE_CHAIN chain = buildOutline( aPolygon );
2423
2424 if( chain.PointCount() < 3 )
2425 {
2426 wxLogTrace( traceAllegroBuilder, "Polygon (0x28) with key %#010x has fewer than 3 points, skipping",
2427 aPolygon.m_Key );
2428 return nullptr;
2429 }
2430
2431 chain.SetClosed( true );
2432 shape->SetPolyShape( chain );
2433
2434 const int lineWidth = 0;
2435 shape->SetWidth( lineWidth );
2436
2437 return shape;
2438}
2439
2440
2441std::vector<std::unique_ptr<PCB_SHAPE>> BOARD_BUILDER::buildPolygonShapes( const BLK_0x28_SHAPE& aShapeData,
2442 BOARD_ITEM_CONTAINER& aParent )
2443{
2444 std::vector<std::unique_ptr<PCB_SHAPE>> shapes;
2445
2446 PCB_LAYER_ID layer = getLayer( aShapeData.m_Layer );
2447
2448 // Walk the segments in this shape and create PCB_SHAPE objects on Edge_Cuts
2449 const LL_WALKER segWalker{ aShapeData.m_FirstSegmentPtr, aShapeData.m_Key, m_brdDb };
2450
2451 for( const BLOCK_BASE* segBlock : segWalker )
2452 {
2453 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( &m_board );
2454 shape->SetLayer( layer );
2455 shape->SetWidth( m_board.GetDesignSettings().GetLineThickness( layer ) );
2456
2457 switch( segBlock->GetBlockType() )
2458 {
2459 case 0x01:
2460 {
2461 const auto& arc = static_cast<const BLOCK<BLK_0x01_ARC>&>( *segBlock ).GetData();
2462
2463 VECTOR2I start = scale( { arc.m_StartX, arc.m_StartY } );
2464 VECTOR2I end = scale( { arc.m_EndX, arc.m_EndY } );
2465 VECTOR2I c = scale( KiROUND( VECTOR2D{ arc.m_CenterX, arc.m_CenterY } ) );
2466
2467 int radius = scale( arc.m_Radius );
2468 if( start == end )
2469 {
2470 shape->SetShape( SHAPE_T::CIRCLE );
2471 shape->SetCenter( c );
2472 shape->SetRadius( radius );
2473 }
2474 else
2475 {
2476 shape->SetShape( SHAPE_T::ARC );
2477
2478 bool clockwise = ( arc.m_SubType & 0x40 ) != 0;
2479
2480 EDA_ANGLE startangle( start - c );
2481 EDA_ANGLE endangle( end - c );
2482
2483 startangle.Normalize();
2484 endangle.Normalize();
2485
2486 EDA_ANGLE angle = endangle - startangle;
2487
2488 if( clockwise && angle < ANGLE_0 )
2489 angle += ANGLE_360;
2490
2491 if( !clockwise && angle > ANGLE_0 )
2492 angle -= ANGLE_360;
2493
2494 VECTOR2I mid = start;
2495 RotatePoint( mid, c, -angle / 2.0 );
2496
2497 shape->SetArcGeometry( start, mid, end );
2498 }
2499 break;
2500 }
2501 case 0x15:
2502 case 0x16:
2503 case 0x17:
2504 {
2505 const auto& seg = static_cast<const BLOCK<BLK_0x15_16_17_SEGMENT>&>( *segBlock ).GetData();
2506 VECTOR2I start = scale( { seg.m_StartX, seg.m_StartY } );
2507 VECTOR2I end = scale( { seg.m_EndX, seg.m_EndY } );
2508
2509 shape->SetShape( SHAPE_T::SEGMENT );
2510 shape->SetStart( start );
2511 shape->SetEnd( end );
2512 shape->SetWidth( m_board.GetDesignSettings().GetLineThickness( layer ) );
2513 break;
2514 }
2515 default:
2516 wxLogTrace( traceAllegroBuilder, " Unhandled segment type in outline: %#04x", segBlock->GetBlockType() );
2517 continue;
2518 }
2519
2520 shapes.push_back( std::move( shape ) );
2521 }
2522
2523 return shapes;
2524}
2525
2526
2528{
2529 uint32_t refKey = 0x00;
2530
2531 if( aFpInstance.m_InstRef.has_value() )
2532 refKey = aFpInstance.m_InstRef.value();
2533
2534 if( !refKey && aFpInstance.m_InstRef16x.has_value() )
2535 refKey = aFpInstance.m_InstRef16x.value();
2536
2537 // This can happen, for example for dimension "symbols".
2538 if( refKey == 0 )
2539 return nullptr;
2540
2542 return blk07;
2543}
2544
2545
2546std::vector<std::unique_ptr<BOARD_ITEM>> BOARD_BUILDER::buildPadItems( const BLK_0x1C_PADSTACK& aPadstack,
2547 FOOTPRINT& aFp, const wxString& aPadName, int aNetcode )
2548{
2549 // Not all Allegro PADSTACKS can be represented by a single KiCad pad. For example, the
2550 // paste and mask layers can have completely independent shapes in Allegro, but in KiCad that
2551 // would require a separate aperture pad.
2552 // Also if there are multiple drills, we will need to make a pad for each
2553 std::vector<std::unique_ptr<BOARD_ITEM>> padItems;
2554
2555 std::vector<std::unique_ptr<PADSTACK::COPPER_LAYER_PROPS>> copperLayers( aPadstack.m_LayerCount );
2556
2557 // Thermal relief gap from antipad/pad size difference on the first layer that has both.
2558 std::optional<int> thermalGap;
2559
2560 const wxString& padStackName = m_brdDb.GetString( aPadstack.m_PadStr );
2561
2562 wxLogTrace( traceAllegroBuilder, "Building pad '%s' with %u layers", padStackName, aPadstack.m_LayerCount );
2563
2564 // First, gather all the copper layers into a set of shape props, which we can then use to decide on the padstack mode
2565 for( size_t i = 0; i < aPadstack.m_LayerCount; ++i )
2566 {
2567 const size_t layerBaseIndex = aPadstack.m_NumFixedCompEntries + i * aPadstack.m_NumCompsPerLayer;
2568 const ALLEGRO::PADSTACK_COMPONENT& padComp = aPadstack.m_Components[layerBaseIndex + BLK_0x1C_PADSTACK::LAYER_COMP_SLOT::PAD];
2569 const ALLEGRO::PADSTACK_COMPONENT& antiPadComp = aPadstack.m_Components[layerBaseIndex + BLK_0x1C_PADSTACK::LAYER_COMP_SLOT::ANTIPAD];
2571
2572 // If this is zero just skip entirely - I don't think we can usefully make pads with just thermal relief
2573 // Flag up if that happens.
2575 {
2576 if( antiPadComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL )
2577 {
2578 m_reporter.Report(
2579 wxString::Format( "Padstack %s: Copper layer %zu has no pad component, but has antipad",
2580 padStackName, i ),
2582 }
2583 if( thermalComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL )
2584 {
2585 m_reporter.Report(
2586 wxString::Format( "Copper layer %zu has no pad component, but has thermal relief", i ),
2588 }
2589 continue;
2590 }
2591
2592 auto& layerCuProps = copperLayers[i];
2593 wxLogTrace( traceAllegroBuilder, " Adding copper layer %zu with pad type %d", i, (int) padComp.m_Type );
2594 layerCuProps = std::make_unique<PADSTACK::COPPER_LAYER_PROPS>();
2595
2596 switch( padComp.m_Type )
2597 {
2599 layerCuProps->shape.shape = PAD_SHAPE::RECTANGLE;
2600 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
2601 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
2602 break;
2604 layerCuProps->shape.shape = PAD_SHAPE::RECTANGLE;
2605 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_W } );
2606 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
2607 break;
2609 layerCuProps->shape.shape = PAD_SHAPE::CIRCLE;
2610 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
2611 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
2612 break;
2615 layerCuProps->shape.shape = PAD_SHAPE::OVAL;
2616 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
2617 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
2618 break;
2620 {
2621 layerCuProps->shape.shape = PAD_SHAPE::ROUNDRECT;
2622 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
2623 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
2624
2625 int minDim = std::min( std::abs( padComp.m_W ), std::abs( padComp.m_H ) );
2626
2627 if( padComp.m_Z1.has_value() && padComp.m_Z1.value() > 0 && minDim > 0 )
2628 layerCuProps->shape.round_rect_radius_ratio = padComp.m_Z1.value() / (double) minDim;
2629 else
2630 layerCuProps->shape.round_rect_radius_ratio = 0.25;
2631
2632 break;
2633 }
2635 {
2636 layerCuProps->shape.shape = PAD_SHAPE::CHAMFERED_RECT;
2637 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
2638 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
2639
2640 int minDim = std::min( std::abs( padComp.m_W ), std::abs( padComp.m_H ) );
2641
2642 if( padComp.m_Z1.has_value() && padComp.m_Z1.value() > 0 && minDim > 0 )
2643 layerCuProps->shape.chamfered_rect_ratio = padComp.m_Z1.value() / (double) minDim;
2644 else
2645 layerCuProps->shape.chamfered_rect_ratio = 0.25;
2646
2647 layerCuProps->shape.chamfered_rect_positions = RECT_CHAMFER_ALL;
2648 break;
2649 }
2651 {
2652 // Approximate octagon as a round rectangle with ~29.3% corner radius
2653 // (tan(22.5°) ≈ 0.414, half of that as ratio ≈ 0.207, but visually 0.293 is closer)
2654 layerCuProps->shape.shape = PAD_SHAPE::CHAMFERED_RECT;
2655 layerCuProps->shape.size = scaleSize( VECTOR2I{ padComp.m_W, padComp.m_H } );
2656 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
2657 layerCuProps->shape.chamfered_rect_ratio = 1.0 - 1.0 / sqrt( 2.0 );
2658 layerCuProps->shape.chamfered_rect_positions = RECT_CHAMFER_ALL;
2659 break;
2660 }
2662 {
2663 // Custom shape defined by a 0x28 polygon. Walk the shape's segments and build
2664 // a polygon primitive for this pad.
2665 const BLK_0x28_SHAPE* shapeData =
2667
2668 if( !shapeData )
2669 {
2670 wxLogTrace( traceAllegroBuilder,
2671 "Padstack %s: SHAPE_SYMBOL on layer %zu has no 0x28 shape at %#010x",
2672 padStackName, i, padComp.m_StrPtr );
2673 break;
2674 }
2675
2677
2678 if( outline.PointCount() >= 3 )
2679 {
2680 outline.SetClosed( true );
2681
2682 layerCuProps->shape.shape = PAD_SHAPE::CUSTOM;
2683 layerCuProps->shape.anchor_shape = PAD_SHAPE::CIRCLE;
2684
2685 // Anchor size based on the shape's bounding box center
2686 BOX2I bbox = outline.BBox();
2687 int anchorSize = static_cast<int>(
2688 std::min( bbox.GetWidth(), bbox.GetHeight() ) / 4 );
2689
2690 if( anchorSize < 1 )
2691 anchorSize = 1;
2692
2693 layerCuProps->shape.size = VECTOR2I( anchorSize, anchorSize );
2694
2695 auto poly = std::make_shared<PCB_SHAPE>( nullptr, SHAPE_T::POLY );
2696 poly->SetPolyShape( SHAPE_POLY_SET( outline ) );
2697 poly->SetFilled( true );
2698 poly->SetWidth( 0 );
2699 layerCuProps->custom_shapes.push_back( poly );
2700 }
2701 else
2702 {
2703 wxLogTrace( traceAllegroBuilder,
2704 "Padstack %s: SHAPE_SYMBOL on layer %zu produced only %d points",
2705 padStackName, i, outline.PointCount() );
2706 }
2707
2708 break;
2709 }
2711 {
2712 layerCuProps->shape.shape = PAD_SHAPE::CUSTOM;
2713 layerCuProps->shape.anchor_shape = PAD_SHAPE::CIRCLE;
2714 layerCuProps->shape.offset = scale( VECTOR2I{ padComp.m_X3, padComp.m_X4 } );
2715
2716 const int w = std::max( padComp.m_W, 300 );
2717 const int h = std::max( padComp.m_H, 220 );
2718
2719 SHAPE_LINE_CHAIN outline;
2720 auto S = [&]( int x, int y )
2721 {
2722 return scale( VECTOR2I{ x, y } );
2723 };
2724
2725 // Regular pentagon with flat bottom edge
2726 outline.Append( S( 0, -h / 2 ) );
2727 outline.Append( S( w / 2, -h / 6 ) );
2728 outline.Append( S( w / 3, h / 2 ) );
2729 outline.Append( S( -w / 3, h / 2 ) );
2730 outline.Append( S( -w / 2, -h / 6 ) );
2731 outline.SetClosed( true );
2732
2733 BOX2I bbox = outline.BBox();
2734 int anchorSize = static_cast<int>(
2735 std::min( bbox.GetWidth(), bbox.GetHeight() ) / 7 );
2736
2737 if( anchorSize < 1 )
2738 anchorSize = 1;
2739
2740 layerCuProps->shape.size = VECTOR2I( anchorSize, anchorSize );
2741
2742 auto poly = std::make_shared<PCB_SHAPE>( nullptr, SHAPE_T::POLY );
2743 poly->SetPolyShape( SHAPE_POLY_SET( outline ) );
2744 poly->SetFilled( true );
2745 poly->SetWidth( 0 );
2746 layerCuProps->custom_shapes.push_back( poly );
2747 break;
2748 }
2749 default:
2750 m_reporter.Report(
2751 wxString::Format( "Padstack %s: unhandled copper pad shape type %d on layer %zu",
2752 padStackName, static_cast<int>( padComp.m_Type ), i ),
2754 break;
2755 }
2756
2757 if( antiPadComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL)
2758 {
2759 if( antiPadComp.m_Type != padComp.m_Type )
2760 {
2761 wxLogTrace( traceAllegroBuilder, "Padstack %s: copper layer %zu antipad shape %d "
2762 "differs from pad shape %d",
2763 padStackName, i, antiPadComp.m_Type, padComp.m_Type );
2764 }
2765
2766 int clearanceX = scale( ( antiPadComp.m_W - padComp.m_W ) / 2 );
2767 int clearanceY = scale( ( antiPadComp.m_H - padComp.m_H ) / 2 );
2768
2769 if( clearanceX && clearanceX != clearanceY )
2770 {
2771 wxLogTrace( traceAllegroBuilder, "Padstack %s: copper layer %zu unequal antipad "
2772 "clearance X=%d Y=%d",
2773 padStackName, i, clearanceX, clearanceY );
2774 }
2775
2776 if( antiPadComp.m_X3 != 0 || antiPadComp.m_X4 != 0 )
2777 {
2778 wxLogTrace( traceAllegroBuilder, "Padstack %s: copper layer %zu antipad offset "
2779 "%d, %d",
2780 padStackName, i, antiPadComp.m_X3, antiPadComp.m_X4 );
2781 }
2782
2783 layerCuProps->clearance = clearanceX;
2784 }
2785
2786 if( thermalComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL && !thermalGap.has_value() )
2787 {
2788 // The thermal gap is the clearance between the pad copper and the surrounding zone.
2789 // We derive it from the antipad-to-pad size difference.
2790 if( antiPadComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL && padComp.m_W > 0 )
2791 {
2792 int gap = scale( ( antiPadComp.m_W - padComp.m_W ) / 2 );
2793
2794 if( gap > 0 )
2795 thermalGap = gap;
2796 }
2797
2798 wxLogTrace( traceAllegroBuilder,
2799 "Padstack %s: thermal relief type=%d, gap=%snm",
2800 padStackName, thermalComp.m_Type,
2801 thermalGap.has_value() ? wxString::Format( "%d", thermalGap.value() )
2802 : wxString( "N/A" ) );
2803 }
2804
2805 // Padstack-level keepouts (antipad relief geometry) are handled via the
2806 // antipad slot in copperLayers above. Board-level keepouts use BLK_0x34.
2807 }
2808
2809 // We have now constructed a list of copper props. We can determine the PADSTACK mode now and assign the shapes
2810 PADSTACK padStack( &aFp );
2811
2812 if( copperLayers.size() == 0 )
2813 {
2814 // SMD aperture PAD or something?
2815 }
2816 else
2817 {
2818 const auto layersEqual = [&](size_t aFrom, size_t aTo) -> bool
2819 {
2820 bool eq = true;
2821 for( size_t i = aFrom + 1; i < aTo; ++i )
2822 {
2823 if( !copperLayers[i - 1] || !copperLayers[i] || *copperLayers[i - 1] != *copperLayers[i] )
2824 {
2825 eq = false;
2826 break;
2827 }
2828 }
2829 return eq;
2830 };
2831
2832 for(size_t i = 0; i < copperLayers.size(); ++i )
2833 {
2834 wxLogTrace( traceAllegroBuilder, " Layer %zu: %s", i, copperLayers[i] ? "present" : "null" );
2835 }
2836
2837 padStack.SetLayerSet( PAD::PTHMask() );
2838
2839 if( copperLayers.front() && copperLayers.back() && layersEqual( 0, copperLayers.size() ) )
2840 {
2841 wxLogTrace( traceAllegroBuilder, " Using NORMAL padstack mode (all layers identical)" );
2842 padStack.SetMode( PADSTACK::MODE::NORMAL );
2843 PADSTACK::COPPER_LAYER_PROPS& layerProps = padStack.CopperLayer( F_Cu );
2844 layerProps = *copperLayers.front();
2845 }
2846 else if( copperLayers.front() && copperLayers.back()
2847 && layersEqual( 1, copperLayers.size() - 1 ) )
2848 {
2849 wxLogTrace( traceAllegroBuilder, " Using FRONT_INNER_BACK padstack mode (inner layers identical)" );
2851 padStack.CopperLayer( F_Cu ) = *copperLayers.front();
2852 padStack.CopperLayer( B_Cu ) = *copperLayers.back();
2853
2854 // May be B_Cu if layers = 2, but that's OK
2855 if( copperLayers.size() > 2 && copperLayers[1] )
2856 padStack.CopperLayer( In1_Cu ) = *copperLayers[1];
2857 }
2858 else
2859 {
2860 wxLogTrace( traceAllegroBuilder, " Using CUSTOM padstack mode (layers differ)" );
2861 padStack.SetMode( PADSTACK::MODE::CUSTOM );
2862
2863 for( size_t i = 0; i < copperLayers.size(); ++i )
2864 {
2865 if( !copperLayers[i] )
2866 continue;
2867
2868 PCB_LAYER_ID layer = F_Cu;
2869
2870 if( i == 0 )
2871 layer = F_Cu;
2872 else if( i == copperLayers.size() - 1 )
2873 layer = B_Cu;
2874 else
2875 layer = ToLAYER_ID( In1_Cu + static_cast<int>( i - 1 ) * 2 );
2876
2877 padStack.CopperLayer( layer ) = *copperLayers[i];
2878 }
2879 }
2880 }
2881
2882 // The drill/slot dimensions are extracted in priority order:
2883 //
2884 // 1. V172+ m_SlotAndUnknownArr[0] and [3] hold the true slot outline dimensions (X and Y).
2885 // For routed slots (round drill bit + routing path), m_DrillArr only has the bit diameter,
2886 // while m_SlotAndUnknownArr has the full slot envelope.
2887 //
2888 // 2. V172+ m_DrillArr[4] (width) and m_DrillArr[7] (height, 0 for round).
2889 // These match m_SlotAndUnknownArr for punched oblong drills but only have the bit
2890 // diameter for routed slots.
2891 //
2892 // 3. Pre-V172 m_Drill field (drill diameter, always round).
2893 int drillW = 0;
2894 int drillH = 0;
2895
2896 if( m_brdDb.m_FmtVer >= FMT_VER::V_172 )
2897 {
2898 if( aPadstack.m_SlotAndUnknownArr.has_value() )
2899 {
2900 const auto& slotArr = aPadstack.m_SlotAndUnknownArr.value();
2901 int slotX = scale( static_cast<int>( slotArr[0] ) );
2902 int slotY = scale( static_cast<int>( slotArr[3] ) );
2903
2904 if( slotX > 0 && slotY > 0 )
2905 {
2906 drillW = slotX;
2907 drillH = slotY;
2908 }
2909 }
2910
2911 if( drillW == 0 )
2912 {
2913 drillW = scale( static_cast<int>( aPadstack.m_DrillArr[4] ) );
2914 drillH = scale( static_cast<int>( aPadstack.m_DrillArr[7] ) );
2915 }
2916 }
2917 else
2918 {
2919 drillW = scale( static_cast<int>( aPadstack.m_Drill ) );
2920 }
2921
2922 if( drillH == 0 )
2923 drillH = drillW;
2924
2925 // Allegro stores slot dimensions as (primary, secondary) regardless of orientation,
2926 // not as (X, Y). Compare the first copper layer pad's aspect ratio to determine if
2927 // the drill needs to be rotated 90 degrees.
2928 if( drillW != drillH && aPadstack.m_LayerCount > 0 )
2929 {
2930 size_t firstCopperIdx = aPadstack.m_NumFixedCompEntries;
2931 const ALLEGRO::PADSTACK_COMPONENT& firstPadComp =
2932 aPadstack.m_Components[firstCopperIdx + BLK_0x1C_PADSTACK::LAYER_COMP_SLOT::PAD];
2933
2934 bool padIsTaller = ( std::abs( firstPadComp.m_H ) > std::abs( firstPadComp.m_W ) );
2935 bool drillIsTaller = ( drillH > drillW );
2936
2937 if( padIsTaller != drillIsTaller )
2938 std::swap( drillW, drillH );
2939 }
2940
2941 bool isSmd = ( drillW == 0 ) || ( aPadstack.m_LayerCount == 1 );
2942
2943 if( isSmd )
2944 {
2945 padStack.Drill().size = VECTOR2I( 0, 0 );
2946 }
2947 else
2948 {
2949 padStack.Drill().size = VECTOR2I( drillW, drillH );
2950
2951 if( drillW != drillH )
2952 padStack.Drill().shape = PAD_DRILL_SHAPE::OBLONG;
2953 else
2954 padStack.Drill().shape = PAD_DRILL_SHAPE::CIRCLE;
2955 }
2956
2957 std::unique_ptr<PAD> pad = std::make_unique<PAD>( &aFp );
2958 pad->SetPadstack( padStack );
2959 pad->SetNumber( aPadName );
2960 pad->SetNetCode( aNetcode );
2961
2962 if( isSmd )
2963 {
2964 pad->SetAttribute( PAD_ATTRIB::SMD );
2965 pad->SetLayerSet( PAD::SMDMask() );
2966 }
2968 {
2969 pad->SetAttribute( PAD_ATTRIB::PTH );
2970 pad->SetLayerSet( PAD::PTHMask() );
2971 }
2972 else
2973 {
2974 pad->SetAttribute( PAD_ATTRIB::NPTH );
2975 pad->SetLayerSet( PAD::UnplatedHoleMask() );
2976 }
2977
2978 if( thermalGap.has_value() )
2979 pad->SetThermalGap( thermalGap.value() );
2980
2981 padItems.push_back( std::move( pad ) );
2982
2983 // Now, for each technical layer, we see if we can include it into the existing padstack, or if we need to add
2984 // it as a standalone pad
2985 for( size_t i = 0; i < aPadstack.m_NumFixedCompEntries; ++i )
2986 {
2987 const ALLEGRO::PADSTACK_COMPONENT& psComp = aPadstack.m_Components[i];
2988
2991 continue;
2992
2993 // All fixed slots are technical layers (solder mask, paste mask, film mask,
2994 // assembly variant, etc). Custom mask expansion extraction is not yet implemented;
2995 // KiCad's default pad-matches-mask behavior applies.
2996 wxLogTrace( traceAllegroBuilder,
2997 "Fixed padstack slot %zu: type=%d, W=%d, H=%d",
2998 i, static_cast<int>( psComp.m_Type ), psComp.m_W, psComp.m_H );
2999 }
3000
3001 return padItems;
3002}
3003
3004
3005std::unique_ptr<FOOTPRINT> BOARD_BUILDER::buildFootprint( const BLK_0x2D_FOOTPRINT_INST& aFpInstance )
3006{
3007 auto fp = std::make_unique<FOOTPRINT>( &m_board );
3008
3009 const BLK_0x07_COMPONENT_INST* fpInstData = getFpInstRef( aFpInstance );
3010
3011 wxLogTrace( traceAllegroBuilder, "Building footprint from 0x2D block key %#010x", aFpInstance.m_Key );
3012
3013 wxString refDesStr;
3014 if( fpInstData )
3015 {
3016 refDesStr = m_brdDb.GetString( fpInstData->m_RefDesStrPtr );
3017
3018 if( refDesStr.IsEmpty() )
3019 {
3020 // Does this happen even when there's an 0x07 block?
3021 m_reporter.Report( wxString::Format( "Empty ref des for 0x2D key %#010x", aFpInstance.m_Key ),
3023 }
3024 }
3025
3026 // We may update the PCB_FIELD layer if it's specified explicitly (e.g. with font size and so on),
3027 // but if not, set the refdes at least, but make it invisible
3028 fp->SetReference( refDesStr );
3029 fp->GetField( FIELD_T::REFERENCE )->SetVisible( false );
3030
3031 wxLogTrace( traceAllegroBuilder, " Footprint reference: '%s'", refDesStr );
3032
3033 const VECTOR2I fpPos = scale( VECTOR2I{ aFpInstance.m_CoordX, aFpInstance.m_CoordY } );
3034 const EDA_ANGLE rotation{ aFpInstance.m_Rotation / 1000., DEGREES_T };
3035
3036 fp->SetPosition( fpPos );
3037 fp->SetOrientation( rotation );
3038
3039 // Allegro stores placed instance data in board-absolute form: bottom-side
3040 // components already have shapes on bottom layers with bottom-side positions.
3041 // Allegro stores placed footprints in board-absolute form with final layers.
3042 // KiCad stores footprints in canonical front-side form and uses Flip() to
3043 // mirror both positions and layers to the back side.
3044 //
3045 // Move back-layer items to their front-side counterpart so that fp->Flip()
3046 // consistently mirrors positions AND layers for all children. Without this,
3047 // bottom-side footprints would have their back-layer graphics double-flipped
3048 // to the front.
3049 const auto canonicalizeLayer = []( BOARD_ITEM* aItem )
3050 {
3051 if( IsBackLayer( aItem->GetLayer() ) )
3052 aItem->SetLayer( FlipLayer( aItem->GetLayer() ) );
3053 };
3054
3055 const LL_WALKER graphicsWalker{ aFpInstance.m_GraphicPtr, aFpInstance.m_Key, m_brdDb };
3056
3057 for( const BLOCK_BASE* graphicsBlock : graphicsWalker )
3058 {
3059 const uint8_t type = graphicsBlock->GetBlockType();
3060
3061 if( type == 0x14 )
3062 {
3063 const auto& graphics = static_cast<const BLOCK<BLK_0x14_GRAPHIC>&>( *graphicsBlock ).GetData();
3064
3065 std::vector<std::unique_ptr<PCB_SHAPE>> shapes = buildShapes( graphics, *fp );
3066
3067 for( std::unique_ptr<PCB_SHAPE>& shape : shapes )
3068 {
3069 canonicalizeLayer( shape.get() );
3070 fp->Add( shape.release() );
3071 }
3072 }
3073 else
3074 {
3075 m_reporter.Report( wxString::Format( "Unexpected type in graphics list: %#04x", type ),
3077 }
3078 }
3079
3080 bool valueFieldSet = false;
3081
3082 const LL_WALKER textWalker{ aFpInstance.m_TextPtr, aFpInstance.m_Key, m_brdDb };
3083
3084 for( const BLOCK_BASE* textBlock : textWalker )
3085 {
3086 const uint8_t type = textBlock->GetBlockType();
3087
3088 if( type != 0x30 )
3089 continue;
3090
3091 const auto& strWrapper = static_cast<const BLOCK<BLK_0x30_STR_WRAPPER>&>( *textBlock ).GetData();
3092
3093 std::unique_ptr<PCB_TEXT> text = buildPcbText( strWrapper, *fp );
3094
3095 if( !text )
3096 continue;
3097
3098 canonicalizeLayer( text.get() );
3099
3100 const uint8_t textClass = strWrapper.m_Layer.m_Class;
3101 const uint8_t textSubclass = strWrapper.m_Layer.m_Subclass;
3102
3103 bool isSilk = ( textSubclass == LAYER_INFO::SUBCLASS::SILKSCREEN_TOP
3104 || textSubclass == LAYER_INFO::SUBCLASS::SILKSCREEN_BOTTOM );
3105 bool isAssembly = ( textSubclass == LAYER_INFO::SUBCLASS::ASSEMBLY_TOP
3106 || textSubclass == LAYER_INFO::SUBCLASS::ASSEMBLY_BOTTOM );
3107
3108 if( textClass == LAYER_INFO::CLASS::REF_DES && isSilk )
3109 {
3110 // Visible silkscreen refdes updates the built-in REFERENCE field.
3111 PCB_FIELD* const refDes = fp->GetField( FIELD_T::REFERENCE );
3112
3113 // KiCad netlisting requires non-digit + digit annotation.
3114 if( !text->GetText().IsEmpty() && !wxIsalpha( text->GetText()[0] ) )
3115 text->SetText( wxString( "UNK" ) + text->GetText() );
3116
3117 *refDes = PCB_FIELD( *text, FIELD_T::REFERENCE );
3118 }
3119 else if( textClass == LAYER_INFO::CLASS::REF_DES && isAssembly )
3120 {
3121 // Assembly refdes becomes a user field with the KiCad reference variable
3122 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "Reference" ) );
3123 field->SetText( wxS( "${REFERENCE}" ) );
3124 field->SetVisible( false );
3125 fp->Add( field, ADD_MODE::APPEND );
3126 }
3127 else if( textClass == LAYER_INFO::CLASS::COMPONENT_VALUE && isAssembly )
3128 {
3129 if( !valueFieldSet )
3130 {
3131 // First COMPONENT_VALUE on assembly updates the built-in VALUE field
3132 PCB_FIELD* valField = fp->GetField( FIELD_T::VALUE );
3133 *valField = PCB_FIELD( *text, FIELD_T::VALUE );
3134 valField->SetVisible( false );
3135 valueFieldSet = true;
3136 }
3137 else
3138 {
3139 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "Component Value" ) );
3140 field->SetVisible( false );
3141 fp->Add( field, ADD_MODE::APPEND );
3142 }
3143 }
3144 else if( textClass == LAYER_INFO::CLASS::DEVICE_TYPE )
3145 {
3146 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "Device Type" ) );
3147 field->SetVisible( !isAssembly );
3148 fp->Add( field, ADD_MODE::APPEND );
3149 }
3150 else if( textClass == LAYER_INFO::CLASS::TOLERANCE )
3151 {
3152 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "Tolerance" ) );
3153 field->SetVisible( isSilk );
3154 fp->Add( field, ADD_MODE::APPEND );
3155 }
3156 else if( textClass == LAYER_INFO::CLASS::USER_PART_NUMBER )
3157 {
3158 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "User Part Number" ) );
3159 field->SetVisible( isSilk );
3160 fp->Add( field, ADD_MODE::APPEND );
3161 }
3162 else if( textClass == LAYER_INFO::CLASS::COMPONENT_VALUE && isSilk )
3163 {
3164 PCB_FIELD* field = new PCB_FIELD( *text, FIELD_T::USER, wxS( "Component Value" ) );
3165 field->SetVisible( true );
3166 fp->Add( field, ADD_MODE::APPEND );
3167 }
3168 else
3169 {
3170 fp->Add( text.release() );
3171 }
3172 }
3173
3174 // Assembly drawing
3175 LL_WALKER assemblyWalker{ aFpInstance.m_AssemblyPtr, aFpInstance.m_Key, m_brdDb };
3176
3177 for( const BLOCK_BASE* assemblyBlock : assemblyWalker )
3178 {
3179 std::vector<std::unique_ptr<BOARD_ITEM>> shapes = buildGraphicItems( *assemblyBlock, *fp );
3180
3181 for( std::unique_ptr<BOARD_ITEM>& item : shapes )
3182 {
3183 canonicalizeLayer( item.get() );
3184 fp->Add( item.release() );
3185 }
3186 }
3187
3188 // Areas (courtyards, etc)
3189 LL_WALKER areaWalker{ aFpInstance.m_AreasPtr, aFpInstance.m_Key, m_brdDb };
3190
3191 // Probably don't need this as we can't have filled zone, but it's cheap
3192 ZONE_FILL_HANDLER zoneFillHandler;
3193
3194 for( const BLOCK_BASE* areaBlock : areaWalker )
3195 {
3196 std::optional<LAYER_INFO> layerInfo = tryLayerFromBlock( *areaBlock );
3197
3198 if( layerInfo.has_value() && layerIsZone( *layerInfo ) )
3199 {
3200 // Zone within a footprint - we can handle keepouts at least
3201 std::unique_ptr<ZONE> zone = buildZone( *areaBlock, {}, zoneFillHandler );
3202 if( zone )
3203 {
3204 canonicalizeLayer( zone.get() );
3205 fp->Add( zone.release() );
3206 }
3207 }
3208 else
3209 {
3210 std::vector<std::unique_ptr<BOARD_ITEM>> shapes = buildGraphicItems( *areaBlock, *fp );
3211
3212 for( std::unique_ptr<BOARD_ITEM>& item : shapes )
3213 {
3214 canonicalizeLayer( item.get() );
3215 fp->Add( item.release() );
3216 }
3217 }
3218 }
3219
3220 // Find the pads
3221 LL_WALKER padWalker{ aFpInstance.m_FirstPadPtr, aFpInstance.m_Key, m_brdDb };
3223 for( const BLOCK_BASE* padBlock : padWalker )
3224 {
3225 const auto& placedPadInfo = static_cast<const BLOCK<BLK_0x32_PLACED_PAD>&>( *padBlock ).GetData();
3226
3227 const BLK_0x04_NET_ASSIGNMENT* netAssignment =
3228 expectBlockByKey<BLK_0x04_NET_ASSIGNMENT>( placedPadInfo.m_NetPtr, 0x04 );
3229 const BLK_0x0D_PAD* padInfo = expectBlockByKey<BLK_0x0D_PAD>( placedPadInfo.m_PadPtr, 0x0D );
3230
3231 if( !padInfo )
3232 continue;
3233
3234 const BLK_0x1C_PADSTACK* padStack = expectBlockByKey<BLK_0x1C_PADSTACK>( padInfo->m_PadStack, 0x1C );
3235
3236 if( !padStack )
3237 continue;
3238
3239 int netCode = NETINFO_LIST::UNCONNECTED;
3240
3241 if( netAssignment )
3242 {
3243 auto netIt = m_netCache.find( netAssignment->m_Net );
3244 if( netIt != m_netCache.end() )
3245 netCode = netIt->second->GetNetCode();
3246 }
3247
3248 const wxString padName = m_brdDb.GetString( padInfo->m_NameStrId );
3249
3250 // 0x0D coordinates and rotation are in the footprint's local (unrotated) space.
3251 // Use SetFPRelativePosition/Orientation to let KiCad handle the transform to
3252 // board-absolute coordinates (rotating by FP orientation and adding FP position).
3253 const VECTOR2I padLocalPos = scale( VECTOR2I{ padInfo->m_CoordsX, padInfo->m_CoordsY } );
3254 const EDA_ANGLE padLocalRot = fromMillidegrees( padInfo->m_Rotation );
3255
3256 std::vector<std::unique_ptr<BOARD_ITEM>> padItems = buildPadItems( *padStack, *fp, padName, netCode );
3257
3258 for( std::unique_ptr<BOARD_ITEM>& item : padItems )
3259 {
3260 if( item->Type() == PCB_PAD_T )
3261 {
3262 PAD* pad = static_cast<PAD*>( item.get() );
3263 pad->SetFPRelativeOrientation( padLocalRot );
3264 }
3265
3266 item->SetFPRelativePosition( padLocalPos );
3267 fp->Add( item.release() );
3268 }
3269 }
3270
3271 // Flip AFTER adding all children so that graphics, text, and pads all get
3272 // their layers and positions mirrored correctly for bottom-layer footprints.
3273 //
3274 // Allegro mirrors bottom-side components via X-mirror (flip around Y axis),
3275 // then rotates by R. KiCad's Flip(TOP_BOTTOM) is a Y-mirror which negates
3276 // the orientation. Since X-mirror = Y-mirror + Rotate(180), we need the
3277 // final orientation to be R+180. Flip negates what we set before it, so
3278 // set -(R+180) to get R+180 after negation.
3279 if( aFpInstance.m_Layer != 0 )
3280 {
3281 fp->SetOrientation( -rotation - ANGLE_180 );
3282 fp->Flip( fpPos, FLIP_DIRECTION::TOP_BOTTOM );
3283 }
3284
3285 return fp;
3286}
3287
3288std::vector<std::unique_ptr<BOARD_ITEM>> BOARD_BUILDER::buildTrack( const BLK_0x05_TRACK& aTrackBlock, int aNetCode )
3289{
3290 std::vector<std::unique_ptr<BOARD_ITEM>> items;
3291
3292 // Anti-etch tracks are thermal relief patterns generated by Allegro.
3293 // These are handled by pad-level thermal relief properties instead.
3294 if( aTrackBlock.m_Layer.m_Class == LAYER_INFO::CLASS::ANTI_ETCH )
3295 {
3296 wxLogTrace( traceAllegroBuilder, "Skipping ANTI_ETCH track (class=%#04x, subclass=%#04x)",
3297 aTrackBlock.m_Layer.m_Class, aTrackBlock.m_Layer.m_Subclass );
3298 return items;
3299 }
3300
3301 const PCB_LAYER_ID layer = getLayer( aTrackBlock.m_Layer );
3302
3303 LL_WALKER segWalker{ aTrackBlock.m_FirstSegPtr, aTrackBlock.m_Key, m_brdDb };
3304 for( const BLOCK_BASE* block : segWalker )
3305 {
3306 const uint8_t segType = block->GetBlockType();
3307
3308 switch( segType )
3309 {
3310 case 0x15:
3311 case 0x16:
3312 case 0x17:
3313 {
3314 const BLK_0x15_16_17_SEGMENT& segInfo =
3315 static_cast<const BLOCK<BLK_0x15_16_17_SEGMENT>&>( *block ).GetData();
3316
3317 VECTOR2I start{ segInfo.m_StartX, segInfo.m_StartY };
3318 VECTOR2I end{ segInfo.m_EndX, segInfo.m_EndY };
3319 int width = static_cast<int>( segInfo.m_Width );
3320
3321 std::unique_ptr<PCB_TRACK> seg = std::make_unique<PCB_TRACK>( &m_board );
3322
3323 seg->SetNetCode( aNetCode );
3324 seg->SetLayer( layer );
3325
3326 seg->SetStart( scale( start ) );
3327 seg->SetEnd( scale( end ) );
3328 seg->SetWidth( scale( width ) );
3329
3330 items.push_back( std::move( seg ) );
3331 break;
3332 }
3333 case 0x01:
3334 {
3335 const BLK_0x01_ARC& arcInfo = static_cast<const BLOCK<BLK_0x01_ARC>&>( *block ).GetData();
3336
3337 VECTOR2I start = scale( { arcInfo.m_StartX, arcInfo.m_StartY } );
3338 VECTOR2I end = scale( { arcInfo.m_EndX, arcInfo.m_EndY } );
3339 VECTOR2I c = scale( KiROUND( VECTOR2D{ arcInfo.m_CenterX, arcInfo.m_CenterY } ) );
3340 int width = scale( static_cast<int>( arcInfo.m_Width ) );
3341
3342 bool clockwise = ( arcInfo.m_SubType & 0x40 ) != 0;
3343
3344 EDA_ANGLE startAngle( start - c );
3345 EDA_ANGLE endAngle( end - c );
3346 startAngle.Normalize();
3347 endAngle.Normalize();
3348
3349 EDA_ANGLE angle = endAngle - startAngle;
3350
3351 if( clockwise && angle < ANGLE_0 )
3352 angle += ANGLE_360;
3353
3354 if( !clockwise && angle > ANGLE_0 )
3355 angle -= ANGLE_360;
3356
3357 VECTOR2I mid = start;
3358 RotatePoint( mid, c, -angle / 2.0 );
3359
3360 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( &m_board );
3361
3362 arc->SetNetCode( aNetCode );
3363 arc->SetLayer( layer );
3364
3365 arc->SetStart( start );
3366 arc->SetMid( mid );
3367 arc->SetEnd( end );
3368 arc->SetWidth( width );
3369
3370 items.push_back( std::move( arc ) );
3371 break;
3372 }
3373 default:
3374 wxLogTrace( traceAllegroBuilder, "Unhandled segment type in track: %#04x", segType );
3375 break;
3376 }
3377 }
3378 return items;
3379}
3380
3381
3382std::unique_ptr<BOARD_ITEM> BOARD_BUILDER::buildVia( const BLK_0x33_VIA& aViaData, int aNetCode )
3383{
3384 VECTOR2I viaPos{ aViaData.m_CoordsX, aViaData.m_CoordsY };
3385
3386 const BLK_0x1C_PADSTACK* viaPadstack = expectBlockByKey<BLK_0x1C_PADSTACK>( aViaData.m_Padstack, 0x1C );
3387
3388 if( !viaPadstack )
3389 return nullptr;
3390
3391 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( &m_board );
3392 via->SetPosition( scale( viaPos ) );
3393 via->SetNetCode( aNetCode );
3394
3395 via->SetTopLayer( F_Cu );
3396 via->SetBottomLayer( B_Cu );
3397
3398 // Extract via size from the first copper layer's pad component
3399 int viaWidth = 0;
3400
3401 if( viaPadstack->m_LayerCount > 0 )
3402 {
3403 const size_t layerBaseIndex = viaPadstack->m_NumFixedCompEntries;
3404 const ALLEGRO::PADSTACK_COMPONENT& padComp =
3405 viaPadstack->m_Components[layerBaseIndex + BLK_0x1C_PADSTACK::LAYER_COMP_SLOT::PAD];
3406
3407 if( padComp.m_Type != PADSTACK_COMPONENT::TYPE_NULL )
3408 {
3409 viaWidth = scale( padComp.m_W );
3410 }
3411 }
3412
3413 int viaDrill = 0;
3414
3415 if( m_brdDb.m_FmtVer >= FMT_VER::V_172 )
3416 viaDrill = scale( static_cast<int>( viaPadstack->m_DrillArr[4] ) );
3417 else
3418 viaDrill = scale( static_cast<int>( viaPadstack->m_Drill ) );
3419
3420 if( viaDrill == 0 )
3421 {
3422 viaDrill = viaWidth / 2;
3423 wxLogTrace( traceAllegroBuilder, "Via at (%d, %d): no drill in padstack, using fallback %d",
3424 aViaData.m_CoordsX, aViaData.m_CoordsY, viaDrill );
3425 }
3426
3427 if( viaWidth <= 0 )
3428 {
3429 wxLogTrace( traceAllegroBuilder, "Via at (%d, %d) has no valid pad component, using drill-based fallback",
3430 aViaData.m_CoordsX, aViaData.m_CoordsY );
3431 viaWidth = viaDrill * 2;
3432 }
3433
3434 via->SetWidth( F_Cu, viaWidth );
3435 via->SetDrill( viaDrill );
3436
3437 return via;
3438}
3439
3440
3442{
3443 wxLogTrace( traceAllegroBuilder, "Creating tracks, vias, and other routed items" );
3444
3445 std::vector<BOARD_ITEM*> newItems;
3446
3447 // We need to walk this list again - we could do this all in createNets, but this seems tidier.
3448 LL_WALKER netWalker{ m_brdDb.m_Header->m_LL_0x1B_Nets, m_brdDb };
3449 for( const BLOCK_BASE* block : netWalker )
3450 {
3451 const uint8_t type = block->GetBlockType();
3452 if( type != BLOCK_TYPE::x1B_NET )
3453 {
3454 reportUnexpectedBlockType( type, BLOCK_TYPE::x1B_NET, 0, block->GetOffset(), "Net" );
3455 continue;
3456 }
3457
3458 const auto& net = static_cast<const BLOCK<BLK_0x1B_NET>&>( *block ).GetData();
3459
3460 auto netIt = m_netCache.find( net.m_Key );
3461
3462 if( netIt == m_netCache.end() )
3463 continue;
3464
3465 const int netCode = netIt->second->GetNetCode();
3466
3467 LL_WALKER assignmentWalker{ net.m_Assignment, net.m_Key, m_brdDb };
3468 for( const BLOCK_BASE* assignBlock : assignmentWalker )
3469 {
3470 if( assignBlock->GetBlockType() != 0x04 )
3471 {
3472 reportUnexpectedBlockType( assignBlock->GetBlockType(), 0x04, 0, block->GetOffset(), "Net assignment" );
3473 continue;
3474 }
3475
3476 const auto& assign = static_cast<const BLOCK<BLK_0x04_NET_ASSIGNMENT>&>( *assignBlock ).GetData();
3477
3478 // Walk the 0x05/0x32/... list
3479 LL_WALKER connWalker{ assign.m_ConnItem, assign.m_Key, m_brdDb };
3480 for( const BLOCK_BASE* connItemBlock : connWalker )
3481 {
3482 const uint8_t connType = connItemBlock->GetBlockType();
3483
3484 // One connected item can be multiple KiCad objects, e.g.
3485 // 0x05 track -> list of segments/arcs
3486 std::vector<std::unique_ptr<BOARD_ITEM>> newItemList;
3487
3488 switch( connType )
3489 {
3490 // Track
3491 case 0x05:
3492 {
3493 const BLK_0x05_TRACK& trackData =
3494 static_cast<const BLOCK<BLK_0x05_TRACK>&>( *connItemBlock ).GetData();
3495 newItemList = buildTrack( trackData, netCode );
3496 break;
3497 }
3498 case 0x33:
3499 {
3500 const BLK_0x33_VIA& viaData = static_cast<const BLOCK<BLK_0x33_VIA>&>( *connItemBlock ).GetData();
3501 newItemList.push_back( buildVia( viaData, netCode ) );
3502 break;
3503 }
3504 case 0x32:
3505 {
3506 // This is a pad in a footprint - we don't need to handle this here, as we do all the footprint
3507 // pads, connected or not, in the footprint step.
3508 break;
3509 }
3510 case 0x28:
3511 {
3512 // 0x28 shapes on the net chain are computed copper fills.
3513 // Collect them for teardrop and polygon import.
3514 const BLK_0x28_SHAPE& fillShape =
3515 static_cast<const BLOCK<BLK_0x28_SHAPE>&>( *connItemBlock ).GetData();
3516
3517 PCB_LAYER_ID fillLayer = getLayer( fillShape.m_Layer );
3518
3519 if( fillLayer != UNDEFINED_LAYER )
3520 m_zoneFillShapes[fillShape.m_Key] = { &fillShape, netCode, fillLayer };
3521
3522 break;
3523 }
3524 case 0x2E:
3525 default:
3526 {
3527 wxLogTrace( traceAllegroBuilder, " Unhandled connected item code: %#04x",
3528 (int) connType );
3529 }
3530 }
3531
3532 for( std::unique_ptr<BOARD_ITEM>& newItem : newItemList )
3533 {
3534 newItems.push_back( newItem.get() );
3535 m_board.Add( newItem.release(), ADD_MODE::BULK_APPEND );
3536 }
3537 }
3538 }
3539 }
3540
3541 m_board.FinalizeBulkAdd( newItems );
3542
3543 wxLogTrace( traceAllegroBuilder, "Finished creating %zu track/via items", newItems.size() );
3544}
3545
3546
3548{
3549 wxLogTrace( traceAllegroBuilder, "Creating shapes" );
3550
3551 // Walk through LL_0x24_0x28 which contains rectangles (0x24) and shapes (0x28)
3552 const LL_WALKER shapeWalker( m_brdDb.m_Header->m_LL_0x24_0x28, m_brdDb );
3553 int blockCount = 0;
3554
3555 std::vector<std::unique_ptr<BOARD_ITEM>> newItems;
3556
3557 for( const BLOCK_BASE* block : shapeWalker )
3558 {
3559 blockCount++;
3560
3561 switch( block->GetBlockType() )
3562 {
3563 case 0x24:
3564 {
3565 const BLK_0x24_RECT& rectData = BlockDataAs<BLK_0x24_RECT>( *block );
3566
3567 // These are zones, we don't handle them here
3568 if( layerIsZone( rectData.m_Layer ) )
3569 continue;
3570
3571 std::unique_ptr<PCB_SHAPE> rectShape = buildRect( rectData, m_board );
3572 newItems.push_back( std::move( rectShape ) );
3573 break;
3574 }
3575 case 0x28:
3576 {
3577 const BLK_0x28_SHAPE& shapeData = BlockDataAs<BLK_0x28_SHAPE>( *block );
3578
3579 // These are zones, we don't handle them here
3580 if( layerIsZone( shapeData.m_Layer ) )
3581 continue;
3582
3583 std::vector<std::unique_ptr<PCB_SHAPE>> shapeItems = buildPolygonShapes( shapeData, m_board );
3584
3585 for( auto& shapeItem : shapeItems )
3586 newItems.push_back( std::move( shapeItem ) );
3587 break;
3588 }
3589 default:
3590 {
3591 wxLogTrace( traceAllegroBuilder, " Unhandled block type in outline walker: %#04x", block->GetBlockType() );
3592 break;
3593 }
3594 }
3595 }
3596
3597 wxLogTrace( traceAllegroBuilder, " Found %d shape blocks", blockCount, newItems.size() );
3598 blockCount = 0;
3599
3600 LL_WALKER outline2Walker( m_brdDb.m_Header->m_LL_Shapes, m_brdDb );
3601 for( const BLOCK_BASE* block : outline2Walker )
3602 {
3603 blockCount++;
3604
3605 switch( block->GetBlockType() )
3606 {
3607 case 0x0E:
3608 {
3609 const BLK_0x0E_RECT& rectData = BlockDataAs<BLK_0x0E_RECT>( *block );
3610
3611 if( layerIsZone( rectData.m_Layer ) )
3612 continue;
3613
3614 std::unique_ptr<PCB_SHAPE> rectShape = buildRect( rectData, m_board );
3615 newItems.push_back( std::move( rectShape ) );
3616 break;
3617 }
3618 case 0x24:
3619 {
3620 const BLK_0x24_RECT& rectData = BlockDataAs<BLK_0x24_RECT>( *block );
3621
3622 if( layerIsZone( rectData.m_Layer ) )
3623 continue;
3624
3625 std::unique_ptr<PCB_SHAPE> rectShape = buildRect( rectData, m_board );
3626 newItems.push_back( std::move( rectShape ) );
3627 break;
3628 }
3629 case 0x28:
3630 {
3631 const BLK_0x28_SHAPE& shapeData = BlockDataAs<BLK_0x28_SHAPE>( *block );
3632
3633 if( layerIsZone( shapeData.m_Layer ) )
3634 continue;
3635
3636 std::vector<std::unique_ptr<PCB_SHAPE>> shapeItems = buildPolygonShapes( shapeData, m_board );
3637
3638 for( auto& shapeItem : shapeItems )
3639 newItems.push_back( std::move( shapeItem ) );
3640 break;
3641 }
3642 default:
3643 {
3644 wxLogTrace( traceAllegroBuilder, " Unhandled block type in outline walker: %#04x", block->GetBlockType() );
3645 break;
3646 }
3647 }
3648 }
3649
3650 wxLogTrace( traceAllegroBuilder, " Found %d outline items in m_LL_Shapes", blockCount );
3651 blockCount = 0;
3652
3653 LL_WALKER graphicContainerWalker( m_brdDb.m_Header->m_LL_0x14, m_brdDb );
3654 for( const BLOCK_BASE* block : graphicContainerWalker )
3655 {
3656 blockCount++;
3657
3658 switch( block->GetBlockType() )
3659 {
3660 case 0x14:
3661 {
3662 const auto& graphicContainer = BlockDataAs<BLK_0x14_GRAPHIC>( *block );
3663
3664 if( layerIsZone( graphicContainer.m_Layer ) )
3665 continue;
3666
3667 std::vector<std::unique_ptr<PCB_SHAPE>> graphicItems = buildShapes( graphicContainer, m_board );
3668
3669 for( auto& item : graphicItems )
3670 newItems.push_back( std::move( item ) );
3671 break;
3672 }
3673 default:
3674 {
3675 wxLogTrace( traceAllegroBuilder, " Unhandled block type in graphic container walker: %#04x",
3676 block->GetBlockType() );
3677 break;
3678 }
3679 }
3680 }
3681
3682 wxLogTrace( traceAllegroBuilder, " Found %d graphic container items", blockCount );
3683
3684 std::vector<BOARD_ITEM*> addedItems;
3685 for( std::unique_ptr<BOARD_ITEM>& item : newItems )
3686 {
3687 addedItems.push_back( item.get() );
3688 m_board.Add( item.release(), ADD_MODE::BULK_APPEND );
3689 }
3690
3691 m_board.FinalizeBulkAdd( addedItems );
3692
3693 wxLogTrace( traceAllegroBuilder, "Created %zu board shapes", addedItems.size() );
3694}
3695
3696
3697const SHAPE_LINE_CHAIN& BOARD_BUILDER::buildSegmentChain( uint32_t aStartKey ) const
3698{
3699 auto cacheIt = m_segChainCache.find( aStartKey );
3700
3701 if( cacheIt != m_segChainCache.end() )
3702 return cacheIt->second;
3703
3704 SHAPE_LINE_CHAIN& outline = m_segChainCache[aStartKey];
3705 uint32_t currentKey = aStartKey;
3706
3707 // Safety limit to prevent infinite loops on corrupt data
3708 static constexpr int MAX_CHAIN_LENGTH = 50000;
3709 int visited = 0;
3710
3711 while( currentKey != 0 && visited < MAX_CHAIN_LENGTH )
3712 {
3713 const BLOCK_BASE* block = m_brdDb.GetObjectByKey( currentKey );
3714
3715 if( !block )
3716 break;
3717
3718 visited++;
3719
3720 switch( block->GetBlockType() )
3721 {
3722 case 0x01:
3723 {
3724 const auto& arc = static_cast<const BLOCK<BLK_0x01_ARC>&>( *block ).GetData();
3725 VECTOR2I start = scale( { arc.m_StartX, arc.m_StartY } );
3726 VECTOR2I end = scale( { arc.m_EndX, arc.m_EndY } );
3727 VECTOR2I center = scale( KiROUND( VECTOR2D{ arc.m_CenterX, arc.m_CenterY } ) );
3728
3729 if( start == end )
3730 {
3731 SHAPE_ARC shapeArc( center, start, ANGLE_360 );
3732 outline.Append( shapeArc );
3733 }
3734 else
3735 {
3736 bool clockwise = ( arc.m_SubType & 0x40 ) != 0;
3737
3738 EDA_ANGLE startAngle( start - center );
3739 EDA_ANGLE endAngle( end - center );
3740 startAngle.Normalize();
3741 endAngle.Normalize();
3742
3743 EDA_ANGLE arcAngle = endAngle - startAngle;
3744
3745 if( clockwise && arcAngle < ANGLE_0 )
3746 arcAngle += ANGLE_360;
3747
3748 if( !clockwise && arcAngle > ANGLE_0 )
3749 arcAngle -= ANGLE_360;
3750
3751 VECTOR2I mid = start;
3752 RotatePoint( mid, center, -arcAngle / 2.0 );
3753
3754 SHAPE_ARC shapeArc( start, mid, end, 0 );
3755 outline.Append( shapeArc );
3756 }
3757
3758 currentKey = arc.m_Next;
3759 break;
3760 }
3761 case 0x15:
3762 case 0x16:
3763 case 0x17:
3764 {
3765 const auto& seg =
3766 static_cast<const BLOCK<BLK_0x15_16_17_SEGMENT>&>( *block ).GetData();
3767 VECTOR2I start = scale( { seg.m_StartX, seg.m_StartY } );
3768
3769 if( outline.PointCount() == 0 || outline.CLastPoint() != start )
3770 outline.Append( start );
3771
3772 VECTOR2I end = scale( { seg.m_EndX, seg.m_EndY } );
3773 outline.Append( end );
3774 currentKey = seg.m_Next;
3775 break;
3776 }
3777 default:
3778 currentKey = 0;
3779 break;
3780 }
3781 }
3782
3783 return outline;
3784}
3785
3786
3788{
3789 SHAPE_LINE_CHAIN outline;
3790
3791 VECTOR2I topLeft = scale( VECTOR2I{ aRect.m_Coords[0], aRect.m_Coords[1] } );
3792 VECTOR2I botRight = scale( VECTOR2I{ aRect.m_Coords[2], aRect.m_Coords[3] } );
3793 VECTOR2I topRight{ botRight.x, topLeft.y };
3794 VECTOR2I botLeft{ topLeft.x, botRight.y };
3795
3796 outline.Append( topLeft );
3797 outline.Append( topRight );
3798 outline.Append( botRight );
3799 outline.Append( botLeft );
3800
3801 const EDA_ANGLE angle = fromMillidegrees( aRect.m_Rotation );
3802 outline.Rotate( angle, topLeft );
3803
3804 return outline;
3805}
3806
3807
3809{
3810 SHAPE_LINE_CHAIN outline;
3811
3812 VECTOR2I topLeft = scale( VECTOR2I{ aRect.m_Coords[0], aRect.m_Coords[1] } );
3813 VECTOR2I botRight = scale( VECTOR2I{ aRect.m_Coords[2], aRect.m_Coords[3] } );
3814 VECTOR2I topRight{ botRight.x, topLeft.y };
3815 VECTOR2I botLeft{ topLeft.x, botRight.y };
3816
3817 outline.Append( topLeft );
3818 outline.Append( topRight );
3819 outline.Append( botRight );
3820 outline.Append( botLeft );
3821
3822 const EDA_ANGLE angle = fromMillidegrees( aRect.m_Rotation );
3823 outline.Rotate( angle, topLeft );
3824
3825 return outline;
3826}
3827
3828
3830{
3831 SHAPE_LINE_CHAIN outline;
3832 const LL_WALKER segWalker{ aShape.m_FirstSegmentPtr, aShape.m_Key, m_brdDb };
3833
3834 for( const BLOCK_BASE* segBlock : segWalker )
3835 {
3836 switch( segBlock->GetBlockType() )
3837 {
3838 case 0x01:
3839 {
3840 const auto& arc = static_cast<const BLOCK<BLK_0x01_ARC>&>( *segBlock ).GetData();
3841 VECTOR2I start = scale( { arc.m_StartX, arc.m_StartY } );
3842 VECTOR2I end = scale( { arc.m_EndX, arc.m_EndY } );
3843 VECTOR2I center = scale( KiROUND( VECTOR2D{ arc.m_CenterX, arc.m_CenterY } ) );
3844
3845 if( start == end )
3846 {
3847 SHAPE_ARC shapeArc( center, start, ANGLE_360 );
3848 outline.Append( shapeArc );
3849 }
3850 else
3851 {
3852 bool clockwise = ( arc.m_SubType & 0x40 ) != 0;
3853
3854 EDA_ANGLE startAngle( start - center );
3855 EDA_ANGLE endAngle( end - center );
3856 startAngle.Normalize();
3857 endAngle.Normalize();
3858
3859 EDA_ANGLE arcAngle = endAngle - startAngle;
3860
3861 if( clockwise && arcAngle < ANGLE_0 )
3862 arcAngle += ANGLE_360;
3863
3864 if( !clockwise && arcAngle > ANGLE_0 )
3865 arcAngle -= ANGLE_360;
3866
3867 VECTOR2I mid = start;
3868 RotatePoint( mid, center, -arcAngle / 2.0 );
3869
3870 SHAPE_ARC shapeArc( start, mid, end, 0 );
3871 outline.Append( shapeArc );
3872 }
3873
3874 break;
3875 }
3876 case 0x15:
3877 case 0x16:
3878 case 0x17:
3879 {
3880 const auto& seg = static_cast<const BLOCK<BLK_0x15_16_17_SEGMENT>&>( *segBlock ).GetData();
3881 VECTOR2I start = scale( { seg.m_StartX, seg.m_StartY } );
3882
3883 if( outline.PointCount() == 0 || outline.CLastPoint() != start )
3884 outline.Append( start );
3885
3886 VECTOR2I end = scale( { seg.m_EndX, seg.m_EndY } );
3887 outline.Append( end );
3888 break;
3889 }
3890 default:
3891 wxLogTrace( traceAllegroBuilder, " Unhandled segment type in shape outline: %#04x",
3892 segBlock->GetBlockType() );
3893 break;
3894 }
3895 }
3896
3897 return outline;
3898}
3899
3900
3902{
3903 SHAPE_POLY_SET polySet;
3905
3906 if( outline.PointCount() < 3 )
3907 {
3908 wxLogTrace( traceAllegroBuilder, " Not enough points for polygon (%d)", outline.PointCount() );
3909 return polySet;
3910 }
3911
3912 outline.SetClosed( true );
3913 polySet.AddOutline( outline );
3914
3915 // Walk 0x34 KEEPOUT chain from m_Ptr4 for holes
3916 uint32_t holeKey = aShape.m_FirstKeepoutPtr;
3917
3918 while( holeKey != 0 )
3919 {
3920 const BLOCK_BASE* holeBlock = m_brdDb.GetObjectByKey( holeKey );
3921
3922 if( !holeBlock || holeBlock->GetBlockType() != 0x34 )
3923 break;
3924
3925 const auto& keepout = static_cast<const BLOCK<BLK_0x34_KEEPOUT>&>( *holeBlock ).GetData();
3926
3927 SHAPE_LINE_CHAIN holeOutline = buildSegmentChain( keepout.m_FirstSegmentPtr );
3928
3929 if( holeOutline.PointCount() >= 3 )
3930 {
3931 holeOutline.SetClosed( true );
3932 polySet.AddHole( holeOutline );
3933 }
3934
3935 holeKey = keepout.m_Next;
3936 }
3937
3938 return polySet;
3939}
3940
3941
3943{
3944 SHAPE_POLY_SET polySet;
3945
3946 switch( aBlock.GetBlockType() )
3947 {
3948 case 0x0E:
3949 {
3950 const auto& rectData = BlockDataAs<BLK_0x0E_RECT>( aBlock );
3951
3952 SHAPE_LINE_CHAIN chain( buildOutline( rectData ) );
3953 chain.SetClosed( true );
3954
3955 polySet = SHAPE_POLY_SET( chain );
3956 break;
3957 }
3958 case 0x14:
3959 {
3960 const auto& graphicContainer = BlockDataAs<BLK_0x14_GRAPHIC>( aBlock );
3961
3962 SHAPE_LINE_CHAIN chain = buildSegmentChain( graphicContainer.m_SegmentPtr );
3963 chain.SetClosed( true );
3964
3965 polySet = SHAPE_POLY_SET( chain );
3966 break;
3967 }
3968 case 0x24:
3969 {
3970 const auto& rectData = BlockDataAs<BLK_0x24_RECT>( aBlock );
3971
3972 SHAPE_LINE_CHAIN chain( buildOutline( rectData ) );
3973 chain.SetClosed( true );
3974
3975 polySet = SHAPE_POLY_SET( chain );
3976 break;
3977 }
3978 case 0x28:
3979 {
3980 const auto& shapeData = BlockDataAs<BLK_0x28_SHAPE>( aBlock );
3981 polySet = shapeToPolySet( shapeData );
3982 break;
3983 }
3984 default:
3985 wxLogTrace( traceAllegroBuilder, " Unhandled block type in tryBuildZoneShape: %#04x", aBlock.GetBlockType() );
3986 }
3987
3988 return polySet;
3989}
3990
3991
3992std::unique_ptr<ZONE> BOARD_BUILDER::buildZone( const BLOCK_BASE& aBoundaryBlock,
3993 const std::vector<const BLOCK_BASE*>& aRelatedBlocks,
3994 ZONE_FILL_HANDLER& aZoneFillHandler )
3995{
3996 int netCode = NETINFO_LIST::UNCONNECTED;
3997 const LAYER_INFO layerInfo = expectLayerFromBlock( aBoundaryBlock );
3998
3999 bool isCopperZone = ( layerInfo.m_Class == LAYER_INFO::CLASS::ETCH
4000 || layerInfo.m_Class == LAYER_INFO::CLASS::BOUNDARY );
4001
4003
4004 if( isCopperZone )
4005 {
4006 // BOUNDARY shares the ETCH layer list, so resolve subclass via ETCH class
4007 if( layerInfo.m_Class == LAYER_INFO::CLASS::BOUNDARY )
4008 {
4009 LAYER_INFO etchLayer{};
4010 etchLayer.m_Class = LAYER_INFO::CLASS::ETCH;
4011 etchLayer.m_Subclass = layerInfo.m_Subclass;
4012 layer = getLayer( etchLayer );
4013 }
4014 else
4015 {
4016 layer = getLayer( layerInfo );
4017 }
4018 }
4019 else
4020 {
4021 layer = F_Cu;
4022 }
4023
4024 if( isCopperZone && layer == UNDEFINED_LAYER )
4025 {
4026 wxLogTrace( traceAllegroBuilder, " Skipping shape on layer %#02x:%#02x - unmapped copper layer",
4027 layerInfo.m_Class, layerInfo.m_Subclass );
4028 return nullptr;
4029 }
4030
4031 const SHAPE_POLY_SET zoneShape = tryBuildZoneShape( aBoundaryBlock );
4032
4033 if( zoneShape.OutlineCount() != 1 )
4034 {
4035 wxLogTrace( traceAllegroBuilder, " Skipping zone with type %#04x, key %#010x - failed to build outline",
4036 aBoundaryBlock.GetBlockType(), aBoundaryBlock.GetKey() );
4037 return nullptr;
4038 }
4039
4040 auto zone = std::make_unique<ZONE>( &m_board );
4041 zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH );
4042
4043 if( isCopperZone )
4044 {
4045 zone->SetLayer( layer );
4046 zone->SetFillMode( ZONE_FILL_MODE::POLYGONS );
4047 }
4048 else
4049 {
4050 LSET layerSet = m_layerMapper->GetRuleAreaLayers( layerInfo );
4051
4052 bool isRouteKeepout = ( layerInfo.m_Class == LAYER_INFO::CLASS::ROUTE_KEEPOUT );
4053 bool isViaKeepout = ( layerInfo.m_Class == LAYER_INFO::CLASS::VIA_KEEPOUT );
4054 bool isPackageKeepout = ( layerInfo.m_Class == LAYER_INFO::CLASS::PACKAGE_KEEPOUT );
4055 bool isRouteKeepin = ( layerInfo.m_Class == LAYER_INFO::CLASS::ROUTE_KEEPIN );
4056 bool isPackageKeepin = ( layerInfo.m_Class == LAYER_INFO::CLASS::PACKAGE_KEEPIN );
4057
4058 zone->SetIsRuleArea( true );
4059 zone->SetLayerSet( layerSet );
4060 zone->SetDoNotAllowTracks( isRouteKeepout );
4061 zone->SetDoNotAllowVias( isViaKeepout );
4062 zone->SetDoNotAllowZoneFills( isRouteKeepout || isViaKeepout );
4063 zone->SetDoNotAllowPads( false );
4064 zone->SetDoNotAllowFootprints( isPackageKeepout );
4065
4066 // Zones don't have native keepin functions, so we leave a note for the user here
4067 // Later, we could consider adding a custom DRC rule for this (or KiCad could add native keepin
4068 // zone support)
4069 if( isRouteKeepin )
4070 zone->SetZoneName( "Route Keepin" );
4071 else if( isPackageKeepin )
4072 zone->SetZoneName( "Package Keepin" );
4073 }
4074
4075 SHAPE_POLY_SET combinedFill;
4076
4077 for( const BLOCK_BASE* block : aRelatedBlocks )
4078 {
4079 if( !block )
4080 continue;
4081
4082 switch( block->GetBlockType() )
4083 {
4084 case 0x1B:
4085 {
4086 const auto it = m_netCache.find( block->GetKey() );
4087
4088 if( it != m_netCache.end() )
4089 {
4090 wxLogTrace( traceAllegroBuilder, " Resolved BOUNDARY %#010x -> net '%s' (code %d)",
4091 aBoundaryBlock.GetKey(), it->second->GetNetname(), it->second->GetNetCode() );
4092
4093 netCode = it->second->GetNetCode();
4094 }
4095 else
4096 {
4097 m_reporter.Report( wxString::Format( "Could not find net key %#010x in cache for BOUNDARY %#010x",
4098 block->GetKey(), aBoundaryBlock.GetKey() ),
4100 }
4101 break;
4102 }
4103 case 0x28:
4104 {
4105 const BLK_0x28_SHAPE& shapeData = static_cast<const BLOCK<BLK_0x28_SHAPE>&>( *block ).GetData();
4106
4107 SHAPE_POLY_SET fillPolySet = shapeToPolySet( shapeData );
4108 combinedFill.Append( fillPolySet );
4109 m_usedZoneFillShapes.emplace( block->GetKey() );
4110 break;
4111 }
4112 default: break;
4113 }
4114 }
4115
4116 // Set net code AFTER layer assignment. SetNetCode checks IsOnCopperLayer() and
4117 // forces net=0 if the zone isn't on a copper layer yet.
4118 zone->SetNetCode( netCode );
4119
4120 for( const SHAPE_LINE_CHAIN& chain : zoneShape.CPolygon( 0 ) )
4121 zone->AddPolygon( chain );
4122
4123 // Add zone fills
4124 if( isCopperZone && !combinedFill.IsEmpty() )
4125 {
4126 // We don't do this here, though it feels like we should. We collect the
4127 // information for batch processing later on (which is conceptually
4128 // easier to parallelise compared to this function). But there is room
4129 // for improvement by threading more of this work: shapeToPolySet
4130 // accounts for about 40% of the remaining single-threaded time in the
4131 // building process.
4132
4133 // combinedFill.ClearArcs();
4134 // zoneOutline.ClearArcs();
4135 // combinedFill.BooleanIntersection( zoneOutline );
4136 // zone->SetFilledPolysList( layer, combinedFill );
4137
4138 zone->SetIsFilled( true );
4139 zone->SetNeedRefill( false );
4140
4141 // Poke these relevant context in here for batch processing later on
4142 aZoneFillHandler.QueuePolygonForZone( *zone, std::move( combinedFill ), layer );
4143 }
4144
4145 return zone;
4146}
4147
4148
4149std::vector<const BLOCK_BASE*> BOARD_BUILDER::getShapeRelatedBlocks( const BLK_0x28_SHAPE& aShape ) const
4150{
4151 // Follow pointer chain: BOUNDARY.TablePtr -> 0x2C TABLE -> Ptr1 -> 0x37 -> m_Ptrs
4152 std::vector<const BLOCK_BASE*> ret;
4153 uint32_t tableKey = aShape.GetTablePtr();
4154
4155 if( tableKey == 0 )
4156 return ret;
4157
4158 const BLK_0x2C_TABLE* tbl = expectBlockByKey<BLK_0x2C_TABLE>( tableKey, 0x2C );
4159
4160 if( !tbl )
4161 return ret;
4162
4163 const BLK_0x37_PTR_ARRAY* ptrArray = expectBlockByKey<BLK_0x37_PTR_ARRAY>( tbl->m_Ptr1, 0x37 );
4164
4165 if( !ptrArray || ptrArray->m_Count == 0 )
4166 return ret;
4167
4168 const size_t count = std::min( std::min( ptrArray->m_Count, ptrArray->m_Capacity ), 100u );
4169 ret.resize( count );
4170
4171 for( size_t i = 0; i < count; i++ )
4172 ret[i] = m_brdDb.GetObjectByKey( ptrArray->m_Ptrs[i] );
4173
4174 return ret;
4175}
4176
4177
4179{
4180 const LL_WALKER textWalker( m_brdDb.m_Header->m_LL_0x03_0x30, m_brdDb );
4181 int textCount = 0;
4182
4183 for( const BLOCK_BASE* block : textWalker )
4184 {
4185 if( block->GetBlockType() != 0x30 )
4186 continue;
4187
4188 const auto& strWrapper =
4189 static_cast<const BLOCK<BLK_0x30_STR_WRAPPER>&>( *block ).GetData();
4190
4191 std::unique_ptr<PCB_TEXT> text = buildPcbText( strWrapper, m_board );
4192
4193 if( !text )
4194 continue;
4195
4196 // If the text is referenced from a group, it's not board-level text,
4197 // and we'll pick up up while iterating the group elsewhere.
4198 if( strWrapper.GetGroupPtr() != 0 )
4199 {
4200 // In a group
4201 continue;
4202 }
4203
4204 wxLogTrace( traceAllegroBuilder, " Board text '%s' on layer %s at (%d, %d)",
4205 text->GetText(), m_board.GetLayerName( text->GetLayer() ),
4206 text->GetPosition().x, text->GetPosition().y );
4207
4208 m_board.Add( text.release(), ADD_MODE::APPEND );
4209 textCount++;
4210 }
4211
4212 wxLogTrace( traceAllegroBuilder, "Created %d board-level text objects", textCount );
4213}
4214
4215
4216template <std::derived_from<BOARD_ITEM> T>
4217void BulkAddToBoard( BOARD& aBoard, std::vector<std::unique_ptr<T>>&& aItems )
4218{
4219 std::vector<BOARD_ITEM*> rawPointers;
4220 rawPointers.reserve( aItems.size() );
4221
4222 for( std::unique_ptr<T>& item : aItems )
4223 {
4224 rawPointers.push_back( item.get() );
4225 aBoard.Add( item.release(), ADD_MODE::BULK_APPEND );
4226 }
4227
4228 aBoard.FinalizeBulkAdd( rawPointers );
4229}
4230
4231
4233{
4234 wxLogTrace( traceAllegroBuilder, "Creating zones from m_LL_Shapes and m_LL_0x24_0x28" );
4235
4236 std::vector<std::unique_ptr<ZONE>> newZones;
4237 std::vector<std::unique_ptr<ZONE>> keepoutZones;
4238
4239 ZONE_FILL_HANDLER zoneFillHandler;
4240
4241 // Walk m_LL_Shapes to find BOUNDARY shapes (zone outlines).
4242 // BOUNDARY shapes use class 0x15 with copper layer subclass indices.
4243 const LL_WALKER shapeWalker( m_brdDb.m_Header->m_LL_Shapes, m_brdDb );
4244
4245 for( const BLOCK_BASE* block : shapeWalker )
4246 {
4247 std::unique_ptr<ZONE> zone;
4248 LAYER_INFO layerInfo{};
4249
4250 switch( block->GetBlockType() )
4251 {
4252 case 0x0e:
4253 {
4254 const BLK_0x0E_RECT& rectData = static_cast<const BLOCK<BLK_0x0E_RECT>&>( *block ).GetData();
4255
4256 if( !layerIsZone( rectData.m_Layer ) )
4257 continue;
4258
4259 zone = buildZone( *block, {}, zoneFillHandler );
4260 layerInfo = rectData.m_Layer;
4261 break;
4262 }
4263 case 0x24:
4264 {
4265 const BLK_0x24_RECT& rectData = static_cast<const BLOCK<BLK_0x24_RECT>&>( *block ).GetData();
4266
4267 if( !layerIsZone( rectData.m_Layer ) )
4268 continue;
4269
4270 zone = buildZone( *block, {}, zoneFillHandler );
4271 layerInfo = rectData.m_Layer;
4272 break;
4273 }
4274 case 0x28:
4275 {
4276 const BLK_0x28_SHAPE& shapeData = static_cast<const BLOCK<BLK_0x28_SHAPE>&>( *block ).GetData();
4277
4278 if( !layerIsZone( shapeData.m_Layer ) )
4279 continue;
4280
4281 zone = buildZone( *block, getShapeRelatedBlocks( shapeData ), zoneFillHandler );
4282 layerInfo = shapeData.m_Layer;
4283 break;
4284 }
4285 default:
4286 {
4287 wxLogTrace( traceAllegroBuilder, "Unhandled block type in zone shape walker: %#04x, key: %#010x",
4288 block->GetBlockType(), block->GetKey() );
4289 break;
4290 }
4291 }
4292
4293 if( zone )
4294 {
4295 wxLogTrace( traceAllegroBuilder, " Zone %#010x net=%d layer=%s class=%#04x:%#04x", block->GetKey(),
4296 zone->GetNetCode(), m_board.GetLayerName( zone->GetFirstLayer() ), layerInfo.m_Class,
4297 layerInfo.m_Subclass );
4298
4299 newZones.push_back( std::move( zone ) );
4300 }
4301 }
4302
4303 // Walk m_LL_0x24_0x28 for keepout/in shapes
4304 const LL_WALKER keepoutWalker( m_brdDb.m_Header->m_LL_0x24_0x28, m_brdDb );
4305
4306 for( const BLOCK_BASE* block : keepoutWalker )
4307 {
4308 std::unique_ptr<ZONE> zone;
4309
4310 switch( block->GetBlockType() )
4311 {
4312 case 0x24:
4313 {
4314 const BLK_0x24_RECT& rectData = static_cast<const BLOCK<BLK_0x24_RECT>&>( *block ).GetData();
4315
4316 if( !layerIsZone( rectData.m_Layer ) )
4317 continue;
4318
4319 wxLogTrace( traceAllegroBuilder, " Processing %s rect %#010x", layerInfoDisplayName( rectData.m_Layer ),
4320 rectData.m_Key );
4321
4322 zone = buildZone( *block, {}, zoneFillHandler );
4323 break;
4324 }
4325 case 0x28:
4326 {
4327 const BLK_0x28_SHAPE& shapeData = static_cast<const BLOCK<BLK_0x28_SHAPE>&>( *block ).GetData();
4328
4329 if( !layerIsZone( shapeData.m_Layer ) )
4330 continue;
4331
4332 wxLogTrace( traceAllegroBuilder, " Processing %s shape %#010x", layerInfoDisplayName( shapeData.m_Layer ),
4333 shapeData.m_Key );
4334
4335 zone = buildZone( *block, {}, zoneFillHandler );
4336 break;
4337 }
4338 default:
4339 break;
4340 }
4341
4342 if( zone )
4343 {
4344 newZones.push_back( std::move( zone ) );
4345 }
4346 }
4347
4348 // Deal with all the collected zone fill polygons now, all at once
4349 zoneFillHandler.ProcessPolygons( true );
4350
4351 int originalCount = newZones.size();
4352
4353 // Merge zones with identical polygons and same net into multi-layer zones.
4354 // Allegro often defines the same zone outline on multiple copper layers (e.g.
4355 // a ground pour spanning all layers). KiCad represents this as a single zone
4356 // with multiple fill layers.
4357 //
4358 // Rule areas (keepouts) can also merge.
4359 std::vector<std::unique_ptr<ZONE>> mergedZones = MergeZonesWithSameOutline( std::move( newZones ) );
4360 int mergedCount = mergedZones.size();
4361
4362 int keepoutCount = 0;
4363 int boundaryCount = 0;
4364
4365 for( const std::unique_ptr<ZONE>& zone : mergedZones )
4366 {
4367 if( zone->GetIsRuleArea() )
4368 keepoutCount++;
4369 else
4370 boundaryCount++;
4371 }
4372
4373 BulkAddToBoard( m_board, std::move( mergedZones ) );
4374
4375 wxLogTrace( traceAllegroBuilder, "Created %d zone outlines and %d keepout areas (%d merged away), ", boundaryCount,
4376 keepoutCount, originalCount - mergedCount );
4377}
4378
4379
4381{
4382 wxLogTrace( traceAllegroBuilder, "Creating tables from m_LL_0x2C" );
4383
4384 const LL_WALKER tableWalker( m_brdDb.m_Header->m_LL_0x2C, m_brdDb );
4385 for( const BLOCK_BASE* block : tableWalker )
4386 {
4387 if( block->GetBlockType() != 0x2C )
4388 continue;
4389
4390 const BLK_0x2C_TABLE& tableData = static_cast<const BLOCK<BLK_0x2C_TABLE>&>( *block ).GetData();
4391
4393 {
4394 // 0x2c tables can have lots of subtypes. Only 0x110 seems useful to iterate in this way for now.
4395 continue;
4396 }
4397
4398 const wxString& tableName = m_brdDb.GetString( tableData.m_StringPtr );
4399
4400 std::vector<std::unique_ptr<BOARD_ITEM>> newItems;
4401
4402 LL_WALKER keyTableWalker{ tableData.m_Ptr1, block->GetKey(), m_brdDb };
4403
4404 for( const BLOCK_BASE* keyTable : keyTableWalker )
4405 {
4406 wxLogTrace( traceAllegroBuilder, " Table '%s' (key %#010x, table block key %#010x)", tableName,
4407 tableData.m_Key, tableData.m_Ptr1 );
4408
4409 if( !keyTable )
4410 {
4411 wxLogTrace( traceAllegroBuilder, " Key table pointer %#010x is invalid", tableData.m_Ptr1 );
4412 continue;
4413 }
4414
4415 switch( keyTable->GetBlockType() )
4416 {
4417 case 0x37:
4418 {
4419 const BLK_0x37_PTR_ARRAY& ptrArray =
4420 static_cast<const BLOCK<BLK_0x37_PTR_ARRAY>&>( *keyTable ).GetData();
4421
4422 uint32_t count = std::min( ptrArray.m_Count, static_cast<uint32_t>( ptrArray.m_Ptrs.size() ) );
4423
4424 wxLogTrace( traceAllegroBuilder, " Pointer array with %zu entries", static_cast<size_t>( count ) );
4425
4426 for( uint32_t ptrIndex = 0; ptrIndex < count; ptrIndex++ )
4427 {
4428 uint32_t ptrKey = ptrArray.m_Ptrs[ptrIndex];
4429
4430 if( ptrKey == 0 )
4431 continue;
4432
4433 const BLOCK_BASE* entryBlock = m_brdDb.GetObjectByKey( ptrKey );
4434
4435 if( !entryBlock )
4436 {
4437 wxLogTrace( traceAllegroBuilder, " Entry pointer %#010x is invalid", ptrKey );
4438 continue;
4439 }
4440
4441 for( std::unique_ptr<BOARD_ITEM>& newItem : buildGraphicItems( *entryBlock, m_board ) )
4442 {
4443 newItems.push_back( std::move( newItem ) );
4444 }
4445 }
4446
4447 break;
4448 }
4449 case 0x3c:
4450 {
4451 const BLK_0x3C_KEY_LIST& keyList = static_cast<const BLOCK<BLK_0x3C_KEY_LIST>&>( *keyTable ).GetData();
4452
4453 wxLogTrace( traceAllegroBuilder, " Key list with %zu entries",
4454 static_cast<size_t>( keyList.m_NumEntries ) );
4455 break;
4456 }
4457 default:
4458 {
4459 wxLogTrace( traceAllegroBuilder, " Table has unhandled key table type %#04x",
4460 keyTable->GetBlockType() );
4461 break;
4462 }
4463 }
4464 }
4465
4466 if( newItems.size() > 0 )
4467 {
4468 wxLogTrace( traceAllegroBuilder, " Creating group '%s' with %zu items", tableName, newItems.size() );
4469
4470 std::unique_ptr<PCB_GROUP> group = std::make_unique<PCB_GROUP>( &m_board );
4471 group->SetName( tableName );
4472
4473 for( const auto& item : newItems )
4474 group->AddItem( item.get() );
4475
4476 newItems.push_back( std::move( group ) );
4477
4478 BulkAddToBoard( m_board, std::move( newItems ) );
4479 }
4480 }
4481}
4482
4483
4485{
4486 if( m_zoneFillShapes.empty() )
4487 return;
4488
4489 PROF_TIMER fillTimer;
4490
4491 wxLogTrace( traceAllegroBuilder, "Applying zone fill polygons from %zu collected fills",
4492 m_zoneFillShapes.size() );
4493
4494
4495 // Unmatched ETCH shapes are either standalone copper polygons or dynamic copper
4496 // (teardrops/fillets). On V172+ boards, m_Unknown2 bit 12 (0x1000) marks auto-generated
4497 // dynamic copper that maps to KiCad teardrop zones. Shapes without this flag are genuine
4498 // standalone copper imported as filled PCB_SHAPE.
4499 int copperShapeCount = 0;
4500 int teardropCount = 0;
4501
4502 for( const auto& [fillKey, fill] : m_zoneFillShapes )
4503 {
4504 if( m_usedZoneFillShapes.contains( fillKey ) )
4505 continue;
4506
4507 SHAPE_POLY_SET polySet = shapeToPolySet( *fill.shape );
4508 polySet.Simplify();
4509
4510 for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
4511 {
4512 SHAPE_POLY_SET fractured( poly );
4513 fractured.Fracture( /* aSimplify */ false );
4514
4515 const bool isDynCopperShape = ( fill.shape->m_Unknown2.value_or( 0 ) & 0x1000 ) != 0;
4516
4517 if( isDynCopperShape )
4518 {
4519 auto zone = std::make_unique<ZONE>( &m_board );
4520
4521 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_VIAPAD );
4522 zone->SetLayer( fill.layer );
4523 zone->SetNetCode( fill.netCode );
4524 zone->SetLocalClearance( 0 );
4525 zone->SetPadConnection( ZONE_CONNECTION::FULL );
4526 zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::NEVER );
4527 zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::INVISIBLE_BORDER );
4528
4529 for( const SHAPE_LINE_CHAIN& chain : poly )
4530 zone->AddPolygon( chain );
4531
4532 zone->SetFilledPolysList( fill.layer, fractured );
4533 zone->SetIsFilled( true );
4534 zone->SetNeedRefill( false );
4535 zone->CalculateFilledArea();
4536
4537 m_board.Add( zone.release(), ADD_MODE::APPEND );
4538 teardropCount++;
4539 }
4540 else
4541 {
4542 auto shape = std::make_unique<PCB_SHAPE>( &m_board, SHAPE_T::POLY );
4543 shape->SetPolyShape( fractured );
4544 shape->SetFilled( true );
4545 shape->SetLayer( fill.layer );
4546 shape->SetNetCode( fill.netCode );
4547 shape->SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
4548
4549 m_board.Add( shape.release(), ADD_MODE::APPEND );
4550 copperShapeCount++;
4551 }
4552 }
4553 }
4554
4555 wxLogTrace( traceAllegroPerf, wxT( " applyZoneFills unmatched loop: %.3f ms (%d shapes, %d teardrops)" ), //format:allow
4556 fillTimer.msecs( true ), copperShapeCount, teardropCount );
4557}
4558
4559
4561{
4562 std::unordered_map<int, std::vector<ZONE*>> teardropsByNet;
4563
4564 for( ZONE* zone : m_board.Zones() )
4565 {
4566 if( zone->IsTeardropArea() )
4567 teardropsByNet[zone->GetNetCode()].push_back( zone );
4568 }
4569
4570 if( teardropsByNet.empty() )
4571 return;
4572
4573 int padCount = 0;
4574 int viaCount = 0;
4575
4576 for( FOOTPRINT* fp : m_board.Footprints() )
4577 {
4578 for( PAD* pad : fp->Pads() )
4579 {
4580 auto it = teardropsByNet.find( pad->GetNetCode() );
4581
4582 if( it == teardropsByNet.end() )
4583 continue;
4584
4585 for( ZONE* tdZone : it->second )
4586 {
4587 if( !pad->IsOnLayer( tdZone->GetLayer() ) )
4588 continue;
4589
4590 if( tdZone->Outline()->Contains( pad->GetPosition() ) )
4591 {
4592 pad->SetTeardropsEnabled( true );
4593 padCount++;
4594 break;
4595 }
4596 }
4597 }
4598 }
4599
4600 for( PCB_TRACK* track : m_board.Tracks() )
4601 {
4602 if( track->Type() != PCB_VIA_T )
4603 continue;
4604
4605 PCB_VIA* via = static_cast<PCB_VIA*>( track );
4606 auto it = teardropsByNet.find( via->GetNetCode() );
4607
4608 if( it == teardropsByNet.end() )
4609 continue;
4610
4611 for( ZONE* tdZone : it->second )
4612 {
4613 if( !via->IsOnLayer( tdZone->GetLayer() ) )
4614 continue;
4615
4616 if( tdZone->Outline()->Contains( via->GetPosition() ) )
4617 {
4618 via->SetTeardropsEnabled( true );
4619 viaCount++;
4620 break;
4621 }
4622 }
4623 }
4624
4625 wxLogTrace( traceAllegroBuilder, "Enabled teardrops on %d pads and %d vias", padCount, viaCount );
4626}
4627
4628
4630{
4631 wxLogTrace( traceAllegroBuilder, "Starting BuildBoard() - Phase 2 of Allegro import" );
4632 wxLogTrace( traceAllegroBuilder, " Format version: %d (V172+ = %s)",
4633 static_cast<int>( m_brdDb.m_FmtVer ),
4634 ( m_brdDb.m_FmtVer >= FMT_VER::V_172 ) ? "yes" : "no" );
4635 wxLogTrace( traceAllegroBuilder, " Allegro version string: %.60s",
4636 m_brdDb.m_Header->m_AllegroVersion.data() );
4637
4638 if( m_progressReporter )
4639 {
4640 m_progressReporter->AddPhases( 4 );
4641 m_progressReporter->AdvancePhase( _( "Constructing caches" ) );
4642 m_progressReporter->KeepRefreshing();
4643 }
4644
4645 PROF_TIMER buildTimer;
4646
4647 wxLogTrace( traceAllegroBuilder, "Caching font definitions and setting up layers" );
4648 cacheFontDefs();
4649 wxLogTrace( traceAllegroPerf, wxT( " cacheFontDefs: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4650
4651 setupLayers();
4652 wxLogTrace( traceAllegroPerf, wxT( " setupLayers: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4653
4654 if( m_progressReporter )
4655 {
4656 m_progressReporter->AdvancePhase( _( "Creating nets" ) );
4657 m_progressReporter->KeepRefreshing();
4658 }
4659
4660 createNets();
4661 wxLogTrace( traceAllegroPerf, wxT( " createNets: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4662
4663 if( m_progressReporter )
4664 {
4665 m_progressReporter->AdvancePhase( _( "Creating tracks" ) );
4666 m_progressReporter->KeepRefreshing();
4667 }
4668
4669 createTracks();
4670 wxLogTrace( traceAllegroPerf, wxT( " createTracks: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4671
4672 if( m_progressReporter )
4673 m_progressReporter->KeepRefreshing();
4674
4676 wxLogTrace( traceAllegroPerf, wxT( " createBoardShapes: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4677
4679 wxLogTrace( traceAllegroPerf, wxT( " createBoardText: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4680
4681 createZones();
4682 wxLogTrace( traceAllegroPerf, wxT( " createZones: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4683
4684 createTables();
4685 wxLogTrace( traceAllegroPerf, wxT( " createTables: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4686
4687 if( m_progressReporter )
4688 m_progressReporter->KeepRefreshing();
4689
4691 wxLogTrace( traceAllegroPerf, wxT( " applyZoneFills: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4692
4694 wxLogTrace( traceAllegroPerf, wxT( " applyConstraintSets: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4695
4697 wxLogTrace( traceAllegroPerf, wxT( " applyNetConstraints: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4698
4700 wxLogTrace( traceAllegroPerf, wxT( " applyMatchGroups: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4701
4702 if( m_progressReporter )
4703 {
4704 m_progressReporter->AdvancePhase( _( "Converting footprints" ) );
4705 m_progressReporter->KeepRefreshing();
4706 }
4707
4708 const LL_WALKER fpWalker( m_brdDb.m_Header->m_LL_0x2B, m_brdDb );
4709 std::vector<BOARD_ITEM*> bulkAddedItems;
4710
4711 THROTTLE refreshThrottle( std::chrono::milliseconds( 100 ) );
4712
4713 for( const BLOCK_BASE* fpContainer : fpWalker )
4714 {
4715 if( fpContainer->GetBlockType() == 0x2B )
4716 {
4717 const BLK_0x2B_FOOTPRINT_DEF& fpBlock =
4718 static_cast<const BLOCK<BLK_0x2B_FOOTPRINT_DEF>&>( *fpContainer ).GetData();
4719
4720 const LL_WALKER instWalker( fpBlock.m_FirstInstPtr, fpBlock.m_Key, m_brdDb );
4721
4722 for( const BLOCK_BASE* instBlock : instWalker )
4723 {
4724 if( instBlock->GetBlockType() != 0x2D )
4725 {
4726 m_reporter.Report(
4727 wxString::Format( "Unexpected object of type %#04x found in footprint %#010x",
4728 instBlock->GetBlockType(), fpBlock.m_Key ),
4730 }
4731 else
4732 {
4733 const auto& inst =
4734 static_cast<const BLOCK<BLK_0x2D_FOOTPRINT_INST>&>( *instBlock ).GetData();
4735
4736 std::unique_ptr<FOOTPRINT> fp = buildFootprint( inst );
4737
4738 if( fp )
4739 {
4740 bulkAddedItems.push_back( fp.get() );
4741 m_board.Add( fp.release(), ADD_MODE::BULK_APPEND, true );
4742 }
4743 else
4744 {
4745 m_reporter.Report(
4746 wxString::Format( "Failed to construct footprint for 0x2D key %#010x",
4747 inst.m_Key ),
4749 }
4750 }
4751
4752 if( m_progressReporter && refreshThrottle.Ready() )
4753 m_progressReporter->KeepRefreshing();
4754 }
4755 }
4756 }
4757
4758 wxLogTrace( traceAllegroPerf, wxT( " convertFootprints (%zu footprints): %.3f ms" ), //format:allow
4759 bulkAddedItems.size(), buildTimer.msecs( true ) );
4760
4761 if( !bulkAddedItems.empty() )
4762 m_board.FinalizeBulkAdd( bulkAddedItems );
4763
4764 wxLogTrace( traceAllegroPerf, wxT( " FinalizeBulkAdd: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4765 wxLogTrace( traceAllegroBuilder, "Converted %zu footprints", bulkAddedItems.size() );
4766
4768 wxLogTrace( traceAllegroPerf, wxT( " enablePadTeardrops: %.3f ms" ), buildTimer.msecs( true ) ); //format:allow
4769 wxLogTrace( traceAllegroPerf, wxT( " Phase 2 total: %.3f ms" ), buildTimer.msecs() ); //format:allow
4770
4771 wxLogTrace( traceAllegroBuilder, "Board construction completed successfully" );
4772 return true;
4773}
const char * name
static std::unordered_set< LAYER_INFO > ScanForLayers(const BRD_DB &aDb)
Look through some lists for a list of layers used.
static uint32_t GetPrimaryNext(const BLOCK_BASE &aBlock)
Gets the next block in the linked list.
static std::optional< LAYER_INFO > tryLayerFromBlock(const BLOCK_BASE &aBlock)
Some blocks report layer info - if they do, return it else std::nullopt.
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...
static bool layerIsZone(const LAYER_INFO &aLayerInfo)
Some layers map to KiCad rule areas (zones) - for example a package keepout on ALL maps to a rule are...
static EDA_ANGLE fromMillidegrees(uint32_t aMilliDegrees)
const BLK_T & BlockDataAs(const BLOCK_BASE &aBlock)
#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.
void BulkAddToBoard(BOARD &aBoard, std::vector< std::unique_ptr< T > > &&aItems)
static uint32_t PadGetNextInFootprint(const BLOCK_BASE &aBlock)
"Get Next" function for the pad list in a footprint's 0x32 list.
LAYER_INFO expectLayerFromBlock(const BLOCK_BASE &aBlock)
Get a layer from a block that has layer info.
static int clampForScale(double aValue)
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
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
SHAPE_POLY_SET shapeToPolySet(const BLK_0x28_SHAPE &aShape) 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.
std::vector< const BLOCK_BASE * > getShapeRelatedBlocks(const BLK_0x28_SHAPE &aShape) const
Get blocks that are related to the BOUNDARY shape, i.e.
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.
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::vector< std::unique_ptr< BOARD_ITEM > > buildDrillMarker(const BLK_0x0C_PIN_DEF &aPinDef, BOARD_ITEM_CONTAINER &aParent)
Build a drill marker from a 0x0C PIN_DEF block.
std::unique_ptr< PCB_SHAPE > buildRect(const BLK_0x24_RECT &aRect, BOARD_ITEM_CONTAINER &aParent)
Build a rectangular shape from a 0x24 RECT block.
std::vector< std::unique_ptr< PCB_SHAPE > > buildPolygonShapes(const BLK_0x28_SHAPE &aShape, BOARD_ITEM_CONTAINER &aParent)
Build graphics from an 0x28 SHAPE, with separate items per segment/arc.
std::unique_ptr< PCB_SHAPE > buildArc(const BLK_0x01_ARC &aArc, const LAYER_INFO &aLayerInfo, PCB_LAYER_ID aLayer, BOARD_ITEM_CONTAINER &aParent)
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::unique_ptr< ZONE > buildZone(const BLOCK_BASE &aBoundaryBlock, const std::vector< const BLOCK_BASE * > &aRelatedBlocks, ZONE_FILL_HANDLER &aZoneFillHandler)
Build a ZONE from an 0x0E, 0x24 or 0x28 block.
SHAPE_LINE_CHAIN buildOutline(const BLK_0x0E_RECT &aRect) const
std::unique_ptr< PCB_SHAPE > buildLineSegment(const BLK_0x15_16_17_SEGMENT &aSegment, const LAYER_INFO &aLayerInfo, PCB_LAYER_ID aLayer, BOARD_ITEM_CONTAINER &aParent)
Build a single line segment.
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, ZoneFillEntry > m_zoneFillShapes
SHAPE_POLY_SET tryBuildZoneShape(const BLOCK_BASE &aBlock)
Try to build a zone shape for the given block, with holes.
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::vector< std::unique_ptr< BOARD_ITEM > > buildGraphicItems(const BLOCK_BASE &aBlock, BOARD_ITEM_CONTAINER &aParent)
Build a list of graphic items, e.g.
std::unique_ptr< BOARD_ITEM > buildVia(const BLK_0x33_VIA &aBlock, 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::unordered_set< uint32_t > m_usedZoneFillShapes
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:949
std::unique_ptr< FILE_HEADER > m_Header
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...
LSET GetRuleAreaLayers(const LAYER_INFO &aLayerInfo)
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.
bool IsLayerMapped(PCB_LAYER_ID aLayerId) const
Return whether this layer ID is something we mapped to, or the catch-all unmapped layer.
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.
PCB_LAYER_ID MapCustomLayer(const LAYER_INFO &aLayerInfo, const wxString &aLayerName)
Record a specific class:subclass layer as mapping to some KiCad user layer, with a given name.
const LAYER_MAPPING_HANDLER & m_layerMappingHandler
Priority task dispatcher for zone fills - we want to do the biggest ones first.
Filled zones have their own outline and the fill itself comes from a bunch of "related" spaces.
void ProcessPolygons(bool aSimplify)
Process the polygons in a thread pool for more fans, more faster.
void QueuePolygonForZone(ZONE &aZone, SHAPE_POLY_SET aFilledArea, PCB_LAYER_ID aLayer)
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
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1223
void FinalizeBulkAdd(std::vector< BOARD_ITEM * > &aNewItems)
Must be used if Add() is used using a BULK_x ADD_MODE to generate a change event for listeners.
Definition board.cpp:1353
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr size_type GetHeight() const
Definition box2.h:215
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 const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:608
static const LSET & AllLayersMask()
Definition lset.cpp:641
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 UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition pad.cpp:390
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:376
void Execute(ContainerT &aItems)
Call this to execute the task on all items in aItems, using the thread pool and dispatching the tasks...
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
Definition seg.h:42
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 Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
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.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
int TotalVertices() const
Return total number of vertices stored in the set.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
int AddPolygon(const POLYGON &apolygon)
Adds a polygon to the set.
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
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 POLYGON & CPolygon(int aIndex) const
const std::vector< POLYGON > & CPolygons() const
Simple container to manage line stroke parameters.
Rate-limiter that fires at most once per interval.
Definition throttle.h:35
bool Ready()
Definition throttle.h:48
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
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:719
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 SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition zone.h:634
const int minSize
Push and Shove router track width and via size dialog.
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE FULL_CIRCLE
Definition eda_angle.h:409
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 IsUserLayer(PCB_LAYER_ID aLayerId)
Test whether a layer is a non copper and a non tech layer.
Definition layer_ids.h:759
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:803
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_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ F_Mask
Definition layer_ids.h:97
@ 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.
std::vector< VECTOR2I > MakeRegularPolygonPoints(const VECTOR2I &aCenter, size_t aN, const VECTOR2I &aPt0)
Get the corners of a regular polygon from the centre, one point and the number of sides.
std::vector< SEG > MakeCrossSegments(const VECTOR2I &aCenter, const VECTOR2I &aSize, EDA_ANGLE aAngle)
Create the two segments for a cross.
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ 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
Class to handle a set of BOARD_ITEMs.
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
Utility functions for working with shapes.
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.
Pin definition with shape type, drill character, coordinates, and size.
std::array< int32_t, 2 > m_Size
std::array< int32_t, 2 > m_Coords
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.
uint32_t m_Rotation
Rotation in millidegrees.
std::array< int32_t, 4 > m_Coords
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...
uint8_t m_Flags
Mask of PAD_FLAGS values.
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.
uint32_t m_Rotation
Rotation in millidegrees.
std::array< int32_t, 4 > m_Coords
Polygon shape defined by a linked list of segments starting at m_FirstSegmentPtr (0x15/0x16/0x17 line...
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.
@ SUBTYPE_GRAPHICAL_GROUP
Used for drill charts and x-section charts.
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
Ordered list of block keys.
uint32_t GetKey() const
Definition allegro_db.h:219
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:705
std::optional< int > GetNetMinLineWidth() const
FIELD_LIST m_Fields
Definition allegro_db.h:724
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:915
const NET * m_Net
Definition allegro_db.h:938
This is all the info needed to do the fill of one layer of one zone.
SHAPE_POLY_SET m_CombinedFill
The wider filled area we will chop a piece out of for this layer of this zone.
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
std::vector< std::unique_ptr< ZONE > > MergeZonesWithSameOutline(std::vector< std::unique_ptr< ZONE > > &&aZones)
Merges zones with identical outlines and nets on different layers into single multi-layer zones.
@ FULL
pads are covered by copper
Definition zones.h:51