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