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