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