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