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