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