KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_altium_pcb_import.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
24
28
34
35#include <base_units.h>
36#include <compoundfilereader.h>
37
38#include <board.h>
40#include <footprint.h>
42#include <common.h>
43#include <core/utf8.h>
44#include <eda_text.h>
45#include <embedded_files.h>
46#include <ki_exception.h>
47#include <netinfo.h>
48#include <netclass.h>
49#include <pad.h>
50#include <pcb_track.h>
51#include <pcb_shape.h>
52#include <pcb_generator.h>
55#include <project.h>
56#include <pcb_text.h>
59#include <zone.h>
60
61#include <algorithm>
62#include <cmath>
63#include <cstring>
64#include <map>
65#include <set>
66#include <string>
67#include <vector>
68
69
71{
73
75 const CFB::COMPOUND_FILE_ENTRY* aEntry )
76 {
77 aImporter.ParseExtendedPrimitiveInformationData( aFile, aEntry );
78 }
79
80 static std::vector<std::pair<PCB_LAYER_ID, int>> getMaskExpansions( ALTIUM_PCB& aImporter, ALTIUM_RECORD aType,
81 int aPrimitiveIndex, ALTIUM_LAYER aLayer )
82 {
83 return aImporter.HelperGetSolderAndPasteMaskExpansions( aType, aPrimitiveIndex, aLayer );
84 }
85
86 static void convertConnectedArc( ALTIUM_PCB& aImporter, const AARC6& aArc, PCB_LAYER_ID aLayer )
87 {
89 aImporter.ConvertArcs6ToBoardItemOnLayer( aArc, aLayer );
90 }
91
92 static std::vector<const ZONE*> collectRuleAreas( const BOARD& aBoard )
93 {
94 std::vector<const ZONE*> ruleAreas;
95
96 for( const ZONE* zone : aBoard.Zones() )
97 {
98 if( zone->GetIsRuleArea() )
99 ruleAreas.push_back( zone );
100 }
101
102 for( const FOOTPRINT* footprint : aBoard.Footprints() )
103 {
104 for( const ZONE* zone : footprint->Zones() )
105 {
106 if( zone->GetIsRuleArea() )
107 ruleAreas.push_back( zone );
108 }
109 }
110
111 return ruleAreas;
112 }
113
114 static int getNetCode( ALTIUM_PCB& aImporter, const std::vector<int>& aNetcodes, uint16_t aId )
115 {
116 aImporter.m_altiumToKicadNetcodes = aNetcodes;
117 return aImporter.GetNetCode( aId );
118 }
119
121};
122
123
124BOOST_FIXTURE_TEST_SUITE( AltiumPcbImport, ALTIUM_PCB_IMPORT_FIXTURE )
125
126
127// An Altium net id equal to the number of known nets used to index one past the end
128BOOST_AUTO_TEST_CASE( NetCodeRejectsIdPastEnd )
129{
130 BOARD board;
131 LAYER_MAPPING_HANDLER layerMappingHandler;
132 ALTIUM_PCB importer( &board, nullptr, layerMappingHandler );
133
134 const std::vector<int> netcodes = { 1, 2, 3 };
135
136 BOOST_CHECK_EQUAL( getNetCode( importer, netcodes, 2 ), 3 );
137 BOOST_CHECK_THROW( getNetCode( importer, netcodes, 3 ), IO_ERROR );
138 BOOST_CHECK_EQUAL( getNetCode( importer, netcodes, ALTIUM_NET_UNCONNECTED ),
140}
141
142
143BOOST_AUTO_TEST_CASE( DimensionBoldPropertyParsing )
144{
145 std::string properties = "|BOLD=TRUE|TEXTLINEWIDTH=6mil";
146 properties.push_back( '\0' );
147
148 const uint16_t recordHeader = 1;
149 const uint32_t propertySize = static_cast<uint32_t>( properties.size() );
150 const size_t recordSize = sizeof( recordHeader ) + sizeof( propertySize ) + properties.size();
151
152 std::unique_ptr<char[]> content = std::make_unique<char[]>( recordSize );
153
154 std::memcpy( content.get(), &recordHeader, sizeof( recordHeader ) );
155 std::memcpy( content.get() + sizeof( recordHeader ), &propertySize, sizeof( propertySize ) );
156 std::memcpy( content.get() + sizeof( recordHeader ) + sizeof( propertySize ), properties.data(),
157 properties.size() );
158
159 ALTIUM_BINARY_PARSER reader( content, recordSize );
160 ADIMENSION6 dimension( reader );
161
162 BOOST_CHECK( dimension.textbold );
163 BOOST_CHECK_EQUAL( dimension.textlinewidth, pcbIUScale.mmToIU( 0.1524 ) );
164 BOOST_CHECK( !reader.HasParsingError() );
166}
167
168
169// Altium stores no per-component mounting style, so derive it from the pads. Leaving it unset
170// drops every footprint from SMD-only placement files and from ODB++/IPC-2581 mount type
171BOOST_AUTO_TEST_CASE( FootprintMountingStyleDerivedFromPads )
172{
173 std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/HiFive/HiFive1.B01.PcbDoc";
174
175 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
176 BOOST_REQUIRE( board );
177 BOOST_REQUIRE( !board->Footprints().empty() );
178
179 int smd = 0;
180 int throughHole = 0;
181
182 for( FOOTPRINT* footprint : board->Footprints() )
183 {
184 const int expected = footprint->GetLikelyAttribute();
185 const int actual = footprint->GetAttributes() & ( FP_SMD | FP_THROUGH_HOLE );
186
187 // Matching the footprint checker's own heuristic is what keeps DRC from reporting a type
188 // mismatch on every footprint we just imported
189 BOOST_CHECK_MESSAGE( actual == expected,
190 wxString::Format( wxT( "Footprint %s has mounting style %d, expected %d" ),
191 footprint->GetReference(), actual, expected ) );
192
193 if( expected == FP_SMD )
194 smd++;
195 else if( expected == FP_THROUGH_HOLE )
196 throughHole++;
197 }
198
199 // Guard against the loop above passing because nothing was classified at all
200 BOOST_CHECK_GT( smd, 0 );
201 BOOST_CHECK_GT( throughHole, 0 );
202}
203
204
205BOOST_AUTO_TEST_CASE( ArcMaskExpansionUsesArcMetadata )
206{
207 std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/issue24456/Fastino_Ground_Isolator.PcbDoc";
208
209 ALTIUM_PCB_COMPOUND_FILE file( wxString::FromUTF8( dataPath.c_str() ) );
210 const CFB::COMPOUND_FILE_ENTRY* data = file.FindStream( { "ExtendedPrimitiveInformation", "Data" } );
211
212 BOOST_REQUIRE( data );
213
214 BOARD board;
215 LAYER_MAPPING_HANDLER layerMappingHandler;
216 ALTIUM_PCB importer( &board, nullptr, layerMappingHandler );
217
218 parseExtendedPrimitiveInformation( importer, file, data );
219
220 std::vector<std::pair<PCB_LAYER_ID, int>> front =
221 getMaskExpansions( importer, ALTIUM_RECORD::ARC, 6, ALTIUM_LAYER::TOP_LAYER );
222
223 BOOST_REQUIRE_EQUAL( front.size(), 1 );
224 BOOST_CHECK_EQUAL( front[0].first, F_Paste );
225 BOOST_CHECK_EQUAL( front[0].second, pcbIUScale.mmToIU( -1.0 ) );
226
227 std::vector<std::pair<PCB_LAYER_ID, int>> back =
228 getMaskExpansions( importer, ALTIUM_RECORD::ARC, 70, ALTIUM_LAYER::BOTTOM_LAYER );
229
230 BOOST_REQUIRE_EQUAL( back.size(), 1 );
231 BOOST_CHECK_EQUAL( back[0].first, B_Paste );
232 BOOST_CHECK_EQUAL( back[0].second, pcbIUScale.mmToIU( -1.0 ) );
233}
234
235
236BOOST_AUTO_TEST_CASE( ConnectedFullCircleArcPreservesRingGeometry )
237{
238 constexpr uint32_t subrecordSize = 57;
239 constexpr size_t recordSize = 1 + sizeof( subrecordSize ) + subrecordSize;
240 std::unique_ptr<char[]> content = std::make_unique<char[]>( recordSize );
241 size_t offset = 0;
242
243 auto append = [&]( auto aValue )
244 {
245 std::memcpy( content.get() + offset, &aValue, sizeof( aValue ) );
246 offset += sizeof( aValue );
247 };
248
249 append( static_cast<uint8_t>( ALTIUM_RECORD::ARC ) );
250 append( subrecordSize );
251 append( static_cast<uint8_t>( ALTIUM_LAYER::TOP_LAYER ) );
252 append( uint8_t{ 0 } );
253 append( uint8_t{ 0 } );
254 append( uint16_t{ 0 } );
255 append( static_cast<uint16_t>( ALTIUM_POLYGON_NONE ) );
256 append( static_cast<uint16_t>( ALTIUM_COMPONENT_NONE ) );
257 append( uint32_t{ 0 } );
258 append( int32_t{ 0 } );
259 append( int32_t{ 0 } );
260 append( uint32_t{ 100000 } );
261 append( double{ 0.0 } );
262 append( double{ 360.0 } );
263 append( uint32_t{ 10000 } );
264 append( uint16_t{ 0 } );
265 append( uint8_t{ 0 } );
266 append( uint32_t{ 0 } );
267 append( static_cast<uint32_t>( ALTIUM_LAYER::UNKNOWN ) );
268 append( uint8_t{ 0 } );
269
270 BOOST_REQUIRE_EQUAL( offset, recordSize );
271
272 ALTIUM_BINARY_PARSER reader( content, recordSize );
273 AARC6 sourceArc( reader );
274 BOARD board;
275 LAYER_MAPPING_HANDLER layerMappingHandler;
276 ALTIUM_PCB importer( &board, nullptr, layerMappingHandler );
277
278 convertConnectedArc( importer, sourceArc, F_Cu );
279
280 BOOST_REQUIRE_EQUAL( board.Tracks().size(), 2 );
281
282 std::vector<const PCB_ARC*> arcs;
283
284 for( const PCB_TRACK* track : board.Tracks() )
285 {
286 BOOST_REQUIRE_EQUAL( track->Type(), PCB_ARC_T );
287 arcs.push_back( static_cast<const PCB_ARC*>( track ) );
288 }
289
290 for( const PCB_ARC* arc : arcs )
291 {
292 BOOST_CHECK_CLOSE( std::abs( arc->GetAngle().AsDegrees() ), 180.0, 1e-6 );
293 BOOST_CHECK_CLOSE( arc->GetRadius(), pcbIUScale.mmToIU( 0.254 ), 1e-6 );
294 BOOST_CHECK_EQUAL( arc->GetWidth(), pcbIUScale.mmToIU( 0.0254 ) );
295 BOOST_CHECK_EQUAL( arc->GetLayer(), F_Cu );
296 }
297
298 BOOST_CHECK( arcs[0]->GetStart() == arcs[1]->GetEnd() );
299 BOOST_CHECK( arcs[0]->GetEnd() == arcs[1]->GetStart() );
300 BOOST_CHECK( board.Drawings().empty() );
301}
302
303
308BOOST_AUTO_TEST_CASE( BoardLoadNoAssertions )
309{
310 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
311 + "plugins/altium/eDP_adapter_dvt1_source/eDP_adapter_dvt1.PcbDoc";
312
313 // Load the board - should not trigger any assertions
314 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
315
316 BOOST_REQUIRE( board );
317
318 // Basic sanity checks
319 BOOST_CHECK( board->GetNetCount() > 0 );
320 BOOST_CHECK( board->Footprints().size() > 0 );
321}
322
323
324// GetImportedCachedLibraryFootprints() is caller-owns, so Altium must clone rather than alias
325// aliasing would let the reconciler (takes ownership) double-free the board
326BOOST_AUTO_TEST_CASE( CachedLibraryFootprintsAreOwnedCopies )
327{
328 std::string dataPath =
329 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/HiFive/HiFive1.B01.PcbDoc";
330
331 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
332
333 BOOST_REQUIRE( board );
334 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
335
336 std::vector<FOOTPRINT*> cached = m_altiumPlugin.GetImportedCachedLibraryFootprints();
337
338 // adopt ownership so the clones free with the test
339 std::vector<std::unique_ptr<FOOTPRINT>> owned;
340
341 for( FOOTPRINT* fp : cached )
342 owned.emplace_back( fp );
343
344 BOOST_CHECK_EQUAL( cached.size(), board->Footprints().size() );
345
346 std::set<FOOTPRINT*> boardFootprints( board->Footprints().begin(), board->Footprints().end() );
347
348 // no returned footprint may alias a board-owned one
349 for( FOOTPRINT* fp : cached )
350 {
351 BOOST_CHECK_MESSAGE( boardFootprints.count( fp ) == 0,
352 "GetImportedCachedLibraryFootprints returned a board-owned footprint" );
353 }
354}
355
356
362BOOST_AUTO_TEST_CASE( UnusedInternalPlanesNotMapped )
363{
364 std::string dataPath =
365 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/eDP_adapter_dvt1_source/eDP_adapter_dvt1.PcbDoc";
366
367 std::set<wxString> mappedLayerNames;
368
369 m_altiumPlugin.RegisterCallback(
370 [&]( const std::vector<INPUT_LAYER_DESC>& aLayers )
371 {
372 for( const INPUT_LAYER_DESC& layer : aLayers )
373 mappedLayerNames.insert( layer.Name );
374
376 } );
377
378 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
379
380 BOOST_REQUIRE( board );
381 BOOST_CHECK( mappedLayerNames.count( wxS( "Internal Plane 1" ) ) );
382 BOOST_CHECK( mappedLayerNames.count( wxS( "Internal Plane 2" ) ) );
383
384 for( int plane = 3; plane <= 16; ++plane )
385 {
386 wxString name = wxString::Format( wxS( "Internal Plane %d" ), plane );
387 BOOST_CHECK_MESSAGE( !mappedLayerNames.count( name ),
388 wxString::Format( "Unused layer '%s' reached the mapper", name ) );
389 }
390}
391
392
400BOOST_AUTO_TEST_CASE( KeepoutRestrictionsImpliedByLayer )
401{
402 std::string dataPath =
403 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/eDP_adapter_dvt1_source/eDP_adapter_dvt1.PcbDoc";
404
405 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
406
407 BOOST_REQUIRE( board );
408
409 int planeSplits = 0;
410 int keepOutLayer = 0;
411 int inert = 0;
412
413 for( const ZONE* zone : collectRuleAreas( *board ) )
414 {
415 const bool tracks = zone->GetDoNotAllowTracks();
416 const bool vias = zone->GetDoNotAllowVias();
417 const bool pads = zone->GetDoNotAllowPads();
418 const bool fills = zone->GetDoNotAllowZoneFills();
419 const bool footprints = zone->GetDoNotAllowFootprints();
420
421 if( fills && !tracks && !vias && !pads && !footprints )
422 planeSplits++;
423 else if( fills && tracks && vias && pads && !footprints )
424 keepOutLayer++;
425 else if( !fills && !tracks && !vias && !pads && !footprints )
426 inert++;
427 }
428
429 BOOST_CHECK_EQUAL( inert, 0 );
430 BOOST_CHECK_EQUAL( planeSplits, 87 );
431 BOOST_CHECK_EQUAL( keepOutLayer, 8 );
432}
433
434
442BOOST_AUTO_TEST_CASE( ExplicitKeepoutRestrictionMaskIsRead )
443{
444 std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/issue24456/Fastino_Ground_Isolator.PcbDoc";
445
446 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
447
448 BOOST_REQUIRE( board );
449
450 int viasOnly = 0;
451 int everything = 0;
452
453 for( const ZONE* zone : collectRuleAreas( *board ) )
454 {
455 const bool tracks = zone->GetDoNotAllowTracks();
456 const bool vias = zone->GetDoNotAllowVias();
457 const bool pads = zone->GetDoNotAllowPads();
458 const bool fills = zone->GetDoNotAllowZoneFills();
459
460 if( vias && !tracks && !pads && !fills )
461 viasOnly++;
462 else if( vias && tracks && pads && fills )
463 everything++;
464 }
465
466 BOOST_CHECK_EQUAL( everything, 0 );
467 BOOST_CHECK_EQUAL( viasOnly, 2 );
468}
469
470
478BOOST_AUTO_TEST_CASE( NetclassAssignment )
479{
480 // HiFive1.B01.PcbDoc has Altium netclass definitions
481 std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/HiFive/HiFive1.B01.PcbDoc";
482
483 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
484
485 BOOST_REQUIRE( board );
486
487 // Get the net settings which contains pattern assignments
488 std::shared_ptr<NET_SETTINGS> netSettings = board->GetDesignSettings().m_NetSettings;
489
490 BOOST_REQUIRE( netSettings );
491
492 // Check if there are any pattern assignments in the board
493 auto& patternAssignments = netSettings->GetNetclassPatternAssignments();
494
495 // The HiFive board should have netclass definitions - require this for the test to be meaningful
496 BOOST_REQUIRE_MESSAGE( patternAssignments.size() > 0,
497 "Test file must have netclass pattern assignments" );
498
499 // For each net that has a pattern assignment, verify that the NETINFO_ITEM
500 // has a netclass directly assigned (not just through pattern resolution)
501 bool foundAssignedNet = false;
502
503 for( NETINFO_ITEM* net : board->GetNetInfo() )
504 {
505 if( net->GetNetCode() <= 0 )
506 continue;
507
508 // Get the netclass directly from the NETINFO_ITEM
509 NETCLASS* directNetclass = net->GetNetClass();
510
511 // Get the effective netclass from pattern resolution
512 std::shared_ptr<NETCLASS> effectiveNetclass =
513 netSettings->GetEffectiveNetClass( net->GetNetname() );
514
515 // If this net has a non-default effective netclass, the direct assignment
516 // should also be non-default (this is what the fix ensures)
517 if( effectiveNetclass && effectiveNetclass->GetName() != NETCLASS::Default )
518 {
519 BOOST_CHECK_MESSAGE(
520 directNetclass != nullptr,
521 wxString::Format( "Net '%s' should have a direct netclass assignment",
522 net->GetNetname() ) );
523
524 if( directNetclass )
525 {
526 foundAssignedNet = true;
527
528 // The direct netclass should match what effective resolution returns
529 // (or be part of the effective class for multi-netclass scenarios)
530 BOOST_CHECK_MESSAGE(
531 directNetclass->GetName() != NETCLASS::Default,
532 wxString::Format( "Net '%s' should not have default netclass, "
533 "expected effective class or component",
534 net->GetNetname() ) );
535 }
536 }
537 }
538
539 // If there were pattern assignments, we should have found at least one assigned net
540 BOOST_CHECK_MESSAGE( foundAssignedNet,
541 "At least one net should have a non-default netclass assigned" );
542}
543
544
550BOOST_AUTO_TEST_CASE( UnnamedNetPlaceholderAvoidsCollisions )
551{
552 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
553 int counter = 0;
554
555 wxString first = AltiumUnnamedNetName( *board, counter );
556 BOOST_CHECK_EQUAL( first, wxT( "__ALTIUM_UNNAMED_NET_1" ) );
557
558 board->Add( new NETINFO_ITEM( board.get(), first, -1 ), ADD_MODE::APPEND );
559 board->Add( new NETINFO_ITEM( board.get(), wxT( "__ALTIUM_UNNAMED_NET_2" ), -1 ),
561
562 wxString second = AltiumUnnamedNetName( *board, counter );
563 BOOST_CHECK_EQUAL( second, wxT( "__ALTIUM_UNNAMED_NET_3" ) );
564
565 board->Add( new NETINFO_ITEM( board.get(), second, -1 ), ADD_MODE::APPEND );
566
567 // Three distinct nets plus the unconnected net; a collision would have aliased one away
568 BOOST_CHECK_EQUAL( board->GetNetInfo().GetNetCount(), 4 );
569}
570
571
578BOOST_AUTO_TEST_CASE( RulesPopulateNetclasses )
579{
580 std::string dataPath =
581 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/issue24456/Fastino_Ground_Isolator.PcbDoc";
582
583 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
584
585 std::shared_ptr<NET_SETTINGS> netSettings = board->GetDesignSettings().m_NetSettings;
586 std::shared_ptr<NETCLASS> defaultNetclass = netSettings->GetDefaultNetclass();
587
588 BOOST_CHECK_EQUAL( defaultNetclass->GetTrackWidth(), pcbIUScale.mmToIU( 0.3 ) );
589 BOOST_CHECK_EQUAL( defaultNetclass->GetViaDiameter(), pcbIUScale.mmToIU( 0.6 ) );
590 BOOST_CHECK_EQUAL( defaultNetclass->GetViaDrill(), pcbIUScale.mmToIU( 0.3 ) );
591
592 auto it = netSettings->GetNetclasses().find( wxT( "50R" ) );
593
594 BOOST_REQUIRE( it != netSettings->GetNetclasses().end() );
595 BOOST_CHECK_EQUAL( it->second->GetTrackWidth(), pcbIUScale.mmToIU( 0.35 ) );
596
597 BOOST_CHECK( board->m_LegacyDesignSettingsLoaded );
598}
599
600
607BOOST_AUTO_TEST_CASE( UnresolvedNetclassRulesAreReported )
608{
609 auto messagesFor =
610 [this]( const std::string& aRelativePath )
611 {
613 m_altiumPlugin.SetReporter( &reporter );
614
615 std::unique_ptr<BOARD> board =
616 m_altiumPlugin.LoadBoard( KI_TEST::GetPcbnewTestDataDir() + aRelativePath, nullptr );
617 m_altiumPlugin.SetReporter( nullptr );
618
619 std::vector<wxString> hits;
620
621 for( const KI_TEST::CAPTURING_REPORTER::MESSAGE& message : reporter.GetMessages() )
622 {
623 if( message.text.Contains( wxS( "does not define" ) ) )
624 hits.push_back( message.text );
625 }
626
627 return hits;
628 };
629
630 std::vector<wxString> hiFive = messagesFor( "plugins/altium/HiFive/HiFive1.B01.PcbDoc" );
631
632 BOOST_REQUIRE_EQUAL( hiFive.size(), 1 );
633 BOOST_CHECK( hiFive[0].Contains( wxS( "'PWR'" ) ) );
634
635 // A board whose rules all resolve must not be told anything
636 BOOST_CHECK_EQUAL( messagesFor( "plugins/altium/issue24456/Fastino_Ground_Isolator.PcbDoc" ).size(), 0 );
637}
638
639
645BOOST_AUTO_TEST_CASE( NetclassRulePriority )
646{
647 auto makeWidthRule = []( int aPriority, bool aEnabled, const wxString& aScope2, int aWidth )
648 {
649 ARULE6 rule;
651 rule.priority = aPriority;
652 rule.enabled = aEnabled;
653 rule.scope1expr = wxT( "InNetClass('SIG')" );
654 rule.scope2expr = aScope2;
655 rule.preferredWidth = aWidth;
656 return rule;
657 };
658
659 BOARD board;
660 NET_SETTINGS& netSettings = *board.GetDesignSettings().m_NetSettings;
661 std::shared_ptr<NETCLASS> sig = std::make_shared<NETCLASS>( wxT( "SIG" ), false );
662
663 netSettings.SetNetclass( wxT( "SIG" ), sig );
664
665 // Sorted by priority ascending, matching the order produced by ParseRules6Data
666 std::map<ALTIUM_RULE_KIND, std::vector<ARULE6>> rules;
667 rules[ALTIUM_RULE_KIND::WIDTH] = {
668 makeWidthRule( 1, true, wxT( "All" ), 100 ),
669 makeWidthRule( 2, true, wxT( "All" ), 200 ),
670 };
671
672 ApplyAltiumNetclassRules( rules, netSettings );
673 BOOST_CHECK_EQUAL( sig->GetTrackWidth(), 100 );
674
675 // A disabled priority 1 rule leaves the next one in charge
676 sig->SetTrackWidth( std::optional<int>() );
677 rules[ALTIUM_RULE_KIND::WIDTH][0].enabled = false;
678
679 ApplyAltiumNetclassRules( rules, netSettings );
680 BOOST_CHECK_EQUAL( sig->GetTrackWidth(), 200 );
681
682 // A restricted second scope means the rule is not simply "this netclass"
683 sig->SetTrackWidth( std::optional<int>() );
684 rules[ALTIUM_RULE_KIND::WIDTH][1].scope2expr = wxT( "IsRegion" );
685
686 ApplyAltiumNetclassRules( rules, netSettings );
687 BOOST_CHECK( !sig->HasTrackWidth() );
688}
689
690
697 const std::string& aRelativePath )
698{
699 std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + aRelativePath;
700
701 std::unique_ptr<BOARD> board = aPlugin.LoadBoard( dataPath, nullptr );
702
703 BOOST_REQUIRE( board );
704
705 int fillZoneCount = 0;
706 int fillZonesWithClearance = 0;
707
708 for( ZONE* zone : board->Zones() )
709 {
710 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() || zone->IsTeardropArea() )
711 continue;
712
713 fillZoneCount++;
714
715 if( zone->GetLocalClearance().has_value() && zone->GetLocalClearance().value() > 0 )
716 fillZonesWithClearance++;
717 }
718
719 BOOST_CHECK_GT( fillZoneCount, 0 );
720
721 BOOST_CHECK_MESSAGE( fillZonesWithClearance == fillZoneCount,
722 wxString::Format( "%s: %d/%d copper fill zones have clearance set",
723 aRelativePath, fillZonesWithClearance,
724 fillZoneCount ) );
725}
726
727
728BOOST_AUTO_TEST_CASE( ZoneClearances_eDP )
729{
731 m_altiumPlugin, "plugins/altium/eDP_adapter_dvt1_source/eDP_adapter_dvt1.PcbDoc" );
732}
733
734
735BOOST_AUTO_TEST_CASE( ZoneClearances_HiFive )
736{
738 "plugins/altium/HiFive/HiFive1.B01.PcbDoc" );
739}
740
741
749BOOST_AUTO_TEST_CASE( ScopeExprMatchesPolygon )
750{
751 // Positive matches: expressions that reference polygons
752 BOOST_CHECK( altiumScopeExprMatchesPolygon( wxT( "InPolygon" ) ) );
753 BOOST_CHECK( altiumScopeExprMatchesPolygon( wxT( "InPoly" ) ) );
754 BOOST_CHECK( altiumScopeExprMatchesPolygon( wxT( "IsPolygon" ) ) );
755 BOOST_CHECK( altiumScopeExprMatchesPolygon( wxT( "IsPoly" ) ) );
756
757 // Case insensitivity
758 BOOST_CHECK( altiumScopeExprMatchesPolygon( wxT( "inpolygon" ) ) );
759 BOOST_CHECK( altiumScopeExprMatchesPolygon( wxT( "INPOLYGON" ) ) );
760 BOOST_CHECK( altiumScopeExprMatchesPolygon( wxT( "inPOLY" ) ) );
761
762 // Contained within longer expressions
763 BOOST_CHECK( altiumScopeExprMatchesPolygon( wxT( "InPolygon And InNet('GND')" ) ) );
764 BOOST_CHECK( altiumScopeExprMatchesPolygon( wxT( "(InPoly) Or IsVia" ) ) );
765
766 // Negative matches: expressions that don't reference polygons
767 BOOST_CHECK( !altiumScopeExprMatchesPolygon( wxT( "All" ) ) );
768 BOOST_CHECK( !altiumScopeExprMatchesPolygon( wxT( "IsVia" ) ) );
769 BOOST_CHECK( !altiumScopeExprMatchesPolygon( wxT( "IsTrack" ) ) );
770 BOOST_CHECK( !altiumScopeExprMatchesPolygon( wxT( "InNet('GND')" ) ) );
771 BOOST_CHECK( !altiumScopeExprMatchesPolygon( wxT( "InComponent('U1')" ) ) );
772 BOOST_CHECK( !altiumScopeExprMatchesPolygon( wxT( "" ) ) );
773}
774
775
781BOOST_AUTO_TEST_CASE( SelectAltiumPolygonRule_PriorityOrder )
782{
783 auto makeRule = []( int aPriority, const wxString& aScope1, const wxString& aScope2,
784 int aClearance )
785 {
786 ARULE6 rule;
787 rule.priority = aPriority;
788 rule.scope1expr = aScope1;
789 rule.scope2expr = aScope2;
790 rule.clearanceGap = aClearance;
791 return rule;
792 };
793
794 // Sorted by priority ascending, matching the order produced by ParseRules6Data.
795 std::vector<ARULE6> rules = {
796 makeRule( 1, wxT( "InPolygon And InNet('GND')" ), wxT( "All" ), 100 ),
797 makeRule( 2, wxT( "InPolygon" ), wxT( "All" ), 200 ),
798 makeRule( 3, wxT( "All" ), wxT( "All" ), 300 ),
799 makeRule( 4, wxT( "All" ), wxT( "All" ), 400 ),
800 };
801
802 const ARULE6* selected = selectAltiumPolygonRule( rules );
803 BOOST_REQUIRE( selected != nullptr );
804 BOOST_CHECK_EQUAL( selected->priority, 1 );
805 BOOST_CHECK_EQUAL( selected->clearanceGap, 100 );
806
807 rules.erase( rules.begin() );
808 selected = selectAltiumPolygonRule( rules );
809 BOOST_REQUIRE( selected != nullptr );
810 BOOST_CHECK_EQUAL( selected->priority, 2 );
811 BOOST_CHECK_EQUAL( selected->clearanceGap, 200 );
812
813 rules.erase( rules.begin() );
814 BOOST_CHECK( selectAltiumPolygonRule( rules ) == nullptr );
815
816 BOOST_CHECK( selectAltiumPolygonRule( {} ) == nullptr );
817
818 // A rule Altium ignores must not beat the one it actually applies
819 rules = {
820 makeRule( 1, wxT( "InPolygon And InNet('GND')" ), wxT( "All" ), 100 ),
821 makeRule( 2, wxT( "InPolygon" ), wxT( "All" ), 200 ),
822 };
823 rules[0].enabled = false;
824
825 selected = selectAltiumPolygonRule( rules );
826 BOOST_REQUIRE( selected != nullptr );
827 BOOST_CHECK_EQUAL( selected->clearanceGap, 200 );
828}
829
830
841BOOST_AUTO_TEST_CASE( Via_HoleReferencedMaskTenting )
842{
843 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
844 + "plugins/altium/issue24456/Fastino_Ground_Isolator.PcbDoc";
845
846 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
847
848 BOOST_REQUIRE( board );
849
850 int viaCount = 0;
851 int frontExposed = 0;
852 int backExposed = 0;
853
854 for( PCB_TRACK* track : board->Tracks() )
855 {
856 if( track->Type() != PCB_VIA_T )
857 continue;
858
859 const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
860 viaCount++;
861
862 if( via->GetFrontTentingMode() != TENTING_MODE::TENTED )
863 frontExposed++;
864
865 if( via->GetBackTentingMode() != TENTING_MODE::TENTED )
866 backExposed++;
867 }
868
869 BOOST_REQUIRE_GT( viaCount, 0 );
870
871 // Every via on this board carries a hole-referenced mask opening narrower than its land, so the
872 // importer must tent both sides of all of them.
873 BOOST_CHECK_MESSAGE( frontExposed == 0,
874 wxString::Format( "%d of %d vias left front-exposed despite a "
875 "hole-referenced mask",
876 frontExposed, viaCount ) );
877 BOOST_CHECK_MESSAGE( backExposed == 0,
878 wxString::Format( "%d of %d vias left back-exposed despite a "
879 "hole-referenced mask",
880 backExposed, viaCount ) );
881
882 // Guard the tenting heuristic's boundary cases directly. A wide hole-referenced opening that
883 // clears the land must NOT tent, a land-referenced via must NOT be silently tented, and an
884 // explicit Altium tent flag must always tent regardless of expansion mode.
885 const uint32_t holeSize = 300000; // 0.3mm
886 const int landWidth = 600000; // 0.6mm
887
888 BOOST_CHECK( !altiumViaSideIsTented( /*tentFlag*/ false, /*manual*/ true, /*fromHole*/ true,
889 holeSize, /*expansion*/ 500000, landWidth ) );
890 BOOST_CHECK( !altiumViaSideIsTented( /*tentFlag*/ false, /*manual*/ true, /*fromHole*/ false,
891 holeSize, /*expansion*/ 30000, landWidth ) );
892 BOOST_CHECK( altiumViaSideIsTented( /*tentFlag*/ true, /*manual*/ false, /*fromHole*/ false,
893 holeSize, /*expansion*/ 0, landWidth ) );
894
895 // A narrow hole-referenced opening (hole + 2 * expansion <= land) tents the side.
896 BOOST_CHECK( altiumViaSideIsTented( /*tentFlag*/ false, /*manual*/ true, /*fromHole*/ true,
897 holeSize, /*expansion*/ 30000, landWidth ) );
898}
899
900
901BOOST_AUTO_TEST_CASE( SlotDrillDimensionsSurviveUnsupportedRotation )
902{
903 bool rotationSupported;
904
905 BOOST_CHECK_EQUAL( altiumSlotDrillSize( 550000, 850000, 0.0, rotationSupported ), VECTOR2I( 850000, 550000 ) );
906 BOOST_CHECK( rotationSupported );
907
908 BOOST_CHECK_EQUAL( altiumSlotDrillSize( 550000, 850000, 90.0, rotationSupported ), VECTOR2I( 550000, 850000 ) );
909 BOOST_CHECK( rotationSupported );
910
911 BOOST_CHECK_EQUAL( altiumSlotDrillSize( 550000, 850000, 45.0, rotationSupported ), VECTOR2I( 850000, 550000 ) );
912 BOOST_CHECK( !rotationSupported );
913}
914
915
923BOOST_AUTO_TEST_CASE( StackupDielectricLossTangent )
924{
925 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
926 + "plugins/altium/issue24456/Fastino_Ground_Isolator.PcbDoc";
927
928 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
929
930 BOOST_REQUIRE( board );
931
932 const BOARD_DESIGN_SETTINGS& designSettings = board->GetDesignSettings();
933 const BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
934
935 BOOST_REQUIRE( designSettings.m_HasStackup );
936 BOOST_CHECK( board->GetStackupOrDefault() == stackup );
937
938 int dielectricCount = 0;
939 int dielectricWithTangent = 0;
940
941 for( const BOARD_STACKUP_ITEM* item : stackup.GetList() )
942 {
943 if( item->GetType() != BS_ITEM_TYPE_DIELECTRIC )
944 continue;
945
946 for( int sub = 0; sub < item->GetSublayersCount(); sub++ )
947 {
948 // Only count dielectric sublayers that carry a real dielectric (non-zero thickness)
949 if( item->GetThickness( sub ) <= 0 )
950 continue;
951
952 dielectricCount++;
953
954 double tangent = item->GetLossTangent( sub );
955
956 if( tangent > 0. )
957 {
958 dielectricWithTangent++;
959
960 // Every prepreg/core dielectric in this board uses a 0.020 loss tangent.
961 BOOST_CHECK_CLOSE( tangent, 0.020, 1e-6 );
962 }
963 }
964 }
965
966 BOOST_REQUIRE_GT( dielectricCount, 0 );
967
968 // All of the board's substantive dielectrics carry a loss tangent in the Altium source, so
969 // every imported dielectric sublayer must receive it.
970 BOOST_CHECK_MESSAGE( dielectricWithTangent == dielectricCount,
971 wxString::Format( "Only %d of %d dielectric sublayers received a loss "
972 "tangent from the Altium stackup",
973 dielectricWithTangent, dielectricCount ) );
974}
975
976
983BOOST_AUTO_TEST_CASE( ProjectParametersToTextVars )
984{
985 std::string dataDir = KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/issue24456/";
986 std::string pcbDoc = dataDir + "Fastino_Ground_Isolator.PcbDoc";
987 std::string prjPcb = dataDir + "Fastino_Ground_Isolator.PrjPcb";
988
989 SETTINGS_MANAGER settingsManager;
990 settingsManager.LoadProject( "" );
991 PROJECT& project = settingsManager.Prj();
992
993 std::map<std::string, UTF8> props;
994 props["project_file"] = prjPcb;
995
996 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( pcbDoc, &props, &project );
997
998 const std::map<wxString, wxString>& textVars = project.GetTextVars();
999
1000 // The parameter that the issue reports as broken must resolve to its value.
1001 BOOST_REQUIRE( textVars.count( wxS( "PCB_REVISION" ) ) );
1002 BOOST_CHECK_EQUAL( textVars.at( wxS( "PCB_REVISION" ) ), wxS( "A" ) );
1003
1004 // A representative sample of the remaining project parameters must all be present.
1005 BOOST_CHECK_EQUAL( textVars.at( wxS( "COMPANY_NAME" ) ), wxS( "ETH Zurich" ) );
1006 BOOST_CHECK_EQUAL( textVars.at( wxS( "PROJECT_NAME" ) ), wxS( "Fastino Ground Isolator" ) );
1007 BOOST_CHECK_EQUAL( textVars.at( wxS( "REVISION_MAJOR" ) ), wxS( "1" ) );
1008 BOOST_CHECK_EQUAL( textVars.at( wxS( "YEAR" ) ), wxS( "2026" ) );
1009
1010 // Board text referencing the special string now resolves through the project variable.
1011 wxString resolved = ExpandTextVars( wxS( "${PCB_REVISION}" ), &project, INTERNAL );
1012 BOOST_CHECK_EQUAL( resolved, wxS( "A" ) );
1013
1014 // End-to-end: an actual imported board text that references ${PCB_REVISION} must render its
1015 // value once the board is linked to the project carrying the variable. This guards against a
1016 // regression in the Altium special-string conversion as well as the variable registration.
1017 board->SetProject( &project, true /* reference only */ );
1018
1019 bool sawResolvedBoardText = false;
1020
1021 for( BOARD_ITEM* item : board->Drawings() )
1022 {
1023 const EDA_TEXT* text = dynamic_cast<const EDA_TEXT*>( item );
1024
1025 if( text && text->GetText().Contains( wxS( "${PCB_REVISION}" ) ) )
1026 {
1027 wxString shown = text->GetShownText( INTERNAL );
1028 BOOST_CHECK( !shown.Contains( wxS( "${PCB_REVISION}" ) ) );
1029 BOOST_CHECK( shown.Contains( wxS( "A" ) ) );
1030 sawResolvedBoardText = true;
1031 }
1032 }
1033
1034 BOOST_CHECK( sawResolvedBoardText );
1035}
1036
1037
1042BOOST_AUTO_TEST_CASE( ProjectParametersPreserveExisting )
1043{
1044 std::string dataDir = KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/issue24456/";
1045 std::string pcbDoc = dataDir + "Fastino_Ground_Isolator.PcbDoc";
1046 std::string prjPcb = dataDir + "Fastino_Ground_Isolator.PrjPcb";
1047
1048 SETTINGS_MANAGER settingsManager;
1049 settingsManager.LoadProject( "" );
1050 PROJECT& project = settingsManager.Prj();
1051 project.GetTextVars()[wxS( "PCB_REVISION" )] = wxS( "user-set" );
1052
1053 std::map<std::string, UTF8> props;
1054 props["project_file"] = prjPcb;
1055
1056 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( pcbDoc, &props, &project );
1057
1058 // A pre-existing variable wins over the imported parameter.
1059 BOOST_CHECK_EQUAL( project.GetTextVars().at( wxS( "PCB_REVISION" ) ), wxS( "user-set" ) );
1060
1061 // Other parameters are still imported.
1062 BOOST_CHECK_EQUAL( project.GetTextVars().at( wxS( "COMPANY_NAME" ) ), wxS( "ETH Zurich" ) );
1063}
1064
1065
1076BOOST_AUTO_TEST_CASE( CopperAndMaskTextCoincide )
1077{
1078 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
1079 + "plugins/altium/issue24456/Fastino_Ground_Isolator.PcbDoc";
1080
1081 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
1082 BOOST_REQUIRE( board );
1083
1084 std::vector<PCB_TEXT*> copperTexts;
1085 std::vector<PCB_TEXT*> maskTexts;
1086
1087 for( BOARD_ITEM* item : board->Drawings() )
1088 {
1089 if( item->Type() != PCB_TEXT_T )
1090 continue;
1091
1092 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
1093 PCB_LAYER_ID layer = text->GetLayer();
1094
1095 if( IsCopperLayer( layer ) )
1096 copperTexts.push_back( text );
1097 else if( layer == F_Mask || layer == B_Mask )
1098 maskTexts.push_back( text );
1099 }
1100
1101 BOOST_REQUIRE_MESSAGE( !copperTexts.empty(), "Board must contain copper-layer text" );
1102 BOOST_REQUIRE_MESSAGE( !maskTexts.empty(), "Board must contain soldermask-layer text" );
1103
1104 // A copper string and a mask string are the same logical label when they share text, rotation
1105 // and mirroring. KiCad's IU tolerance for "coincident" is tight; before the fix the gap was
1106 // 50000-75000 IU (0.05-0.075 mm).
1107 const int tolerance = 1000; // 1 micron
1108
1109 int matchedPairs = 0;
1110
1111 for( PCB_TEXT* copper : copperTexts )
1112 {
1113 for( PCB_TEXT* mask : maskTexts )
1114 {
1115 if( copper->GetText() != mask->GetText()
1116 || copper->GetTextAngle() != mask->GetTextAngle()
1117 || copper->IsMirrored() != mask->IsMirrored() )
1118 {
1119 continue;
1120 }
1121
1122 // Only treat them as the same label when they are already near each other; distinct
1123 // labels that happen to share a glyph (e.g. several "+" pads) must not be cross-matched.
1124 VECTOR2I delta = copper->GetTextPos() - mask->GetTextPos();
1125
1126 if( std::abs( delta.x ) > 500000 || std::abs( delta.y ) > 500000 )
1127 continue;
1128
1129 matchedPairs++;
1130
1131 BOOST_CHECK_MESSAGE(
1132 std::abs( delta.x ) <= tolerance && std::abs( delta.y ) <= tolerance,
1133 wxString::Format( "Copper/mask copies of '%s' diverge by (%d, %d) IU",
1134 copper->GetText(), delta.x, delta.y ) );
1135 }
1136 }
1137
1138 BOOST_CHECK_MESSAGE( matchedPairs > 0,
1139 "Expected at least one coincident copper/soldermask text pair" );
1140}
1141
1142
1153BOOST_AUTO_TEST_CASE( LengthTuningPatterns )
1154{
1155 std::string dataPath =
1156 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/issue24654/PCB1.PcbDoc";
1157
1158 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
1159
1160 BOOST_REQUIRE( board );
1161
1162 int tuningCount = 0;
1163 int singleCount = 0;
1164 int diffPairCount = 0;
1165
1166 std::set<wxString> patternNames;
1167
1168 for( PCB_GENERATOR* generator : board->Generators() )
1169 {
1170 PCB_TUNING_PATTERN* pattern = dynamic_cast<PCB_TUNING_PATTERN*>( generator );
1171
1172 if( !pattern )
1173 continue;
1174
1175 tuningCount++;
1176 patternNames.insert( pattern->GetName() );
1177
1178 // Each pattern must wrap the real imported copper and carry Altium's meander parameters.
1179 BOOST_CHECK_MESSAGE( !pattern->GetBoardItems().empty(),
1180 "Imported tuning pattern wraps no copper" );
1181 BOOST_CHECK_GT( pattern->GetMaxAmplitude(), 0 );
1182 BOOST_CHECK_GT( pattern->GetSpacing(), 0 );
1183
1184 // The members must be copper tracks/arcs that the importer placed on the board.
1185 std::set<int> memberNets;
1186
1187 for( BOARD_ITEM* item : pattern->GetBoardItems() )
1188 {
1189 BOOST_CHECK( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T );
1190 BOOST_CHECK( item->GetParentGroup() == pattern );
1191
1192 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
1193 memberNets.insert( bci->GetNetCode() );
1194 }
1195
1196 if( pattern->GetTuningMode() == DIFF_PAIR )
1197 {
1198 diffPairCount++;
1199
1200 // A differential-pair meander must keep both nets; collapsing them to one would
1201 // corrupt half the routing.
1202 BOOST_CHECK_EQUAL( memberNets.size(), 2 );
1203 }
1204 else if( pattern->GetTuningMode() == SINGLE )
1205 {
1206 singleCount++;
1207 BOOST_CHECK_EQUAL( memberNets.size(), 1 );
1208 }
1209 }
1210
1211 BOOST_CHECK_EQUAL( tuningCount, 8 );
1212 BOOST_CHECK_EQUAL( singleCount, 4 );
1213 BOOST_CHECK_EQUAL( diffPairCount, 4 );
1214
1215 // Each meander keeps the interactive union name Altium assigned it (from the UnionNames stream).
1216 const std::set<wxString> expectedNames = {
1217 wxT( "Interactive Length Tunings" ),
1218 wxT( "Interactive Length Tunings 1" ),
1219 wxT( "Interactive Length Tunings 2" ),
1220 wxT( "Interactive Length Tunings 3" ),
1221 wxT( "Interactive Diffpair Length Tunings" ),
1222 wxT( "Interactive Diffpair Length Tunings 1" ),
1223 wxT( "Interactive Diffpair Length Tunings 2" ),
1224 wxT( "Interactive Diffpair Length Tunings 3" ),
1225 };
1226
1227 BOOST_CHECK( patternNames == expectedNames );
1228}
1229
1230
1240BOOST_AUTO_TEST_CASE( LengthTuningPatternsAreSelectable )
1241{
1242 std::string dataPath =
1243 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/issue24654/PCB1.PcbDoc";
1244
1245 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
1246
1247 BOOST_REQUIRE( board );
1248
1249 int checked = 0;
1250
1251 for( PCB_GENERATOR* generator : board->Generators() )
1252 {
1253 PCB_TUNING_PATTERN* pattern = dynamic_cast<PCB_TUNING_PATTERN*>( generator );
1254
1255 if( !pattern )
1256 continue;
1257
1258 checked++;
1259
1260 BOOST_REQUIRE_MESSAGE( pattern->GetBaseLine().has_value(),
1261 "Imported tuning pattern has no baseline" );
1262 BOOST_REQUIRE_GT( pattern->GetBaseLine()->PointCount(), 1 );
1263
1264 const SHAPE_LINE_CHAIN& baseLine = *pattern->GetBaseLine();
1265
1266 // Editing snaps the origin onto copper, so the endpoints have to be the baseline's own
1267 BOOST_CHECK( pattern->GetPosition() == baseLine.CPoint( 0 ) );
1268 BOOST_CHECK( pattern->GetEnd() == baseLine.CLastPoint() );
1269
1270 if( pattern->GetTuningMode() == DIFF_PAIR )
1271 {
1272 BOOST_REQUIRE_MESSAGE( pattern->GetBaseLineCoupled().has_value(),
1273 "Imported diff-pair tuning pattern has no coupled baseline" );
1274 BOOST_CHECK_GT( pattern->GetBaseLineCoupled()->PointCount(), 1 );
1275 BOOST_CHECK_GT( pattern->GetDiffPairGap(), 0 );
1276 }
1277
1278 BOOST_CHECK_MESSAGE( !pattern->GetLastNetName().IsEmpty(),
1279 "Imported tuning pattern is not attributed to a net" );
1280
1281 // Clicking anywhere along the meander has to land on the generator
1282 for( int i = 0; i < baseLine.PointCount(); ++i )
1283 {
1284 BOOST_CHECK_MESSAGE( pattern->HitTest( baseLine.CPoint( i ), 0 ),
1285 "Tuning pattern is not hittable on its own baseline" );
1286 }
1287
1288 BOX2I bbox = pattern->GetBoundingBox();
1289
1290 BOOST_CHECK_MESSAGE( bbox.Contains( baseLine.BBox() ),
1291 "Tuning pattern bounding box does not cover its baseline" );
1292
1293 for( BOARD_ITEM* item : pattern->GetBoardItems() )
1294 {
1295 BOOST_CHECK_MESSAGE( bbox.Contains( item->GetBoundingBox() ),
1296 "Tuning pattern bounding box does not cover its member copper" );
1297 }
1298 }
1299
1300 BOOST_CHECK_EQUAL( checked, 8 );
1301}
1302
1303
1304// https://gitlab.com/kicad/code/kicad/-/issues/24847
1305// Keepout regions defined inside an Altium footprint must be imported at the footprint's board
1306// location. The importer keeps the Altium absolute coordinates instead of re-basing them to the
1307// footprint origin, so the keepout zone lands far from the footprint that owns it.
1308BOOST_AUTO_TEST_CASE( Issue24847_FootprintKeepoutPlacement )
1309{
1310 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
1311 + "plugins/altium/issue24847/PCB1.PcbDoc";
1312
1313 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
1314
1315 BOOST_REQUIRE( board );
1316 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
1317
1318 BOOST_CHECK_EQUAL( board->GetDesignSettings().m_CopperEdgeClearance, pcbIUScale.mmToIU( 0.4 ) );
1319
1320 int keepoutZoneCount = 0;
1321
1322 for( FOOTPRINT* footprint : board->Footprints() )
1323 {
1324 const VECTOR2I fpPos = footprint->GetPosition();
1325
1326 for( ZONE* zone : footprint->Zones() )
1327 {
1328 keepoutZoneCount++;
1329
1330 const VECTOR2I zoneCenter = zone->GetBoundingBox().GetCenter();
1331 const double distMm = ( zoneCenter - fpPos ).EuclideanNorm() / 1e6;
1332
1333 BOOST_TEST_MESSAGE( "footprint " << footprint->GetReference().ToStdString()
1334 << " at (" << fpPos.x << "," << fpPos.y
1335 << ") keepout center (" << zoneCenter.x << ","
1336 << zoneCenter.y << ") dist " << distMm << " mm" );
1337
1338 // A footprint-local keepout sits on its footprint anchor; the pre-fix regression put
1339 // it over 100mm away, so anything past a couple of mm is a placement failure.
1340 BOOST_CHECK_MESSAGE( distMm < 2.0,
1341 "Keepout in footprint " << footprint->GetReference().ToStdString()
1342 << " is " << distMm << " mm from its footprint origin" );
1343 }
1344 }
1345
1346 // PCB1.PcbDoc carries exactly three footprint-local keepouts (Z1, Z2, R1); dropping any is a
1347 // regression the placement check alone would not catch.
1348 BOOST_CHECK_EQUAL( keepoutZoneCount, 3 );
1349}
1350
1351
1352// https://gitlab.com/kicad/code/kicad/-/issues/13750
1353// Copper regions can carry a soldermask relief; the importer dropped it, losing the aperture
1354BOOST_AUTO_TEST_CASE( RegionSolderMaskExpansion )
1355{
1356 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
1357 + "plugins/altium/issue13750/"
1358 "altium2kicad_region_soldermask_expansion.PcbDoc";
1359
1360 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
1361 BOOST_REQUIRE( board );
1362
1363 std::vector<PCB_SHAPE*> copperPolys;
1364 std::vector<PCB_SHAPE*> maskPolys;
1365
1366 for( BOARD_ITEM* item : board->Drawings() )
1367 {
1368 if( item->Type() != PCB_SHAPE_T )
1369 continue;
1370
1371 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
1372
1373 if( shape->GetShape() != SHAPE_T::POLY )
1374 continue;
1375
1376 if( shape->GetLayer() == F_Cu )
1377 copperPolys.push_back( shape );
1378 else if( shape->GetLayer() == F_Mask )
1379 maskPolys.push_back( shape );
1380 }
1381
1382 // Two top-copper regions, exactly one of which carries the soldermask relief
1383 BOOST_REQUIRE_EQUAL( copperPolys.size(), 2 );
1384 BOOST_REQUIRE_EQUAL( maskPolys.size(), 1 );
1385
1386 // Zero expansion here, so the mask aperture must exactly match its region, not just overlap it,
1387 // and must match exactly one region, else an off-by-one primitive association would still pass
1388 PCB_SHAPE* mask = maskPolys.front();
1389 int exactMatches = 0;
1390
1391 for( PCB_SHAPE* copper : copperPolys )
1392 {
1393 SHAPE_POLY_SET missing = copper->GetPolyShape().CloneDropTriangulation();
1394 missing.BooleanSubtract( mask->GetPolyShape() );
1395
1397 extra.BooleanSubtract( copper->GetPolyShape() );
1398
1399 if( missing.IsEmpty() && extra.IsEmpty() )
1400 exactMatches++;
1401 }
1402
1403 BOOST_CHECK_MESSAGE( exactMatches == 1, "F_Mask relief aperture must exactly match exactly one copper region" );
1404}
1405
1406
1407// A component copper region carries a net in Altium, but as a custom pad it used to import
1408// unconnected, breaking every connection the part makes through its own copper
1409BOOST_AUTO_TEST_CASE( ComponentCopperRegionNets )
1410{
1411 std::string dataPath =
1412 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/issue24456/Fastino_Ground_Isolator.PcbDoc";
1413
1414 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
1415
1416 int regionCount = 0;
1417 int connectedCount = 0;
1418
1419 for( FOOTPRINT* footprint : board->Footprints() )
1420 {
1421 for( PAD* pad : footprint->Pads() )
1422 {
1423 PCB_LAYER_ID copperLayer = pad->IsOnLayer( F_Cu ) ? F_Cu : B_Cu;
1424
1425 if( pad->GetShape( copperLayer ) != PAD_SHAPE::CUSTOM || !pad->GetNumber().IsEmpty() )
1426 continue;
1427
1428 regionCount++;
1429
1430 if( pad->GetNetCode() > 0 )
1431 {
1432 connectedCount++;
1433 BOOST_CHECK( !pad->GetNetname().IsEmpty() );
1434 }
1435 }
1436 }
1437
1438 BOOST_REQUIRE_GT( regionCount, 0 );
1439 BOOST_CHECK_GT( connectedCount, 0 );
1440}
1441
1442
1448BOOST_AUTO_TEST_CASE( ComponentCopperRegionOrientation )
1449{
1450 std::string dataPath =
1451 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/issue24456/Fastino_Ground_Isolator.PcbDoc";
1452
1453 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
1454
1455 int regionCount = 0;
1456
1457 for( FOOTPRINT* footprint : board->Footprints() )
1458 {
1459 if( footprint->GetOrientation().IsZero() )
1460 continue;
1461
1462 for( PAD* pad : footprint->Pads() )
1463 {
1464 PCB_LAYER_ID copperLayer = pad->IsOnLayer( F_Cu ) ? F_Cu : B_Cu;
1465
1466 // Region-derived pads are the custom-shaped ones Altium left without a number
1467 if( pad->GetShape( copperLayer ) != PAD_SHAPE::CUSTOM || !pad->GetNumber().IsEmpty() )
1468 continue;
1469
1470 regionCount++;
1471
1472 // PAD normalizes the library-frame angle, so cancelling the footprint rotation
1473 // leaves the absolute angle at a full turn rather than at zero
1474 EDA_ANGLE absolute = pad->GetOrientation();
1475 absolute.Normalize();
1476
1477 BOOST_CHECK_MESSAGE( absolute.IsZero(),
1478 wxString::Format( wxT( "Region pad in %s is rotated %.1f degrees" ),
1479 footprint->GetReference(), absolute.AsDegrees() ) );
1480 BOOST_CHECK( !pad->GetFPRelativeOrientation().IsZero() );
1481 }
1482 }
1483
1484 BOOST_REQUIRE_GT( regionCount, 0 );
1485}
1486
1487
1488// The importer recentres the board on the page after parsing and shifts both origins by that same
1489// vector, so only the origin's offset from the board outline survives the move
1491 const std::string& aRelativePath,
1492 double aExpectedXmm, double aExpectedYmm )
1493{
1494 std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + aRelativePath;
1495
1496 std::unique_ptr<BOARD> board = aPlugin.LoadBoard( dataPath, nullptr );
1497
1498 BOOST_REQUIRE( board );
1499
1500 const BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
1501
1502 // Altium carries a single relative origin, so both KiCad origins have to land on it
1503 BOOST_CHECK( bds.GetGridOrigin() == bds.GetAuxOrigin() );
1504
1505 BOX2I outline = board->GetBoardEdgesBoundingBox();
1506
1507 BOOST_REQUIRE_GT( outline.GetArea(), 0 );
1508
1509 double offsetXmm = pcbIUScale.IUTomm( bds.GetGridOrigin().x - outline.GetLeft() );
1510 double offsetYmm = pcbIUScale.IUTomm( bds.GetGridOrigin().y - outline.GetTop() );
1511
1512 // Absorbs the edge-cut stroke width the bounding box adds, far below the tens of millimetres
1513 // separating the relative origin from the sheet corner on these boards
1514 const double toleranceMm = 1.0;
1515
1516 BOOST_CHECK_MESSAGE( std::abs( offsetXmm - aExpectedXmm ) < toleranceMm,
1517 wxString::Format( "%s: origin sits %.3f mm right of the outline corner, "
1518 "expected %.3f",
1519 aRelativePath, offsetXmm, aExpectedXmm ) );
1520
1521 BOOST_CHECK_MESSAGE( std::abs( offsetYmm - aExpectedYmm ) < toleranceMm,
1522 wxString::Format( "%s: origin sits %.3f mm below the outline corner, "
1523 "expected %.3f",
1524 aRelativePath, offsetYmm, aExpectedYmm ) );
1525}
1526
1527
1528// https://gitlab.com/kicad/code/kicad/-/issues/25057
1529BOOST_AUTO_TEST_CASE( RelativeOriginNotSheetCorner )
1530{
1531 // The sheet corner would land 186.600/142.375 mm from these offsets
1532 checkImportedOriginOffset( m_altiumPlugin,
1533 "plugins/altium/eDP_adapter_dvt1_source/eDP_adapter_dvt1.PcbDoc",
1534 46.300, 7.400 );
1535
1536 // Origin sits on the left edge; the sheet corner would land 51.308/50.165 mm away
1537 checkImportedOriginOffset( m_altiumPlugin, "plugins/altium/issue24847/PCB1.PcbDoc", 0.000,
1538 15.000 );
1539}
1540
1541
1542// https://gitlab.com/kicad/code/kicad/-/issues/25362
1543// FB36 has two component bodies naming one model; the importer used to hash and link the
1544// freed duplicate that AddFile() had already dropped
1545BOOST_AUTO_TEST_CASE( SharedComponentBodyModelEmbeddedOnce )
1546{
1547 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
1548 + "plugins/altium/issue25362/OpenRex_V1I1_PCB_reduced.PcbDoc";
1549
1550 std::unique_ptr<BOARD> board = m_altiumPlugin.LoadBoard( dataPath, nullptr );
1551
1552 BOOST_REQUIRE_EQUAL( board->Footprints().size(), 1 );
1553
1554 FOOTPRINT* footprint = board->Footprints().front();
1555 const EMBEDDED_FILES* files = footprint->GetEmbeddedFiles();
1556
1557 BOOST_REQUIRE_EQUAL( files->EmbeddedFileMap().size(), 1 );
1558
1559 const std::shared_ptr<EMBEDDED_FILES::EMBEDDED_FILE>& file =
1560 files->EmbeddedFileMap().begin()->second;
1561
1562 BOOST_CHECK_EQUAL( file->name, wxString( wxS( "C0603.step" ) ) );
1563 BOOST_CHECK( file->type == EMBEDDED_FILES::EMBEDDED_FILE::FILE_TYPE::MODEL );
1564
1565 // A freed file yields garbage here rather than the STEP payload, and the exact length plus
1566 // the closing tag pin the inflate loop against silently truncating a model
1567 const std::string step( file->decompressedData.begin(), file->decompressedData.end() );
1568
1569 BOOST_REQUIRE_EQUAL( step.size(), 126391u );
1570 BOOST_CHECK( step.starts_with( "ISO-10303-21;" ) );
1571 BOOST_CHECK( step.ends_with( "END-ISO-10303-21;\r\n" ) );
1572
1573 // CompressAndEncode runs on a worker thread against the file AddFile() handed back, so an
1574 // empty payload means that thread wrote into freed memory instead
1575 BOOST_CHECK_GT( file->compressedEncodedData.size(), 0 );
1576 BOOST_CHECK( file->Validate() );
1577
1578 // Both bodies keep their own placement but must resolve to the one embedded model
1579 BOOST_REQUIRE_EQUAL( footprint->Models().size(), 2 );
1580
1581 for( const FP_3DMODEL& model : footprint->Models() )
1582 BOOST_CHECK_EQUAL( model.m_Filename, file->GetLink() );
1583}
1584
1585
1586// The MID6 copper polygon of TI's TIDA-00204_revE3_PCB.PcbDoc, whose third vertex reads
1587// -111339.9634mil. That is 2.8 m, past the int range, so it used to clamp to INT_MIN + 8 and
1588// stretch the zone across the whole coordinate space
1589BOOST_AUTO_TEST_CASE( PolygonVertexOutsideCoordinateRangeIsDiscarded )
1590{
1591 std::map<wxString, wxString> props = {
1592 { wxT( "VX0" ), wxT( "3481.9999mil" ) }, { wxT( "VY0" ), wxT( "2818.9999mil" ) },
1593 { wxT( "VX1" ), wxT( "3506.9999mil" ) }, { wxT( "VY1" ), wxT( "2843.9999mil" ) },
1594 { wxT( "VX2" ), wxT( "-111339.9634mil" ) }, { wxT( "VY2" ), wxT( "2834mil" ) },
1595 { wxT( "VX3" ), wxT( "3756.9998mil" ) }, { wxT( "VY3" ), wxT( "2834mil" ) },
1596 { wxT( "VX4" ), wxT( "3771.9999mil" ) }, { wxT( "VY4" ), wxT( "2818.9999mil" ) },
1597 { wxT( "VX5" ), wxT( "3772mil" ) }, { wxT( "VY5" ), wxT( "2634mil" ) },
1598 { wxT( "VX6" ), wxT( "3757mil" ) }, { wxT( "VY6" ), wxT( "2619mil" ) },
1599 { wxT( "VX7" ), wxT( "3614.5mil" ) }, { wxT( "VY7" ), wxT( "2619.0001mil" ) },
1600 { wxT( "VX8" ), wxT( "3583.0001mil" ) }, { wxT( "VY8" ), wxT( "2587.5mil" ) },
1601 { wxT( "VX9" ), wxT( "3573.9998mil" ) }, { wxT( "VY9" ), wxT( "2587.4999mil" ) },
1602 { wxT( "VX10" ), wxT( "3542.5005mil" ) }, { wxT( "VY10" ), wxT( "2619.0001mil" ) },
1603 { wxT( "VX11" ), wxT( "3522mil" ) }, { wxT( "VY11" ), wxT( "2619mil" ) },
1604 { wxT( "VX12" ), wxT( "3497mil" ) }, { wxT( "VY12" ), wxT( "2619mil" ) },
1605 { wxT( "VX13" ), wxT( "3482mil" ) }, { wxT( "VY13" ), wxT( "2634mil" ) },
1606 { wxT( "VX14" ), wxT( "3481.9999mil" ) }, { wxT( "VY14" ), wxT( "2818.9999mil" ) },
1607 };
1608
1609 std::vector<ALTIUM_VERTICE> vertices;
1610 int discarded = 0;
1611
1612 altium_parse_polygons( props, vertices, &discarded );
1613
1614 BOOST_CHECK_EQUAL( discarded, 1 );
1615 BOOST_REQUIRE_EQUAL( vertices.size(), 14 );
1616
1617 BOX2I bbox;
1618
1619 for( const ALTIUM_VERTICE& vertex : vertices )
1620 bbox.Merge( vertex.position );
1621
1622 // The representable vertices span 7.366 x 6.5151 mm around x = 88.4 mm; keeping the clamped
1623 // one put the left edge at INT_MIN + 8 and overflowed the width itself
1624 BOOST_CHECK_EQUAL( bbox.GetLeft(), 88442800 );
1625 BOOST_CHECK_EQUAL( bbox.GetWidth(), 7366000 );
1626 BOOST_CHECK_EQUAL( bbox.GetHeight(), 6515100 );
1627
1628 // Every survivor keeps its own coordinates, so nothing was renumbered or shifted
1629 BOOST_CHECK_EQUAL( vertices[1].position, VECTOR2I( 89077800, -72237600 ) );
1630 BOOST_CHECK_EQUAL( vertices[2].position, VECTOR2I( 95427800, -71983600 ) );
1631}
1632
1633
1641BOOST_AUTO_TEST_CASE( NetNamesTakeSchematicCase )
1642{
1643 std::string dataDir = KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/issue25374/";
1644
1645 std::map<std::string, UTF8> props;
1646 props["sch0"] = dataDir + "altium_import_netname.SchDoc";
1647
1649 std::unique_ptr<BOARD> board = plugin.LoadBoard( dataDir + "altium_import_netname.CSPcbDoc", &props );
1650
1651 BOOST_CHECK( board->FindNet( wxT( "all_low_letter" ) ) );
1652 BOOST_CHECK( board->FindNet( wxT( "UperCamelCase" ) ) );
1653 BOOST_CHECK( board->FindNet( wxT( "lowerCamelCase" ) ) );
1654 BOOST_CHECK( board->FindNet( wxT( "ALL_BIG_LETTER" ) ) );
1655 BOOST_CHECK( board->FindNet( wxT( "GND" ) ) );
1656 BOOST_CHECK( !board->FindNet( wxT( "ALL_LOW_LETTER" ) ) );
1657
1658 PCB_IO_ALTIUM_CIRCUIT_STUDIO standalonePlugin;
1659 std::unique_ptr<BOARD> standaloneBoard = standalonePlugin.LoadBoard( dataDir + "altium_import_netname.CSPcbDoc" );
1660
1661 BOOST_CHECK( standaloneBoard->FindNet( wxT( "ALL_LOW_LETTER" ) ) );
1662 BOOST_CHECK( !standaloneBoard->FindNet( wxT( "all_low_letter" ) ) );
1663}
1664
1665
const char * name
bool altiumScopeExprMatchesPolygon(const wxString &aExpr)
Return true if an Altium rule scope expression targets polygon pour primitives (matches InPolygon,...
void altium_parse_polygons(std::map< wxString, wxString > &aProps, std::vector< ALTIUM_VERTICE > &aVertices, int *aDiscarded)
Read the VX/VY/KIND/R/SA/EA/CX/CY vertex series out of a polygon or board outline record.
VECTOR2I altiumSlotDrillSize(uint32_t aHoleSize, uint32_t aSlotSize, double aSlotRotation, bool &aRotationSupported)
Convert an Altium slot's independent rotation and dimensions to KiCad's cardinal drill shape.
const ARULE6 * selectAltiumPolygonRule(const std::vector< ARULE6 > &aRulesByPriorityAsc)
Select the highest Altium-priority rule whose scope references polygons.
bool altiumViaSideIsTented(bool aTentFlag, bool aManual, bool aFromHole, uint32_t aHoleSize, int32_t aMaskExpansion, int aLandDiameter)
Decide whether one side of an Altium via should be tented when imported into KiCad.
const uint16_t ALTIUM_NET_UNCONNECTED
const uint16_t ALTIUM_POLYGON_NONE
ALTIUM_RECORD
const int ALTIUM_COMPONENT_NONE
wxString AltiumUnnamedNetName(const BOARD &aBoard, int &aCounter)
Invent a placeholder name for an Altium net that has none.
void ApplyAltiumNetclassRules(const std::map< ALTIUM_RULE_KIND, std::vector< ARULE6 > > &aRulesByKind, NET_SETTINGS &aNetSettings, std::vector< const ARULE6 * > *aUnresolved)
Copy the values of Altium rules scoped to a single netclass onto that netclass.
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
General utilities for PCB file IO for QA programs.
@ BS_ITEM_TYPE_DIELECTRIC
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
const CFB::COMPOUND_FILE_ENTRY * FindStream(const std::vector< std::string > &aStreamPath) const
std::vector< int > m_altiumToKicadNetcodes
Definition altium_pcb.h:357
std::vector< std::pair< PCB_LAYER_ID, int > > HelperGetSolderAndPasteMaskExpansions(const ALTIUM_RECORD aType, const int aPrimitiveIndex, const ALTIUM_LAYER aAltiumLayer)
void ParseExtendedPrimitiveInformationData(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
int GetNetCode(uint16_t aId) const
void ConvertArcs6ToBoardItemOnLayer(const AARC6 &aElem, PCB_LAYER_ID aLayer)
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
const VECTOR2I & GetGridOrigin() const
const VECTOR2I & GetAuxOrigin() const
BOARD_STACKUP & GetStackupDescriptor()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
Manage one layer needed to make a physical board.
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
const ZONES & Zones() const
Definition board.h:467
const FOOTPRINTS & Footprints() const
Definition board.h:463
const TRACKS & Tracks() const
Definition board.h:461
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1299
const DRAWINGS & Drawings() const
Definition board.h:465
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:653
constexpr ecoord_type GetArea() const
Return the area of the rectangle.
Definition box2.h:756
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:165
constexpr coord_type GetTop() const
Definition box2.h:226
EDA_ANGLE Normalize()
Definition eda_angle.h:229
double AsDegrees() const
Definition eda_angle.h:116
bool IsZero() const
Definition eda_angle.h:136
wxString GetName() const
Definition eda_group.h:61
SHAPE_POLY_SET & GetPolyShape()
SHAPE_T GetShape() const
Definition eda_shape.h:175
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:94
const std::map< wxString, std::shared_ptr< EMBEDDED_FILE > > & EmbeddedFileMap() const
Provide an iterable view of the file collection.
std::vector< FP_3DMODEL > & Models()
Definition footprint.h:424
EMBEDDED_FILES * GetEmbeddedFiles() override
Definition footprint.h:1389
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
Custom REPORTER that captures all messages for later analysis in the unit test framework.
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:43
static const char Default[]
the name of the default NETCLASS
Definition netclass.h:45
const wxString GetName() const
Gets the name of this (maybe aggregate) netclass in a format for internal usage or for export to exte...
Definition netclass.cpp:368
Handle the data for a net.
Definition netinfo.h:50
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:280
NET_SETTINGS stores various net-related settings in a project context.
void SetNetclass(const wxString &netclassName, std::shared_ptr< NETCLASS > &netclass)
Sets the given netclass Calling user is responsible for resetting the effective netclass calculation ...
Definition pad.h:61
VECTOR2I GetPosition() const override
std::unordered_set< BOARD_ITEM * > GetBoardItems() const
static std::map< wxString, PCB_LAYER_ID > DefaultLayerMappingCallback(const std::vector< INPUT_LAYER_DESC > &aInputLayerDescriptionVector)
Return the automapped layers.
std::unique_ptr< BOARD > LoadBoard(const wxString &aFileName, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Load information from some input file format that this PCB_IO implementation knows about into new BOA...
Definition pcb_io.cpp:72
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition pcb_shape.h:68
const VECTOR2I & GetEnd() const
LENGTH_TUNING_MODE GetTuningMode() const
const wxString & GetLastNetName() const
const std::optional< SHAPE_LINE_CHAIN > & GetBaseLine() const
const std::optional< SHAPE_LINE_CHAIN > & GetBaseLineCoupled() const
bool HitTest(const VECTOR2I &aPosition, int aAccuracy=0) const override
Test if aPosition is inside or on the boundary of this item.
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Container for project specific data.
Definition project.h:63
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Represent a set of closed polygons.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
SHAPE_POLY_SET CloneDropTriangulation() const
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
Handle a list of polygons defining a copper zone.
Definition zone.h:70
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
@ INTERNAL
Definition common.h:92
@ FP_SMD
Definition footprint.h:86
@ FP_THROUGH_HOLE
Definition footprint.h:85
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_Paste
Definition layer_ids.h:100
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ B_Paste
Definition layer_ids.h:101
@ F_Cu
Definition layer_ids.h:60
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
@ DIFF_PAIR
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.
uint32_t textlinewidth
static int getNetCode(ALTIUM_PCB &aImporter, const std::vector< int > &aNetcodes, uint16_t aId)
ALTIUM_PCB_IMPORT_FIXTURE()=default
static std::vector< std::pair< PCB_LAYER_ID, int > > getMaskExpansions(ALTIUM_PCB &aImporter, ALTIUM_RECORD aType, int aPrimitiveIndex, ALTIUM_LAYER aLayer)
static void parseExtendedPrimitiveInformation(ALTIUM_PCB &aImporter, const ALTIUM_PCB_COMPOUND_FILE &aFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
static void convertConnectedArc(ALTIUM_PCB &aImporter, const AARC6 &aArc, PCB_LAYER_ID aLayer)
static std::vector< const ZONE * > collectRuleAreas(const BOARD &aBoard)
PCB_IO_ALTIUM_DESIGNER m_altiumPlugin
ALTIUM_RULE_KIND kind
wxString scope1expr
wxString scope2expr
Describes an imported layer and how it could be mapped to KiCad Layers.
static void checkAllCopperFillZonesHaveClearance(PCB_IO_ALTIUM_DESIGNER &aPlugin, const std::string &aRelativePath)
Verify that copper zones in imported Altium boards have non-zero local clearance values derived from ...
static void checkImportedOriginOffset(PCB_IO_ALTIUM_DESIGNER &aPlugin, const std::string &aRelativePath, double aExpectedXmm, double aExpectedYmm)
BOOST_AUTO_TEST_CASE(NetCodeRejectsIdPastEnd)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
IbisParser parser & reporter
KIBIS_MODEL * model
VECTOR3I expected(15, 30, 45)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
int actual
BOOST_CHECK_EQUAL(result, "25.4")
int delta
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:84
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683