KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_pads_sch_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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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
20#include <boost/test/unit_test.hpp>
22
23#include <json_common.h>
24
25#include <base_units.h>
26#include <bitmap_base.h>
27#include <connection_graph.h>
28#include <default_values.h>
30#include <embedded_files.h>
31#include <gr_text.h>
32#include <lib_symbol.h>
34#include <reporter.h>
35#include <sch_field.h>
36#include <sch_reference_list.h>
37#include <sch_bus_entry.h>
38#include <sch_bitmap.h>
39#include <sch_connection.h>
40#include <sch_junction.h>
41#include <sch_label.h>
42#include <schematic.h>
47#include <sch_io/sch_io_mgr.h>
49#include <sch_line.h>
50#include <sch_screen.h>
51#include <sch_shape.h>
52#include <sch_sheet.h>
53#include <sch_sheet_path.h>
54#include <sch_symbol.h>
55#include <sch_text.h>
58#include <string_utils.h>
59
60#include <algorithm>
61#include <array>
62#include <cstdlib>
63#include <cstdint>
64#include <filesystem>
65#include <fstream>
66#include <map>
67#include <numeric>
68#include <set>
69#include <sstream>
70#include <vector>
71#include <wx/filename.h>
72
73
74namespace
75{
76
77struct PADS_SCH_IMPORT_FIXTURE
78{
79 PADS_SCH_IMPORT_FIXTURE() :
80 m_schematic( nullptr )
81 {
82 m_settingsManager.LoadProject( "" );
83 m_schematic.SetProject( &m_settingsManager.Prj() );
84 m_schematic.Reset();
85 }
86
87 ~PADS_SCH_IMPORT_FIXTURE() { m_schematic.Reset(); }
88
89 SETTINGS_MANAGER m_settingsManager;
90 SCHEMATIC m_schematic;
91};
92
93
94struct CAPTURING_REPORTER : REPORTER
95{
96 REPORTER& Report( const wxString& aText, SEVERITY aSeverity = RPT_SEVERITY_UNDEFINED ) override
97 {
98 messages.emplace_back( aText, aSeverity );
99 return *this;
100 }
101
102 std::vector<std::pair<wxString, SEVERITY>> messages;
103};
104
105
106static wxString binaryFixture( const wxString& aName )
107{
108 return wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() ) + wxS( "/plugins/pads/binary/" ) + aName
109 + wxS( ".sch" );
110}
111
112
113static PADS_SCH_BINARY::PADS_SCH_MODEL parseBinaryFixture( const wxString& aName )
114{
115 wxString path = binaryFixture( aName );
116 std::vector<uint8_t> bytes;
119}
120
121
122struct OBJECT_GRAPH_SNAPSHOT
123{
124 std::vector<const SCH_SHEET*> topLevelSheets;
125 std::vector<const SCH_ITEM*> rootItems;
126 std::vector<const SCH_ITEM*> appendItems;
127 int pageWidth = 0;
128 int pageHeight = 0;
129 wxString title;
130
131 bool operator==( const OBJECT_GRAPH_SNAPSHOT& ) const = default;
132};
133
134
135static OBJECT_GRAPH_SNAPSHOT objectGraphSnapshot( const SCHEMATIC& aSchematic, const SCH_SHEET* aAppendToMe )
136{
137 OBJECT_GRAPH_SNAPSHOT snapshot;
138 std::vector<SCH_SHEET*> topLevelSheets = aSchematic.GetTopLevelSheets();
139 snapshot.topLevelSheets.assign( topLevelSheets.begin(), topLevelSheets.end() );
140
141 for( const SCH_ITEM* item : aSchematic.Root().GetScreen()->Items() )
142 snapshot.rootItems.push_back( item );
143
144 if( aAppendToMe && aAppendToMe->GetScreen() )
145 {
146 for( const SCH_ITEM* item : aAppendToMe->GetScreen()->Items() )
147 snapshot.appendItems.push_back( item );
148
149 snapshot.pageWidth = aAppendToMe->GetScreen()->GetPageSettings().GetWidthMils();
150 snapshot.pageHeight = aAppendToMe->GetScreen()->GetPageSettings().GetHeightMils();
151 snapshot.title = aAppendToMe->GetScreen()->GetTitleBlock().GetTitle();
152 }
153
154 return snapshot;
155}
156
157
158static size_t countPowerSymbols( SCH_SHEET* aRoot )
159{
160 size_t count = 0;
161
162 for( const SCH_SHEET_PATH& path : SCH_SHEET_LIST( aRoot ) )
163 {
164 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
165 {
166 if( static_cast<SCH_SYMBOL*>( item )->GetRef( &path ).StartsWith( wxS( "#PWR" ) ) )
167 count++;
168 }
169 }
170
171 return count;
172}
173
174
175// The same check eeschema runs before "Update PCB from Schematic"; a duplicated reference
176// surfaces here as "Duplicate items <ref>"
177static int checkAnnotation( const std::vector<SCH_SHEET*>& aRoots, std::vector<wxString>& aMessages )
178{
179 SCH_REFERENCE_LIST references;
180
181 for( SCH_SHEET* root : aRoots )
182 {
183 SCH_SHEET_LIST sheets( root );
184
185 for( SCH_SHEET_PATH& sheet : sheets )
186 sheet.GetSymbols( references, SYMBOL_FILTER_ALL, true );
187 }
188
189 return references.CheckAnnotation(
190 [&]( ERCE_T, const wxString& aMessage, SCH_REFERENCE*, SCH_REFERENCE* )
191 {
192 aMessages.push_back( aMessage );
193 } );
194}
195
196
197static size_t itemCount( SCH_SCREEN* aScreen, KICAD_T aType )
198{
199 size_t count = 0;
200
201 for( SCH_ITEM* item : aScreen->Items().OfType( aType ) )
202 {
203 (void) item;
204 ++count;
205 }
206
207 return count;
208}
209
210
211static VECTOR2I localPoint( const PADS_SCH_BINARY::SOURCE_POINT& aPoint )
212{
213 return { schIUScale.MilsToIU( static_cast<double>( aPoint.x ) / 2.0 ),
214 -schIUScale.MilsToIU( static_cast<double>( aPoint.y ) / 2.0 ) };
215}
216
217
218static VECTOR2I placedFieldOffset( const PADS_SCH_BINARY::MODEL_FIELD& aField )
219{
220 return localPoint( aField.position );
221}
222
223
224static VECTOR2I pagePoint( const PADS_SCH_BINARY::SOURCE_POINT& aPoint, int aPageHeight )
225{
226 return { schIUScale.MilsToIU( static_cast<double>( aPoint.x ) / 2.0 ),
227 aPageHeight - schIUScale.MilsToIU( static_cast<double>( aPoint.y ) / 2.0 ) };
228}
229
230
231static std::multiset<wxString> connectivitySnapshot( SCHEMATIC& aSchematic )
232{
233 std::multiset<wxString> result;
234 SCH_SHEET_LIST hierarchy = aSchematic.BuildSheetListSortedByPageNumbers();
235 aSchematic.ConnectionGraph()->Recalculate( hierarchy, true );
236
237 for( const SCH_SHEET_PATH& path : hierarchy )
238 {
239 SCH_SCREEN* screen = path.LastScreen();
240 BOOST_REQUIRE( screen );
241
242 for( SCH_ITEM* item : screen->Items() )
243 {
244 wxString geometry;
245
246 if( auto* line = dynamic_cast<SCH_LINE*>( item ) )
247 {
248 geometry = wxString::Format( wxS( "%d:%d,%d:%d,%d:%d:%d" ), line->GetLayer(), line->GetStartPoint().x,
249 line->GetStartPoint().y, line->GetEndPoint().x, line->GetEndPoint().y,
250 line->GetStroke().GetWidth(),
251 static_cast<int>( line->GetStroke().GetLineStyle() ) );
252 }
253 else if( auto* entry = dynamic_cast<SCH_BUS_WIRE_ENTRY*>( item ) )
254 {
255 geometry = wxString::Format( wxS( "entry:%d,%d:%d,%d" ), entry->GetPosition().x, entry->GetPosition().y,
256 entry->GetSize().x, entry->GetSize().y );
257 }
258 else if( auto* shape = dynamic_cast<SCH_SHAPE*>( item ) )
259 {
260 geometry = wxString::Format( wxS( "shape:%d:%d:%d:%d" ), static_cast<int>( shape->GetShape() ),
261 static_cast<int>( shape->GetFillMode() ), shape->GetStroke().GetWidth(),
262 static_cast<int>( shape->GetStroke().GetLineStyle() ) );
263
264 if( shape->GetShape() == SHAPE_T::CIRCLE )
265 {
266 const VECTOR2I center = shape->GetCenter();
267 const int radius =
268 KiROUND( std::hypot( static_cast<double>( shape->GetEnd().x - shape->GetStart().x ),
269 static_cast<double>( shape->GetEnd().y - shape->GetStart().y ) ) );
270 geometry += wxString::Format( wxS( ":%d,%d:%d" ), center.x, center.y, radius );
271 }
272 else
273 {
274 geometry += wxString::Format( wxS( ":%d,%d:%d,%d:%d,%d" ), shape->GetStart().x, shape->GetStart().y,
275 shape->GetEnd().x, shape->GetEnd().y, shape->GetArcMid().x,
276 shape->GetArcMid().y );
277 }
278
279 for( const VECTOR2I& point : shape->GetPolyPoints() )
280 geometry += wxString::Format( wxS( ":%d,%d" ), point.x, point.y );
281 }
282 else if( auto* text = dynamic_cast<EDA_TEXT*>( item ) )
283 {
284 geometry = wxString::Format( wxS( "text:%d,%d:%g:%d,%d:%d:%d:%d:%d:%d:%d:%s" ), text->GetTextPos().x,
285 text->GetTextPos().y, text->GetTextAngleDegrees(), text->GetTextSize().x,
286 text->GetTextSize().y, text->GetTextThickness(),
287 static_cast<int>( text->GetHorizJustify() ),
288 static_cast<int>( text->GetVertJustify() ), text->IsBold(),
289 text->IsItalic(), text->IsVisible(), text->GetText() );
290 }
291 else
292 {
293 geometry = wxString::Format( wxS( "point:%d,%d" ), item->GetPosition().x, item->GetPosition().y );
294 }
295
296 result.insert( wxString::Format( wxS( "%s:%s:%d:%s" ), path.Path().AsString(), path.GetPageNumber(),
297 static_cast<int>( item->Type() ), geometry ) );
298
299 if( SCH_CONNECTION* connection = item->Connection( &path ) )
300 result.insert( wxS( "net:" ) + connection->GetNetName() );
301
302 if( auto* symbol = dynamic_cast<SCH_SYMBOL*>( item ) )
303 {
304 for( SCH_PIN* pin : symbol->GetPins( &path ) )
305 {
306 if( SCH_CONNECTION* connection = pin->Connection( &path ) )
307 result.insert( wxS( "net:" ) + connection->GetNetName() );
308 }
309 }
310 }
311 }
312
313 return result;
314}
315
316
317static bool hasNetName( const std::multiset<wxString>& aSnapshot, const wxString& aName )
318{
319 return std::ranges::any_of( aSnapshot,
320 [&]( const wxString& aValue )
321 {
322 return aValue == wxS( "net:" ) + aName || aValue.EndsWith( wxS( "/" ) + aName );
323 } );
324}
325
326
327static bool netNameMatches( const wxString& aActual, const wxString& aExpected )
328{
329 const wxString actual = UnescapeString( aActual );
330 return actual == aExpected || actual.EndsWith( wxS( "/" ) + aExpected );
331}
332
333
334struct PADS_NETLIST_SIGNATURE
335{
336 std::set<std::string> parts;
337 std::multiset<std::vector<std::string>> partitions;
338};
339
340
341static PADS_NETLIST_SIGNATURE padsNetlistSignature( const std::string& aPath )
342{
343 enum class SECTION
344 {
345 NONE,
346 PARTS,
347 NETS
348 };
349
350 std::ifstream input( aPath );
351 PADS_NETLIST_SIGNATURE result;
352 SECTION section = SECTION::NONE;
353 std::vector<std::string> pins;
354 auto flush = [&]()
355 {
356 if( pins.empty() )
357 return;
358
359 std::ranges::sort( pins );
360 result.partitions.insert( pins );
361 pins.clear();
362 };
363 std::string line;
364
365 while( std::getline( input, line ) )
366 {
367 if( !line.empty() && line.back() == '\r' )
368 line.pop_back();
369
370 if( line.starts_with( "*PART*" ) )
371 {
372 flush();
373 section = SECTION::PARTS;
374 continue;
375 }
376
377 if( line.starts_with( "*NET*" ) )
378 {
379 flush();
380 section = SECTION::NETS;
381 continue;
382 }
383
384 if( line.starts_with( "*SIGNAL*" ) )
385 {
386 flush();
387 continue;
388 }
389
390 if( line.starts_with( '*' ) )
391 {
392 flush();
393 section = SECTION::NONE;
394 continue;
395 }
396
397 std::istringstream fields( line );
398
399 if( section == SECTION::PARTS )
400 {
401 std::string reference;
402
403 if( fields >> reference )
404 result.parts.insert( std::move( reference ) );
405 }
406 else if( section == SECTION::NETS )
407 {
408 std::string pin;
409
410 while( fields >> pin )
411 pins.push_back( std::move( pin ) );
412 }
413 }
414
415 flush();
416 return result;
417}
418
419
420static SCH_SHEET_PATH sourceSheetPath( SCHEMATIC& aSchematic, const PADS_SCH_BINARY::PADS_SCH_MODEL& aModel,
422{
423 using namespace PADS_SCH_BINARY;
424
425 auto sourceSheet = std::ranges::find( aModel.sheets, aSheetId, &MODEL_SHEET::id );
426 BOOST_REQUIRE( sourceSheet != aModel.sheets.end() );
427 SCH_SHEET_LIST hierarchy = aSchematic.BuildSheetListSortedByPageNumbers();
428
429 if( aModel.sheets.size() == 1 )
430 {
431 BOOST_REQUIRE_EQUAL( hierarchy.size(), 1u );
432 return hierarchy.front();
433 }
434
435 const bool flatTopLevel = aSchematic.GetTopLevelSheets().size() == aModel.sheets.size();
436 wxString expectedPage = wxString::Format( wxS( "%zu" ), sourceSheet->index + ( flatTopLevel ? 1 : 2 ) );
437 auto path = std::ranges::find_if( hierarchy,
438 [&]( const SCH_SHEET_PATH& aPath )
439 {
440 return aPath.size() == ( flatTopLevel ? 1u : 2u )
441 && aPath.GetPageNumber() == expectedPage;
442 } );
443 BOOST_REQUIRE_MESSAGE( path != hierarchy.end(), "missing typed source sheet index " << sourceSheet->index );
444 BOOST_CHECK_EQUAL( path->Last()->GetField( FIELD_T::SHEET_NAME )->GetText(), sourceSheet->name.text );
445 return *path;
446}
447
448
449static void roundTripTopLevelSheets( SCHEMATIC& aSchematic, const wxString& aDirectory )
450{
452 std::vector<TOP_LEVEL_SHEET_INFO> sheetInfos;
453
454 for( size_t index = 0; index < aSchematic.GetTopLevelSheets().size(); ++index )
455 {
456 SCH_SHEET* sheet = aSchematic.GetTopLevelSheet( index );
457 BOOST_REQUIRE( sheet );
458 wxString file =
459 aDirectory + wxFileName::GetPathSeparator() + wxString::Format( wxS( "top_%zu.kicad_sch" ), index + 1 );
460 BOOST_REQUIRE_NO_THROW( io.SaveSchematicFile( file, sheet, &aSchematic ) );
461 sheetInfos.emplace_back( sheet->m_Uuid, sheet->GetName(), file );
462 }
463
464 aSchematic.Reset();
465 std::vector<SCH_SHEET*> loadedSheets;
466
467 for( const TOP_LEVEL_SHEET_INFO& info : sheetInfos )
468 {
469 SCH_SHEET* loaded = nullptr;
470 BOOST_REQUIRE_NO_THROW( loaded = io.LoadSchematicFile( info.filename, &aSchematic ) );
471 BOOST_REQUIRE( loaded );
472 const_cast<KIID&>( loaded->m_Uuid ) = info.uuid;
473 loaded->SetName( info.name );
474 loadedSheets.push_back( loaded );
475 }
476
477 aSchematic.SetTopLevelSheets( loadedSheets );
478 aSchematic.RefreshHierarchy();
479}
480
481
482struct CONNECTIVITY_ORACLE_COUNTS
483{
484 size_t pinEndpoints = 0;
485 size_t powerLabels = 0;
486};
487
488
489static CONNECTIVITY_ORACLE_COUNTS assertSourceConnectivity( const PADS_SCH_BINARY::PADS_SCH_MODEL& aModel,
490 SCHEMATIC& aSchematic )
491{
492 using namespace PADS_SCH_BINARY;
493
494 CONNECTIVITY_ORACLE_COUNTS counts;
495 SCH_SHEET_LIST hierarchy = aSchematic.BuildSheetListSortedByPageNumbers();
496 aSchematic.ConnectionGraph()->Recalculate( hierarchy, true );
497
498 auto samePoint = []( const SOURCE_POINT& aLeft, const SOURCE_POINT& aRight )
499 {
500 return aLeft.x == aRight.x && aLeft.y == aRight.y;
501 };
502
503 using OWNED_SEGMENT = std::tuple<uint32_t, uint32_t, int64_t, int64_t, int64_t, int64_t>;
504 std::set<OWNED_SEGMENT> busEntrySegments;
505
506 auto segmentKey = []( SHEET_ID aSheet, NET_ID aNet, const SOURCE_POINT& aStart, const SOURCE_POINT& aEnd )
507 {
508 if( std::tie( aStart.x, aStart.y ) <= std::tie( aEnd.x, aEnd.y ) )
509 return OWNED_SEGMENT( aSheet.Value(), aNet.Value(), aStart.x, aStart.y, aEnd.x, aEnd.y );
510
511 return OWNED_SEGMENT( aSheet.Value(), aNet.Value(), aEnd.x, aEnd.y, aStart.x, aStart.y );
512 };
513
514 for( const MODEL_BUS& bus : aModel.buses )
515 {
516 SCH_SHEET_PATH path = sourceSheetPath( aSchematic, aModel, bus.sheet.id );
517 SCH_SCREEN* screen = path.LastScreen();
518 BOOST_REQUIRE( screen );
519 const int pageHeight = screen->GetPageSettings().GetHeightIU( schIUScale.IU_PER_MILS );
520 auto sourceSheet = std::ranges::find( aModel.sheets, bus.sheet.id, &MODEL_SHEET::id );
521 BOOST_REQUIRE( sourceSheet != aModel.sheets.end() );
522
523 for( size_t vertex = 1; vertex < bus.vertices.size(); ++vertex )
524 {
525 bool found = false;
526
527 for( SCH_ITEM* item : screen->Items().OfType( SCH_LINE_T ) )
528 {
529 auto* line = static_cast<SCH_LINE*>( item );
530
531 if( line->GetLayer() == LAYER_BUS
532 && line->GetStartPoint() == pagePoint( bus.vertices[vertex - 1], pageHeight )
533 && line->GetEndPoint() == pagePoint( bus.vertices[vertex], pageHeight ) )
534 {
535 BOOST_CHECK_EQUAL( line->GetStroke().GetWidth(),
536 schIUScale.MilsToIU( sourceSheet->defaultBusWidth / 2.0 ) );
537 found = true;
538 break;
539 }
540 }
541
542 BOOST_CHECK_MESSAGE( found, "missing bus segment " << bus.source.recordIndex << ':' << vertex );
543 }
544
545 wxString memberSuffix;
546
547 if( !bus.declaredMembers.empty() )
548 {
549 memberSuffix = wxS( "{" );
550
551 for( size_t member = 0; member < bus.declaredMembers.size(); ++member )
552 {
553 if( member )
554 memberSuffix += wxS( " " );
555
556 memberSuffix += bus.declaredMembers[member].text;
557 }
558
559 memberSuffix += wxS( "}" );
560 }
561 std::vector<wxString> aliases;
562
563 if( bus.aliases.empty() )
564 aliases.push_back( bus.name.text );
565 else
566 {
567 for( const SOURCE_STRING& alias : bus.aliases )
568 aliases.push_back( alias.text );
569 }
570
571 for( const wxString& alias : aliases )
572 {
573 bool foundBusLabel = false;
574
575 for( SCH_ITEM* item : screen->Items().OfType( SCH_LABEL_T ) )
576 {
577 auto* label = static_cast<SCH_LABEL*>( item );
578 foundBusLabel |= label->GetText() == alias + memberSuffix
579 && label->GetPosition() == pagePoint( bus.vertices.front(), pageHeight );
580 }
581
582 BOOST_CHECK_MESSAGE( foundBusLabel, alias );
583 }
584
585 for( const MODEL_BUS_ENTRY& entry : bus.entries )
586 {
587 auto ownerNet = std::ranges::find( aModel.nets, entry.memberNet.id, &MODEL_NET::id );
588 BOOST_REQUIRE( ownerNet != aModel.nets.end() );
589 std::vector<SOURCE_POINT> adjacent;
590
591 for( const MODEL_CONNECTION& connection : ownerNet->connections )
592 {
593 if( connection.vertices.size() >= 2 && samePoint( connection.vertices.front(), entry.position ) )
594 adjacent.push_back( connection.vertices[1] );
595 else if( connection.vertices.size() >= 2 && samePoint( connection.vertices.back(), entry.position ) )
596 adjacent.push_back( connection.vertices[connection.vertices.size() - 2] );
597 }
598
599 BOOST_REQUIRE_EQUAL( adjacent.size(), 1u );
600 busEntrySegments.insert( segmentKey( bus.sheet.id, ownerNet->id, entry.position, adjacent.front() ) );
601 bool found = false;
602
603 for( SCH_ITEM* item : screen->Items().OfType( SCH_BUS_WIRE_ENTRY_T ) )
604 {
605 auto* builtEntry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
606
607 if( builtEntry->GetPosition() == pagePoint( entry.position, pageHeight ) )
608 {
609 const VECTOR2I expectedEnd = pagePoint( adjacent.front(), pageHeight );
610 bool reachesWire = builtEntry->GetEnd() == expectedEnd;
611
612 for( SCH_ITEM* lineItem : screen->Items().OfType( SCH_LINE_T ) )
613 {
614 auto* line = static_cast<SCH_LINE*>( lineItem );
615
616 if( line->GetLayer() == LAYER_WIRE && line->GetStartPoint() == builtEntry->GetEnd()
617 && line->GetEndPoint() == expectedEnd )
618 {
619 reachesWire = true;
620 break;
621 }
622 }
623
624 BOOST_CHECK( reachesWire );
625 BOOST_REQUIRE( builtEntry->Connection( &path ) );
626 found = true;
627 break;
628 }
629 }
630
631 BOOST_CHECK_MESSAGE( found, "missing bus entry " << entry.source.recordIndex );
632 }
633 }
634
635 for( const MODEL_NET& net : aModel.nets )
636 {
637 SCH_SHEET_PATH path = sourceSheetPath( aSchematic, aModel, net.sheet.id );
638 SCH_SCREEN* screen = path.LastScreen();
639 BOOST_REQUIRE( screen );
640 const int pageHeight = screen->GetPageSettings().GetHeightIU( schIUScale.IU_PER_MILS );
641 const MODEL_SHEET& sourceSheet = *std::ranges::find( aModel.sheets, net.sheet.id, &MODEL_SHEET::id );
642
643 for( const MODEL_CONNECTION& sourceConnection : net.connections )
644 {
645 BOOST_REQUIRE_GE( sourceConnection.vertices.size(), 2u );
646
647 for( size_t vertex = 1; vertex < sourceConnection.vertices.size(); ++vertex )
648 {
649 if( busEntrySegments.contains( segmentKey( net.sheet.id, net.id, sourceConnection.vertices[vertex - 1],
650 sourceConnection.vertices[vertex] ) ) )
651 {
652 continue;
653 }
654
655 bool found = false;
656
657 for( SCH_ITEM* item : screen->Items().OfType( SCH_LINE_T ) )
658 {
659 auto* line = static_cast<SCH_LINE*>( item );
660
661 if( line->GetLayer() == LAYER_WIRE
662 && line->GetStartPoint() == pagePoint( sourceConnection.vertices[vertex - 1], pageHeight )
663 && line->GetEndPoint() == pagePoint( sourceConnection.vertices[vertex], pageHeight ) )
664 {
665 BOOST_CHECK_EQUAL( line->GetStroke().GetWidth(), 0 );
666 SCH_CONNECTION* connection = line->Connection( &path );
667 BOOST_REQUIRE( connection );
668 BOOST_CHECK_MESSAGE( netNameMatches( connection->GetNetName(), net.name.text ),
669 "wire net actual='" << connection->GetNetName() << "' expected='"
670 << net.name.text << "' sheet=" << net.sheet.id.Value()
671 << " record=" << sourceConnection.source.recordIndex );
672 found = true;
673 break;
674 }
675 }
676
677 BOOST_CHECK_MESSAGE( found, "missing owned wire segment " << sourceConnection.source.recordIndex << ':'
678 << vertex );
679 }
680
681 for( const MODEL_CONNECTION_ENDPOINT& endpoint : sourceConnection.endpoints )
682 {
683 BOOST_CHECK( samePoint( endpoint.point, sourceConnection.vertices.front() )
684 || samePoint( endpoint.point, sourceConnection.vertices.back() ) );
685
686 if( endpoint.kind != MODEL_ENDPOINT_KIND::PIN )
687 continue;
688
689 BOOST_REQUIRE( endpoint.placement );
690 BOOST_REQUIRE( endpoint.pin );
691 ++counts.pinEndpoints;
692 auto placement = std::ranges::find( aModel.placements, endpoint.placement->id, &MODEL_PLACEMENT::id );
693 BOOST_REQUIRE( placement != aModel.placements.end() );
694 BOOST_CHECK_EQUAL( placement->sheet.id.Value(), net.sheet.id.Value() );
695 BOOST_CHECK( std::ranges::any_of( placement->pins,
696 [&]( const PLACED_PIN_REFERENCE& aPin )
697 {
698 return aPin.id == endpoint.pin->id;
699 } ) );
700 const MODEL_PIN_DEFINITION* sourcePin = nullptr;
701
702 for( const MODEL_SYMBOL_DEFINITION& definition : aModel.definitions )
703 {
704 auto pin = std::ranges::find( definition.pins, endpoint.pin->id, &MODEL_PIN_DEFINITION::id );
705
706 if( pin != definition.pins.end() )
707 {
708 BOOST_REQUIRE( !sourcePin );
709 sourcePin = &*pin;
710 }
711 }
712
713 BOOST_REQUIRE( sourcePin );
714 auto part = std::ranges::find( aModel.partTypes, placement->partType.id, &MODEL_PART_TYPE::id );
715 BOOST_REQUIRE( part != aModel.partTypes.end() );
716 BOOST_REQUIRE( placement->gate );
717 auto gate = std::ranges::find( part->gates, placement->gate->id, &MODEL_GATE::id );
718 BOOST_REQUIRE( gate != part->gates.end() );
719 auto pinReference = std::ranges::find( placement->pins, endpoint.pin->id, &PIN_REFERENCE::id );
720 BOOST_REQUIRE( pinReference != placement->pins.end() );
721 const size_t pinOrdinal = std::distance( placement->pins.begin(), pinReference );
722 wxString expectedNumber = sourcePin->number.text;
723 wxString expectedName = sourcePin->name.text;
724
725 if( !gate->connectorPins.empty() )
726 {
727 BOOST_REQUIRE_GT( placement->unit, 0u );
728 BOOST_REQUIRE_LE( placement->unit, gate->connectorPins.size() );
729 expectedNumber = gate->connectorPins[placement->unit - 1].number.text;
730 expectedName = gate->connectorPins[placement->unit - 1].name.text;
731 }
732 else if( !gate->logicalPins.empty() )
733 {
734 BOOST_REQUIRE_LT( pinOrdinal, gate->logicalPins.size() );
735 expectedNumber = gate->logicalPins[pinOrdinal].number.text;
736 expectedName = gate->logicalPins[pinOrdinal].name.text;
737 }
738
739 SCH_SYMBOL* builtSymbol = nullptr;
740
741 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
742 {
743 auto* symbol = static_cast<SCH_SYMBOL*>( item );
744
745 if( !symbol->GetRef( &path ).StartsWith( wxS( "#PWR" ) )
746 && symbol->GetPosition() == pagePoint( placement->position, pageHeight )
747 && symbol->GetUnit() == static_cast<int>( placement->unit ) )
748 {
749 builtSymbol = symbol;
750 break;
751 }
752 }
753
754 BOOST_REQUIRE( builtSymbol );
755 std::vector<SCH_PIN*> builtPins = builtSymbol->GetPins( &path );
756 auto builtPin = std::ranges::find_if( builtPins,
757 [&]( SCH_PIN* aPin )
758 {
759 return aPin->GetNumber() == expectedNumber
760 && aPin->GetName() == expectedName
761 && aPin->GetPosition()
762 == pagePoint( endpoint.point, pageHeight );
763 } );
764
765 if( builtPin == builtPins.end() )
766 {
767 for( SCH_PIN* candidate : builtPins )
768 {
769 if( candidate->GetNumber() == expectedNumber || candidate->GetName() == expectedName )
770 BOOST_TEST_MESSAGE( "candidate " << candidate->GetNumber() << ' ' << candidate->GetName()
771 << " at " << candidate->GetPosition().x << ','
772 << candidate->GetPosition().y << " orientation "
773 << builtSymbol->GetOrientation() << " raw mirror "
774 << placement->mirrorFlags );
775 }
776 }
777
778 BOOST_REQUIRE_MESSAGE( builtPin != builtPins.end(),
779 placement->reference.text << " pin " << expectedNumber << " " << expectedName
780 << " net " << net.name.text << " at "
781 << endpoint.point.x << ',' << endpoint.point.y );
782 SCH_CONNECTION* pinConnection = ( *builtPin )->Connection( &path );
783 BOOST_REQUIRE( pinConnection );
784
785 if( !netNameMatches( pinConnection->GetNetName(), net.name.text ) )
786 {
787 for( SCH_ITEM* lineItem : screen->Items().OfType( SCH_LINE_T ) )
788 {
789 auto* line = static_cast<SCH_LINE*>( lineItem );
790
791 if( line->GetStartPoint() == ( *builtPin )->GetPosition()
792 || line->GetEndPoint() == ( *builtPin )->GetPosition() )
793 {
794 BOOST_TEST_MESSAGE( "pin-adjacent line layer="
795 << line->GetLayer() << " start=" << line->GetStartPoint().x << ','
796 << line->GetStartPoint().y << " end=" << line->GetEndPoint().x << ','
797 << line->GetEndPoint().y );
798 }
799 }
800
801 for( SCH_ITEM* labelItem : screen->Items().OfType( SCH_LABEL_T ) )
802 {
803 auto* label = static_cast<SCH_LABEL*>( labelItem );
804
805 if( label->GetPosition() == ( *builtPin )->GetPosition() )
806 BOOST_TEST_MESSAGE( "pin-adjacent label '" << label->GetText() << "'" );
807 }
808 }
809
810 BOOST_CHECK_MESSAGE( netNameMatches( pinConnection->GetNetName(), net.name.text ),
811 "pin net actual='" << pinConnection->GetNetName() << "' expected='"
812 << net.name.text << "' pin=" << placement->reference.text << '.'
813 << expectedNumber << " sheet=" << net.sheet.id.Value() );
814 }
815 }
816 }
817
818 for( const MODEL_LABEL& label : aModel.labels )
819 {
821 {
822 continue;
823 }
824
825 SCH_SHEET_PATH path = sourceSheetPath( aSchematic, aModel, label.sheet.id );
826 SCH_SCREEN* screen = path.LastScreen();
827 BOOST_REQUIRE( screen );
828 const int pageHeight = screen->GetPageSettings().GetHeightIU( schIUScale.IU_PER_MILS );
829 auto ownerNet =
830 std::ranges::find_if( aModel.nets,
831 [&]( const MODEL_NET& aNet )
832 {
833 return aNet.sheet.id == label.sheet.id && aNet.name.text == label.text.text;
834 } );
835 BOOST_REQUIRE( ownerNet != aModel.nets.end() );
836
838 {
839 ++counts.powerLabels;
840 bool foundPower = false;
841
842 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
843 {
844 auto* symbol = static_cast<SCH_SYMBOL*>( item );
845
846 if( !symbol->GetRef( &path ).StartsWith( wxS( "#PWR" ) )
847 || symbol->GetPosition() != pagePoint( label.position, pageHeight )
848 || symbol->GetValue( &path, RAW_VALUE ) != label.text.text )
849 {
850 continue;
851 }
852
853 std::vector<SCH_PIN*> pins = symbol->GetPins( &path );
854 BOOST_REQUIRE_EQUAL( pins.size(), 1u );
855 BOOST_CHECK_EQUAL( pins.front()->IsVisible(), false );
856 BOOST_CHECK_EQUAL( pins.front()->GetPosition(), pagePoint( label.position, pageHeight ) );
857 SCH_CONNECTION* connection = pins.front()->Connection( &path );
858 BOOST_REQUIRE( connection );
859 BOOST_CHECK( netNameMatches( connection->GetNetName(), ownerNet->name.text ) );
860 foundPower = true;
861 break;
862 }
863
864 BOOST_CHECK_MESSAGE( foundPower, "missing exact power net " << label.text.text );
865 continue;
866 }
867
870 : SCH_LABEL_T;
871 bool found = false;
872
873 for( SCH_ITEM* item : screen->Items().OfType( type ) )
874 {
875 auto* builtLabel = static_cast<SCH_LABEL_BASE*>( item );
876
877 if( builtLabel->GetText() == label.text.text
878 && builtLabel->GetPosition() == pagePoint( label.position, pageHeight ) )
879 {
880 SCH_CONNECTION* connection = builtLabel->Connection( &path );
881 BOOST_REQUIRE( connection );
882 BOOST_CHECK( netNameMatches( connection->GetNetName(), ownerNet->name.text ) );
883 found = true;
884 break;
885 }
886 }
887
888 BOOST_CHECK_MESSAGE( found, "missing exact label net " << label.text.text );
889 }
890
891 for( const MODEL_JUNCTION& junction : aModel.junctions )
892 {
893 auto relationship = std::ranges::find_if( junction.properties,
894 []( const SOURCE_PROPERTY& aProperty )
895 {
896 return aProperty.name.text == wxS( "connection_record" );
897 } );
898 BOOST_REQUIRE( relationship != junction.properties.end() );
899 unsigned long connectionRecord = 0;
900 BOOST_REQUIRE( relationship->value.text.ToULong( &connectionRecord ) );
901 const MODEL_NET* ownerNet = nullptr;
902
903 for( const MODEL_NET& net : aModel.nets )
904 {
905 if( net.sheet.id != junction.sheet.id )
906 continue;
907
908 auto connection = std::ranges::find_if( net.connections,
909 [&]( const MODEL_CONNECTION& aConnection )
910 {
911 return aConnection.source.recordIndex == connectionRecord;
912 } );
913
914 if( connection != net.connections.end() )
915 {
916 BOOST_REQUIRE( !ownerNet );
917 ownerNet = &net;
918 }
919 }
920
921 BOOST_REQUIRE( ownerNet );
922 SCH_SHEET_PATH path = sourceSheetPath( aSchematic, aModel, junction.sheet.id );
923 SCH_SCREEN* screen = path.LastScreen();
924 BOOST_REQUIRE( screen );
925 const int pageHeight = screen->GetPageSettings().GetHeightIU( schIUScale.IU_PER_MILS );
926 bool found = false;
927
928 for( SCH_ITEM* item : screen->Items().OfType( SCH_JUNCTION_T ) )
929 {
930 if( item->GetPosition() != pagePoint( junction.position, pageHeight ) )
931 continue;
932
933 SCH_CONNECTION* connection = item->Connection( &path );
934 BOOST_REQUIRE( connection );
935 BOOST_CHECK( netNameMatches( connection->GetNetName(), ownerNet->name.text ) );
936 found = true;
937 break;
938 }
939
940 BOOST_CHECK_MESSAGE( found, "missing typed junction " << junction.source.recordIndex );
941 }
942
943 return counts;
944}
945
946
947static std::vector<const PADS_SCH_BINARY::SOURCE_PROPERTY*>
948allSourceProperties( const PADS_SCH_BINARY::PADS_SCH_MODEL& aModel )
949{
950 using namespace PADS_SCH_BINARY;
951
952 std::vector<const SOURCE_PROPERTY*> result;
953 auto append = [&]( const std::vector<SOURCE_PROPERTY>& aProperties )
954 {
955 for( const SOURCE_PROPERTY& property : aProperties )
956 result.push_back( &property );
957 };
958 auto appendPresentation = [&]( const MODEL_TEXT_PRESENTATION& aPresentation )
959 {
960 append( aPresentation.properties );
961 };
962
963 append( aModel.settings.properties );
964
965 for( const MODEL_SHEET& sheet : aModel.sheets )
966 {
967 append( sheet.properties );
968
969 for( const MODEL_GRAPHIC& graphic : sheet.border )
970 {
971 append( graphic.properties );
972 appendPresentation( graphic.presentation );
973 }
974
975 for( const MODEL_FIELD& field : sheet.titleBlockFields )
976 {
977 append( field.properties );
978 appendPresentation( field.presentation );
979 }
980 }
981
982 for( const MODEL_SYMBOL_DEFINITION& definition : aModel.definitions )
983 {
984 append( definition.properties );
985
986 for( const MODEL_GRAPHIC& graphic : definition.graphics )
987 {
988 append( graphic.properties );
989 appendPresentation( graphic.presentation );
990 }
991
992 for( const MODEL_PIN_DEFINITION& pin : definition.pins )
993 {
994 append( pin.properties );
995 appendPresentation( pin.presentation );
996 appendPresentation( pin.namePresentation );
997 appendPresentation( pin.numberPresentation );
998 }
999
1000 for( const MODEL_FIELD& field : definition.fields )
1001 {
1002 append( field.properties );
1003 appendPresentation( field.presentation );
1004 }
1005 }
1006
1007 for( const MODEL_PART_TYPE& part : aModel.partTypes )
1008 {
1009 append( part.properties );
1010
1011 for( const MODEL_GATE& gate : part.gates )
1012 append( gate.properties );
1013
1014 for( const MODEL_FIELD& field : part.fields )
1015 {
1016 append( field.properties );
1017 appendPresentation( field.presentation );
1018 }
1019 }
1020
1021 for( const MODEL_PLACEMENT& placement : aModel.placements )
1022 {
1023 append( placement.properties );
1024
1025 for( const MODEL_FIELD& field : placement.fields )
1026 {
1027 append( field.properties );
1028 appendPresentation( field.presentation );
1029 }
1030 }
1031
1032 for( const MODEL_NET& net : aModel.nets )
1033 {
1034 append( net.properties );
1035
1036 for( const MODEL_CONNECTION& connection : net.connections )
1037 {
1038 append( connection.properties );
1039
1040 for( const MODEL_CONNECTION_ENDPOINT& endpoint : connection.endpoints )
1041 append( endpoint.properties );
1042 }
1043 }
1044
1045 for( const MODEL_BUS& bus : aModel.buses )
1046 {
1047 append( bus.properties );
1048
1049 for( const MODEL_BUS_ENTRY& entry : bus.entries )
1050 append( entry.properties );
1051 }
1052
1053 for( const MODEL_LABEL& label : aModel.labels )
1054 {
1055 append( label.properties );
1056 appendPresentation( label.presentation );
1057 }
1058
1059 for( const MODEL_JUNCTION& junction : aModel.junctions )
1060 append( junction.properties );
1061
1062 for( const MODEL_TEXT& text : aModel.texts )
1063 {
1064 append( text.properties );
1065 appendPresentation( text.presentation );
1066 }
1067
1068 for( const MODEL_PAGE_GRAPHIC& graphic : aModel.graphics )
1069 {
1070 append( graphic.graphic.properties );
1071 appendPresentation( graphic.graphic.presentation );
1072 }
1073
1074 for( const MODEL_WORKSHEET& worksheet : aModel.worksheets )
1075 {
1076 for( const MODEL_GRAPHIC& graphic : worksheet.graphics )
1077 {
1078 append( graphic.properties );
1079 appendPresentation( graphic.presentation );
1080 }
1081 }
1082
1083 return result;
1084}
1085
1086
1087static PIN_ORIENTATION pinOrientation( const PADS_SCH_BINARY::MODEL_PIN_DEFINITION& aPin )
1088{
1089 if( aPin.decalName.text.Contains( wxS( "VRT" ) ) )
1091
1092 switch( PADS_SCH_BINARY::NormalizeAngle( aPin.angle ) )
1093 {
1094 case 900: return aPin.side >= 2 ? PIN_ORIENTATION::PIN_DOWN : PIN_ORIENTATION::PIN_UP;
1095 case 1800: return ( aPin.side & 1 ) != 0 ? PIN_ORIENTATION::PIN_RIGHT : PIN_ORIENTATION::PIN_LEFT;
1096 case 2700: return aPin.side >= 2 ? PIN_ORIENTATION::PIN_UP : PIN_ORIENTATION::PIN_DOWN;
1097 default: return ( aPin.side & 1 ) != 0 ? PIN_ORIENTATION::PIN_LEFT : PIN_ORIENTATION::PIN_RIGHT;
1098 }
1099}
1100
1101
1102static ELECTRICAL_PINTYPE pinType( uint32_t aType )
1103{
1104 const std::array<ELECTRICAL_PINTYPE, 9> types = {
1108 };
1109 return aType < types.size() ? types[aType] : ELECTRICAL_PINTYPE::PT_UNSPECIFIED;
1110}
1111
1112
1113static GRAPHIC_PINSHAPE pinShape( uint32_t aStyle )
1114{
1115 switch( aStyle )
1116 {
1117 case 1: return GRAPHIC_PINSHAPE::INVERTED;
1118 case 2: return GRAPHIC_PINSHAPE::CLOCK;
1119 case 3: return GRAPHIC_PINSHAPE::INVERTED_CLOCK;
1120 default: return GRAPHIC_PINSHAPE::LINE;
1121 }
1122}
1123
1124
1125static LINE_STYLE lineStyle( PADS_SCH_BINARY::MODEL_LINE_STYLE aStyle )
1126{
1127 switch( aStyle )
1128 {
1132 default: return LINE_STYLE::SOLID;
1133 }
1134}
1135
1136
1137static GR_TEXT_H_ALIGN_T horizontalJustification( PADS_SCH_BINARY::MODEL_JUSTIFICATION aJustification )
1138{
1139 switch( aJustification )
1140 {
1143 default: return GR_TEXT_H_ALIGN_LEFT;
1144 }
1145}
1146
1147
1148static GR_TEXT_V_ALIGN_T verticalJustification( PADS_SCH_BINARY::MODEL_JUSTIFICATION aJustification )
1149{
1150 switch( aJustification )
1151 {
1154 default: return GR_TEXT_V_ALIGN_CENTER;
1155 }
1156}
1157
1158
1159static void checkTextPresentation( const EDA_TEXT& aText,
1160 const PADS_SCH_BINARY::MODEL_TEXT_PRESENTATION& aPresentation )
1161{
1162 if( aPresentation.height > 0 )
1163 {
1164 BOOST_CHECK_EQUAL( aText.GetTextSize().x, schIUScale.MilsToIU( aPresentation.height / 2.0 ) );
1165 BOOST_CHECK_EQUAL( aText.GetTextSize().y, schIUScale.MilsToIU( aPresentation.height / 2.0 ) );
1166 }
1167
1168 if( aPresentation.width > 0 )
1169 {
1170 // PADS gives the stroke it renders. Bold is an independent flag in KiCad that multiplies
1171 // the stored thickness, so the import has to store the pre-multiplied value.
1172 const int imported = schIUScale.MilsToIU( aPresentation.width / 2.0 );
1173 const int rendered = aText.GetEffectiveTextPenWidth();
1174 const int expected = ClampTextPenSize( imported, aText.GetTextSize() );
1175
1176 BOOST_CHECK_MESSAGE( std::abs( rendered - expected ) <= 2,
1177 "rendered stroke " << rendered << " does not match the imported " << expected );
1178 }
1179
1180 BOOST_CHECK( aText.GetHorizJustify() == horizontalJustification( aPresentation.horizontalJustification ) );
1181 BOOST_CHECK( aText.GetVertJustify() == verticalJustification( aPresentation.verticalJustification ) );
1182 BOOST_CHECK_EQUAL( aText.IsBold(), aPresentation.bold );
1183 BOOST_CHECK_EQUAL( aText.IsItalic(), aPresentation.italic );
1184 BOOST_CHECK_EQUAL( aText.IsVisible(), aPresentation.visible );
1185}
1186
1187} // namespace
1188
1189
1190BOOST_FIXTURE_TEST_SUITE( PadsSchImport, PADS_SCH_IMPORT_FIXTURE )
1191
1192
1193// A zero OLE trailer box must be skipped before the aspect-ratio divide. llround on the inf that
1194// divide produces is undefined, and the page-size guard that would have caught it runs after the
1195// rescale.
1196BOOST_AUTO_TEST_CASE( BinaryEmbeddedImageDegenerateBoxIsSkipped )
1197{
1198 using namespace PADS_SCH_BINARY;
1199
1200 PADS_SCH_MODEL model = parseBinaryFixture( wxS( "ole_images" ) );
1201
1202 BOOST_REQUIRE( !model.images.empty() );
1203
1204 for( MODEL_EMBEDDED_IMAGE& image : model.images )
1205 image.size.x = 0;
1206
1207 SCH_SHEET* root = m_schematic.GetTopLevelSheet();
1208
1209 BOOST_REQUIRE( root );
1210 BOOST_REQUIRE( root->GetScreen() );
1211
1213 PADS_SCH_BINARY_BUILDER().Build( model, &m_schematic, nullptr, binaryFixture( wxS( "ole_images" ) ) );
1214
1215 BOOST_CHECK_EQUAL( result.counts.images, 0u );
1216 BOOST_CHECK_EQUAL( itemCount( root->GetScreen(), SCH_BITMAP_T ), 0u );
1217}
1218
1219
1220BOOST_AUTO_TEST_CASE( BinaryEmbeddedImages )
1221{
1222 using namespace PADS_SCH_BINARY;
1223
1224 PADS_SCH_MODEL model = parseBinaryFixture( wxS( "ole_images" ) );
1225 BOOST_REQUIRE_GE( model.images[0].data.size(), 14u );
1226 MODEL_EMBEDDED_IMAGE dib = model.images[0];
1227 dib.id = IMAGE_ID( 2 );
1229 dib.streamName = wxS( "synthetic DIB view of Ole10Native BMP" );
1230 dib.position.x += 2000;
1231 dib.data.erase( dib.data.begin(), dib.data.begin() + 14 );
1232 model.images.push_back( std::move( dib ) );
1233
1234 SCH_SHEET* root = m_schematic.GetTopLevelSheet();
1235 BOOST_REQUIRE( root );
1236 BOOST_REQUIRE( root->GetScreen() );
1237
1239 PADS_SCH_BINARY_BUILDER().Build( model, &m_schematic, nullptr, binaryFixture( wxS( "ole_images" ) ) );
1240
1241 BOOST_CHECK_EQUAL( result.counts.images, 3u );
1242 BOOST_REQUIRE_EQUAL( itemCount( root->GetScreen(), SCH_BITMAP_T ), 3u );
1243 const int pageHeight = root->GetScreen()->GetPageSettings().GetHeightIU( schIUScale.IU_PER_MILS );
1244 std::vector<SCH_BITMAP*> bitmaps;
1245
1246 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_BITMAP_T ) )
1247 bitmaps.push_back( static_cast<SCH_BITMAP*>( item ) );
1248
1249 for( const MODEL_EMBEDDED_IMAGE& source : model.images )
1250 {
1251 auto bitmap = std::ranges::find( bitmaps, pagePoint( source.position, pageHeight ), &SCH_BITMAP::GetPosition );
1252 BOOST_REQUIRE( bitmap != bitmaps.end() );
1253 BOOST_CHECK_EQUAL( ( *bitmap )->GetPosition(), pagePoint( source.position, pageHeight ) );
1254 BOOST_CHECK_LE( std::abs( ( *bitmap )->GetReferenceImage().GetSize().x
1255 - schIUScale.MilsToIU( static_cast<double>( source.size.x ) / 2.0 ) ),
1256 2 );
1257 BOOST_CHECK_LE( std::abs( ( *bitmap )->GetReferenceImage().GetSize().y
1258 - schIUScale.MilsToIU( static_cast<double>( source.size.y ) / 2.0 ) ),
1259 schIUScale.MilsToIU( 2 ) );
1260 BOOST_REQUIRE( ( *bitmap )->GetReferenceImage().GetImage().GetOriginalImageData() );
1261 BOOST_CHECK( ( *bitmap )->GetReferenceImage().GetImage().GetOriginalImageData()->IsOk() );
1262 }
1263}
1264
1265
1266BOOST_AUTO_TEST_CASE( CanReadSchematicFile )
1267{
1268 SCH_IO_PADS plugin;
1269
1270 wxString padsFile = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/simple_schematic.txt" );
1271
1272 BOOST_CHECK( plugin.CanReadSchematicFile( padsFile ) );
1273}
1274
1275
1276BOOST_AUTO_TEST_CASE( CanReadSchematicFile_RejectNonPads )
1277{
1278 SCH_IO_PADS plugin;
1279
1280 wxString kicadFile = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/simple_schematic.txt" );
1281
1282 BOOST_CHECK( plugin.CanReadSchematicFile( kicadFile ) );
1283}
1284
1285
1286BOOST_AUTO_TEST_CASE( BinaryDispatch )
1287{
1288 using namespace PADS_SCH_BINARY;
1289
1290 SCH_IO_PADS plugin;
1291 std::vector<uint8_t> v13;
1292 BOOST_REQUIRE( PADS_SCH_BINARY_READER::ReadFile( binaryFixture( wxS( "page_graphics" ) ), v13 ) );
1293 BOOST_REQUIRE_GE( v13.size(), 0x250u );
1294 BOOST_CHECK( PADS_SCH_BINARY_READER::IsBinaryFamily( v13 ) );
1295 BOOST_CHECK( PADS_SCH_BINARY_READER::IsBinarySch( v13 ) );
1296 BOOST_CHECK( PADS_SCH_BINARY_READER::IsSupportedVersion( 0x000C ) );
1297 BOOST_CHECK( PADS_SCH_BINARY_READER::IsSupportedVersion( 0x000D ) );
1298
1299 std::vector<uint8_t> v12 = v13;
1300 v12[2] = 0x0C;
1301 v12[3] = 0x00;
1302 v12[4] = 0x01;
1303 v12[5] = 0x00;
1304 BOOST_CHECK( PADS_SCH_BINARY_READER::IsBinaryFamily( v12 ) );
1305 BOOST_CHECK( PADS_SCH_BINARY_READER::IsBinarySch( v12 ) );
1306
1307 std::vector<uint8_t> malformed = v13;
1308 malformed[1] = 0xFF;
1309 BOOST_CHECK( !PADS_SCH_BINARY_READER::IsBinaryFamily( malformed ) );
1310 BOOST_CHECK( !PADS_SCH_BINARY_READER::IsBinarySch( malformed ) );
1311 BOOST_CHECK( !PADS_SCH_BINARY_READER::IsBinaryFamily( { 0x00 } ) );
1312
1313 std::vector<uint8_t> truncatedHeader( 31, 0x00 );
1314 truncatedHeader[1] = 0xFE;
1315 truncatedHeader[2] = 0x0D;
1316
1317 const std::vector<std::pair<std::vector<uint8_t>, wxString>> truncations = {
1318 { { 0x00, 0xFE }, wxS( "file too small for PADS Logic binary version" ) },
1319 { { 0x00, 0xFE, 0x0D }, wxS( "file too small for PADS Logic binary version" ) },
1320 { { 0x00, 0xFE, 0x0D, 0x00 }, wxS( "file too small for PADS Logic binary header" ) },
1321 { truncatedHeader, wxS( "file too small for PADS Logic binary header" ) }
1322 };
1323
1324 for( const auto& [truncated, expectedError] : truncations )
1325 {
1326 BOOST_CHECK( PADS_SCH_BINARY_READER::IsBinaryFamily( truncated ) );
1327 wxString truncatedBase = wxFileName::CreateTempFileName( wxS( "pads_truncated_" ) );
1328 BOOST_REQUIRE( wxRemoveFile( truncatedBase ) );
1329 wxString truncatedPath = truncatedBase + wxS( ".sch" );
1330 {
1331 std::ofstream output( truncatedPath.fn_str(), std::ios::binary );
1332 output.write( reinterpret_cast<const char*>( truncated.data() ), truncated.size() );
1333 }
1334 BOOST_CHECK( plugin.CanReadSchematicFile( truncatedPath ) );
1335 wxString truncatedError;
1336
1337 try
1338 {
1339 plugin.LoadSchematicFile( truncatedPath, &m_schematic );
1340 BOOST_FAIL( "truncated binary schematic was accepted" );
1341 }
1342 catch( const IO_ERROR& error )
1343 {
1344 truncatedError = error.What();
1345 }
1346
1347 BOOST_CHECK( truncatedError.Contains( wxS( "PADS Logic binary v0x" ) ) );
1348 BOOST_CHECK( truncatedError.Contains( expectedError ) );
1349 BOOST_CHECK( !truncatedError.Contains( wxS( "ASCII" ) ) );
1350 BOOST_CHECK( wxRemoveFile( truncatedPath ) );
1351 }
1352
1353 const wxString ascii =
1354 wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() ) + wxS( "/plugins/pads/simple_schematic.txt" );
1355 BOOST_CHECK( plugin.CanReadSchematicFile( ascii ) );
1356
1357 wxString unrelatedBase = wxFileName::CreateTempFileName( wxS( "pads_unrelated_" ) );
1358 BOOST_REQUIRE( wxRemoveFile( unrelatedBase ) );
1359 wxString unrelated = unrelatedBase + wxS( ".sch" );
1360 {
1361 std::ofstream output( unrelated.fn_str(), std::ios::binary );
1362 output << "unrelated schematic";
1363 }
1364 BOOST_CHECK( !plugin.CanReadSchematicFile( unrelated ) );
1365
1366 std::vector<uint8_t> unsupported = v13;
1367 unsupported[2] = 0x00;
1368 unsupported[3] = 0xFE;
1369 wxString unsupportedBase = wxFileName::CreateTempFileName( wxS( "pads_unsupported_" ) );
1370 BOOST_REQUIRE( wxRemoveFile( unsupportedBase ) );
1371 wxString unsupportedPath = unsupportedBase + wxS( ".sch" );
1372 {
1373 std::ofstream output( unsupportedPath.fn_str(), std::ios::binary );
1374 output.write( reinterpret_cast<const char*>( unsupported.data() ), unsupported.size() );
1375 }
1376 BOOST_CHECK( plugin.CanReadSchematicFile( unsupportedPath ) );
1377 wxString unsupportedError;
1378
1379 try
1380 {
1381 plugin.LoadSchematicFile( unsupportedPath, &m_schematic );
1382 BOOST_FAIL( "unsupported binary schematic was accepted" );
1383 }
1384 catch( const IO_ERROR& error )
1385 {
1386 unsupportedError = error.What();
1387 }
1388
1389 BOOST_CHECK( unsupportedError.Contains( wxS( "v0xFE00" ) ) );
1390 BOOST_CHECK( unsupportedError.Contains( wxS( "unsupported PADS Logic binary version" ) ) );
1391 BOOST_CHECK( !unsupportedError.Contains( wxS( "ASCII" ) ) );
1392
1393 m_schematic.Reset();
1394 CAPTURING_REPORTER reporter;
1395 plugin.SetReporter( &reporter );
1396 BOOST_REQUIRE_NO_THROW( plugin.LoadSchematicFile( binaryFixture( wxS( "page_graphics" ) ), &m_schematic ) );
1397 BOOST_CHECK( std::ranges::none_of( reporter.messages,
1398 []( const auto& aMessage )
1399 {
1400 return aMessage.second == RPT_SEVERITY_INFO;
1401 } ) );
1402 BOOST_CHECK( wxRemoveFile( unrelated ) );
1403 BOOST_CHECK( wxRemoveFile( unsupportedPath ) );
1404}
1405
1406
1408{
1409 IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_PADS ) );
1410 BOOST_CHECK_NE( pi.get(), nullptr );
1411}
1412
1413
1414BOOST_AUTO_TEST_CASE( MultiGateImport )
1415{
1416 SCH_IO_PADS plugin;
1417
1418 wxString padsFile =
1419 wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/multigate_schematic.txt" );
1420
1421 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( padsFile, &m_schematic );
1422 BOOST_REQUIRE( rootSheet );
1423 BOOST_REQUIRE( rootSheet->GetScreen() );
1424
1425 SCH_SCREEN* screen = rootSheet->GetScreen();
1426
1427 // Collect U1 symbols
1428 std::vector<SCH_SYMBOL*> u1Symbols;
1429 SCH_SHEET_PATH rootPath;
1430 rootPath.push_back( rootSheet );
1431
1432 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
1433 {
1434 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
1435
1436 if( sym->GetRef( &rootPath ) == wxT( "U1" ) )
1437 u1Symbols.push_back( sym );
1438 }
1439
1440 BOOST_REQUIRE_EQUAL( u1Symbols.size(), 2u );
1441
1442 // Sort by unit number for deterministic checks
1443 std::sort( u1Symbols.begin(), u1Symbols.end(),
1444 []( const SCH_SYMBOL* a, const SCH_SYMBOL* b )
1445 {
1446 return a->GetUnit() < b->GetUnit();
1447 } );
1448
1449 // Unit 1 (gate A with TL082A decal) should have 5 pins
1450 BOOST_CHECK_EQUAL( u1Symbols[0]->GetUnit(), 1 );
1451 BOOST_CHECK_EQUAL( u1Symbols[0]->GetLibPins().size(), 5u );
1452
1453 // Unit 2 (gate B with TL082 decal) should have 3 pins
1454 BOOST_CHECK_EQUAL( u1Symbols[1]->GetUnit(), 2 );
1455 BOOST_CHECK_EQUAL( u1Symbols[1]->GetLibPins().size(), 3u );
1456
1457 // Both should share the same multi-unit LIB_SYMBOL with 2 units
1458 BOOST_CHECK( u1Symbols[0]->IsMultiUnit() );
1459 BOOST_CHECK_EQUAL( u1Symbols[0]->GetUnitCount(), 2 );
1460
1461 // Both references should be "U1" (not "U1-A" or "U1-B")
1462 BOOST_CHECK_EQUAL( u1Symbols[0]->GetRef( &rootPath ), wxT( "U1" ) );
1463 BOOST_CHECK_EQUAL( u1Symbols[1]->GetRef( &rootPath ), wxT( "U1" ) );
1464}
1465
1466
1467BOOST_AUTO_TEST_CASE( Issue23420_HeaderWithCodePageSuffix )
1468{
1469 // Regression test for https://gitlab.com/kicad/code/kicad/-/issues/23420
1470 // PADS Logic schematics exported with a code page suffix in the header
1471 // (e.g. *PADS-LOGIC-V9.0-CP1250*) must be detected and parsed.
1472 SCH_IO_PADS plugin;
1473
1474 wxString padsFile =
1475 wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/issue23420_codepage_schematic.txt" );
1476
1477 BOOST_CHECK( plugin.CanReadSchematicFile( padsFile ) );
1478
1479 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( padsFile, &m_schematic );
1480
1481 BOOST_REQUIRE( rootSheet );
1482 BOOST_REQUIRE( rootSheet->GetScreen() );
1483}
1484
1485
1486BOOST_AUTO_TEST_CASE( CanReadLibrary )
1487{
1488 SCH_IO_PADS plugin;
1489
1490 wxString padsFile = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/symbols_schematic.txt" );
1491
1492 BOOST_CHECK( plugin.CanReadLibrary( padsFile ) );
1493}
1494
1495
1496// Only the schematic path reads the binary format, so the library predicate must refuse a binary
1497// container even though the schematic predicate accepts it. The file is named .txt because
1498// CanReadLibrary() screens .sch out by extension, which would make the check vacuous.
1499BOOST_AUTO_TEST_CASE( CanReadLibraryRefusesBinaryContainer )
1500{
1501 // Minimum container the binary sniffer accepts: magic 00 FE, version 0x000D, 0x250 bytes
1502 std::vector<uint8_t> container( 0x250, 0 );
1503
1504 container[0] = 0x00;
1505 container[1] = 0xFE;
1506 container[2] = 0x0D;
1507 container[3] = 0x00;
1508
1509 std::filesystem::path binaryAsTxt =
1510 std::filesystem::temp_directory_path() / "kicad_pads_binary_container.txt";
1511
1512 {
1513 std::ofstream out( binaryAsTxt, std::ios::binary );
1514
1515 out.write( reinterpret_cast<const char*>( container.data() ),
1516 static_cast<std::streamsize>( container.size() ) );
1517 }
1518
1519 SCH_IO_PADS plugin;
1520 wxString path = wxString::FromUTF8( binaryAsTxt.string() );
1521
1522 BOOST_CHECK( plugin.CanReadSchematicFile( path ) );
1523 BOOST_CHECK( !plugin.CanReadLibrary( path ) );
1524
1525 std::filesystem::remove( binaryAsTxt );
1526}
1527
1528
1529BOOST_AUTO_TEST_CASE( EnumerateSymbolLib_NamesFromSchematic )
1530{
1531 SCH_IO_PADS plugin;
1532
1533 wxString padsFile = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/symbols_schematic.txt" );
1534
1535 wxArrayString names;
1536 BOOST_CHECK_NO_THROW( plugin.EnumerateSymbolLib( names, padsFile ) );
1537 BOOST_CHECK_GT( names.GetCount(), 0u );
1538}
1539
1540
1541BOOST_AUTO_TEST_CASE( EnumerateSymbolLib_ReturnsLibSymbols )
1542{
1543 SCH_IO_PADS plugin;
1544
1545 wxString padsFile = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/symbols_schematic.txt" );
1546
1547 std::vector<LIB_SYMBOL*> symbols;
1548 BOOST_CHECK_NO_THROW( plugin.EnumerateSymbolLib( symbols, padsFile ) );
1549 BOOST_CHECK_GT( symbols.size(), 0u );
1550
1551 for( LIB_SYMBOL* sym : symbols )
1552 BOOST_REQUIRE( sym != nullptr );
1553}
1554
1555
1556BOOST_AUTO_TEST_CASE( LoadSymbol_ByName )
1557{
1558 SCH_IO_PADS plugin;
1559
1560 wxString padsFile = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/symbols_schematic.txt" );
1561
1562 wxArrayString names;
1563 plugin.EnumerateSymbolLib( names, padsFile );
1564
1565 BOOST_REQUIRE_GT( names.GetCount(), 0u );
1566
1567 LIB_SYMBOL* sym = plugin.LoadSymbol( padsFile, names.Item( 0 ) );
1568 BOOST_REQUIRE( sym != nullptr );
1569 BOOST_CHECK_EQUAL( sym->GetName(), names.Item( 0 ) );
1570}
1571
1572
1573BOOST_AUTO_TEST_CASE( LoadSymbol_UnknownReturnsNull )
1574{
1575 SCH_IO_PADS plugin;
1576
1577 wxString padsFile = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/symbols_schematic.txt" );
1578
1579 LIB_SYMBOL* sym = plugin.LoadSymbol( padsFile, wxT( "NO_SUCH_SYMBOL_12345" ) );
1580 BOOST_CHECK( sym == nullptr );
1581}
1582
1583
1584BOOST_AUTO_TEST_CASE( MultiGatePartTypeBecomesMultiUnitLibSymbol )
1585{
1586 SCH_IO_PADS plugin;
1587
1588 wxString padsFile =
1589 wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/multigate_schematic.txt" );
1590
1591 std::vector<LIB_SYMBOL*> symbols;
1592 BOOST_CHECK_NO_THROW( plugin.EnumerateSymbolLib( symbols, padsFile ) );
1593
1594 bool foundMultiUnit = false;
1595
1596 for( LIB_SYMBOL* sym : symbols )
1597 {
1598 if( sym && sym->GetUnitCount() > 1 )
1599 {
1600 foundMultiUnit = true;
1601 break;
1602 }
1603 }
1604
1605 BOOST_CHECK( foundMultiUnit );
1606}
1607
1608
1609BOOST_AUTO_TEST_CASE( IsLibraryNotWritable )
1610{
1611 SCH_IO_PADS plugin;
1612
1613 wxString padsFile = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/symbols_schematic.txt" );
1614
1615 BOOST_CHECK( !plugin.IsLibraryWritable( padsFile ) );
1616}
1617
1618
1619BOOST_AUTO_TEST_CASE( Issue24284_TextItemsPlacedOnCorrectSheet )
1620{
1621 // Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24284
1622 // Multi-sheet PADS Logic schematics have one *TEXT* and *LINES* block per
1623 // *SHT*. Before the fix every text/line item was placed on the first
1624 // sheet, causing page-number text from all sheets to stack on top of each
1625 // other and border graphics to overlap.
1626 SCH_IO_PADS plugin;
1627
1628 wxString padsFile =
1629 wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/issue24284_multisheet_text.txt" );
1630
1631 BOOST_REQUIRE( plugin.CanReadSchematicFile( padsFile ) );
1632
1633 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( padsFile, &m_schematic );
1634 BOOST_REQUIRE( rootSheet );
1635 BOOST_REQUIRE( rootSheet->GetScreen() );
1636
1637 // Collect text and line content keyed by hierarchical sheet name.
1638 std::map<wxString, std::vector<wxString>> textBySheet;
1639 std::map<wxString, int> lineCountBySheet;
1640
1641 for( SCH_ITEM* item : rootSheet->GetScreen()->Items().OfType( SCH_SHEET_T ) )
1642 {
1643 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
1644 wxString sheetName = sheet->GetField( FIELD_T::SHEET_NAME )->GetText();
1645
1646 for( SCH_ITEM* screenItem : sheet->GetScreen()->Items().OfType( SCH_TEXT_T ) )
1647 {
1648 SCH_TEXT* txt = static_cast<SCH_TEXT*>( screenItem );
1649 textBySheet[sheetName].push_back( txt->GetText() );
1650 }
1651
1652 for( SCH_ITEM* screenItem : sheet->GetScreen()->Items().OfType( SCH_LINE_T ) )
1653 {
1654 (void) screenItem;
1655 lineCountBySheet[sheetName]++;
1656 }
1657 }
1658
1659 for( int sheetNum = 1; sheetNum <= 3; ++sheetNum )
1660 {
1661 wxString sheetName = wxString::Format( wxT( "Page%d" ), sheetNum );
1662 wxString pageText = wxString::Format( wxT( "PAGE %d OF 3" ), sheetNum );
1663 wxString bodyText = wxString::Format( wxT( "TEXT ON SHEET %d" ), sheetNum );
1664
1665 BOOST_REQUIRE_EQUAL( textBySheet.count( sheetName ), 1u );
1666 BOOST_CHECK_EQUAL( textBySheet[sheetName].size(), 2u );
1667 BOOST_CHECK( std::find( textBySheet[sheetName].begin(), textBySheet[sheetName].end(), pageText )
1668 != textBySheet[sheetName].end() );
1669 BOOST_CHECK( std::find( textBySheet[sheetName].begin(), textBySheet[sheetName].end(), bodyText )
1670 != textBySheet[sheetName].end() );
1671 BOOST_CHECK_EQUAL( lineCountBySheet[sheetName], 1 );
1672 }
1673}
1674
1675
1676// Issue 23855 (#1): an off-page connector whose stub wire is zero-length must take its
1677// global-label orientation from the authoritative *NETNAMES* offset, not from the
1678// degenerate wire direction. The two SP1 anchors carry opposite X offsets and must yield
1679// opposite spin styles.
1680BOOST_AUTO_TEST_CASE( Issue23855_GlobalLabelOrientationFromNetNames )
1681{
1682 SCH_IO_PADS plugin;
1683
1684 wxString padsFile =
1685 wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/issue23855_schematic.txt" );
1686
1687 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( padsFile, &m_schematic );
1688 BOOST_REQUIRE( rootSheet );
1689 BOOST_REQUIRE( rootSheet->GetScreen() );
1690
1691 SCH_SCREEN* screen = rootSheet->GetScreen();
1692
1693 // PADS anchor positions in mils -> KiCad screen X (Y-up flipped on import).
1694 const int milToIU = schIUScale.MilsToIU( 1 );
1695 const int cnSideX = 1400 * milToIU; // @@@O0, x_offset +350 -> text reads right
1696 const int r1SideX = 2800 * milToIU; // @@@O1, x_offset -360 -> text reads left
1697
1700 bool foundCn = false;
1701 bool foundR1 = false;
1702
1703 for( SCH_ITEM* item : screen->Items().OfType( SCH_GLOBAL_LABEL_T ) )
1704 {
1705 SCH_LABEL_BASE* lbl = static_cast<SCH_LABEL_BASE*>( item );
1706
1707 if( lbl->GetText() != wxT( "SP1" ) )
1708 continue;
1709
1710 if( lbl->GetPosition().x == cnSideX )
1711 {
1712 foundCn = true;
1713 cnSpin = lbl->GetSpinStyle();
1714 }
1715 else if( lbl->GetPosition().x == r1SideX )
1716 {
1717 foundR1 = true;
1718 r1Spin = lbl->GetSpinStyle();
1719 }
1720 }
1721
1722 BOOST_REQUIRE( foundCn );
1723 BOOST_REQUIRE( foundR1 );
1724
1725 // The CN1-side label extends to the right; the R1-side label (degenerate wire)
1726 // extends to the left thanks to the NETNAMES override.
1727 BOOST_CHECK( cnSpin == SPIN_STYLE::RIGHT );
1728 BOOST_CHECK( r1Spin == SPIN_STYLE::LEFT );
1729}
1730
1731
1732// Issue 23855 (#5): a 90 degree rotated part must place its reference and value fields at
1733// the absolute coordinates authored in PADS. PADS stores attribute offsets in the placed
1734// (post-rotation) frame, so the importer applies the offset directly without re-rotating.
1735BOOST_AUTO_TEST_CASE( Issue23855_RotatedPartFieldPositions )
1736{
1737 SCH_IO_PADS plugin;
1738
1739 wxString padsFile =
1740 wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/issue23855_schematic.txt" );
1741
1742 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( padsFile, &m_schematic );
1743 BOOST_REQUIRE( rootSheet );
1744 BOOST_REQUIRE( rootSheet->GetScreen() );
1745
1746 SCH_SCREEN* screen = rootSheet->GetScreen();
1747 SCH_SHEET_PATH rootPath;
1748 rootPath.push_back( rootSheet );
1749
1750 SCH_SYMBOL* d5 = nullptr;
1751
1752 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
1753 {
1754 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
1755
1756 if( sym->GetRef( &rootPath ) == wxT( "D5" ) )
1757 d5 = sym;
1758 }
1759
1760 BOOST_REQUIRE( d5 != nullptr );
1761
1762 SCH_FIELD* refF = d5->GetField( FIELD_T::REFERENCE );
1763 SCH_FIELD* valF = d5->GetField( FIELD_T::VALUE );
1764
1765 VECTOR2I symPos = d5->GetPosition();
1766 VECTOR2I refRel = refF->GetPosition() - symPos;
1767 VECTOR2I valRel = valF->GetPosition() - symPos;
1768
1769 // REF-DES PADS offset (210, 230); PART-TYPE/value PADS offset (-70, 520). PADS Y is up,
1770 // so the screen Y offset is negated.
1771 BOOST_CHECK_EQUAL( refRel.x, schIUScale.MilsToIU( 210 ) );
1772 BOOST_CHECK_EQUAL( refRel.y, -schIUScale.MilsToIU( 230 ) );
1773 BOOST_CHECK_EQUAL( valRel.x, schIUScale.MilsToIU( -70 ) );
1774 BOOST_CHECK_EQUAL( valRel.y, -schIUScale.MilsToIU( 520 ) );
1775
1776 // Rotated attribute text keeps the PADS text angle and the authored justification
1777 // (codes 4 and 5 both decode to top-left in the text's reading frame).
1778 BOOST_CHECK_EQUAL( refF->GetTextAngle().AsDegrees(), 90.0 );
1779 BOOST_CHECK_EQUAL( valF->GetTextAngle().AsDegrees(), 90.0 );
1784}
1785
1786
1787BOOST_AUTO_TEST_CASE( BinarySymbolsAndSheets )
1788{
1789 using namespace PADS_SCH_BINARY;
1790
1791 PADS_SCH_MODEL model = parseBinaryFixture( wxS( "symbol_primitives" ) );
1792 auto setTitleField = [&]( const wxString& aName, const wxString& aValue )
1793 {
1794 auto field = std::ranges::find( model.sheets.front().titleBlockFields, aName,
1795 []( const MODEL_FIELD& aField )
1796 {
1797 return aField.name.text;
1798 } );
1799 BOOST_REQUIRE( field != model.sheets.front().titleBlockFields.end() );
1800 field->value.text = aValue;
1801 };
1802 setTitleField( wxS( "Drawn By" ), wxS( "DB" ) );
1803 setTitleField( wxS( "QC By" ), wxS( "QB" ) );
1804 setTitleField( wxS( "Released By" ), wxS( "RB" ) );
1805 setTitleField( wxS( "QC Date" ), wxS( "QD" ) );
1806 setTitleField( wxS( "Release Date" ), wxS( "RD" ) );
1807 setTitleField( wxS( "Company Name" ), wxS( "Company" ) );
1808 setTitleField( wxS( "Code" ), wxS( "Code" ) );
1809
1810 BOOST_REQUIRE( !model.worksheets.empty() );
1811 auto worksheetTemplate = std::ranges::find_if( model.worksheets.front().graphics,
1812 []( const MODEL_GRAPHIC& aGraphic )
1813 {
1814 return aGraphic.kind == MODEL_GRAPHIC_KIND::TEXT;
1815 } );
1816 BOOST_REQUIRE( worksheetTemplate != model.worksheets.front().graphics.end() );
1817 MODEL_WORKSHEET worksheet;
1818 worksheet.source = worksheetTemplate->source;
1819 worksheet.sheet = { model.sheets.front().id, worksheet.source };
1820 worksheet.name.text = wxS( "CI_WORKSHEET" );
1821 worksheet.name.source = worksheet.source;
1822 const std::array<std::pair<wxString, SOURCE_POINT>, 4> worksheetMarkers = {
1823 std::pair{ wxS( "TOP_LEFT" ), SOURCE_POINT{ model.sheets.front().pageSize.x, 0 } },
1824 std::pair{ wxS( "TOP_RIGHT" ), SOURCE_POINT{ 0, 0 } },
1825 std::pair{ wxS( "BOTTOM_LEFT" ), model.sheets.front().pageSize },
1826 std::pair{ wxS( "BOTTOM_RIGHT" ), SOURCE_POINT{ 0, model.sheets.front().pageSize.y } }
1827 };
1828
1829 for( const auto& [text, position] : worksheetMarkers )
1830 {
1831 MODEL_GRAPHIC graphic = *worksheetTemplate;
1832 graphic.text.text = text;
1833 graphic.points = { position };
1834 worksheet.graphics.push_back( std::move( graphic ) );
1835 }
1836
1837 model.worksheets = { std::move( worksheet ) };
1838
1839 SCH_SHEET* destination = m_schematic.GetTopLevelSheet();
1840 BOOST_REQUIRE( destination );
1841 BOOST_REQUIRE( destination->GetScreen() );
1842
1844 BUILD_RESULT result = builder.Build( model, &m_schematic, nullptr, binaryFixture( wxS( "symbol_primitives" ) ) );
1845
1846 BOOST_CHECK_EQUAL( result.counts.sheets, 1u );
1847 BOOST_CHECK_EQUAL( result.counts.symbols, model.placements.size() );
1848 BOOST_CHECK( destination->m_Uuid == destination->GetScreen()->GetUuid() );
1849
1851 path.push_back( destination );
1852 std::map<wxString, SCH_SYMBOL*> symbols;
1853
1854 for( SCH_ITEM* item : destination->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
1855 {
1856 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
1857 symbols.emplace( symbol->GetRef( &path ), symbol );
1858 }
1859
1860 for( const MODEL_PLACEMENT& placement : model.placements )
1861 {
1862 auto it = symbols.find( placement.reference.text );
1863 BOOST_REQUIRE_MESSAGE( it != symbols.end(), placement.reference.text );
1864 SCH_SYMBOL* symbol = it->second;
1865 BOOST_CHECK_EQUAL( symbol->GetUnit(), placement.unit );
1866 BOOST_CHECK_EQUAL( symbol->GetPosition().x,
1867 schIUScale.MilsToIU( static_cast<double>( placement.position.x ) / 2.0 ) );
1868 BOOST_CHECK_EQUAL( symbol->GetLibPins().size(), placement.pins.size() );
1869 BOOST_REQUIRE( symbol->GetLibSymbolRef() );
1870 BOOST_CHECK( !symbol->GetLibSymbolRef()->GetDrawItems().empty() );
1871 BOOST_CHECK( destination->GetScreen()->GetLibSymbols().contains( symbol->GetSchSymbolLibraryName() ) );
1872 }
1873
1874 SCH_SYMBOL* primitiveSymbol = symbols.at( wxS( "U1" ) );
1875 const MODEL_SYMBOL_DEFINITION& primitiveDefinition =
1876 *std::ranges::find_if( model.definitions,
1877 []( const MODEL_SYMBOL_DEFINITION& aDefinition )
1878 {
1879 return aDefinition.name.text == wxS( "BATCHB_PRIMITIVES" );
1880 } );
1881 std::vector<SCH_SHAPE*> primitiveShapes;
1882 std::vector<SCH_TEXT*> primitiveTexts;
1883
1884 for( const SCH_ITEM& item : primitiveSymbol->GetLibSymbolRef()->GetDrawItems() )
1885 {
1886 if( item.Type() == SCH_SHAPE_T )
1887 primitiveShapes.push_back( static_cast<SCH_SHAPE*>( const_cast<SCH_ITEM*>( &item ) ) );
1888 else if( item.Type() == SCH_TEXT_T )
1889 primitiveTexts.push_back( static_cast<SCH_TEXT*>( const_cast<SCH_ITEM*>( &item ) ) );
1890 }
1891
1892 BOOST_REQUIRE_EQUAL( primitiveShapes.size(), 5u );
1893 auto shapeOfType = [&]( SHAPE_T aType )
1894 {
1895 return std::ranges::find_if( primitiveShapes,
1896 [&]( const SCH_SHAPE* aShape )
1897 {
1898 return aShape->GetShape() == aType;
1899 } );
1900 };
1901 BOOST_CHECK_EQUAL( std::ranges::count_if( primitiveShapes,
1902 []( const SCH_SHAPE* aShape )
1903 {
1904 return aShape->GetShape() == SHAPE_T::POLY;
1905 } ),
1906 3 );
1907 auto circle = shapeOfType( SHAPE_T::CIRCLE );
1908 auto arc = shapeOfType( SHAPE_T::ARC );
1909 BOOST_REQUIRE( circle != primitiveShapes.end() );
1910 BOOST_REQUIRE( arc != primitiveShapes.end() );
1912 ( *arc )->GetStroke().GetWidth(),
1913 schIUScale.MilsToIU( static_cast<double>( primitiveDefinition.graphics[3].strokeWidth ) / 2.0 ) );
1914 BOOST_CHECK( ( *arc )->GetEffectiveLineStyle() == LINE_STYLE::SOLID );
1916 ( *arc )->GetCenter().x,
1917 schIUScale.MilsToIU( static_cast<double>( primitiveDefinition.graphics[3].arcCenter.x ) / 2.0 ) );
1919 ( *arc )->GetCenter().y,
1920 -schIUScale.MilsToIU( static_cast<double>( primitiveDefinition.graphics[3].arcCenter.y ) / 2.0 ) );
1921 BOOST_CHECK( std::ranges::any_of( primitiveShapes,
1922 []( const SCH_SHAPE* aShape )
1923 {
1924 return aShape->GetFillMode() == FILL_T::FILLED_SHAPE;
1925 } ) );
1926 BOOST_REQUIRE_EQUAL( primitiveTexts.size(), 1u );
1927 BOOST_CHECK_EQUAL( primitiveTexts[0]->GetText(), primitiveDefinition.graphics[5].text.text );
1928 BOOST_CHECK_EQUAL( primitiveTexts[0]->GetPosition(), localPoint( primitiveDefinition.graphics[5].points[0] ) );
1930 primitiveTexts[0]->GetTextHeight(),
1931 schIUScale.MilsToIU( static_cast<double>( primitiveDefinition.graphics[5].presentation.height ) / 2.0 ) );
1932
1934 model.sheets.front().pageSize.x / 2 );
1936 model.sheets.front().pageSize.y / 2 );
1937 BOOST_CHECK_EQUAL( destination->GetScreen()->GetTitleBlock().GetTitle(), model.sheets.front().title.text );
1938 const EMBEDDED_FILES::EMBEDDED_FILE* embeddedWorksheet =
1939 m_schematic.GetEmbeddedFiles()->GetEmbeddedFile( wxS( "pads_import.kicad_wks" ) );
1940 BOOST_REQUIRE( embeddedWorksheet );
1941 const std::string worksheetData( embeddedWorksheet->decompressedData.begin(),
1942 embeddedWorksheet->decompressedData.end() );
1943 const double pageWidthMm = model.sheets.front().pageSize.x * 0.0127;
1944 const double pageHeightMm = model.sheets.front().pageSize.y * 0.0127;
1945 std::ostringstream topRight;
1946 std::ostringstream bottomLeft;
1947 std::ostringstream bottomRight;
1948 topRight.imbue( std::locale::classic() );
1949 bottomLeft.imbue( std::locale::classic() );
1950 bottomRight.imbue( std::locale::classic() );
1951 topRight << "(tbtext \"TOP_RIGHT\" (name \"\") (pos " << pageWidthMm << " 0";
1952 bottomLeft << "(tbtext \"BOTTOM_LEFT\" (name \"\") (pos 0 " << pageHeightMm;
1953 bottomRight << "(tbtext \"BOTTOM_RIGHT\" (name \"\") (pos " << pageWidthMm << ' ' << pageHeightMm;
1954 BOOST_CHECK_NE( worksheetData.find( "(tbtext \"TOP_LEFT\" (name \"\") (pos 0 0" ), std::string::npos );
1955 BOOST_CHECK_NE( worksheetData.find( topRight.str() ), std::string::npos );
1956 BOOST_CHECK_NE( worksheetData.find( bottomLeft.str() ), std::string::npos );
1957 BOOST_CHECK_NE( worksheetData.find( bottomRight.str() ), std::string::npos );
1958 const TITLE_BLOCK& titleBlock = destination->GetScreen()->GetTitleBlock();
1959 BOOST_CHECK_EQUAL( titleBlock.GetCompany(), wxS( "Company" ) );
1960 BOOST_CHECK_EQUAL( titleBlock.GetComment( 0 ), wxS( "QB" ) );
1961 BOOST_CHECK_EQUAL( titleBlock.GetComment( 1 ), wxS( "RB" ) );
1962 BOOST_CHECK_EQUAL( titleBlock.GetComment( 2 ), wxS( "DB" ) );
1963 BOOST_CHECK_EQUAL( titleBlock.GetComment( 3 ), wxString() );
1964 BOOST_CHECK_EQUAL( titleBlock.GetComment( 4 ), wxString() );
1965 BOOST_CHECK_EQUAL( titleBlock.GetComment( 5 ), wxS( "QD" ) );
1966 BOOST_CHECK_EQUAL( titleBlock.GetComment( 6 ), wxS( "RD" ) );
1967 BOOST_CHECK_EQUAL( titleBlock.GetComment( 7 ), wxString() );
1968 BOOST_CHECK_EQUAL( titleBlock.GetComment( 8 ), wxString() );
1969
1970 size_t builtGraphics = itemCount( destination->GetScreen(), SCH_SHAPE_T );
1971
1972 for( SCH_ITEM* item : destination->GetScreen()->Items().OfType( SCH_LINE_T ) )
1973 {
1974 if( item->GetLayer() == LAYER_NOTES )
1975 ++builtGraphics;
1976 }
1977
1978 BOOST_CHECK_EQUAL( result.counts.graphics, builtGraphics );
1979
1980 m_schematic.Reset();
1981 destination = m_schematic.GetTopLevelSheet();
1982 PADS_SCH_MODEL pinModel = parseBinaryFixture( wxS( "pin_styles" ) );
1983 MODEL_PART_TYPE& pinPart = *std::ranges::find_if( pinModel.partTypes,
1984 []( const MODEL_PART_TYPE& aPart )
1985 {
1986 return aPart.name.text == wxS( "BATCHB-PIN-STYLES" );
1987 } );
1988 BOOST_REQUIRE_EQUAL( pinPart.gates.size(), 1u );
1989 BOOST_REQUIRE_EQUAL( pinPart.gates[0].logicalPins.size(), 7u );
1990
1991 for( MODEL_SYMBOL_DEFINITION& definition : pinModel.definitions )
1992 {
1993 if( definition.name.text != wxS( "BATCHB_PIN_STYLES" ) )
1994 continue;
1995
1996 for( MODEL_PIN_DEFINITION& pin : definition.pins )
1997 {
1998 pin.number.text = wxS( "decal-number" );
1999 pin.name.text = wxS( "decal-name" );
2000 }
2001 }
2002
2003 result = builder.Build( pinModel, &m_schematic, destination, binaryFixture( wxS( "pin_styles" ) ) );
2004 SCH_SHEET_PATH pinPath;
2005 pinPath.push_back( destination );
2006 SCH_SYMBOL* pinSymbol = nullptr;
2007
2008 for( SCH_ITEM* item : destination->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
2009 {
2010 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2011
2012 if( symbol->GetRef( &pinPath ) == wxS( "U2" ) )
2013 pinSymbol = symbol;
2014 }
2015
2016 BOOST_REQUIRE( pinSymbol );
2017 BOOST_CHECK( destination->GetScreen()->GetLibSymbols().contains( pinSymbol->GetSchSymbolLibraryName() ) );
2018 const MODEL_SYMBOL_DEFINITION& pinDefinition =
2019 *std::ranges::find_if( pinModel.definitions,
2020 []( const MODEL_SYMBOL_DEFINITION& aDefinition )
2021 {
2022 return aDefinition.name.text == wxS( "BATCHB_PIN_STYLES" );
2023 } );
2024 std::vector<SCH_PIN*> builtPins = pinSymbol->GetLibPins();
2025 BOOST_REQUIRE_EQUAL( builtPins.size(), pinDefinition.pins.size() + pinPart.signalPins.size() );
2026
2027 size_t sizedNamePins = 0;
2028 size_t sizedNumberPins = 0;
2029
2030 for( size_t pinOrdinal = 0; pinOrdinal < pinDefinition.pins.size(); ++pinOrdinal )
2031 {
2032 const MODEL_PIN_DEFINITION& sourcePin = pinDefinition.pins[pinOrdinal];
2033 const MODEL_GATE_PIN& logicalPin = pinPart.gates[0].logicalPins[pinOrdinal];
2034 auto built = std::ranges::find( builtPins, logicalPin.number.text, &SCH_PIN::GetNumber );
2035 BOOST_REQUIRE_MESSAGE( built != builtPins.end(), logicalPin.number.text );
2036 BOOST_CHECK_EQUAL( ( *built )->GetName(), logicalPin.name.text );
2037 BOOST_CHECK_EQUAL( ( *built )->GetPosition(), localPoint( sourcePin.position ) );
2038 BOOST_CHECK_EQUAL( ( *built )->GetLength(),
2039 schIUScale.MilsToIU( static_cast<double>( sourcePin.length ) / 2.0 ) );
2040 BOOST_CHECK( ( *built )->GetOrientation() == pinOrientation( sourcePin ) );
2041 BOOST_CHECK( ( *built )->GetType() == pinType( sourcePin.electricalType ) );
2042 BOOST_CHECK( ( *built )->GetShape() == pinShape( sourcePin.graphicStyle ) );
2043 BOOST_CHECK_EQUAL( ( *built )->IsVisible(), sourcePin.presentation.visible );
2044 BOOST_CHECK_EQUAL( ( *built )->GetNameTextSize(),
2045 schIUScale.MilsToIU( static_cast<double>( sourcePin.namePresentation.height ) / 2.0 ) );
2046 BOOST_CHECK_EQUAL( ( *built )->GetNumberTextSize(),
2047 schIUScale.MilsToIU( static_cast<double>( sourcePin.numberPresentation.height ) / 2.0 ) );
2048
2049 if( sourcePin.namePresentation.height > 0 )
2050 sizedNamePins++;
2051
2052 if( sourcePin.numberPresentation.height > 0 )
2053 sizedNumberPins++;
2054 }
2055
2056 // Each check compares a derived value against a derived value, so it passes on a zero height no
2057 // matter what the builder does
2058 BOOST_CHECK_GT( sizedNamePins, 0u );
2059 BOOST_CHECK_GT( sizedNumberPins, 0u );
2060
2061 m_schematic.Reset();
2062 destination = m_schematic.GetTopLevelSheet();
2063 const PADS_SCH_MODEL transformModel = parseBinaryFixture( wxS( "placement_transform" ) );
2064 result = builder.Build( transformModel, &m_schematic, destination, binaryFixture( wxS( "placement_transform" ) ) );
2065 SCH_SHEET_PATH transformPath;
2066 transformPath.push_back( destination );
2067
2068 for( SCH_ITEM* item : destination->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
2069 {
2070 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2071 const MODEL_PLACEMENT& placement =
2072 *std::ranges::find_if( transformModel.placements,
2073 [&]( const MODEL_PLACEMENT& aPlacement )
2074 {
2075 return aPlacement.reference.text == symbol->GetRef( &transformPath );
2076 } );
2077 int expectedOrientation = SYM_ORIENT_0;
2078
2079 switch( placement.angle )
2080 {
2081 case 900: expectedOrientation = SYM_ORIENT_90; break;
2082 case 1800: expectedOrientation = SYM_ORIENT_180; break;
2083 case 2700: expectedOrientation = SYM_ORIENT_270; break;
2084 default: break;
2085 }
2086
2087 if( placement.mirrorFlags & 1 )
2088 expectedOrientation |= SYM_MIRROR_Y;
2089
2090 if( placement.mirrorFlags & 2 )
2091 expectedOrientation |= SYM_MIRROR_X;
2092
2093 SCH_SYMBOL expected( *symbol );
2094 expected.SetOrientation( expectedOrientation );
2095 BOOST_CHECK( symbol->GetTransform() == expected.GetTransform() );
2096 }
2097
2098 BOOST_CHECK_EQUAL( itemCount( destination->GetScreen(), SCH_SYMBOL_T ), transformModel.placements.size() );
2099
2100 PADS_SCH_MODEL unknownTransform = transformModel;
2101 auto rawAngleIt = std::ranges::find_if( unknownTransform.placements.front().properties,
2102 []( const SOURCE_PROPERTY& aProperty )
2103 {
2104 return aProperty.name.text == wxS( "raw_angle" );
2105 } );
2106 BOOST_REQUIRE( rawAngleIt != unknownTransform.placements.front().properties.end() );
2107 SOURCE_PROPERTY& rawAngle = *rawAngleIt;
2108 rawAngle.value.text = wxS( "3600" );
2110 unknownTransform.placements.front().angle = 0;
2111 result = builder.Build( unknownTransform, &m_schematic, destination, wxS( "unknown_transform.sch" ) );
2112 BOOST_CHECK( std::ranges::any_of( result.diagnostics,
2113 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
2114 {
2115 return aDiagnostic.message.Contains( wxS( "raw_angle" ) )
2116 && aDiagnostic.source == rawAngle.source;
2117 } ) );
2118
2119 m_schematic.Reset();
2120 destination = m_schematic.GetTopLevelSheet();
2121 const PADS_SCH_MODEL multiModel = parseBinaryFixture( wxS( "multigate" ) );
2122 result = builder.Build( multiModel, &m_schematic, destination, binaryFixture( wxS( "multigate" ) ) );
2123 SCH_SHEET_PATH multiPath;
2124 multiPath.push_back( destination );
2125 std::vector<SCH_SYMBOL*> multiSymbols;
2126
2127 for( SCH_ITEM* item : destination->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
2128 {
2129 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2130
2131 if( symbol->GetRef( &multiPath ) == wxS( "U3" ) )
2132 multiSymbols.push_back( symbol );
2133 }
2134
2135 BOOST_REQUIRE_EQUAL( multiSymbols.size(), 2u );
2136 std::ranges::sort( multiSymbols, {}, &SCH_SYMBOL::GetUnit );
2137 BOOST_CHECK_EQUAL( multiSymbols[0]->GetUnit(), 1 );
2138 BOOST_CHECK_EQUAL( multiSymbols[1]->GetUnit(), 2 );
2139 BOOST_CHECK_EQUAL( multiSymbols[0]->GetUnitCount(), 2 );
2140 BOOST_CHECK_EQUAL( multiSymbols[1]->GetUnitCount(), 2 );
2141 const MODEL_PART_TYPE& multiPart = *std::ranges::find_if( multiModel.partTypes,
2142 []( const MODEL_PART_TYPE& aPart )
2143 {
2144 return aPart.name.text == wxS( "BATCHD-MULTIGATE" );
2145 } );
2146 BOOST_CHECK_EQUAL( multiSymbols[0]->GetLibPins().size(),
2147 multiModel.placements[1].pins.size() + multiPart.signalPins.size() );
2148 BOOST_CHECK_EQUAL( multiSymbols[1]->GetLibPins().size(),
2149 multiModel.placements[2].pins.size() + multiPart.signalPins.size() );
2150
2151 m_schematic.Reset();
2152 destination = m_schematic.GetTopLevelSheet();
2153 const PADS_SCH_MODEL connectorModel = parseBinaryFixture( wxS( "connectors" ) );
2154 result = builder.Build( connectorModel, &m_schematic, destination, binaryFixture( wxS( "connectors" ) ) );
2155 SCH_SHEET_PATH connectorPath;
2156 connectorPath.push_back( destination );
2157 SCH_SYMBOL* connector = nullptr;
2158
2159 for( SCH_ITEM* item : destination->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
2160 {
2161 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2162
2163 if( symbol->GetRef( &connectorPath ) == wxS( "P1" ) )
2164 connector = symbol;
2165 }
2166
2167 BOOST_REQUIRE( connector );
2168 BOOST_CHECK_EQUAL( connector->GetUnit(), 1 );
2169 BOOST_CHECK_EQUAL( connector->GetUnitCount(), 26 );
2170 const MODEL_PART_TYPE& connectorPart =
2171 *std::ranges::find_if( connectorModel.partTypes,
2172 []( const MODEL_PART_TYPE& aPart )
2173 {
2174 return std::ranges::any_of( aPart.gates,
2175 []( const MODEL_GATE& aGate )
2176 {
2177 return !aGate.connectorPins.empty();
2178 } );
2179 } );
2180 const MODEL_GATE& connectorGate = *std::ranges::find_if( connectorPart.gates,
2181 []( const MODEL_GATE& aGate )
2182 {
2183 return !aGate.connectorPins.empty();
2184 } );
2185 auto connectorPlacementIt = std::ranges::find_if( connectorModel.placements,
2186 [&]( const MODEL_PLACEMENT& aPlacement )
2187 {
2188 return aPlacement.partType.id == connectorPart.id;
2189 } );
2190 BOOST_REQUIRE( connectorPlacementIt != connectorModel.placements.end() );
2191 const MODEL_PLACEMENT& connectorPlacement = *connectorPlacementIt;
2192 const MODEL_SYMBOL_DEFINITION& connectorDefinition = *std::ranges::find(
2193 connectorModel.definitions, connectorPlacement.definition.id, &MODEL_SYMBOL_DEFINITION::id );
2194 BOOST_REQUIRE_EQUAL( connectorGate.connectorPins.size(), 26u );
2195 BOOST_REQUIRE( !connectorDefinition.pins.empty() );
2196 std::vector<SCH_PIN*> builtConnectorPins = connector->GetAllLibPins();
2197
2198 for( size_t index = 0; index < connectorGate.connectorPins.size(); ++index )
2199 {
2200 const MODEL_CONNECTOR_PIN& sourcePin = connectorGate.connectorPins[index];
2201
2202 for( const MODEL_PIN_DEFINITION& graphicPin : connectorDefinition.pins )
2203 {
2204 auto builtPin =
2205 std::ranges::find_if( builtConnectorPins,
2206 [&]( const SCH_PIN* aPin )
2207 {
2208 return aPin->GetUnit() == static_cast<int>( index + 1 )
2209 && aPin->GetPosition() == localPoint( graphicPin.position );
2210 } );
2211 BOOST_REQUIRE_MESSAGE( builtPin != builtConnectorPins.end(), index + 1 );
2212 BOOST_CHECK_EQUAL( ( *builtPin )->GetNumber(), sourcePin.number.text );
2213 BOOST_CHECK_EQUAL( ( *builtPin )->GetName(), sourcePin.name.text );
2214 BOOST_CHECK( ( *builtPin )->GetType() == pinType( sourcePin.electricalType ) );
2215 BOOST_CHECK_EQUAL( ( *builtPin )->GetLength(),
2216 schIUScale.MilsToIU( static_cast<double>( graphicPin.length ) / 2.0 ) );
2217 BOOST_CHECK( ( *builtPin )->GetOrientation() == pinOrientation( graphicPin ) );
2218 BOOST_CHECK( ( *builtPin )->GetShape() == pinShape( graphicPin.graphicStyle ) );
2219 BOOST_CHECK_EQUAL( ( *builtPin )->IsVisible(), graphicPin.presentation.visible );
2220 BOOST_CHECK_EQUAL( ( *builtPin )->GetNameTextSize(),
2221 schIUScale.MilsToIU( static_cast<double>( graphicPin.namePresentation.height ) / 2.0 ) );
2223 ( *builtPin )->GetNumberTextSize(),
2224 schIUScale.MilsToIU( static_cast<double>( graphicPin.numberPresentation.height ) / 2.0 ) );
2225 }
2226 }
2227
2228 m_schematic.Reset();
2229 destination = m_schematic.GetTopLevelSheet();
2230 PADS_SCH_MODEL fieldModel = parseBinaryFixture( wxS( "fields" ) );
2231 fieldModel.placements.front().mirrored = true;
2232 fieldModel.placements.front().mirrorFlags = 3;
2233 result = builder.Build( fieldModel, &m_schematic, destination, binaryFixture( wxS( "fields" ) ) );
2234 BOOST_REQUIRE_EQUAL( result.counts.symbols, fieldModel.placements.size() );
2235
2236 SCH_SHEET_PATH fieldPath;
2237 fieldPath.push_back( destination );
2238 SCH_SYMBOL* r1 = nullptr;
2239
2240 for( SCH_ITEM* item : destination->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
2241 {
2242 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2243
2244 if( symbol->GetRef( &fieldPath ) == wxS( "R1" ) )
2245 r1 = symbol;
2246 }
2247
2248 BOOST_REQUIRE( r1 );
2249
2250 size_t sizedFields = 0;
2251 size_t strokedFields = 0;
2252
2253 for( const MODEL_FIELD& sourceField : fieldModel.placements.front().fields )
2254 {
2255 SCH_FIELD* builtField = nullptr;
2256
2257 if( sourceField.name.text == wxS( "REF-DES" ) )
2258 builtField = r1->GetField( FIELD_T::REFERENCE );
2259 else if( sourceField.name.text == wxS( "PART-TYPE" ) )
2260 builtField = r1->GetField( FIELD_T::VALUE );
2261 else
2262 builtField = r1->GetField( sourceField.name.text );
2263
2264 BOOST_REQUIRE_MESSAGE( builtField, sourceField.name.text );
2265 BOOST_CHECK_EQUAL( builtField->GetText(), sourceField.value.text );
2266 BOOST_CHECK_EQUAL( builtField->IsVisible(), sourceField.visible );
2267 if( sourceField.presentation.height != 0 )
2268 {
2269 BOOST_CHECK_EQUAL( builtField->GetTextHeight(),
2270 schIUScale.MilsToIU( static_cast<double>( sourceField.presentation.height ) / 2.0 ) );
2271 sizedFields++;
2272 }
2273
2274 if( sourceField.presentation.width != 0 )
2275 {
2276 BOOST_CHECK_EQUAL( builtField->GetTextThickness(),
2277 schIUScale.MilsToIU( static_cast<double>( sourceField.presentation.width ) / 2.0 ) );
2278 strokedFields++;
2279 }
2280 BOOST_CHECK_EQUAL( builtField->GetTextAngle().AsTenthsOfADegree(), sourceField.angle );
2281 BOOST_CHECK_EQUAL( builtField->GetPosition(), r1->GetPosition() + placedFieldOffset( sourceField ) );
2282 BOOST_CHECK_EQUAL( builtField->IsBold(), sourceField.presentation.bold );
2283 BOOST_CHECK_EQUAL( builtField->IsItalic(), sourceField.presentation.italic );
2284 BOOST_CHECK( builtField->GetHorizJustify()
2285 == horizontalJustification( sourceField.presentation.horizontalJustification ) );
2286 BOOST_CHECK( builtField->GetVertJustify()
2287 == verticalJustification( sourceField.presentation.verticalJustification ) );
2288
2289 if( !sourceField.presentation.font.text.IsEmpty()
2290 && sourceField.presentation.font.text != wxS( "Default Font" ) )
2291 {
2292 BOOST_CHECK( !builtField->GetFontName().IsEmpty() );
2293 }
2294 }
2295
2296 // Both size checks are skipped on a zero presentation, so a decoder that emitted nothing but
2297 // zeros would satisfy the loop without comparing anything
2298 BOOST_CHECK_GT( sizedFields, 0u );
2299 BOOST_CHECK_GT( strokedFields, 0u );
2300}
2301
2302
2303BOOST_AUTO_TEST_CASE( BinaryAlternateDefinitionPins )
2304{
2305 using namespace PADS_SCH_BINARY;
2306
2307 PADS_SCH_MODEL model = parseBinaryFixture( wxS( "multigate" ) );
2308 auto hasPlacedPins = []( const MODEL_PLACEMENT& aPlacement )
2309 {
2310 return aPlacement.gate.has_value() && !aPlacement.pins.empty();
2311 };
2312 auto placement = std::ranges::find_if( model.placements, hasPlacedPins );
2313 BOOST_REQUIRE( placement != model.placements.end() );
2314
2315 auto part = std::ranges::find( model.partTypes, placement->partType.id, &MODEL_PART_TYPE::id );
2316 BOOST_REQUIRE( part != model.partTypes.end() );
2317 auto gate = std::ranges::find( part->gates, placement->gate->id, &MODEL_GATE::id );
2318 BOOST_REQUIRE( gate != part->gates.end() );
2319 auto primary = std::ranges::find( model.definitions, gate->definition.id, &MODEL_SYMBOL_DEFINITION::id );
2320 BOOST_REQUIRE( primary != model.definitions.end() );
2321
2322 MODEL_SYMBOL_DEFINITION alternate = *primary;
2323 alternate.id = DEFINITION_ID( 0x00F00000 );
2324 alternate.name.text += wxS( "_ALTERNATE" );
2325 alternate.fields.clear();
2326 std::map<uint32_t, PIN_ID> alternatePinIds;
2327
2328 for( size_t index = 0; index < alternate.pins.size(); ++index )
2329 {
2330 const uint32_t primaryId = alternate.pins[index].id.Value();
2331 alternate.pins[index].id = PIN_ID( 0x00F10000 + static_cast<uint32_t>( index ) );
2332 alternate.pins[index].number.text.Prepend( wxS( "ALT-" ) );
2333 alternatePinIds.emplace( primaryId, alternate.pins[index].id );
2334 }
2335
2336 gate->alternateDefinitions.push_back( { alternate.id, gate->source } );
2337 placement->definition = { alternate.id, placement->definition.source };
2338
2339 for( PLACED_PIN_REFERENCE& pin : placement->pins )
2340 {
2341 auto alternatePin = alternatePinIds.find( pin.id.Value() );
2342 BOOST_REQUIRE( alternatePin != alternatePinIds.end() );
2343 pin.id = alternatePin->second;
2344 }
2345
2346 for( MODEL_NET& net : model.nets )
2347 {
2348 for( MODEL_CONNECTION& connection : net.connections )
2349 {
2350 for( MODEL_CONNECTION_ENDPOINT& endpoint : connection.endpoints )
2351 {
2352 if( endpoint.placement && endpoint.placement->id == placement->id && endpoint.pin )
2353 {
2354 auto alternatePin = alternatePinIds.find( endpoint.pin->id.Value() );
2355 BOOST_REQUIRE( alternatePin != alternatePinIds.end() );
2356 endpoint.pin->id = alternatePin->second;
2357 }
2358 }
2359 }
2360 }
2361
2362 const wxString reference = placement->reference.text;
2363 model.definitions.push_back( std::move( alternate ) );
2364 BOOST_REQUIRE_NO_THROW( model.ValidateOrThrow() );
2365
2367 BOOST_REQUIRE_NO_THROW( builder.Build( model, &m_schematic, nullptr, wxS( "alternate_definition.sch" ) ) );
2368
2370 path.push_back( m_schematic.GetTopLevelSheet() );
2371 SCH_SYMBOL* builtSymbol = nullptr;
2372
2373 for( SCH_ITEM* item : m_schematic.GetTopLevelSheet()->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
2374 {
2375 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2376
2377 if( symbol->GetRef( &path ) == reference )
2378 builtSymbol = symbol;
2379 }
2380
2381 BOOST_REQUIRE( builtSymbol );
2382
2383 std::vector<SCH_PIN*> selectedUnitPins = builtSymbol->GetLibPins();
2384
2385 BOOST_REQUIRE_EQUAL( selectedUnitPins.size(), gate->logicalPins.size() );
2386
2387 for( const MODEL_GATE_PIN& logicalPin : gate->logicalPins )
2388 {
2389 auto builtPin = std::ranges::find( selectedUnitPins, logicalPin.number.text, &SCH_PIN::GetNumber );
2390 BOOST_REQUIRE_MESSAGE( builtPin != selectedUnitPins.end(), logicalPin.number.text );
2391 BOOST_CHECK_EQUAL( ( *builtPin )->GetName(), logicalPin.name.text );
2392 BOOST_CHECK( !( *builtPin )->GetNumber().StartsWith( wxS( "ALT-" ) ) );
2393 }
2394}
2395
2396
2397BOOST_AUTO_TEST_CASE( BinaryMultiSheetHierarchy )
2398{
2399 const PADS_SCH_BINARY::PADS_SCH_MODEL model = parseBinaryFixture( wxS( "multisheet_connectivity" ) );
2401 SCH_SHEET* originalRoot = m_schematic.GetTopLevelSheet();
2402 BOOST_REQUIRE( originalRoot );
2403 const KIID originalRootUuid = originalRoot->m_Uuid;
2405 builder.Build( model, &m_schematic, nullptr, binaryFixture( wxS( "multisheet_connectivity" ) ) );
2406 BOOST_CHECK_EQUAL( result.counts.sheets, model.sheets.size() );
2407 std::vector<SCH_SHEET*> topSheets = m_schematic.GetTopLevelSheets();
2408 BOOST_REQUIRE_EQUAL( topSheets.size(), model.sheets.size() );
2409 BOOST_CHECK( std::ranges::none_of( topSheets,
2410 [&]( const SCH_SHEET* aSheet )
2411 {
2412 return aSheet == originalRoot || aSheet->m_Uuid == originalRootUuid;
2413 } ) );
2414
2415 std::set<wxString> filenames;
2416
2417 for( size_t i = 0; i < topSheets.size(); ++i )
2418 {
2419 SCH_SHEET* sheet = topSheets[i];
2420 BOOST_REQUIRE( sheet );
2421 BOOST_REQUIRE( sheet->GetScreen() );
2422 BOOST_CHECK( m_schematic.IsTopLevelSheet( sheet ) );
2423 BOOST_CHECK( sheet->GetScreen()->Items().OfType( SCH_SHEET_T ).empty() );
2424 BOOST_CHECK_EQUAL( sheet->GetField( FIELD_T::SHEET_NAME )->GetText(), model.sheets[i].name.text );
2425 wxString filename = sheet->GetField( FIELD_T::SHEET_FILENAME )->GetText();
2426 BOOST_CHECK( !filename.Contains( wxS( "/" ) ) );
2427 BOOST_CHECK( !filename.Contains( wxS( ":" ) ) );
2428 BOOST_CHECK( !filename.Contains( wxS( "*" ) ) );
2429 BOOST_CHECK( filenames.insert( filename ).second );
2430 const wxString expectedFilename = i == 0 ? wxS( "[1]DUP_SAFE__.kicad_sch" ) : wxS( "[2]DUP_SAFE__.kicad_sch" );
2431 BOOST_CHECK_EQUAL( filename, expectedFilename );
2433 path.push_back( sheet );
2434 BOOST_CHECK_EQUAL( path.GetPageNumber(), wxString::Format( wxS( "%zu" ), i + 1 ) );
2435 BOOST_CHECK_EQUAL( sheet->GetScreen()->GetPageNumber(), wxString::Format( wxS( "%zu" ), i + 1 ) );
2436 BOOST_CHECK_EQUAL( sheet->GetScreen()->GetPageSettings().GetWidthMils(), model.sheets[i].pageSize.x / 2 );
2437 BOOST_CHECK_EQUAL( sheet->GetScreen()->GetPageSettings().GetHeightMils(), model.sheets[i].pageSize.y / 2 );
2438
2439 std::multiset<std::pair<wxString, VECTOR2I>> expectedLabels;
2440 std::multiset<std::pair<wxString, VECTOR2I>> builtLabels;
2441
2442 for( const PADS_SCH_BINARY::MODEL_LABEL& label : model.labels )
2443 {
2444 if( label.sheet.id == model.sheets[i].id && !label.linkedSheets.empty() )
2445 {
2446 expectedLabels.emplace( label.text.text, VECTOR2I( schIUScale.MilsToIU( label.position.x / 2 ),
2447 schIUScale.MilsToIU( model.sheets[i].pageSize.y / 2
2448 - label.position.y / 2 ) ) );
2449 }
2450 }
2451
2452 for( SCH_ITEM* item : sheet->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
2453 {
2454 auto* label = static_cast<SCH_GLOBALLABEL*>( item );
2455
2456 std::pair<wxString, VECTOR2I> identity( label->GetText(), label->GetPosition() );
2457
2458 if( expectedLabels.contains( identity ) )
2459 builtLabels.insert( std::move( identity ) );
2460 }
2461
2462 BOOST_CHECK( builtLabels == expectedLabels );
2463
2464 for( SCH_ITEM* item : sheet->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
2465 {
2466 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2467 SCH_SYMBOL_INSTANCE instance;
2468 BOOST_REQUIRE( symbol->GetInstance( instance, path.Path(), false ) );
2469 BOOST_CHECK( instance.m_Path == path.Path() );
2470 BOOST_CHECK_EQUAL( symbol->GetRef( &path ), instance.m_Reference );
2471 }
2472 }
2473
2474 BOOST_CHECK_EQUAL( result.counts.labels,
2475 model.buses.size()
2476 + std::ranges::count_if( model.labels,
2477 []( const PADS_SCH_BINARY::MODEL_LABEL& aLabel )
2478 {
2479 return aLabel.kind
2480 != PADS_SCH_BINARY::MODEL_LABEL_KIND::UNSUPPORTED;
2481 } ) );
2482
2483 using namespace PADS_SCH_BINARY;
2484
2485 PADS_SCH_MODEL orderedModel;
2486 orderedModel.version = 0x000D;
2487 constexpr std::array<size_t, 11> physicalOrder{ 0, 2, 10, 7, 4, 1, 9, 5, 3, 8, 6 };
2488
2489 for( size_t sourceIndex : physicalOrder )
2490 {
2491 MODEL_SHEET sheet;
2492 sheet.id = SHEET_ID( static_cast<uint32_t>( sourceIndex + 1 ) );
2493 sheet.index = sourceIndex;
2494 sheet.name.text = wxString::Format( wxS( "[%zu]SOURCE_ORDER" ), sourceIndex + 1 );
2495 sheet.pageSize = { 34000, 22000 };
2496 orderedModel.sheets.push_back( std::move( sheet ) );
2497 }
2498
2499 m_schematic.Reset();
2500 BOOST_REQUIRE_NO_THROW( builder.Build( orderedModel, &m_schematic, nullptr, wxS( "source_order.sch" ) ) );
2501
2502 auto checkSourceOrder = [&]( const SCHEMATIC& aSchematic )
2503 {
2504 SCH_SHEET_LIST hierarchy = aSchematic.BuildSheetListSortedByPageNumbers();
2505 BOOST_REQUIRE_EQUAL( hierarchy.size(), 11u );
2506 BOOST_REQUIRE_EQUAL( aSchematic.GetTopLevelSheets().size(), 11u );
2507 std::set<wxString> pageNumbers;
2508
2509 for( const SCH_SHEET_PATH& path : hierarchy )
2510 BOOST_CHECK( pageNumbers.insert( path.GetPageNumber() ).second );
2511
2512 for( size_t sourceIndex = 0; sourceIndex < 11; ++sourceIndex )
2513 {
2514 const SCH_SHEET_PATH& path = hierarchy[sourceIndex];
2515 BOOST_REQUIRE_EQUAL( path.size(), 1u );
2516 BOOST_CHECK( aSchematic.IsTopLevelSheet( path.Last() ) );
2517 BOOST_CHECK_EQUAL( path.GetPageNumber(), wxString::Format( wxS( "%zu" ), sourceIndex + 1 ) );
2518 BOOST_CHECK_EQUAL( path.Last()->GetField( FIELD_T::SHEET_NAME )->GetText(),
2519 wxString::Format( wxS( "[%zu]SOURCE_ORDER" ), sourceIndex + 1 ) );
2520 }
2521 };
2522
2523 checkSourceOrder( m_schematic );
2524
2525 wxString tempDir = wxFileName::CreateTempFileName( wxS( "pads_binary_sheet_order_" ) );
2526 BOOST_REQUIRE( wxRemoveFile( tempDir ) );
2527 BOOST_REQUIRE( wxFileName::Mkdir( tempDir ) );
2529 std::vector<wxString> files;
2530 std::vector<TOP_LEVEL_SHEET_INFO> sheetInfos;
2531
2532 for( const SCH_SHEET_PATH& path : m_schematic.BuildSheetListSortedByPageNumbers() )
2533 {
2534 wxString file =
2535 tempDir + wxFileName::GetPathSeparator() + path.Last()->GetField( FIELD_T::SHEET_FILENAME )->GetText();
2536 BOOST_REQUIRE_NO_THROW( io.SaveSchematicFile( file, path.Last(), &m_schematic ) );
2537 files.push_back( file );
2538 sheetInfos.emplace_back( path.Last()->m_Uuid, path.Last()->GetName(), file );
2539 }
2540
2541 m_schematic.Reset();
2542 SCH_SHEET* defaultSheet = m_schematic.GetTopLevelSheet();
2543 std::vector<SCH_SHEET*> loadedSheets;
2544
2545 for( size_t index = 0; index < files.size(); ++index )
2546 {
2547 SCH_SHEET* loaded = nullptr;
2548 BOOST_REQUIRE_NO_THROW( loaded = io.LoadSchematicFile( files[index], &m_schematic ) );
2549 BOOST_REQUIRE( loaded );
2550 const_cast<KIID&>( loaded->m_Uuid ) = sheetInfos[index].uuid;
2551 loaded->SetName( sheetInfos[index].name );
2552 loadedSheets.push_back( loaded );
2553 }
2554
2555 m_schematic.SetTopLevelSheets( loadedSheets );
2556 BOOST_CHECK( std::ranges::none_of( loadedSheets,
2557 [&]( const SCH_SHEET* aSheet )
2558 {
2559 return aSheet == defaultSheet;
2560 } ) );
2561 m_schematic.RefreshHierarchy();
2562 checkSourceOrder( m_schematic );
2563 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
2564}
2565
2566
2567BOOST_AUTO_TEST_CASE( BinaryAppendIsAtomic )
2568{
2569 using namespace PADS_SCH_BINARY;
2570
2571 SCH_SHEET* destination = m_schematic.GetTopLevelSheet();
2572 BOOST_REQUIRE( destination );
2573 BOOST_REQUIRE( destination->GetScreen() );
2575
2576 LIB_ID preservedLibId;
2577 preservedLibId.SetLibNickname( wxS( "source_library" ) );
2578 preservedLibId.SetLibItemName( wxS( "source_symbol" ) );
2579 auto preservedSymbol = std::make_unique<LIB_SYMBOL>( wxS( "source_symbol" ) );
2580 preservedSymbol->SetLibId( preservedLibId );
2581 const wxString preservedKey = wxS( "pads_import:preserved_cache_key" );
2582 destination->GetScreen()->AddLibSymbol( preservedKey, std::move( preservedSymbol ) );
2583
2584 PADS_SCH_MODEL single = parseBinaryFixture( wxS( "placement_transform" ) );
2585 size_t beforeSymbols = itemCount( destination->GetScreen(), SCH_SYMBOL_T );
2586 BUILD_RESULT singleResult =
2587 builder.Build( single, &m_schematic, destination, binaryFixture( wxS( "placement_transform" ) ) );
2588 BOOST_CHECK_EQUAL( itemCount( destination->GetScreen(), SCH_SYMBOL_T ),
2589 beforeSymbols + singleResult.counts.symbols );
2590 BOOST_REQUIRE( destination->GetScreen()->GetLibSymbols().contains( preservedKey ) );
2591 BOOST_CHECK( destination->GetScreen()->GetLibSymbols().at( preservedKey )->GetLibId() == preservedLibId );
2592
2593 std::set<wxString> cacheKeys;
2594
2595 for( const auto& [key, symbol] : destination->GetScreen()->GetLibSymbols() )
2596 cacheKeys.insert( key );
2597
2598 builder.Build( single, &m_schematic, destination, binaryFixture( wxS( "placement_transform" ) ) );
2599 std::set<wxString> repeatedCacheKeys;
2600
2601 for( const auto& [key, symbol] : destination->GetScreen()->GetLibSymbols() )
2602 repeatedCacheKeys.insert( key );
2603
2604 BOOST_CHECK( repeatedCacheKeys == cacheKeys );
2605 BOOST_REQUIRE( destination->GetScreen()->GetLibSymbols().contains( preservedKey ) );
2606 BOOST_CHECK( destination->GetScreen()->GetLibSymbols().at( preservedKey )->GetLibId() == preservedLibId );
2607
2608 PADS_SCH_MODEL multi = parseBinaryFixture( wxS( "multisheet_connectivity" ) );
2609 auto existingChild = std::make_unique<SCH_SHEET>( destination );
2610 SCH_SHEET* existingChildPtr = existingChild.get();
2611 existingChild->SetScreen( new SCH_SCREEN( &m_schematic ) );
2612 existingChild->GetField( FIELD_T::SHEET_NAME )->SetText( wxS( "Existing" ) );
2613 existingChild->GetField( FIELD_T::SHEET_FILENAME )->SetText( wxS( "existing.kicad_sch" ) );
2614 destination->GetScreen()->Append( existingChild.get() );
2615 existingChild.release();
2616 size_t beforeChildren = itemCount( destination->GetScreen(), SCH_SHEET_T );
2617 BUILD_RESULT multiResult =
2618 builder.Build( multi, &m_schematic, destination, binaryFixture( wxS( "multisheet_connectivity" ) ) );
2619 BOOST_CHECK_EQUAL( itemCount( destination->GetScreen(), SCH_SHEET_T ), beforeChildren + multiResult.counts.sheets );
2620 BOOST_CHECK( destination->GetScreen()->Items().contains( existingChildPtr ) );
2621
2622 PADS_SCH_MODEL malformed = single;
2623 BOOST_REQUIRE( !malformed.placements.empty() );
2624 auto definition = std::find_if( malformed.definitions.begin(), malformed.definitions.end(),
2625 [&]( const MODEL_SYMBOL_DEFINITION& aDefinition )
2626 {
2627 return aDefinition.id == malformed.placements.front().definition.id;
2628 } );
2629 BOOST_REQUIRE( definition != malformed.definitions.end() );
2630 BOOST_REQUIRE( !definition->graphics.empty() );
2631 definition->graphics.front().kind = MODEL_GRAPHIC_KIND::TEXT;
2632 definition->graphics.front().text.text = wxS( "broken staged text" );
2633 definition->graphics.front().points.clear();
2634 OBJECT_GRAPH_SNAPSHOT before = objectGraphSnapshot( m_schematic, destination );
2635 BOOST_CHECK_THROW( builder.Build( malformed, &m_schematic, destination, wxS( "malformed.sch" ) ), IO_ERROR );
2636 BOOST_CHECK( objectGraphSnapshot( m_schematic, destination ) == before );
2637
2638 PADS_SCH_BINARY_BUILDER commitFailure(
2639 []
2640 {
2641 THROW_IO_ERROR( wxS( "injected failure before schematic adoption" ) );
2642 } );
2643 before = objectGraphSnapshot( m_schematic, destination );
2644 BOOST_CHECK_THROW( commitFailure.Build( single, &m_schematic, destination, wxS( "commit_failure.sch" ) ),
2645 IO_ERROR );
2646 BOOST_CHECK( objectGraphSnapshot( m_schematic, destination ) == before );
2647
2648 auto oldCurrentChild = std::make_unique<SCH_SHEET>( destination );
2649 oldCurrentChild->SetScreen( new SCH_SCREEN( &m_schematic ) );
2650 SCH_SHEET_PATH oldCurrentPath;
2651 oldCurrentPath.push_back( destination );
2652 oldCurrentPath.push_back( oldCurrentChild.get() );
2653 destination->GetScreen()->Append( oldCurrentChild.get() );
2654 oldCurrentChild.release();
2655 m_schematic.SetCurrentSheet( oldCurrentPath );
2656 BOOST_REQUIRE_EQUAL( m_schematic.CurrentSheet().size(), 2u );
2657
2658 builder.Build( single, &m_schematic, nullptr, wxS( "replacement.sch" ) );
2659 BOOST_REQUIRE_EQUAL( m_schematic.CurrentSheet().size(), 1u );
2660 BOOST_CHECK( m_schematic.CurrentSheet().at( 0 ) == destination );
2661 BOOST_CHECK( m_schematic.CurrentSheet().LastScreen() == destination->GetScreen() );
2662 SCH_SHEET_PATH freshRootPath;
2663 freshRootPath.push_back( destination );
2664 BOOST_CHECK_EQUAL( m_schematic.CurrentSheet().GetCurrentHash(), freshRootPath.GetCurrentHash() );
2665}
2666
2667
2668BOOST_AUTO_TEST_CASE( BinaryConnectivityAndGraphics )
2669{
2670 using namespace PADS_SCH_BINARY;
2671
2673
2674 PADS_SCH_MODEL fieldVisibility = parseBinaryFixture( wxS( "fields" ) );
2675 BOOST_REQUIRE( !fieldVisibility.placements.empty() );
2676 BOOST_REQUIRE_GE( fieldVisibility.placements.front().fields.size(), 2u );
2677 MODEL_PLACEMENT& visibilityPlacement = fieldVisibility.placements.front();
2678 visibilityPlacement.fields[0].visible = false;
2679 visibilityPlacement.fields[0].presentation.visible = true;
2680 visibilityPlacement.fields[1].visible = true;
2681 visibilityPlacement.fields[1].presentation.visible = false;
2682 builder.Build( fieldVisibility, &m_schematic, nullptr, binaryFixture( wxS( "fields" ) ) );
2683 SCH_SHEET_PATH visibilityPath = m_schematic.CurrentSheet();
2684 SCH_SYMBOL* visibilitySymbol = nullptr;
2685
2686 for( SCH_ITEM* item : visibilityPath.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
2687 {
2688 auto* symbol = static_cast<SCH_SYMBOL*>( item );
2689
2690 if( symbol->GetRef( &visibilityPath ) == visibilityPlacement.reference.text )
2691 {
2692 visibilitySymbol = symbol;
2693 break;
2694 }
2695 }
2696
2697 BOOST_REQUIRE( visibilitySymbol );
2698
2699 for( size_t index = 0; index < 2; ++index )
2700 {
2701 const MODEL_FIELD& sourceField = visibilityPlacement.fields[index];
2702 SCH_FIELD* field = sourceField.name.text.CmpNoCase( wxS( "REF-DES" ) ) == 0
2703 ? visibilitySymbol->GetField( FIELD_T::REFERENCE )
2704 : sourceField.name.text.CmpNoCase( wxS( "PART-TYPE" ) ) == 0
2705 || sourceField.name.text.CmpNoCase( wxS( "VALUE" ) ) == 0
2706 ? visibilitySymbol->GetField( FIELD_T::VALUE )
2707 : visibilitySymbol->GetField( sourceField.name.text );
2708 BOOST_REQUIRE( field );
2709 BOOST_CHECK_EQUAL( field->IsVisible(), sourceField.visible && sourceField.presentation.visible );
2710 }
2711
2712 m_schematic.Reset();
2713 PADS_SCH_MODEL connectivity = parseBinaryFixture( wxS( "connectivity_topology" ) );
2714 const PADS_SCH_MODEL connectivityOracle = connectivity;
2715 auto foundGround = std::ranges::find( connectivity.labels, MODEL_LABEL_KIND::GROUND, &MODEL_LABEL::kind );
2716 auto foundPower = std::ranges::find( connectivity.labels, MODEL_LABEL_KIND::POWER, &MODEL_LABEL::kind );
2717 BOOST_REQUIRE( foundGround != connectivity.labels.end() );
2718 BOOST_REQUIRE( foundPower != connectivity.labels.end() );
2719
2720 // Copy the templates out before growing the vector; the loops below invalidate both iterators
2721 const MODEL_LABEL groundTemplate = *foundGround;
2722 const MODEL_LABEL powerTemplate = *foundPower;
2723
2724 for( uint8_t variant = 0; variant < 3; ++variant )
2725 {
2726 MODEL_LABEL label = groundTemplate;
2727 label.position = { 1000 + 500 * variant, 1000 };
2728 label.symbolVariant = variant;
2729 label.text.text = wxString::Format( wxS( "TEST_GND_%u" ), variant );
2730 connectivity.labels.push_back( std::move( label ) );
2731 }
2732
2733 for( uint8_t variant = 0; variant < 5; ++variant )
2734 {
2735 MODEL_LABEL label = powerTemplate;
2736 label.position = { 1000 + 500 * variant, 2000 };
2737 label.symbolVariant = variant;
2738 label.text.text = wxString::Format( wxS( "TEST_PWR_%u" ), variant );
2739 connectivity.labels.push_back( std::move( label ) );
2740 }
2741
2742 SCH_SHEET* root = m_schematic.GetTopLevelSheet();
2743 BOOST_REQUIRE( root );
2744 builder.Build( connectivity, &m_schematic, nullptr, binaryFixture( wxS( "connectivity_topology" ) ) );
2745 assertSourceConnectivity( connectivityOracle, m_schematic );
2746
2747 size_t expectedWires = 0;
2748
2749 for( const MODEL_NET& net : connectivity.nets )
2750 {
2751 for( const MODEL_CONNECTION& connection : net.connections )
2752 expectedWires += connection.vertices.size() - 1;
2753 }
2754
2755 size_t builtWires = 0;
2756
2757 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_LINE_T ) )
2758 {
2759 if( item->GetLayer() == LAYER_WIRE )
2760 ++builtWires;
2761 }
2762
2763 BOOST_CHECK_EQUAL( builtWires, expectedWires );
2764 BOOST_CHECK_EQUAL( itemCount( root->GetScreen(), SCH_JUNCTION_T ), connectivity.junctions.size() );
2765
2766 const int pageHeight = root->GetScreen()->GetPageSettings().GetHeightIU( schIUScale.IU_PER_MILS );
2767
2768 for( const MODEL_JUNCTION& junction : connectivity.junctions )
2769 {
2770 bool found = false;
2771
2772 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_JUNCTION_T ) )
2773 found |= item->GetPosition() == pagePoint( junction.position, pageHeight );
2774
2775 BOOST_CHECK( found );
2776 }
2777
2778 size_t expectedPower = std::ranges::count_if( connectivity.labels,
2779 []( const MODEL_LABEL& aLabel )
2780 {
2781 return aLabel.kind == MODEL_LABEL_KIND::POWER
2782 || aLabel.kind == MODEL_LABEL_KIND::GROUND;
2783 } );
2784 size_t builtPower = 0;
2785
2786 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
2787 {
2788 auto* symbol = static_cast<SCH_SYMBOL*>( item );
2789
2790 if( symbol->GetRef( &m_schematic.CurrentSheet() ).StartsWith( wxS( "#PWR" ) ) )
2791 {
2792 ++builtPower;
2793 std::vector<SCH_PIN*> pins = symbol->GetPins( &m_schematic.CurrentSheet() );
2794 BOOST_REQUIRE_EQUAL( pins.size(), 1u );
2795 BOOST_CHECK( pins.front()->GetType() == ELECTRICAL_PINTYPE::PT_POWER_IN );
2796 BOOST_CHECK_EQUAL( pins.front()->GetPosition(), symbol->GetPosition() );
2797 }
2798 }
2799
2800 BOOST_CHECK_EQUAL( builtPower, expectedPower );
2801
2802 auto builtPowerSymbol = [&]( const wxString& aValue ) -> SCH_SYMBOL*
2803 {
2804 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
2805 {
2806 auto* symbol = static_cast<SCH_SYMBOL*>( item );
2807
2808 if( symbol->GetValue( &m_schematic.CurrentSheet(), RAW_VALUE ) == aValue )
2809 return symbol;
2810 }
2811
2812 return nullptr;
2813 };
2814 auto polyPoints = []( const SCH_SHAPE& aShape )
2815 {
2816 std::vector<VECTOR2I> points;
2817
2818 for( size_t vertex = 0; vertex < aShape.GetPolyShape().VertexCount(); ++vertex )
2819 points.push_back( aShape.GetPolyShape().CVertex( vertex ) );
2820
2821 return points;
2822 };
2823 auto shapeByType = []( const SCH_SYMBOL& aSymbol, SHAPE_T aType )
2824 {
2825 std::vector<const SCH_SHAPE*> shapes;
2826
2827 for( const SCH_ITEM& item : aSymbol.GetLibSymbolRef()->GetDrawItems() )
2828 {
2829 if( item.Type() == SCH_SHAPE_T && static_cast<const SCH_SHAPE&>( item ).GetShape() == aType )
2830 shapes.push_back( &static_cast<const SCH_SHAPE&>( item ) );
2831 }
2832
2833 return shapes;
2834 };
2835 auto hasPoly = [&]( const std::vector<const SCH_SHAPE*>& aShapes, const std::vector<VECTOR2I>& aPoints )
2836 {
2837 return std::ranges::any_of( aShapes,
2838 [&]( const SCH_SHAPE* aShape )
2839 {
2840 return polyPoints( *aShape ) == aPoints;
2841 } );
2842 };
2843 auto mil = []( int aMils )
2844 {
2845 return schIUScale.MilsToIU( aMils );
2846 };
2847
2848 SCH_SYMBOL* gnd = builtPowerSymbol( wxS( "TEST_GND_0" ) );
2849 BOOST_REQUIRE( gnd );
2850 auto gndLines = shapeByType( *gnd, SHAPE_T::POLY );
2851 BOOST_REQUIRE_EQUAL( gndLines.size(), 4u );
2852 BOOST_CHECK( hasPoly( gndLines, { { 0, 0 }, { 0, -mil( 100 ) } } ) );
2853 BOOST_CHECK( hasPoly( gndLines, { { -mil( 100 ), -mil( 100 ) }, { mil( 100 ), -mil( 100 ) } } ) );
2854 BOOST_CHECK( hasPoly( gndLines, { { -mil( 60 ), -mil( 150 ) }, { mil( 60 ), -mil( 150 ) } } ) );
2855 BOOST_CHECK( hasPoly( gndLines, { { -mil( 20 ), -mil( 200 ) }, { mil( 20 ), -mil( 200 ) } } ) );
2856
2857 SCH_SYMBOL* gnda = builtPowerSymbol( wxS( "TEST_GND_1" ) );
2858 BOOST_REQUIRE( gnda );
2859 auto gndaLines = shapeByType( *gnda, SHAPE_T::POLY );
2860 BOOST_REQUIRE_EQUAL( gndaLines.size(), 1u );
2861 BOOST_CHECK_EQUAL( polyPoints( *gndaLines.front() ), ( std::vector<VECTOR2I>{ { 0, 0 },
2862 { 0, -mil( 50 ) },
2863 { -mil( 100 ), -mil( 50 ) },
2864 { 0, -mil( 200 ) },
2865 { mil( 100 ), -mil( 50 ) },
2866 { 0, -mil( 50 ) } } ) );
2867
2868 SCH_SYMBOL* gndch = builtPowerSymbol( wxS( "TEST_GND_2" ) );
2869 BOOST_REQUIRE( gndch );
2870 auto gndchLines = shapeByType( *gndch, SHAPE_T::POLY );
2871 BOOST_REQUIRE_EQUAL( gndchLines.size(), 5u );
2872 BOOST_CHECK( hasPoly( gndchLines, { { 0, 0 }, { 0, -mil( 100 ) } } ) );
2873 BOOST_CHECK( hasPoly( gndchLines, { { -mil( 100 ), -mil( 100 ) }, { mil( 100 ), -mil( 100 ) } } ) );
2874 BOOST_CHECK( hasPoly( gndchLines, { { -mil( 100 ), -mil( 100 ) }, { -mil( 150 ), -mil( 200 ) } } ) );
2875 BOOST_CHECK( hasPoly( gndchLines, { { 0, -mil( 100 ) }, { -mil( 50 ), -mil( 200 ) } } ) );
2876 BOOST_CHECK( hasPoly( gndchLines, { { mil( 100 ), -mil( 100 ) }, { mil( 50 ), -mil( 200 ) } } ) );
2877
2878 for( uint8_t variant : { 0, 2 } )
2879 {
2880 SCH_SYMBOL* power = builtPowerSymbol( wxString::Format( wxS( "TEST_PWR_%u" ), variant ) );
2881 BOOST_REQUIRE( power );
2882 auto circles = shapeByType( *power, SHAPE_T::CIRCLE );
2883 auto lines = shapeByType( *power, SHAPE_T::POLY );
2884 BOOST_REQUIRE_EQUAL( circles.size(), 1u );
2885 BOOST_REQUIRE_EQUAL( lines.size(), 1u );
2886 BOOST_CHECK_EQUAL( circles.front()->GetCenter(), VECTOR2I( 0, mil( 150 ) ) );
2887 BOOST_CHECK_EQUAL( circles.front()->GetRadius(), mil( 50 ) );
2888 BOOST_CHECK_EQUAL( polyPoints( *lines.front() ), ( std::vector<VECTOR2I>{ { 0, 0 }, { 0, mil( 100 ) } } ) );
2889 }
2890
2891 for( const auto& [variant, stem] : { std::pair<uint8_t, int>{ 1, 250 }, { 3, 250 }, { 4, 200 } } )
2892 {
2893 SCH_SYMBOL* power = builtPowerSymbol( wxString::Format( wxS( "TEST_PWR_%u" ), variant ) );
2894 BOOST_REQUIRE( power );
2895 auto polygons = shapeByType( *power, SHAPE_T::POLY );
2896 BOOST_REQUIRE_EQUAL( polygons.size(), 2u );
2897 BOOST_CHECK( hasPoly( polygons, { { 0, 0 }, { 0, mil( stem ) } } ) );
2898 const std::vector<VECTOR2I> triangle{
2899 { 0, mil( stem ) }, { -mil( 50 ), mil( 100 ) }, { mil( 50 ), mil( 100 ) }, { 0, mil( stem ) }
2900 };
2901 auto triangleShape = std::ranges::find_if( polygons,
2902 [&]( const SCH_SHAPE* aShape )
2903 {
2904 return polyPoints( *aShape ) == triangle;
2905 } );
2906 BOOST_REQUIRE( triangleShape != polygons.end() );
2907 BOOST_CHECK( ( *triangleShape )->GetFillMode() == FILL_T::FILLED_SHAPE );
2908 }
2909
2910 const SCH_SHEET_PATH& rootPath = m_schematic.CurrentSheet();
2911
2912 for( const MODEL_LABEL& sourceLabel : connectivity.labels )
2913 {
2914 if( sourceLabel.kind == MODEL_LABEL_KIND::UNSUPPORTED )
2915 continue;
2916
2917 bool found = false;
2918
2919 if( sourceLabel.kind == MODEL_LABEL_KIND::POWER || sourceLabel.kind == MODEL_LABEL_KIND::GROUND )
2920 {
2921 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
2922 {
2923 auto* symbol = static_cast<SCH_SYMBOL*>( item );
2924
2925 if( symbol->GetPosition() == pagePoint( sourceLabel.position, pageHeight )
2926 && symbol->GetValue( &rootPath, RAW_VALUE ) == sourceLabel.text.text )
2927 {
2928 SOURCE_POINT expectedTextPosition = sourceLabel.position;
2929 expectedTextPosition.x += sourceLabel.textOffset.x;
2930 expectedTextPosition.y += sourceLabel.textOffset.y;
2933 pagePoint( expectedTextPosition, pageHeight ) );
2934 found = true;
2935 break;
2936 }
2937 }
2938 }
2939 else
2940 {
2941 KICAD_T type = sourceLabel.kind == MODEL_LABEL_KIND::GLOBAL ? SCH_GLOBAL_LABEL_T
2942 : sourceLabel.kind == MODEL_LABEL_KIND::HIERARCHICAL ? SCH_HIER_LABEL_T
2943 : SCH_LABEL_T;
2944
2945 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( type ) )
2946 {
2947 auto* label = static_cast<SCH_LABEL_BASE*>( item );
2948
2949 if( label->GetPosition() == pagePoint( sourceLabel.position, pageHeight )
2950 && label->GetText() == sourceLabel.text.text )
2951 {
2952 BOOST_CHECK_CLOSE( label->GetTextAngleDegrees(), sourceLabel.angle / 10.0, 0.001 );
2953
2954 if( sourceLabel.presentation.height > 0 )
2955 {
2956 BOOST_CHECK_EQUAL( label->GetTextHeight(),
2957 schIUScale.MilsToIU( sourceLabel.presentation.height / 2.0 ) );
2958 }
2959
2960 checkTextPresentation( *label, sourceLabel.presentation );
2961 found = true;
2962 break;
2963 }
2964 }
2965 }
2966
2967 BOOST_CHECK_MESSAGE( found, sourceLabel.text.text );
2968 }
2969
2970 std::multiset<wxString> singleSnapshot = connectivitySnapshot( m_schematic );
2971
2972 for( const MODEL_NET& net : connectivity.nets )
2973 BOOST_CHECK_MESSAGE( hasNetName( singleSnapshot, net.name.text ), net.name.text );
2974
2975 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_LINE_T ) )
2976 {
2977 auto* line = static_cast<SCH_LINE*>( item );
2978
2979 if( line->GetLayer() != LAYER_WIRE )
2980 continue;
2981
2982 SCH_CONNECTION* connection = line->Connection( &rootPath );
2983 BOOST_REQUIRE( connection );
2984 BOOST_CHECK( std::ranges::any_of( connectivity.nets,
2985 [&]( const MODEL_NET& aNet )
2986 {
2987 return connection->GetNetName() == aNet.name.text
2988 || connection->GetNetName().EndsWith( wxS( "/" ) + aNet.name.text );
2989 } ) );
2990 }
2991
2992 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
2993 {
2994 auto* symbol = static_cast<SCH_SYMBOL*>( item );
2995
2996 if( !symbol->GetRef( &rootPath ).StartsWith( wxS( "#PWR" ) ) )
2997 continue;
2998
2999 for( SCH_PIN* pin : symbol->GetPins( &rootPath ) )
3000 {
3001 SCH_CONNECTION* connection = pin->Connection( &rootPath );
3002 BOOST_REQUIRE( connection );
3003 BOOST_CHECK( connection->GetNetName() == symbol->GetValue( &rootPath, RAW_VALUE )
3004 || connection->GetNetName().EndsWith( wxS( "/" ) + symbol->GetValue( &rootPath, RAW_VALUE ) ) );
3005 }
3006 }
3007
3008 m_schematic.Reset();
3009 root = m_schematic.GetTopLevelSheet();
3010 PADS_SCH_MODEL buses = parseBinaryFixture( wxS( "connectivity_topology" ) );
3011 builder.Build( buses, &m_schematic, nullptr, binaryFixture( wxS( "connectivity_topology" ) ) );
3012 connectivitySnapshot( m_schematic );
3013 size_t expectedBusSegments = 0;
3014 size_t expectedBusEntries = 0;
3015
3016 for( const MODEL_BUS& bus : buses.buses )
3017 {
3018 expectedBusSegments += bus.vertices.size() - 1;
3019 expectedBusEntries += bus.entries.size();
3020 }
3021
3022 BOOST_CHECK_EQUAL( itemCount( root->GetScreen(), SCH_BUS_WIRE_ENTRY_T ), expectedBusEntries );
3023 size_t builtBusSegments = 0;
3024
3025 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_LINE_T ) )
3026 {
3027 if( item->GetLayer() == LAYER_BUS )
3028 ++builtBusSegments;
3029 }
3030
3031 BOOST_CHECK_EQUAL( builtBusSegments, expectedBusSegments );
3032
3033 for( const MODEL_BUS& bus : buses.buses )
3034 {
3035 for( const MODEL_BUS_ENTRY& sourceEntry : bus.entries )
3036 {
3037 bool found = false;
3038 auto sourceNet = std::ranges::find( buses.nets, sourceEntry.memberNet.id, &MODEL_NET::id );
3039 BOOST_REQUIRE( sourceNet != buses.nets.end() );
3040
3041 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_BUS_WIRE_ENTRY_T ) )
3042 {
3043 if( item->GetPosition()
3044 == pagePoint( sourceEntry.position,
3045 root->GetScreen()->GetPageSettings().GetHeightIU( schIUScale.IU_PER_MILS ) ) )
3046 {
3047 found = true;
3048 SCH_CONNECTION* connection = item->Connection( &m_schematic.CurrentSheet() );
3049 BOOST_REQUIRE( connection );
3050 BOOST_CHECK( connection->GetNetName() == sourceNet->name.text
3051 || connection->GetNetName().EndsWith( wxS( "/" ) + sourceNet->name.text ) );
3052 }
3053 }
3054
3055 BOOST_CHECK( found );
3056 }
3057 }
3058
3059 PADS_SCH_MODEL coincident = parseBinaryFixture( wxS( "connectivity_topology" ) );
3060 BOOST_REQUIRE( !coincident.buses.empty() );
3061 BOOST_REQUIRE( !coincident.buses.front().entries.empty() );
3062 const MODEL_BUS_ENTRY& coincidentEntry = coincident.buses.front().entries.front();
3063 auto ownerNet = std::ranges::find( coincident.nets, coincidentEntry.memberNet.id, &MODEL_NET::id );
3064 BOOST_REQUIRE( ownerNet != coincident.nets.end() );
3065 auto otherNet = std::ranges::find_if( coincident.nets,
3066 [&]( const MODEL_NET& aNet )
3067 {
3068 return aNet.sheet.id == ownerNet->sheet.id && aNet.id != ownerNet->id;
3069 } );
3070 BOOST_REQUIRE( otherNet != coincident.nets.end() );
3071 auto ownerConnection = std::ranges::find_if(
3072 ownerNet->connections,
3073 [&]( const MODEL_CONNECTION& aConnection )
3074 {
3075 auto samePoint = []( const SOURCE_POINT& aLeft, const SOURCE_POINT& aRight )
3076 {
3077 return aLeft.x == aRight.x && aLeft.y == aRight.y;
3078 };
3079 return aConnection.vertices.size() >= 2
3080 && ( samePoint( aConnection.vertices.front(), coincidentEntry.position )
3081 || samePoint( aConnection.vertices.back(), coincidentEntry.position ) );
3082 } );
3083 BOOST_REQUIRE( ownerConnection != ownerNet->connections.end() );
3084 SOURCE_POINT adjacent = ownerConnection->vertices.front().x == coincidentEntry.position.x
3085 && ownerConnection->vertices.front().y == coincidentEntry.position.y
3086 ? ownerConnection->vertices[1]
3087 : ownerConnection->vertices[ownerConnection->vertices.size() - 2];
3088 MODEL_CONNECTION distinctNetConnection = *ownerConnection;
3089 distinctNetConnection.vertices = { coincidentEntry.position, adjacent };
3090 distinctNetConnection.endpoints.resize( 2 );
3091
3092 for( size_t endpoint = 0; endpoint < distinctNetConnection.endpoints.size(); ++endpoint )
3093 {
3094 distinctNetConnection.endpoints[endpoint].kind = MODEL_ENDPOINT_KIND::POINT;
3095 distinctNetConnection.endpoints[endpoint].placement.reset();
3096 distinctNetConnection.endpoints[endpoint].pin.reset();
3097 distinctNetConnection.endpoints[endpoint].point = distinctNetConnection.vertices[endpoint];
3098 }
3099
3100 otherNet->connections.push_back( distinctNetConnection );
3101
3102 m_schematic.Reset();
3103 root = m_schematic.GetTopLevelSheet();
3104 builder.Build( coincident, &m_schematic, nullptr, binaryFixture( wxS( "connectivity_topology" ) ) );
3105 const int coincidentPageHeight = root->GetScreen()->GetPageSettings().GetHeightIU( schIUScale.IU_PER_MILS );
3106 const VECTOR2I entryStart = pagePoint( coincidentEntry.position, coincidentPageHeight );
3107 const VECTOR2I entryEnd = pagePoint( adjacent, coincidentPageHeight );
3108 const VECTOR2I entryDelta = entryEnd - entryStart;
3109 const int entrySpan = std::max( std::abs( entryDelta.x ), std::abs( entryDelta.y ) );
3110 const int shortSpan = std::min( entrySpan, schIUScale.MilsToIU( DEFAULT_SCH_ENTRY_SIZE ) );
3111 const VECTOR2I shortEntryEnd =
3112 entrySpan == 0 ? entryEnd
3113 : entryStart
3114 + VECTOR2I( int64_t( entryDelta.x ) * shortSpan / entrySpan,
3115 int64_t( entryDelta.y ) * shortSpan / entrySpan );
3116 size_t coincidentWireSegments = 0;
3117 bool exactEntry = false;
3118
3119 for( SCH_ITEM* item : root->GetScreen()->Items() )
3120 {
3121 if( auto* line = dynamic_cast<SCH_LINE*>( item ); line && line->GetLayer() == LAYER_WIRE )
3122 {
3123 coincidentWireSegments += ( line->GetStartPoint() == entryStart && line->GetEndPoint() == entryEnd )
3124 || ( line->GetStartPoint() == entryEnd && line->GetEndPoint() == entryStart );
3125 }
3126 else if( auto* entry = dynamic_cast<SCH_BUS_WIRE_ENTRY*>( item ) )
3127 {
3128 exactEntry |= entry->GetPosition() == entryStart && entry->GetSize() == shortEntryEnd - entryStart;
3129 }
3130 }
3131
3132 BOOST_CHECK( exactEntry );
3133 BOOST_CHECK_EQUAL( coincidentWireSegments, 1u );
3134
3135 m_schematic.Reset();
3136 root = m_schematic.GetTopLevelSheet();
3137 PADS_SCH_MODEL graphics = parseBinaryFixture( wxS( "page_graphics" ) );
3138 BUILD_RESULT graphicsResult =
3139 builder.Build( graphics, &m_schematic, nullptr, binaryFixture( wxS( "page_graphics" ) ) );
3140 size_t expectedShapes = 0;
3141 size_t expectedTexts = graphics.texts.size();
3142 size_t expectedNoteLines = 0;
3143
3144 auto countGraphic = [&]( const MODEL_GRAPHIC& aGraphic )
3145 {
3146 if( aGraphic.kind == MODEL_GRAPHIC_KIND::TEXT )
3147 ++expectedTexts;
3148 else if( ( aGraphic.kind == MODEL_GRAPHIC_KIND::LINE || aGraphic.kind == MODEL_GRAPHIC_KIND::POLYLINE )
3149 && aGraphic.fill == MODEL_FILL_STYLE::NONE )
3150 expectedNoteLines += aGraphic.points.size() - 1;
3151 else
3152 ++expectedShapes;
3153 };
3154
3155 for( const MODEL_PAGE_GRAPHIC& graphic : graphics.graphics )
3156 countGraphic( graphic.graphic );
3157
3158 for( const MODEL_GRAPHIC& graphic : graphics.sheets.front().border )
3159 countGraphic( graphic );
3160
3161 size_t builtNoteLines = 0;
3162
3163 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_LINE_T ) )
3164 {
3165 if( item->GetLayer() == LAYER_NOTES )
3166 ++builtNoteLines;
3167 }
3168
3169 BOOST_CHECK_EQUAL( itemCount( root->GetScreen(), SCH_TEXT_T ), expectedTexts );
3170 BOOST_CHECK_EQUAL( itemCount( root->GetScreen(), SCH_SHAPE_T ), expectedShapes );
3171 BOOST_CHECK_EQUAL( builtNoteLines, expectedNoteLines );
3172
3173 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHAPE_T ) )
3174 {
3175 auto* shape = static_cast<SCH_SHAPE*>( item );
3176
3177 if( shape->GetShape() == SHAPE_T::POLY )
3178 BOOST_CHECK( shape->GetFillMode() != FILL_T::NO_FILL );
3179 }
3180
3181 const int graphicsPageHeight = root->GetScreen()->GetPageSettings().GetHeightIU( schIUScale.IU_PER_MILS );
3182
3183 auto checkGraphic = [&]( const MODEL_GRAPHIC& aGraphic )
3184 {
3185 if( aGraphic.kind == MODEL_GRAPHIC_KIND::TEXT )
3186 {
3187 bool found = false;
3188
3189 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3190 {
3191 auto* text = static_cast<SCH_TEXT*>( item );
3192
3193 if( text->GetText() == aGraphic.text.text
3194 && text->GetPosition() == pagePoint( aGraphic.points.front(), graphicsPageHeight ) )
3195 {
3196 BOOST_CHECK_CLOSE( text->GetTextAngleDegrees(), aGraphic.angle / 10.0, 0.001 );
3197 checkTextPresentation( *text, aGraphic.presentation );
3198 const bool approximatedFont = !aGraphic.presentation.font.text.IsEmpty()
3199 && aGraphic.presentation.font.text != wxS( "Default Font" );
3201 std::ranges::count_if( graphicsResult.diagnostics,
3202 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
3203 {
3204 return aDiagnostic.source == aGraphic.presentation.source
3205 && aDiagnostic.message.Contains( wxS( "font" ) );
3206 } ),
3207 approximatedFont ? 1u : 0u );
3208 found = true;
3209 break;
3210 }
3211 }
3212
3213 BOOST_CHECK_MESSAGE( found, aGraphic.text.text );
3214 return;
3215 }
3216
3217 if( ( aGraphic.kind == MODEL_GRAPHIC_KIND::LINE || aGraphic.kind == MODEL_GRAPHIC_KIND::POLYLINE )
3218 && aGraphic.fill == MODEL_FILL_STYLE::NONE )
3219 {
3220 for( size_t point = 1; point < aGraphic.points.size(); ++point )
3221 {
3222 bool found = false;
3223
3224 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_LINE_T ) )
3225 {
3226 auto* line = static_cast<SCH_LINE*>( item );
3227
3228 if( line->GetLayer() == LAYER_NOTES
3229 && line->GetStartPoint() == pagePoint( aGraphic.points[point - 1], graphicsPageHeight )
3230 && line->GetEndPoint() == pagePoint( aGraphic.points[point], graphicsPageHeight ) )
3231 {
3232 BOOST_CHECK_EQUAL( line->GetStroke().GetWidth(),
3233 schIUScale.MilsToIU( aGraphic.strokeWidth / 2.0 ) );
3234 BOOST_CHECK( line->GetStroke().GetLineStyle() == lineStyle( aGraphic.lineStyle ) );
3235 found = true;
3236 break;
3237 }
3238 }
3239
3240 BOOST_CHECK( found );
3241 }
3242
3243 return;
3244 }
3245
3246 SHAPE_T expectedType = aGraphic.kind == MODEL_GRAPHIC_KIND::RECTANGLE ? SHAPE_T::RECTANGLE
3247 : aGraphic.kind == MODEL_GRAPHIC_KIND::CIRCLE ? SHAPE_T::CIRCLE
3248 : aGraphic.kind == MODEL_GRAPHIC_KIND::ARC ? SHAPE_T::ARC
3249 : SHAPE_T::POLY;
3250 bool found = false;
3251
3252 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHAPE_T ) )
3253 {
3254 auto* shape = static_cast<SCH_SHAPE*>( item );
3255
3256 if( shape->GetShape() != expectedType )
3257 continue;
3258
3259 if( expectedType == SHAPE_T::POLY )
3260 {
3261 std::vector<VECTOR2I> expectedPoints;
3262
3263 for( const SOURCE_POINT& point : aGraphic.points )
3264 expectedPoints.push_back( pagePoint( point, graphicsPageHeight ) );
3265
3266 if( shape->GetPolyPoints() != expectedPoints )
3267 continue;
3268 }
3269 else if( expectedType == SHAPE_T::ARC )
3270 {
3271 if( shape->GetStart() != pagePoint( aGraphic.points.front(), graphicsPageHeight )
3272 || shape->GetEnd() != pagePoint( aGraphic.points.back(), graphicsPageHeight )
3273 || shape->GetCenter() != pagePoint( aGraphic.arcCenter, graphicsPageHeight ) )
3274 {
3275 continue;
3276 }
3277
3278 BOOST_CHECK_EQUAL( std::abs( shape->GetArcAngle().AsTenthsOfADegree() ),
3279 std::abs( aGraphic.arcSweepAngle ) );
3280 BOOST_CHECK_EQUAL( shape->GetArcAngle().AsTenthsOfADegree(),
3281 aGraphic.arcClockwise ? std::abs( aGraphic.arcSweepAngle )
3282 : -std::abs( aGraphic.arcSweepAngle ) );
3283 const VECTOR2I boundsStart = pagePoint( aGraphic.arcBoundsStart, graphicsPageHeight );
3284 const VECTOR2I boundsEnd = pagePoint( aGraphic.arcBoundsEnd, graphicsPageHeight );
3285 const int expectedRadiusX = std::abs( boundsEnd.x - boundsStart.x ) / 2;
3286 const int expectedRadiusY = std::abs( boundsEnd.y - boundsStart.y ) / 2;
3287 const int actualRadius =
3288 KiROUND( std::hypot( static_cast<double>( shape->GetStart().x - shape->GetCenter().x ),
3289 static_cast<double>( shape->GetStart().y - shape->GetCenter().y ) ) );
3290 BOOST_CHECK_EQUAL( actualRadius, expectedRadiusX );
3291 BOOST_CHECK_EQUAL( actualRadius, expectedRadiusY );
3292 }
3293 else if( expectedType == SHAPE_T::CIRCLE )
3294 {
3296 center.x = ( aGraphic.points.front().x + aGraphic.points.back().x ) / 2;
3297 center.y = ( aGraphic.points.front().y + aGraphic.points.back().y ) / 2;
3298 const VECTOR2I expectedCenter = pagePoint( center, graphicsPageHeight );
3299 const VECTOR2I expectedEdge = pagePoint( aGraphic.points.back(), graphicsPageHeight );
3300 const int expectedRadius =
3301 KiROUND( std::hypot( static_cast<double>( expectedEdge.x - expectedCenter.x ),
3302 static_cast<double>( expectedEdge.y - expectedCenter.y ) ) );
3303 const int actualRadius =
3304 KiROUND( std::hypot( static_cast<double>( shape->GetEnd().x - shape->GetStart().x ),
3305 static_cast<double>( shape->GetEnd().y - shape->GetStart().y ) ) );
3306
3307 if( shape->GetCenter() != expectedCenter || actualRadius != expectedRadius )
3308 continue;
3309 }
3310 else if( expectedType == SHAPE_T::RECTANGLE )
3311 {
3312 if( shape->GetStart() != pagePoint( aGraphic.points.front(), graphicsPageHeight )
3313 || shape->GetEnd() != pagePoint( aGraphic.points.back(), graphicsPageHeight ) )
3314 {
3315 continue;
3316 }
3317 }
3318 else if( shape->GetStart() != pagePoint( aGraphic.points.front(), graphicsPageHeight ) )
3319 {
3320 continue;
3321 }
3322
3323 BOOST_CHECK_EQUAL( shape->GetStroke().GetWidth(), schIUScale.MilsToIU( aGraphic.strokeWidth / 2.0 ) );
3324 BOOST_CHECK( shape->GetStroke().GetLineStyle() == lineStyle( aGraphic.lineStyle ) );
3325 BOOST_CHECK( shape->GetFillMode()
3326 == ( aGraphic.fill == MODEL_FILL_STYLE::NONE ? FILL_T::NO_FILL
3327 : aGraphic.fill == MODEL_FILL_STYLE::HATCHED ? FILL_T::HATCH
3329 found = true;
3330 break;
3331 }
3332
3333 BOOST_CHECK( found );
3334 };
3335
3336 for( const MODEL_PAGE_GRAPHIC& graphic : graphics.graphics )
3337 checkGraphic( graphic.graphic );
3338
3339 for( const MODEL_GRAPHIC& graphic : graphics.sheets.front().border )
3340 checkGraphic( graphic );
3341
3342 for( const MODEL_TEXT& sourceText : graphics.texts )
3343 {
3344 bool found = false;
3345
3346 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3347 {
3348 auto* text = static_cast<SCH_TEXT*>( item );
3349
3350 if( text->GetText() == sourceText.text.text
3351 && text->GetPosition() == pagePoint( sourceText.position, graphicsPageHeight ) )
3352 {
3353 BOOST_CHECK_CLOSE( text->GetTextAngleDegrees(), sourceText.angle / 10.0, 0.001 );
3354 checkTextPresentation( *text, sourceText.presentation );
3355 const bool approximatedFont = !sourceText.presentation.font.text.IsEmpty()
3356 && sourceText.presentation.font.text != wxS( "Default Font" );
3357 BOOST_CHECK_EQUAL( std::ranges::count_if( graphicsResult.diagnostics,
3358 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
3359 {
3360 return aDiagnostic.source
3361 == sourceText.presentation.source
3362 && aDiagnostic.message.Contains( wxS( "font" ) );
3363 } ),
3364 approximatedFont ? 1u : 0u );
3365 found = true;
3366 break;
3367 }
3368 }
3369
3370 BOOST_CHECK_MESSAGE( found, sourceText.text.text );
3371 }
3372
3373 m_schematic.Reset();
3374 PADS_SCH_MODEL multi = parseBinaryFixture( wxS( "multisheet_connectivity" ) );
3375 builder.Build( multi, &m_schematic, nullptr, binaryFixture( wxS( "multisheet_connectivity" ) ) );
3376 assertSourceConnectivity( multi, m_schematic );
3377 std::multiset<wxString> multiSnapshot = connectivitySnapshot( m_schematic );
3378
3379 for( const MODEL_NET& net : multi.nets )
3380 BOOST_CHECK_MESSAGE( hasNetName( multiSnapshot, net.name.text ), net.name.text );
3381}
3382
3383
3384BOOST_AUTO_TEST_CASE( BinaryConnectivityRoundTrip )
3385{
3386 using namespace PADS_SCH_BINARY;
3387
3388 const PADS_SCH_MODEL model = parseBinaryFixture( wxS( "multisheet_connectivity" ) );
3389 PADS_SCH_BINARY_BUILDER().Build( model, &m_schematic, nullptr, binaryFixture( wxS( "multisheet_connectivity" ) ) );
3390 assertSourceConnectivity( model, m_schematic );
3391 std::multiset<wxString> before = connectivitySnapshot( m_schematic );
3392
3393 for( const MODEL_NET& net : model.nets )
3394 BOOST_CHECK_MESSAGE( hasNetName( before, net.name.text ), net.name.text );
3395
3396 wxString tempDir = wxFileName::CreateTempFileName( wxS( "pads_binary_connectivity_" ) );
3397 BOOST_REQUIRE( wxRemoveFile( tempDir ) );
3398 BOOST_REQUIRE( wxFileName::Mkdir( tempDir ) );
3399 roundTripTopLevelSheets( m_schematic, tempDir );
3400 assertSourceConnectivity( model, m_schematic );
3401 std::multiset<wxString> after = connectivitySnapshot( m_schematic );
3402
3403 for( const wxString& value : before )
3404 {
3405 if( before.count( value ) != after.count( value ) )
3406 BOOST_TEST_MESSAGE( "before-only/count mismatch: " << value );
3407 }
3408
3409 for( const wxString& value : after )
3410 {
3411 if( before.count( value ) != after.count( value ) )
3412 BOOST_TEST_MESSAGE( "after-only/count mismatch: " << value );
3413 }
3414
3415 BOOST_CHECK( before == after );
3416
3417 for( const MODEL_NET& net : model.nets )
3418 BOOST_CHECK_MESSAGE( hasNetName( after, net.name.text ), net.name.text );
3419
3420 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
3421
3422 size_t pinEndpointsBefore = 0;
3423 size_t pinEndpointsAfter = 0;
3424 size_t powerLabelsBefore = 0;
3425 size_t powerLabelsAfter = 0;
3426
3427 for( const wxString& fixture :
3428 { wxS( "minimal_v13" ), wxS( "placement_transform" ), wxS( "fields" ), wxS( "connectors" ),
3429 wxS( "text_encoding" ), wxS( "page_graphics" ), wxS( "connectivity_topology" ),
3430 wxS( "multisheet_connectivity" ), wxS( "symbol_primitives" ), wxS( "pin_styles" ), wxS( "multigate" ) } )
3431 {
3432 const PADS_SCH_MODEL sourceModel = parseBinaryFixture( fixture );
3433
3434 if( sourceModel.nets.empty() && sourceModel.buses.empty() && sourceModel.labels.empty()
3435 && sourceModel.junctions.empty() )
3436 {
3437 continue;
3438 }
3439
3440 m_schematic.Reset();
3441 PADS_SCH_BINARY_BUILDER().Build( sourceModel, &m_schematic, nullptr, binaryFixture( fixture ) );
3442 CONNECTIVITY_ORACLE_COUNTS beforeCounts = assertSourceConnectivity( sourceModel, m_schematic );
3443 const size_t expectedPinEndpoints =
3444 std::accumulate( sourceModel.nets.begin(), sourceModel.nets.end(), size_t( 0 ),
3445 []( size_t aCount, const MODEL_NET& aNet )
3446 {
3447 for( const MODEL_CONNECTION& connection : aNet.connections )
3448 aCount += std::ranges::count( connection.endpoints, MODEL_ENDPOINT_KIND::PIN,
3449 &MODEL_CONNECTION_ENDPOINT::kind );
3450
3451 return aCount;
3452 } );
3453 const size_t expectedPowerLabels = std::ranges::count_if( sourceModel.labels,
3454 []( const MODEL_LABEL& aLabel )
3455 {
3456 return aLabel.kind == MODEL_LABEL_KIND::POWER
3457 || aLabel.kind == MODEL_LABEL_KIND::GROUND;
3458 } );
3459 BOOST_CHECK_EQUAL( beforeCounts.pinEndpoints, expectedPinEndpoints );
3460 BOOST_CHECK_EQUAL( beforeCounts.powerLabels, expectedPowerLabels );
3461
3462 if( fixture == wxS( "minimal_v13" ) )
3463 BOOST_CHECK_GT( beforeCounts.pinEndpoints, 0u );
3464
3465 pinEndpointsBefore += beforeCounts.pinEndpoints;
3466 powerLabelsBefore += beforeCounts.powerLabels;
3467
3468 wxString fixtureTemp = wxFileName::CreateTempFileName( wxS( "pads_binary_source_oracle_" ) );
3469 BOOST_REQUIRE( wxRemoveFile( fixtureTemp ) );
3470 BOOST_REQUIRE( wxFileName::Mkdir( fixtureTemp ) );
3471 roundTripTopLevelSheets( m_schematic, fixtureTemp );
3472 CONNECTIVITY_ORACLE_COUNTS afterCounts = assertSourceConnectivity( sourceModel, m_schematic );
3473 BOOST_CHECK_EQUAL( afterCounts.pinEndpoints, beforeCounts.pinEndpoints );
3474 BOOST_CHECK_EQUAL( afterCounts.powerLabels, beforeCounts.powerLabels );
3475 pinEndpointsAfter += afterCounts.pinEndpoints;
3476 powerLabelsAfter += afterCounts.powerLabels;
3477 BOOST_CHECK( wxFileName::Rmdir( fixtureTemp, wxPATH_RMDIR_RECURSIVE ) );
3478 }
3479
3480 BOOST_CHECK_GT( pinEndpointsBefore, 0u );
3481 BOOST_CHECK_EQUAL( pinEndpointsAfter, pinEndpointsBefore );
3482 BOOST_CHECK_GT( powerLabelsBefore, 0u );
3483 BOOST_CHECK_EQUAL( powerLabelsAfter, powerLabelsBefore );
3484
3485 m_schematic.Reset();
3486 const PADS_SCH_MODEL graphics = parseBinaryFixture( wxS( "page_graphics" ) );
3487 SCH_SHEET* graphicsRoot = m_schematic.GetTopLevelSheet();
3488 PADS_SCH_BINARY_BUILDER().Build( graphics, &m_schematic, nullptr, binaryFixture( wxS( "page_graphics" ) ) );
3489 std::multiset<wxString> graphicsBefore = connectivitySnapshot( m_schematic );
3490 wxString graphicsFile = wxFileName::CreateTempFileName( wxS( "pads_binary_graphics_" ) );
3491 SCH_IO_KICAD_SEXPR graphicsIo;
3492 BOOST_REQUIRE_NO_THROW( graphicsIo.SaveSchematicFile( graphicsFile, graphicsRoot, &m_schematic ) );
3493 m_schematic.Reset();
3494 SCH_SHEET* defaultSheet = m_schematic.GetTopLevelSheet();
3495 SCH_SHEET* loaded = nullptr;
3496 BOOST_REQUIRE_NO_THROW( loaded = graphicsIo.LoadSchematicFile( graphicsFile, &m_schematic ) );
3497 BOOST_REQUIRE( loaded );
3498 m_schematic.AddTopLevelSheet( loaded );
3499 m_schematic.RemoveTopLevelSheet( defaultSheet );
3500 delete defaultSheet;
3501 m_schematic.RefreshHierarchy();
3502 std::multiset<wxString> graphicsAfter = connectivitySnapshot( m_schematic );
3503
3504 for( const wxString& value : graphicsBefore )
3505 {
3506 if( graphicsBefore.count( value ) != graphicsAfter.count( value ) )
3507 BOOST_TEST_MESSAGE( "graphics before-only/count mismatch: " << value );
3508 }
3509
3510 for( const wxString& value : graphicsAfter )
3511 {
3512 if( graphicsBefore.count( value ) != graphicsAfter.count( value ) )
3513 BOOST_TEST_MESSAGE( "graphics after-only/count mismatch: " << value );
3514 }
3515
3516 BOOST_CHECK( graphicsBefore == graphicsAfter );
3517 BOOST_CHECK( wxRemoveFile( graphicsFile ) );
3518}
3519
3520
3521BOOST_AUTO_TEST_CASE( BinaryPropertyDispositionWarnings )
3522{
3523 using namespace PADS_SCH_BINARY;
3524
3525 SOURCE_PROVENANCE keySource{ wxS( "key.sch" ), 13, wxS( "placement" ), 17, 23, 41, 8, 2 };
3526 PARSER_DIAGNOSTIC keyDiagnostic = MakePropertyDiagnostic( RPT_SEVERITY_WARNING, keySource, wxS( "property" ),
3527 PROPERTY_DISPOSITION::PRESERVED, wxS( "message" ) );
3528 std::set<DIAGNOSTIC_PROPERTY_KEY> propertyKeys;
3529 BOOST_REQUIRE( DiagnosticPropertyKey( keyDiagnostic ) );
3530 propertyKeys.insert( *DiagnosticPropertyKey( keyDiagnostic ) );
3531 propertyKeys.insert( *DiagnosticPropertyKey( keyDiagnostic ) );
3532
3533 auto insertChangedKey = [&]( auto aMutator )
3534 {
3535 PARSER_DIAGNOSTIC changed = keyDiagnostic;
3536 aMutator( changed );
3538 propertyKeys.insert( *DiagnosticPropertyKey( changed ) );
3539 };
3540 insertChangedKey(
3541 []( PARSER_DIAGNOSTIC& aDiagnostic )
3542 {
3543 aDiagnostic.source.file += 'x';
3544 } );
3545 insertChangedKey(
3546 []( PARSER_DIAGNOSTIC& aDiagnostic )
3547 {
3548 ++aDiagnostic.source.version;
3549 } );
3550 insertChangedKey(
3551 []( PARSER_DIAGNOSTIC& aDiagnostic )
3552 {
3553 aDiagnostic.source.objectClass += 'x';
3554 } );
3555 insertChangedKey(
3556 []( PARSER_DIAGNOSTIC& aDiagnostic )
3557 {
3558 ++aDiagnostic.source.controller;
3559 } );
3560 insertChangedKey(
3561 []( PARSER_DIAGNOSTIC& aDiagnostic )
3562 {
3563 ++aDiagnostic.source.recordIndex;
3564 } );
3565 insertChangedKey(
3566 []( PARSER_DIAGNOSTIC& aDiagnostic )
3567 {
3568 ++aDiagnostic.source.absoluteOffset;
3569 } );
3570 insertChangedKey(
3571 []( PARSER_DIAGNOSTIC& aDiagnostic )
3572 {
3573 ++aDiagnostic.source.length;
3574 } );
3575 insertChangedKey(
3576 []( PARSER_DIAGNOSTIC& aDiagnostic )
3577 {
3578 ++aDiagnostic.source.sheet;
3579 } );
3580 insertChangedKey(
3581 []( PARSER_DIAGNOSTIC& aDiagnostic )
3582 {
3583 aDiagnostic.property->name += 'x';
3584 } );
3585 insertChangedKey(
3586 []( PARSER_DIAGNOSTIC& aDiagnostic )
3587 {
3588 aDiagnostic.property->disposition = PROPERTY_DISPOSITION::UNSUPPORTED;
3589 } );
3590 BOOST_CHECK_EQUAL( propertyKeys.size(), 11u );
3591 BOOST_CHECK( !DiagnosticPropertyKey( PARSER_DIAGNOSTIC{} ) );
3592
3593 for( const wxString& fixture :
3594 { wxS( "minimal_v13" ), wxS( "placement_transform" ), wxS( "fields" ), wxS( "connectors" ),
3595 wxS( "text_encoding" ), wxS( "page_graphics" ), wxS( "connectivity_topology" ),
3596 wxS( "multisheet_connectivity" ), wxS( "symbol_primitives" ), wxS( "pin_styles" ), wxS( "multigate" ) } )
3597 {
3598 m_schematic.Reset();
3599 PADS_SCH_MODEL corpusModel = parseBinaryFixture( fixture );
3600 std::vector<const SOURCE_PROPERTY*> properties = allSourceProperties( corpusModel );
3601 BOOST_REQUIRE_MESSAGE( !properties.empty(), fixture );
3602 BUILD_RESULT corpusResult =
3603 PADS_SCH_BINARY_BUILDER().Build( corpusModel, &m_schematic, nullptr, binaryFixture( fixture ) );
3604
3605 for( const SOURCE_PROPERTY* property : properties )
3606 {
3607 const size_t multiplicity =
3608 std::ranges::count_if( properties,
3609 [&]( const SOURCE_PROPERTY* aOther )
3610 {
3611 return aOther->source == property->source
3612 && aOther->name.text == property->name.text
3613 && aOther->disposition == property->disposition;
3614 } );
3615 const size_t parserOwned =
3616 std::ranges::count_if( corpusModel.diagnostics,
3617 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
3618 {
3619 return aDiagnostic.source == property->source && aDiagnostic.property
3620 && aDiagnostic.property->name == property->name.text
3621 && aDiagnostic.property->disposition == property->disposition;
3622 } );
3623 const size_t builderOwned =
3624 std::ranges::count_if( corpusResult.diagnostics,
3625 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
3626 {
3627 return aDiagnostic.source == property->source && aDiagnostic.property
3628 && aDiagnostic.property->name == property->name.text
3629 && aDiagnostic.property->disposition == property->disposition;
3630 } );
3631 BOOST_CHECK_LE( parserOwned, multiplicity );
3632
3633 if( property->disposition == PROPERTY_DISPOSITION::EXACT
3634 || property->disposition == PROPERTY_DISPOSITION::PRESERVED )
3635 {
3636 BOOST_CHECK_EQUAL( builderOwned, 0u );
3637 BOOST_CHECK_EQUAL( parserOwned, 0u );
3638 }
3639 else
3640 {
3641 BOOST_CHECK_EQUAL( builderOwned + parserOwned, multiplicity );
3642 BOOST_CHECK( builderOwned == 0u || parserOwned == 0u );
3643 }
3644 }
3645
3646 m_schematic.Reset();
3647 SCH_IO_PADS plugin;
3648 CAPTURING_REPORTER reporter;
3649 plugin.SetReporter( &reporter );
3650 BOOST_REQUIRE_NO_THROW( plugin.LoadSchematicFile( binaryFixture( fixture ), &m_schematic ) );
3651 std::vector<PARSER_DIAGNOSTIC> expectedDiagnostics = corpusModel.diagnostics;
3652 expectedDiagnostics.insert( expectedDiagnostics.end(), corpusResult.diagnostics.begin(),
3653 corpusResult.diagnostics.end() );
3654 std::map<std::pair<wxString, SEVERITY>, std::pair<const PARSER_DIAGNOSTIC*, size_t>> diagnosticGroups;
3655
3656 for( const PARSER_DIAGNOSTIC& diagnostic : expectedDiagnostics )
3657 {
3658 auto& [first, count] = diagnosticGroups[{ diagnostic.message, diagnostic.severity }];
3659
3660 if( !first )
3661 first = &diagnostic;
3662
3663 ++count;
3664 }
3665
3666 std::map<std::pair<wxString, SEVERITY>, size_t> expectedWarningCounts;
3667
3668 for( const auto& [key, group] : diagnosticGroups )
3669 {
3670 const auto& [message, severity] = key;
3671 const auto& [first, count] = group;
3672 wxString groupedMessage = message;
3673
3674 if( count > 1 )
3675 groupedMessage += wxString::Format( wxS( " (%zu occurrences)" ), count );
3676
3677 const wxString formatted = FormatParserError( first->source, groupedMessage );
3678 ++expectedWarningCounts[{ formatted, severity }];
3679 BOOST_CHECK( formatted.Contains( wxString::Format( wxS( "v0x%04X" ), first->source.version ) ) );
3680 BOOST_CHECK( formatted.Contains( first->source.objectClass ) );
3681 BOOST_CHECK( formatted.Contains(
3682 wxString::Format( wxS( "controller %d, record %llu" ), first->source.controller,
3683 static_cast<unsigned long long>( first->source.recordIndex ) ) ) );
3684 BOOST_CHECK( formatted.Contains( wxString::Format(
3685 wxS( "offset 0x%llX" ), static_cast<unsigned long long>( first->source.absoluteOffset ) ) ) );
3686 }
3687
3688 std::map<std::pair<wxString, SEVERITY>, size_t> reportedWarningCounts;
3689
3690 for( const auto& [message, severity] : reporter.messages )
3691 {
3692 if( severity != RPT_SEVERITY_INFO )
3693 ++reportedWarningCounts[{ message, severity }];
3694 }
3695
3696 BOOST_CHECK( reportedWarningCounts == expectedWarningCounts );
3697 }
3698
3699 auto assertParserOwnership =
3700 [&]( PADS_SCH_MODEL& aModel, const SOURCE_PROPERTY& aProperty, const wxString& aFixture )
3701 {
3702 m_schematic.Reset();
3703 BUILD_RESULT ownedResult =
3704 PADS_SCH_BINARY_BUILDER().Build( aModel, &m_schematic, nullptr, binaryFixture( aFixture ) );
3705 BOOST_CHECK_EQUAL( std::ranges::count_if( aModel.diagnostics,
3706 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
3707 {
3708 return aDiagnostic.source == aProperty.source
3709 && aDiagnostic.property
3710 && aDiagnostic.property->name == aProperty.name.text
3711 && aDiagnostic.property->disposition
3712 == aProperty.disposition;
3713 } ),
3714 1u );
3715 BOOST_CHECK_EQUAL( std::ranges::count_if( ownedResult.diagnostics,
3716 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
3717 {
3718 return aDiagnostic.source == aProperty.source
3719 && aDiagnostic.property
3720 && aDiagnostic.property->name == aProperty.name.text
3721 && aDiagnostic.property->disposition
3722 == aProperty.disposition;
3723 } ),
3724 0u );
3725 };
3726
3727 PADS_SCH_MODEL pageRelationship = parseBinaryFixture( wxS( "page_graphics" ) );
3728 BOOST_REQUIRE( !pageRelationship.graphics.empty() );
3729 SOURCE_PROPERTY relationship;
3730 relationship.name.text = wxS( "preserved_drawing_text_relationship" );
3731 relationship.value.text = wxS( "synthetic" );
3732 relationship.source = pageRelationship.graphics.front().graphic.source;
3734 pageRelationship.graphics.front().graphic.properties.push_back( relationship );
3735 pageRelationship.diagnostics.push_back( MakePropertyDiagnostic(
3736 RPT_SEVERITY_WARNING, relationship, wxS( "parser retained a page drawing relationship" ) ) );
3737 assertParserOwnership( pageRelationship, relationship, wxS( "page_graphics" ) );
3738
3739 PADS_SCH_MODEL fontPayloads = parseBinaryFixture( wxS( "fields" ) );
3740 BOOST_REQUIRE( !fontPayloads.placements.empty() );
3741 SOURCE_PROPERTY inlineFont;
3742 inlineFont.name.text = wxS( "inline_font_payload" );
3743 inlineFont.value.text = wxS( "synthetic" );
3744 inlineFont.source = fontPayloads.placements.front().source;
3746 fontPayloads.placements.front().properties.push_back( inlineFont );
3747 fontPayloads.diagnostics.push_back(
3748 MakePropertyDiagnostic( RPT_SEVERITY_WARNING, inlineFont, wxS( "parser retained inline font bytes" ) ) );
3749 SOURCE_PROPERTY fontFlags = inlineFont;
3750 fontFlags.name.text = wxS( "unsupported_font_style_flags" );
3751 fontPayloads.placements.front().properties.push_back( fontFlags );
3752 fontPayloads.diagnostics.push_back(
3753 MakePropertyDiagnostic( RPT_SEVERITY_WARNING, fontFlags, wxS( "parser retained font flag bits" ) ) );
3754 assertParserOwnership( fontPayloads, inlineFont, wxS( "fields" ) );
3755 assertParserOwnership( fontPayloads, fontFlags, wxS( "fields" ) );
3756
3757 PADS_SCH_MODEL busAlias = parseBinaryFixture( wxS( "connectivity_topology" ) );
3758 BOOST_REQUIRE( !busAlias.buses.empty() );
3759 SOURCE_PROPERTY aliasMembers;
3760 aliasMembers.name.text = wxS( "preserved_bus_alias_members" );
3761 aliasMembers.value.text = wxS( "synthetic" );
3762 aliasMembers.source = busAlias.buses.front().source;
3764 busAlias.buses.front().properties.push_back( aliasMembers );
3765 busAlias.diagnostics.push_back( MakePropertyDiagnostic( RPT_SEVERITY_WARNING, aliasMembers,
3766 wxS( "parser retained expanded bus membership" ) ) );
3767 assertParserOwnership( busAlias, aliasMembers, wxS( "connectivity_topology" ) );
3768
3769 PADS_SCH_MODEL unsupportedLabel = parseBinaryFixture( wxS( "connectivity_topology" ) );
3770 BOOST_REQUIRE( !unsupportedLabel.labels.empty() );
3771 unsupportedLabel.labels.front().kind = MODEL_LABEL_KIND::UNSUPPORTED;
3772 SOURCE_PROPERTY labelKind;
3773 labelKind.name.text = wxS( "unsupported_offpage_decal" );
3774 labelKind.value.text = wxS( "synthetic" );
3775 labelKind.source = unsupportedLabel.labels.front().source;
3777 unsupportedLabel.labels.front().properties.push_back( labelKind );
3778 unsupportedLabel.diagnostics.push_back(
3779 MakePropertyDiagnostic( RPT_SEVERITY_WARNING, labelKind, wxS( "parser retained label kind" ) ) );
3780 assertParserOwnership( unsupportedLabel, labelKind, wxS( "connectivity_topology" ) );
3781
3782 m_schematic.Reset();
3783 PADS_SCH_MODEL model = parseBinaryFixture( wxS( "connectivity_topology" ) );
3784 BOOST_REQUIRE( !model.labels.empty() );
3785 SOURCE_PROPERTY approximate;
3786 approximate.name.text = wxS( "qa_approximate_label_presentation" );
3787 approximate.value.text = wxS( "retained" );
3788 approximate.source = model.labels.front().source;
3790 model.labels.front().presentation.properties.push_back( approximate );
3791 SOURCE_PROPERTY exact = approximate;
3792 exact.name.text = wxS( "qa_exact_label_presentation" );
3794 model.labels.front().presentation.properties.push_back( exact );
3795 SOURCE_PROPERTY preserved = approximate;
3796 preserved.name.text = wxS( "qa_preserved_label_presentation" );
3798 model.labels.front().presentation.properties.push_back( preserved );
3799 SOURCE_PROPERTY unsupported = approximate;
3800 unsupported.name.text = wxS( "qa_unsupported_label_presentation" );
3802 model.labels.front().presentation.properties.push_back( unsupported );
3803
3804 BUILD_RESULT result = PADS_SCH_BINARY_BUILDER().Build( model, &m_schematic, nullptr,
3805 binaryFixture( wxS( "connectivity_topology" ) ) );
3806 for( const SOURCE_PROPERTY* property : { &approximate, &unsupported } )
3807 {
3808 BOOST_CHECK_EQUAL( std::ranges::count_if( result.diagnostics,
3809 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
3810 {
3811 return aDiagnostic.source == property->source
3812 && aDiagnostic.property
3813 && aDiagnostic.property->name == property->name.text
3814 && aDiagnostic.property->disposition
3815 == property->disposition;
3816 } ),
3817 1 );
3818 }
3819
3820 BOOST_CHECK( std::ranges::none_of( result.diagnostics,
3821 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
3822 {
3823 return aDiagnostic.source == preserved.source && aDiagnostic.property
3824 && aDiagnostic.property->name == preserved.name.text
3825 && aDiagnostic.property->disposition == preserved.disposition;
3826 } ) );
3827
3828 BOOST_CHECK( std::ranges::none_of( result.diagnostics,
3829 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
3830 {
3831 return aDiagnostic.property && aDiagnostic.property->name == exact.name.text
3832 && aDiagnostic.property->disposition == exact.disposition;
3833 } ) );
3834}
3835
3836
3837// PADS off-page power ports carry no reference designator, so the importer invents one
3838// A per-sheet counter restarts at #PWR0001 on sheet two and every power symbol on it
3839// reports as a duplicate item, blocking annotation and Update PCB from Schematic
3840BOOST_AUTO_TEST_CASE( MultiSheetPowerReferencesAreUnique )
3841{
3842 SCH_IO_PADS plugin;
3843
3844 wxString padsFile = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir()
3845 + "/plugins/pads/binary/multisheet_connectivity.txt" );
3846
3847 SCH_SHEET* rootSheet = plugin.LoadSchematicFile( padsFile, &m_schematic );
3848 BOOST_REQUIRE( rootSheet );
3849
3850 // Both sheets carry a $PWR_SYMS +5V and a $GND_SYMS GND anchor
3851 BOOST_REQUIRE_EQUAL( countPowerSymbols( rootSheet ), 4u );
3852
3853 std::vector<wxString> messages;
3854 BOOST_CHECK_EQUAL( checkAnnotation( { rootSheet }, messages ), 0 );
3855 BOOST_CHECK( messages.empty() );
3856}
3857
3858
3859BOOST_AUTO_TEST_CASE( BinaryMultiSheetPowerReferencesAreUnique )
3860{
3861 const PADS_SCH_BINARY::PADS_SCH_MODEL model = parseBinaryFixture( wxS( "multisheet_connectivity" ) );
3863
3864 builder.Build( model, &m_schematic, nullptr, binaryFixture( wxS( "multisheet_connectivity" ) ) );
3865
3866 std::vector<SCH_SHEET*> roots = m_schematic.GetTopLevelSheets();
3867 size_t powerCount = 0;
3868
3869 for( SCH_SHEET* sheet : roots )
3870 powerCount += countPowerSymbols( sheet );
3871
3872 BOOST_REQUIRE_EQUAL( powerCount, 4u );
3873
3874 std::vector<wxString> messages;
3875 BOOST_CHECK_EQUAL( checkAnnotation( roots, messages ), 0 );
3876 BOOST_CHECK( messages.empty() );
3877}
3878
3879
3880// Appending a PADS schematic must not reuse a reference the destination already carries
3881BOOST_AUTO_TEST_CASE( BinaryAppendPowerReferencesSkipExisting )
3882{
3883 const PADS_SCH_BINARY::PADS_SCH_MODEL model = parseBinaryFixture( wxS( "multisheet_connectivity" ) );
3885 SCH_SHEET* destination = m_schematic.GetTopLevelSheet();
3886 BOOST_REQUIRE( destination );
3887
3888 builder.Build( model, &m_schematic, destination, binaryFixture( wxS( "multisheet_connectivity" ) ) );
3889 BOOST_REQUIRE_EQUAL( countPowerSymbols( destination ), 4u );
3890
3891 builder.Build( model, &m_schematic, destination, binaryFixture( wxS( "multisheet_connectivity" ) ) );
3892 BOOST_REQUIRE_EQUAL( countPowerSymbols( destination ), 8u );
3893
3894 // Appending a design onto itself duplicates its ordinary parts, which is the user's
3895 // to resolve; no power reference may be among them
3896 std::vector<wxString> messages;
3897 checkAnnotation( { destination }, messages );
3898
3899 BOOST_CHECK( std::ranges::none_of( messages,
3900 []( const wxString& aMessage )
3901 {
3902 return aMessage.Contains( wxS( "#PWR" ) );
3903 } ) );
3904}
3905
3906
3907// The clamp that shortens a bus-entry stub to the KiCad default entry size scales the direction
3908// vector. In 32-bit that product overflows once the stub passes a third of an inch, and the entry
3909// plus its compensating wire are drawn from the wrapped result.
3910BOOST_AUTO_TEST_CASE( BinaryLongBusEntryStaysOnTheWire )
3911{
3912 using namespace PADS_SCH_BINARY;
3913
3914 auto samePoint = []( const SOURCE_POINT& aLeft, const SOURCE_POINT& aRight )
3915 {
3916 return aLeft.x == aRight.x && aLeft.y == aRight.y;
3917 };
3918
3919 PADS_SCH_MODEL model = parseBinaryFixture( wxS( "connectivity_topology" ) );
3920 BOOST_REQUIRE( !model.buses.empty() );
3921
3922 // 4000 half-mils is a two inch stub, ordinary on a real sheet and well past the clamp
3923 const int stub = 4000;
3924 size_t stretched = 0;
3925
3926 for( const MODEL_BUS& bus : model.buses )
3927 {
3928 for( const MODEL_BUS_ENTRY& entry : bus.entries )
3929 {
3930 auto net = std::ranges::find( model.nets, entry.memberNet.id, &MODEL_NET::id );
3931 BOOST_REQUIRE( net != model.nets.end() );
3932
3933 for( MODEL_CONNECTION& connection : net->connections )
3934 {
3935 if( connection.vertices.size() < 2 )
3936 continue;
3937
3938 SOURCE_POINT* farPt = nullptr;
3939
3940 if( samePoint( connection.vertices.front(), entry.position ) )
3941 farPt = &connection.vertices[1];
3942 else if( samePoint( connection.vertices.back(), entry.position ) )
3943 farPt = &connection.vertices[connection.vertices.size() - 2];
3944
3945 if( !farPt )
3946 continue;
3947
3948 farPt->x = entry.position.x + stub;
3949 farPt->y = entry.position.y + stub;
3950 ++stretched;
3951 }
3952 }
3953 }
3954
3955 BOOST_REQUIRE_GT( stretched, 0u );
3956
3958 builder.Build( model, &m_schematic, nullptr, binaryFixture( wxS( "connectivity_topology" ) ) );
3959
3960 SCH_SHEET* root = m_schematic.GetTopLevelSheet();
3961 BOOST_REQUIRE( root );
3962 const int pageHeight = root->GetScreen()->GetPageSettings().GetHeightIU( schIUScale.IU_PER_MILS );
3963 size_t checked = 0;
3964
3965 for( const MODEL_BUS& bus : model.buses )
3966 {
3967 for( const MODEL_BUS_ENTRY& entry : bus.entries )
3968 {
3969 auto net = std::ranges::find( model.nets, entry.memberNet.id, &MODEL_NET::id );
3970 BOOST_REQUIRE( net != model.nets.end() );
3971 std::vector<SOURCE_POINT> adjacent;
3972
3973 for( const MODEL_CONNECTION& connection : net->connections )
3974 {
3975 if( connection.vertices.size() < 2 )
3976 continue;
3977
3978 if( samePoint( connection.vertices.front(), entry.position ) )
3979 adjacent.push_back( connection.vertices[1] );
3980 else if( samePoint( connection.vertices.back(), entry.position ) )
3981 adjacent.push_back( connection.vertices[connection.vertices.size() - 2] );
3982 }
3983
3984 BOOST_REQUIRE_EQUAL( adjacent.size(), 1u );
3985
3986 const VECTOR2I start = pagePoint( entry.position, pageHeight );
3987 const VECTOR2I end = pagePoint( adjacent.front(), pageHeight );
3988 const VECTOR2I run = end - start;
3989 const int64_t span = std::max( std::abs( run.x ), std::abs( run.y ) );
3990 const int64_t entrySpan = std::min<int64_t>( span, schIUScale.MilsToIU( DEFAULT_SCH_ENTRY_SIZE ) );
3991
3992 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_BUS_WIRE_ENTRY_T ) )
3993 {
3994 if( item->GetPosition() != start )
3995 continue;
3996
3997 const VECTOR2I size = static_cast<SCH_BUS_WIRE_ENTRY*>( item )->GetSize();
3998
3999 BOOST_CHECK_MESSAGE( std::abs( size.x - int64_t( run.x ) * entrySpan / span ) <= 1,
4000 "bus entry " << entry.source.recordIndex << " x is " << size.x );
4001 BOOST_CHECK_MESSAGE( std::abs( size.y - int64_t( run.y ) * entrySpan / span ) <= 1,
4002 "bus entry " << entry.source.recordIndex << " y is " << size.y );
4003 ++checked;
4004 }
4005 }
4006 }
4007
4008 BOOST_REQUIRE_GT( checked, 0u );
4009}
4010
4011
4012// PADS gives the stroke width it renders. KiCad multiplies a stored thickness by the bold factor,
4013// so importing the PADS width verbatim onto a bold text renders it 1.6x too thick.
4014BOOST_AUTO_TEST_CASE( BinaryBoldTextKeepsTheRenderedStrokeWidth )
4015{
4016 using namespace PADS_SCH_BINARY;
4017
4018 PADS_SCH_MODEL model = parseBinaryFixture( wxS( "text_encoding" ) );
4019 BOOST_REQUIRE_EQUAL( model.texts.size(), 1u );
4020 BOOST_REQUIRE_GT( model.texts.front().presentation.width, 0 );
4021 model.texts.front().presentation.bold = true;
4022
4024 builder.Build( model, &m_schematic, nullptr, binaryFixture( wxS( "text_encoding" ) ) );
4025
4026 SCH_SHEET* root = m_schematic.GetTopLevelSheet();
4027 BOOST_REQUIRE( root );
4028 SCH_TEXT* built = nullptr;
4029
4030 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
4031 built = static_cast<SCH_TEXT*>( item );
4032
4033 BOOST_REQUIRE( built );
4034 BOOST_REQUIRE( built->IsBold() );
4035 checkTextPresentation( *built, model.texts.front().presentation );
4036}
4037
4038
4039// A LIB_ID is what the saved file keys lib_symbols on. Two placements of one part type that build
4040// different symbols must not share it, or one variant wins the save and the reload differs from
4041// the import.
4042BOOST_AUTO_TEST_CASE( BinaryPlacementVariantsGetDistinctLibIds )
4043{
4044 using namespace PADS_SCH_BINARY;
4045
4046 PADS_SCH_MODEL model = parseBinaryFixture( wxS( "connectivity_topology" ) );
4047 BOOST_REQUIRE( !model.placements.empty() );
4048
4049 // A second placement of the same part type that hides its pin numbers is a different symbol
4050 uint32_t nextId = 0;
4051
4052 for( const MODEL_PLACEMENT& placement : model.placements )
4053 nextId = std::max( nextId, placement.id.Value() + 1 );
4054
4055 MODEL_PLACEMENT copy = model.placements.front();
4056 copy.id = PLACEMENT_ID( nextId );
4057 copy.fields.clear();
4058 copy.reference.text = model.placements.front().reference.text + wxS( "X" );
4059 copy.pinNumbersVisible = !model.placements.front().pinNumbersVisible;
4060 copy.position.x += 4000;
4061 model.placements.push_back( copy );
4062
4063 const MODEL_PLACEMENT* first = &model.placements.front();
4064 const MODEL_PLACEMENT* second = &model.placements.back();
4065
4067 builder.Build( model, &m_schematic, nullptr, binaryFixture( wxS( "connectivity_topology" ) ) );
4068
4069 SCH_SHEET* root = m_schematic.GetTopLevelSheet();
4070 BOOST_REQUIRE( root );
4071 SCH_SHEET_PATH path = m_schematic.CurrentSheet();
4072
4073 auto libIdFor = [&]( const wxString& aReference ) -> wxString
4074 {
4075 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
4076 {
4077 auto* symbol = static_cast<SCH_SYMBOL*>( item );
4078
4079 if( symbol->GetRef( &path ) == aReference )
4080 return symbol->GetLibId().GetLibItemName();
4081 }
4082
4083 return wxString();
4084 };
4085
4086 const wxString firstId = libIdFor( first->reference.text );
4087 const wxString secondId = libIdFor( second->reference.text );
4088
4089 BOOST_REQUIRE( !firstId.IsEmpty() );
4090 BOOST_REQUIRE( !secondId.IsEmpty() );
4091 BOOST_CHECK_MESSAGE( firstId != secondId, "both placements resolved to " << firstId );
4092}
4093
4094
4095// KiCad's schematic format carries no visibility for a plain text and the reader forces every one
4096// visible, so a hidden PADS note has to be reported rather than imported into a state the first
4097// save discards.
4098BOOST_AUTO_TEST_CASE( BinaryHiddenFreeTextIsReportedNotImported )
4099{
4100 using namespace PADS_SCH_BINARY;
4101
4102 PADS_SCH_MODEL model = parseBinaryFixture( wxS( "text_encoding" ) );
4103 BOOST_REQUIRE_EQUAL( model.texts.size(), 1u );
4104 model.texts.front().presentation.visible = false;
4105
4107 BUILD_RESULT result = builder.Build( model, &m_schematic, nullptr, binaryFixture( wxS( "text_encoding" ) ) );
4108
4109 SCH_SHEET* root = m_schematic.GetTopLevelSheet();
4110 BOOST_REQUIRE( root );
4111 SCH_TEXT* built = nullptr;
4112
4113 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
4114 built = static_cast<SCH_TEXT*>( item );
4115
4116 BOOST_REQUIRE( built );
4117 BOOST_CHECK( built->IsVisible() );
4118
4119 BOOST_CHECK( std::ranges::any_of( result.diagnostics,
4120 []( const PARSER_DIAGNOSTIC& aDiagnostic )
4121 {
4122 return aDiagnostic.message.Contains( wxS( "hidden PADS text" ) );
4123 } ) );
4124}
4125
4126
4127// The gate decal name is a lookup key, not the part's identity. Overwriting the part type with it
4128// renames every power symbol whose decal is named differently.
4129BOOST_AUTO_TEST_CASE( AsciiPowerSymbolValueIsThePartType )
4130{
4131 SCH_IO_PADS plugin;
4132 wxString path = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() )
4133 + wxS( "/plugins/pads/power_gate_decal.txt" );
4134
4135 SCH_SHEET* root = plugin.LoadSchematicFile( path, &m_schematic, nullptr, nullptr );
4136 BOOST_REQUIRE( root );
4137 BOOST_REQUIRE( root->GetScreen() );
4138
4139 SCH_SYMBOL* symbol = nullptr;
4140
4141 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
4142 symbol = static_cast<SCH_SYMBOL*>( item );
4143
4144 BOOST_REQUIRE( symbol );
4145 BOOST_CHECK_EQUAL( symbol->GetField( FIELD_T::VALUE )->GetText(), wxS( "+5V" ) );
4146}
4147
4148
4149
4150// Adopting a whole document cannot satisfy the hierarchical-sheet loader's ownership contract, and
4151// the ASCII branch replaces the live schematic's top-level sheets before it can find that out.
4152BOOST_AUTO_TEST_CASE( AsciiHierarchicalSheetLoadIsRefused )
4153{
4154 SCH_IO_PADS plugin;
4155 std::map<std::string, UTF8> properties;
4156 properties["hierarchical_sheet_load"] = "1";
4157
4158 wxString path = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() ) + wxS( "/plugins/pads/parts_schematic.txt" );
4159
4160 SCH_SHEET* existing = m_schematic.GetTopLevelSheet();
4161 BOOST_REQUIRE( existing );
4162
4163 BOOST_CHECK_THROW( plugin.LoadSchematicFile( path, &m_schematic, nullptr, &properties ), IO_ERROR );
4164 BOOST_CHECK_EQUAL( m_schematic.GetTopLevelSheet(), existing );
4165}
4166
4167
int index
const char * name
bool operator==(const wxAuiPaneInfo &aLhs, const wxAuiPaneInfo &aRhs)
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
const wxString & GetPageNumber() const
void Recalculate(const SCH_SHEET_LIST &aSheetList, bool aUnconditional=false, std::function< void(SCH_ITEM *)> *aChangedItemHandler=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Update the connection graph for the given list of sheets.
int AsTenthsOfADegree() const
Definition eda_angle.h:118
double AsDegrees() const
Definition eda_angle.h:116
const KIID m_Uuid
Definition eda_item.h:597
FILL_T GetFillMode() const
Definition eda_shape.h:148
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
virtual VECTOR2I GetTextSize() const
Definition eda_text.h:301
wxString GetFontName() const
bool IsItalic() const
Definition eda_text.h:200
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:118
virtual bool IsVisible() const
Definition eda_text.h:226
virtual int GetTextHeight() const
Definition eda_text.h:307
GR_TEXT_H_ALIGN_T GetHorizJustify() const
Definition eda_text.h:239
virtual EDA_ANGLE GetTextAngle() const
Definition eda_text.h:178
int GetEffectiveTextPenWidth(int aDefaultPenWidth=0) const
The EffectiveTextPenWidth uses the text thickness if > 1 or aDefaultPenWidth.
Definition eda_text.cpp:422
bool IsBold() const
Definition eda_text.h:215
GR_TEXT_V_ALIGN_T GetVertJustify() const
Definition eda_text.h:242
virtual int GetTextThickness() const
Definition eda_text.h:159
bool contains(const SCH_ITEM *aItem, bool aRobust=false) const
Determine if a given item exists in the tree.
Definition sch_rtree.h:140
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
virtual void SetReporter(REPORTER *aReporter)
Set an optional reporter for warnings/errors.
Definition io_base.h:89
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
Definition kiid.h:46
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int SetLibItemName(const UTF8 &aLibItemName)
Override the library item name portion of the LIB_ID to aLibItemName.
Definition lib_id.cpp:124
int SetLibNickname(const UTF8 &aLibNickname)
Override the logical library name portion of the LIB_ID to aLibNickname.
Definition lib_id.cpp:113
const UTF8 & GetLibItemName() const
Definition lib_id.h:98
Define a library symbol object.
Definition lib_symbol.h:119
LIB_ITEMS_CONTAINER & GetDrawItems()
Return a reference to the draw item list.
Definition lib_symbol.h:832
wxString GetName() const override
Definition lib_symbol.h:181
bool empty(int aType=UNDEFINED_TYPE) const
constexpr ValueType Value() const
BUILD_RESULT Build(const PADS_SCH_MODEL &aModel, SCHEMATIC *aSchematic, SCH_SHEET *aAppendToMe, const wxString &aSourcePath)
PADS_SCH_MODEL Parse(const std::vector< uint8_t > &aBytes, const wxString &aSourceName={}) const
static bool ReadFile(const wxString &aFileName, std::vector< uint8_t > &aData)
static bool IsBinarySch(const std::vector< uint8_t > &aData)
static bool IsSupportedVersion(uint16_t aVersion)
static bool IsBinaryFamily(const std::vector< uint8_t > &aData)
int GetHeightIU(double aIUScale) const
Gets the page height in IU.
Definition page_info.h:164
double GetHeightMils() const
Definition page_info.h:143
double GetWidthMils() const
Definition page_info.h:138
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
Holds all the data relating to one schematic.
Definition schematic.h:148
void Reset()
Initialize this schematic to a blank one, unloading anything existing.
bool IsTopLevelSheet(const SCH_SHEET *aSheet) const
Check if a sheet is a top-level sheet (direct child of virtual root).
SCH_SHEET_LIST BuildSheetListSortedByPageNumbers() const
SCH_SHEET * GetTopLevelSheet(int aIndex=0) const
CONNECTION_GRAPH * ConnectionGraph() const
Definition schematic.h:317
void SetTopLevelSheets(const std::vector< SCH_SHEET * > &aSheets)
Replace the top level sheets, rebuilding the hierarchy and connectivity around them.
SCH_SHEET & Root() const
Definition schematic.h:199
std::vector< SCH_SHEET * > GetTopLevelSheets() const
Get the list of top-level sheets.
void RefreshHierarchy()
Object to handle a bitmap image that can be inserted in a schematic.
Definition sch_bitmap.h:36
VECTOR2I GetPosition() const override
Class for a wire to bus entry.
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
wxString GetNetName() const
VECTOR2I GetPosition() const override
virtual const wxString & GetText() const override
Return the string associated with the text object.
Definition sch_field.h:138
A SCH_IO derivation for loading schematic files using the new s-expression file format.
void SaveSchematicFile(const wxString &aFileName, SCH_SHEET *aSheet, SCHEMATIC *aSchematic, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aSchematic to a storage file in a format that this SCH_IO implementation knows about,...
SCH_SHEET * LoadSchematicFile(const wxString &aFileName, SCHEMATIC *aSchematic, SCH_SHEET *aAppendToMe=nullptr, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load information from some input file format that this SCH_IO implementation knows about,...
A SCH_IO derivation for loading PADS Logic schematic files.
Definition sch_io_pads.h:39
LIB_SYMBOL * LoadSymbol(const wxString &aLibraryPath, const wxString &aPartName, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load a LIB_SYMBOL object having aPartName from the aLibraryPath containing a library format that this...
void EnumerateSymbolLib(wxArrayString &aSymbolNameList, const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Populate a list of LIB_SYMBOL alias names contained within the library aLibraryPath.
bool CanReadLibrary(const wxString &aFileName) const override
Checks if this IO object can read the specified library file/directory.
bool IsLibraryWritable(const wxString &aLibraryPath) override
Return true if the library at aLibraryPath is writable.
Definition sch_io_pads.h:73
bool CanReadSchematicFile(const wxString &aFileName) const override
Checks if this SCH_IO can read the specified schematic file.
SCH_SHEET * LoadSchematicFile(const wxString &aFileName, SCHEMATIC *aSchematic, SCH_SHEET *aAppendToMe=nullptr, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load information from some input file format that this SCH_IO implementation knows about,...
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
int GetUnit() const
Definition sch_item.h:237
SPIN_STYLE GetSpinStyle() const
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
const wxString & GetName() const
Definition sch_pin.cpp:503
VECTOR2I GetPosition() const override
Definition sch_pin.cpp:354
const wxString & GetNumber() const
Definition sch_pin.h:142
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
int CheckAnnotation(ANNOTATION_ERROR_HANDLER aErrorHandler)
Check for annotations errors.
A helper to define a symbol's reference designator in a schematic.
const PAGE_INFO & GetPageSettings() const
Definition sch_screen.h:140
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
void AddLibSymbol(LIB_SYMBOL *aLibSymbol)
Add aLibSymbol to the library symbol map.
const std::map< wxString, LIB_SYMBOL * > & GetLibSymbols() const
Fetch a list of unique LIB_SYMBOL object pointers required to properly render each SCH_SYMBOL in this...
Definition sch_screen.h:503
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:118
const KIID & GetUuid() const
Definition sch_screen.h:540
TITLE_BLOCK & GetTitleBlock()
Definition sch_screen.h:164
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
size_t GetCurrentHash() const
SCH_SCREEN * LastScreen()
wxString GetPageNumber() const
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
size_t size() const
Forwarded method from std::vector.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this sheet.
wxString GetName() const
Definition sch_sheet.h:142
void SetName(const wxString &aName)
Definition sch_sheet.h:143
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Schematic symbol object.
Definition sch_symbol.h:75
wxString GetSchSymbolLibraryName() const
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
VECTOR2I GetPosition() const override
Definition sch_symbol.h:934
const LIB_ID & GetLibId() const override
Definition sch_symbol.h:164
std::vector< SCH_PIN * > GetLibPins() const
Populate a vector with all the pins from the library object that match the current unit and bodyStyle...
bool GetInstance(SCH_SYMBOL_INSTANCE &aInstance, const KIID_PATH &aSheetPath, bool aTestFromEnd=false) const
const wxString GetValue(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString) const override
int GetUnitCount() const override
Return the number of units per package of the symbol.
int GetOrientation() const override
Get the display symbol orientation.
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:183
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
VECTOR2I GetPosition() const override
Definition sch_text.h:143
const TRANSFORM & GetTransform() const
Definition symbol.h:243
Hold the information shown in the lower right corner of a plot, printout, or editing view.
Definition title_block.h:38
const wxString & GetCompany() const
Definition title_block.h:93
const wxString & GetComment(int aIdx) const
const wxString & GetTitle() const
Definition title_block.h:60
@ RAW_VALUE
Definition common.h:94
#define DEFAULT_SCH_ENTRY_SIZE
The default text size in mils. (can be changed in preference menu)
@ NONE
Definition eda_fill.h:42
@ NO_FILL
Definition eda_fill.h:30
@ HATCH
Definition eda_fill.h:34
@ FILLED_WITH_BG_BODYCOLOR
Definition eda_fill.h:32
@ FILLED_SHAPE
Fill with object color.
Definition eda_fill.h:31
SHAPE_T
Definition eda_shape.h:54
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
ERCE_T
ERC error codes.
int ClampTextPenSize(int aPenSize, int aSize, bool aStrict)
Pen width should not allow characters to become cluttered up in their own fatness.
Definition gr_text.cpp:69
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
@ LAYER_WIRE
Definition layer_ids.h:474
@ LAYER_NOTES
Definition layer_ids.h:489
@ LAYER_BUS
Definition layer_ids.h:475
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
constexpr int NormalizeAngle(int aAngle)
CONTROLLER_ID< NET_ID_TAG > NET_ID
CONTROLLER_ID< IMAGE_ID_TAG > IMAGE_ID
CONTROLLER_ID< DEFINITION_ID_TAG > DEFINITION_ID
CONTROLLER_ID< SHEET_ID_TAG > SHEET_ID
CONTROLLER_ID< PIN_ID_TAG > PIN_ID
wxString FormatParserError(const SOURCE_PROVENANCE &aSource, const wxString &aMessage)
CONTROLLER_ID< PLACEMENT_ID_TAG > PLACEMENT_ID
std::optional< DIAGNOSTIC_PROPERTY_KEY > DiagnosticPropertyKey(const PARSER_DIAGNOSTIC &aDiagnostic)
PARSER_DIAGNOSTIC MakePropertyDiagnostic(SEVERITY aSeverity, const SOURCE_PROPERTY &aProperty, const wxString &aMessage)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
ELECTRICAL_PINTYPE
The symbol library pin object electrical types used in ERC tests.
Definition pin_type.h:32
@ PT_INPUT
usual pin input: must be connected
Definition pin_type.h:33
@ PT_OUTPUT
usual output
Definition pin_type.h:34
@ PT_TRISTATE
tri state bus pin
Definition pin_type.h:36
@ PT_BIDI
input or output (like port for a microprocessor)
Definition pin_type.h:35
@ PT_OPENEMITTER
pin type open emitter
Definition pin_type.h:45
@ PT_OPENCOLLECTOR
pin type open collector
Definition pin_type.h:44
@ PT_POWER_IN
power input (GND, VCC for ICs). Must be connected to a power output.
Definition pin_type.h:42
@ PT_UNSPECIFIED
unknown electrical properties: creates always a warning when connected
Definition pin_type.h:41
@ PT_PASSIVE
pin for passive symbols: must be connected, and can be connected to any pin.
Definition pin_type.h:39
PIN_ORIENTATION
The symbol library pin object orientations.
Definition pin_type.h:101
@ PIN_UP
The pin extends upwards from the connection point: Probably on the bottom side of the symbol.
Definition pin_type.h:123
@ PIN_RIGHT
The pin extends rightwards from the connection point.
Definition pin_type.h:107
@ PIN_LEFT
The pin extends leftwards from the connection point: Probably on the right side of the symbol.
Definition pin_type.h:114
@ PIN_DOWN
The pin extends downwards from the connection: Probably on the top side of the symbol.
Definition pin_type.h:131
GRAPHIC_PINSHAPE
Definition pin_type.h:80
SEVERITY
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_UNDEFINED
@ RPT_SEVERITY_INFO
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
@ SYMBOL_FILTER_ALL
wxString UnescapeString(const wxString &aSource)
LINE_STYLE
Dashed line types.
std::vector< char > decompressedData
std::vector< PARSER_DIAGNOSTIC > diagnostics
std::vector< SOURCE_PROPERTY > properties
SOURCE_PROVENANCE source
NET_REFERENCE memberNet
std::vector< SOURCE_PROPERTY > properties
SOURCE_POINT position
std::vector< SOURCE_PROPERTY > properties
std::vector< MODEL_BUS_ENTRY > entries
std::vector< SOURCE_STRING > declaredMembers
std::vector< SOURCE_STRING > aliases
std::vector< SOURCE_POINT > vertices
std::optional< PLACEMENT_REFERENCE > placement
std::vector< MODEL_CONNECTION_ENDPOINT > endpoints
std::vector< SOURCE_PROPERTY > properties
std::vector< SOURCE_POINT > vertices
MODEL_TEXT_PRESENTATION presentation
std::vector< SOURCE_PROPERTY > properties
std::vector< MODEL_CONNECTOR_PIN > connectorPins
std::vector< SOURCE_PROPERTY > properties
std::vector< MODEL_GATE_PIN > logicalPins
std::vector< DEFINITION_REFERENCE > alternateDefinitions
std::vector< SOURCE_PROPERTY > properties
std::vector< SOURCE_POINT > points
std::vector< SOURCE_PROPERTY > properties
MODEL_TEXT_PRESENTATION presentation
std::vector< SOURCE_PROPERTY > properties
std::vector< SHEET_REFERENCE > linkedSheets
std::vector< MODEL_CONNECTION > connections
std::vector< SOURCE_PROPERTY > properties
std::vector< MODEL_SIGNAL_PIN > signalPins
std::vector< SOURCE_PROPERTY > properties
std::vector< PLACED_PIN_REFERENCE > pins
std::vector< SOURCE_PROPERTY > properties
std::optional< GATE_REFERENCE > gate
std::vector< SOURCE_PROPERTY > properties
std::vector< MODEL_FIELD > titleBlockFields
std::vector< MODEL_GRAPHIC > border
std::vector< MODEL_PIN_DEFINITION > pins
MODEL_TEXT_PRESENTATION presentation
std::vector< MODEL_GRAPHIC > graphics
std::vector< MODEL_SYMBOL_DEFINITION > definitions
std::vector< MODEL_JUNCTION > junctions
std::vector< MODEL_PAGE_GRAPHIC > graphics
std::vector< MODEL_WORKSHEET > worksheets
std::vector< MODEL_LABEL > labels
std::vector< MODEL_PART_TYPE > partTypes
std::vector< MODEL_SHEET > sheets
std::vector< MODEL_PLACEMENT > placements
std::vector< PARSER_DIAGNOSTIC > diagnostics
std::optional< DIAGNOSTIC_PROPERTY_IDENTITY > property
A simple container for schematic symbol instance information.
Information about a top-level schematic sheet.
@ SYM_ORIENT_270
Definition symbol.h:38
@ SYM_MIRROR_Y
Definition symbol.h:40
@ SYM_ORIENT_180
Definition symbol.h:37
@ SYM_MIRROR_X
Definition symbol.h:39
@ SYM_ORIENT_90
Definition symbol.h:36
@ SYM_ORIENT_0
Definition symbol.h:35
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
IbisParser parser & reporter
KIBIS_MODEL * model
KIBIS_PIN * pin
VECTOR3I expected(15, 30, 45)
BOOST_AUTO_TEST_CASE(BinaryEmbeddedImageDegenerateBoxIsSkipped)
VECTOR2I center
int radius
VECTOR2I end
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
int actual
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
GR_TEXT_H_ALIGN_T
This is API surface mapped to common.types.HorizontalAlignment.
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
GR_TEXT_V_ALIGN_T
This is API surface mapped to common.types.VertialAlignment.
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:70
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_LABEL_T
Definition typeinfo.h:163
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_SHAPE_T
Definition typeinfo.h:145
@ SCH_HIER_LABEL_T
Definition typeinfo.h:165
@ SCH_TEXT_T
Definition typeinfo.h:147
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:157
@ SCH_BITMAP_T
Definition typeinfo.h:160
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:164
@ SCH_JUNCTION_T
Definition typeinfo.h:155
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683