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