KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_pads_sch_binary_parser.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 option)
9 * 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 along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
22
27
28#include <io/pads/pads_common.h>
29
30#include <ki_exception.h>
31#include <algorithm>
32#include <cmath>
33#include <filesystem>
34#include <fstream>
35#include <iterator>
36#include <limits>
37#include <map>
38#include <optional>
39#include <set>
40#include <sstream>
41#include <string>
42#include <type_traits>
43#include <variant>
44#include <vector>
45#include <wx/filename.h>
46
47using namespace PADS_SCH_BINARY;
48
49namespace
50{
51
52static std::vector<uint8_t> loadMinimalV13()
53{
54 std::ifstream file( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/minimal_v13.sch", std::ios::binary );
55 BOOST_REQUIRE( file );
56 return { std::istreambuf_iterator<char>( file ), std::istreambuf_iterator<char>() };
57}
58
59
60static std::vector<uint8_t> loadBinaryFixture( const std::string& aName )
61{
62 std::ifstream file( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/" + aName, std::ios::binary );
63 BOOST_REQUIRE( file );
64 return { std::istreambuf_iterator<char>( file ), std::istreambuf_iterator<char>() };
65}
66
67
68static uint32_t readU32( const std::vector<uint8_t>& aBytes, size_t aOffset )
69{
70 return static_cast<uint32_t>( aBytes[aOffset] ) | ( static_cast<uint32_t>( aBytes[aOffset + 1] ) << 8 )
71 | ( static_cast<uint32_t>( aBytes[aOffset + 2] ) << 16 )
72 | ( static_cast<uint32_t>( aBytes[aOffset + 3] ) << 24 );
73}
74
75
76static uint16_t readU16( const std::vector<uint8_t>& aBytes, size_t aOffset )
77{
78 return static_cast<uint16_t>( aBytes[aOffset] ) | ( static_cast<uint16_t>( aBytes[aOffset + 1] ) << 8 );
79}
80
81
82static void writeU16( std::vector<uint8_t>& aBytes, size_t aOffset, uint16_t aValue )
83{
84 aBytes[aOffset] = static_cast<uint8_t>( aValue );
85 aBytes[aOffset + 1] = static_cast<uint8_t>( aValue >> 8 );
86}
87
88
89static void writeU32( std::vector<uint8_t>& aBytes, size_t aOffset, uint32_t aValue )
90{
91 writeU16( aBytes, aOffset, static_cast<uint16_t>( aValue ) );
92 writeU16( aBytes, aOffset + 2, static_cast<uint16_t>( aValue >> 16 ) );
93}
94
95
96static size_t outerControllerOffset( const std::vector<uint8_t>& aBytes, size_t aController )
97{
98 size_t offset = 0x254;
99
100 for( size_t controller = 1; controller < aController; ++controller )
101 offset += readU32( aBytes, 0x20 + controller * 28 + 12 );
102
103 return offset;
104}
105
106
107static size_t sheetControllerOffset( const std::vector<uint8_t>& aBytes, size_t aController )
108{
109 const size_t sheetOffset = readU32( aBytes, outerControllerOffset( aBytes, 3 ) );
110 size_t offset = sheetOffset + 20 + 24 * 28;
111
112 for( size_t controller = 1; controller < aController; ++controller )
113 offset += readU32( aBytes, sheetOffset + 20 + ( controller - 1 ) * 28 + 16 );
114
115 return offset;
116}
117
118
119static uint32_t sheetControllerCount( const std::vector<uint8_t>& aBytes, size_t aController )
120{
121 const size_t sheetOffset = readU32( aBytes, outerControllerOffset( aBytes, 3 ) );
122 return readU32( aBytes, sheetOffset + 20 + ( aController - 1 ) * 28 + 12 );
123}
124
125
126template <typename Item>
127static const Item& itemNamed( const std::vector<Item>& aItems, const wxString& aName )
128{
129 auto item = std::ranges::find_if( aItems,
130 [&]( const Item& aItem )
131 {
132 return aItem.name.text == aName;
133 } );
134 BOOST_REQUIRE( item != aItems.end() );
135 return *item;
136}
137
138
139static wxString propertyValue( const std::vector<SOURCE_PROPERTY>& aProperties, const wxString& aName )
140{
141 auto property = std::ranges::find_if( aProperties,
142 [&]( const SOURCE_PROPERTY& aProperty )
143 {
144 return aProperty.name.text == aName;
145 } );
146 BOOST_REQUIRE( property != aProperties.end() );
147 return property->value.text;
148}
149
150
151static std::string canonicalModel( const PADS_SCH_MODEL& aModel )
152{
153 std::ostringstream out;
154 out << aModel.version << '|' << aModel.source.file << '|' << aModel.source.absoluteOffset << '|'
156
157 auto provenance = [&]( const SOURCE_PROVENANCE& aSource )
158 {
159 out << ':' << aSource.version << ',' << aSource.objectClass << ',' << aSource.controller << ','
160 << aSource.recordIndex << ',' << aSource.absoluteOffset << ',' << aSource.length << ',' << aSource.sheet;
161 };
162
163 for( const MODEL_SHEET& sheet : aModel.sheets )
164 {
165 out << "|sheet," << sheet.id.Value() << ',' << sheet.index << ',' << sheet.name.text;
166 provenance( sheet.source );
167 }
168
169 out << "|definitions=" << aModel.definitions.size() << "|parts=" << aModel.partTypes.size()
170 << "|placements=" << aModel.placements.size() << "|nets=" << aModel.nets.size()
171 << "|buses=" << aModel.buses.size() << "|labels=" << aModel.labels.size()
172 << "|junctions=" << aModel.junctions.size() << "|texts=" << aModel.texts.size()
173 << "|graphics=" << aModel.graphics.size() << "|diagnostics=" << aModel.diagnostics.size();
174 return out.str();
175}
176
177
178enum class CANONICAL_KIND
179{
180 SETTINGS,
181 SHEET,
183 GRAPHIC,
184 PIN,
185 FIELD,
186 PART_TYPE,
187 GATE,
188 GATE_PIN_MAPPING,
189 PLACEMENT,
190 NET,
191 CONNECTION,
192 BUS,
193 BUS_ENTRY,
194 BUS_ALIAS,
195 BUS_MEMBER,
196 LABEL,
197 JUNCTION,
198 TEXT
199};
200
201using CANONICAL_VALUE = std::variant<int64_t, bool, std::string, std::vector<std::string>>;
202
203struct CANONICAL_PROPERTY
204{
205 CANONICAL_VALUE value;
206 PROPERTY_DISPOSITION disposition = PROPERTY_DISPOSITION::EXACT;
207 bool operator==( const CANONICAL_PROPERTY& ) const = default;
208 bool operator<( const CANONICAL_PROPERTY& aOther ) const
209 {
210 return std::tie( value, disposition ) < std::tie( aOther.value, aOther.disposition );
211 }
212};
213
214struct CANONICAL_POINT
215{
216 int64_t xHalfMils = 0;
217 int64_t yHalfMils = 0;
218 bool operator==( const CANONICAL_POINT& ) const = default;
219 bool operator<( const CANONICAL_POINT& aOther ) const
220 {
221 return std::tie( xHalfMils, yHalfMils ) < std::tie( aOther.xHalfMils, aOther.yHalfMils );
222 }
223};
224
225struct CANONICAL_GEOMETRY
226{
227 std::vector<CANONICAL_POINT> points;
228 std::optional<int64_t> angleTenths;
229 bool operator==( const CANONICAL_GEOMETRY& ) const = default;
230};
231
232struct CANONICAL_SEMANTIC_RECORD
233{
234 int sheet = -1;
235 CANONICAL_KIND kind = CANONICAL_KIND::SETTINGS;
236 std::string stableKey;
237 std::map<std::string, CANONICAL_PROPERTY> properties;
238 CANONICAL_GEOMETRY geometry;
239 bool operator==( const CANONICAL_SEMANTIC_RECORD& ) const = default;
240 bool operator<( const CANONICAL_SEMANTIC_RECORD& aOther ) const
241 {
242 return std::tie( sheet, kind, stableKey, properties, geometry.points, geometry.angleTenths )
243 < std::tie( aOther.sheet, aOther.kind, aOther.stableKey, aOther.properties, aOther.geometry.points,
244 aOther.geometry.angleTenths );
245 }
246};
247
248using SNAPSHOT_ALLOWLIST = std::set<std::pair<std::string, PROPERTY_DISPOSITION>>;
249
250static CANONICAL_POINT point( const SOURCE_POINT& aPoint )
251{
252 return { aPoint.x, aPoint.y };
253}
254static CANONICAL_POINT point( const PADS_SCH::POINT& aPoint )
255{
256 return { static_cast<int64_t>( std::llround( aPoint.x * 2.0 ) ),
257 static_cast<int64_t>( std::llround( aPoint.y * 2.0 ) ) };
258}
259
260static void addSourceProperties( CANONICAL_SEMANTIC_RECORD& aRecord, const std::vector<SOURCE_PROPERTY>& aProperties,
261 const std::string& aPrefix = {} )
262{
263 for( const SOURCE_PROPERTY& property : aProperties )
264 aRecord.properties[aPrefix + property.name.text.ToStdString()] = { property.value.text.ToStdString(),
265 property.disposition };
266}
267
268static CANONICAL_PROPERTY unknownEnum( int64_t aValue )
269{
270 return { "unknown:" + std::to_string( aValue ), PROPERTY_DISPOSITION::UNSUPPORTED };
271}
272
273static CANONICAL_PROPERTY canonicalGraphicType( MODEL_GRAPHIC_KIND aKind )
274{
275 switch( aKind )
276 {
277 case MODEL_GRAPHIC_KIND::LINE: return { "line" };
278 case MODEL_GRAPHIC_KIND::POLYLINE: return { "polyline" };
279 case MODEL_GRAPHIC_KIND::RECTANGLE: return { "rectangle" };
280 case MODEL_GRAPHIC_KIND::CIRCLE: return { "circle" };
281 case MODEL_GRAPHIC_KIND::ARC: return { "arc" };
282 case MODEL_GRAPHIC_KIND::TEXT: return { "text" };
283 }
284
285 return unknownEnum( static_cast<int64_t>( aKind ) );
286}
287
288static CANONICAL_PROPERTY canonicalGraphicType( PADS_SCH::GRAPHIC_TYPE aKind )
289{
290 switch( aKind )
291 {
292 case PADS_SCH::GRAPHIC_TYPE::LINE: return { "line" };
293 case PADS_SCH::GRAPHIC_TYPE::POLYLINE: return { "polyline" };
294 case PADS_SCH::GRAPHIC_TYPE::RECTANGLE: return { "rectangle" };
295 case PADS_SCH::GRAPHIC_TYPE::CIRCLE: return { "circle" };
296 case PADS_SCH::GRAPHIC_TYPE::ARC: return { "arc" };
297 }
298
299 return unknownEnum( static_cast<int64_t>( aKind ) );
300}
301
302static CANONICAL_PROPERTY canonicalGraphicType( const PADS_SCH::SYMBOL_GRAPHIC& aGraphic )
303{
304 if( std::ranges::any_of( aGraphic.points,
305 []( const PADS_SCH::GRAPHIC_POINT& aPoint )
306 {
307 return aPoint.arc.has_value();
308 } ) )
309 {
310 return { "arc" };
311 }
312
313 if( aGraphic.type == PADS_SCH::GRAPHIC_TYPE::POLYLINE && aGraphic.points.size() == 2 )
314 return { "line" };
315
316 return canonicalGraphicType( aGraphic.type );
317}
318
319static CANONICAL_PROPERTY canonicalLineStyle( MODEL_LINE_STYLE aStyle )
320{
321 switch( aStyle )
322 {
324 case MODEL_LINE_STYLE::SOLID: return { "solid" };
325 case MODEL_LINE_STYLE::DASH: return { "dash" };
326 case MODEL_LINE_STYLE::DOT: return { "dot" };
327 case MODEL_LINE_STYLE::DASH_DOT: return { "dash_dot" };
328 }
329
330 return unknownEnum( static_cast<int64_t>( aStyle ) );
331}
332
333static CANONICAL_PROPERTY canonicalLineStyle( int aStyle )
334{
335 switch( static_cast<int8_t>( aStyle & 0xFF ) )
336 {
337 case 1:
338 case -1: return { "solid" };
339 case 0:
340 case -2: return { "dash" };
341 case -3: return { "dot" };
342 case -4: return { "dash_dot" };
343 case -5: return { "dash_dot_dot" };
344 default: return unknownEnum( aStyle );
345 }
346}
347
348static CANONICAL_PROPERTY canonicalFill( MODEL_FILL_STYLE aFill )
349{
350 switch( aFill )
351 {
352 case MODEL_FILL_STYLE::NONE: return { "none" };
353 case MODEL_FILL_STYLE::FILLED: return { "filled" };
354 case MODEL_FILL_STYLE::HATCHED: return { "hatched" };
355 }
356
357 return unknownEnum( static_cast<int64_t>( aFill ) );
358}
359
360static CANONICAL_PROPERTY canonicalFill( bool aFilled )
361{
362 return { aFilled ? "filled" : "none" };
363}
364
365static CANONICAL_PROPERTY canonicalPinType( uint32_t aType )
366{
367 switch( aType )
368 {
369 case 0: return { "passive" };
370 case 1: return { "input" };
371 case 2: return { "output" };
372 case 3: return { "bidirectional" };
373 case 4: return { "tristate" };
374 case 5: return { "open_collector" };
375 case 6: return { "open_emitter" };
376 case 7: return { "power" };
377 case 8: return { "unspecified" };
378 default: return unknownEnum( aType );
379 }
380}
381
382static CANONICAL_PROPERTY canonicalPinType( PADS_SCH::PIN_TYPE aType )
383{
384 switch( aType )
385 {
386 case PADS_SCH::PIN_TYPE::PASSIVE: return { "passive" };
387 case PADS_SCH::PIN_TYPE::INPUT: return { "input" };
388 case PADS_SCH::PIN_TYPE::OUTPUT: return { "output" };
389 case PADS_SCH::PIN_TYPE::BIDIRECTIONAL: return { "bidirectional" };
390 case PADS_SCH::PIN_TYPE::TRISTATE: return { "tristate" };
391 case PADS_SCH::PIN_TYPE::OPEN_COLLECTOR: return { "open_collector" };
392 case PADS_SCH::PIN_TYPE::OPEN_EMITTER: return { "open_emitter" };
393 case PADS_SCH::PIN_TYPE::POWER: return { "power" };
394 case PADS_SCH::PIN_TYPE::UNSPECIFIED: return { "unspecified" };
395 }
396
397 return unknownEnum( static_cast<int64_t>( aType ) );
398}
399
400static CANONICAL_PROPERTY canonicalPinType( char aType )
401{
402 switch( aType )
403 {
404 case 'U': return { "passive" };
405 case 'L': return { "input" };
406 case 'S': return { "output" };
407 case 'B': return { "bidirectional" };
408 case 'T': return { "tristate" };
409 case 'C': return { "open_collector" };
410 case 'E': return { "open_emitter" };
411 case 'P':
412 case 'G': return { "power" };
413 default: return { "unspecified" };
414 }
415}
416
417static CANONICAL_PROPERTY canonicalPinStyle( uint32_t aStyle )
418{
419 switch( aStyle )
420 {
421 case 0: return { "normal" };
422 case 1: return { "inverted" };
423 case 2: return { "clock" };
424 case 3: return { "inverted_clock" };
425 default: return unknownEnum( aStyle );
426 }
427}
428
429static CANONICAL_PROPERTY canonicalPinStyle( bool aInverted, bool aClock )
430{
431 return canonicalPinStyle( ( aInverted ? 1U : 0U ) | ( aClock ? 2U : 0U ) );
432}
433
434static CANONICAL_PROPERTY canonicalJustification( MODEL_JUSTIFICATION aJustification )
435{
436 switch( aJustification )
437 {
438 case MODEL_JUSTIFICATION::LEFT: return { "left" };
439 case MODEL_JUSTIFICATION::CENTER: return { "center" };
440 case MODEL_JUSTIFICATION::RIGHT: return { "right" };
441 }
442
443 return unknownEnum( static_cast<int64_t>( aJustification ) );
444}
445
446static CANONICAL_PROPERTY canonicalVerticalJustification( MODEL_JUSTIFICATION aJustification )
447{
448 switch( aJustification )
449 {
450 case MODEL_JUSTIFICATION::LEFT: return { "top" };
451 case MODEL_JUSTIFICATION::CENTER: return { "center" };
452 case MODEL_JUSTIFICATION::RIGHT: return { "bottom" };
453 }
454
455 return unknownEnum( static_cast<int64_t>( aJustification ) );
456}
457
458static CANONICAL_PROPERTY canonicalHorizontalJustification( int aJustification )
459{
460 const int justification = aJustification & 0xFF;
461 const int horizontal = justification >= 8 ? justification - 8
462 : justification >= 2 ? justification - 2
463 : justification;
464
465 switch( horizontal )
466 {
467 default:
468 case 0: return { "left" };
469 case 1: return { "right" };
470 case 4: return { "center" };
471 }
472}
473
474static CANONICAL_PROPERTY canonicalVerticalJustification( int aJustification )
475{
476 if( aJustification >= 8 )
477 return { "center" };
478
479 if( aJustification >= 2 )
480 return { "top" };
481
482 if( aJustification >= 0 )
483 return { "bottom" };
484
485 return unknownEnum( aJustification );
486}
487
488static CANONICAL_PROPERTY canonicalLabelKind( MODEL_LABEL_KIND aKind )
489{
490 switch( aKind )
491 {
492 case MODEL_LABEL_KIND::LOCAL: return { "local" };
493 case MODEL_LABEL_KIND::GLOBAL: return { "global" };
494 case MODEL_LABEL_KIND::HIERARCHICAL: return { "hierarchical" };
495 case MODEL_LABEL_KIND::GROUND: return { "ground" };
496 case MODEL_LABEL_KIND::POWER: return { "power" };
497 case MODEL_LABEL_KIND::BUS: return { "bus" };
498 case MODEL_LABEL_KIND::UNSUPPORTED: return unknownEnum( static_cast<int64_t>( aKind ) );
499 }
500
501 return unknownEnum( static_cast<int64_t>( aKind ) );
502}
503
504static CANONICAL_PROPERTY canonicalEndpointKind( MODEL_ENDPOINT_KIND aKind )
505{
506 switch( aKind )
507 {
509 case MODEL_ENDPOINT_KIND::POINT: return { "point" };
510 case MODEL_ENDPOINT_KIND::PIN: return { "pin" };
511 }
512
513 return unknownEnum( static_cast<int64_t>( aKind ) );
514}
515
516static int64_t canonicalAngle( int64_t aAngleTenths )
517{
518 return NormalizeAngle( static_cast<int>( aAngleTenths ) );
519}
520
521static std::vector<CANONICAL_SEMANTIC_RECORD> normalizeBinaryModel( const PADS_SCH_MODEL& aModel )
522{
523 std::vector<CANONICAL_SEMANTIC_RECORD> out;
524 auto add = [&]( CANONICAL_KIND aKind, int aSheet, std::string aKey = {} ) -> CANONICAL_SEMANTIC_RECORD&
525 {
526 out.push_back( { aSheet, aKind, std::move( aKey ) } );
527 return out.back();
528 };
529 auto addOwned = [&]( CANONICAL_KIND aKind, const SHEET_REFERENCE& aSheet,
530 std::string aKey = {} ) -> CANONICAL_SEMANTIC_RECORD&
531 {
532 auto sheet = std::ranges::find_if( aModel.sheets,
533 [&]( const MODEL_SHEET& aCandidate )
534 {
535 return aCandidate.id == aSheet.id;
536 } );
537 auto& record =
538 add( aKind, sheet == aModel.sheets.end() ? -1 : static_cast<int>( sheet->index ), std::move( aKey ) );
539 record.properties["owner_sheet"] = sheet == aModel.sheets.end()
540 ? unknownEnum( aSheet.id.Value() )
541 : CANONICAL_PROPERTY{ sheet->name.text.ToStdString() };
542 return record;
543 };
544 auto graphic = [&]( const MODEL_GRAPHIC& aGraphic, int aSheet )
545 {
546 auto& r = add( CANONICAL_KIND::GRAPHIC, aSheet );
547 r.properties["type"] = canonicalGraphicType( aGraphic.kind );
548 r.properties["line_style"] = canonicalLineStyle( aGraphic.lineStyle );
549 r.properties["stroke_half_mils"] = { aGraphic.strokeWidth };
550 r.properties["fill"] = canonicalFill( aGraphic.fill );
551 r.properties["text"] = { aGraphic.text.text.ToStdString() };
552 r.properties["font"] = { aGraphic.presentation.font.text.ToStdString() };
553 r.properties["visible"] = { aGraphic.presentation.visible };
554 r.properties["height_half_mils"] = { aGraphic.presentation.height };
555 r.properties["width_half_mils"] = { aGraphic.presentation.width };
556
557 if( aGraphic.kind == MODEL_GRAPHIC_KIND::ARC )
558 {
559 r.properties["arc_center_x_half_mils"] = { aGraphic.arcCenter.x };
560 r.properties["arc_center_y_half_mils"] = { aGraphic.arcCenter.y };
561 r.properties["arc_bounds_x1_half_mils"] = { aGraphic.arcBoundsStart.x };
562 r.properties["arc_bounds_y1_half_mils"] = { aGraphic.arcBoundsStart.y };
563 r.properties["arc_bounds_x2_half_mils"] = { aGraphic.arcBoundsEnd.x };
564 r.properties["arc_bounds_y2_half_mils"] = { aGraphic.arcBoundsEnd.y };
565 r.properties["arc_sweep_tenths"] = { int64_t( aGraphic.arcSweepAngle ) };
566 r.properties["arc_clockwise"] = { aGraphic.arcClockwise };
567 }
568
569 for( const SOURCE_POINT& p : aGraphic.points )
570 r.geometry.points.push_back( point( p ) );
571 r.geometry.angleTenths = canonicalAngle( aGraphic.angle );
572 addSourceProperties( r, aGraphic.properties );
573 };
574 auto field = [&]( const MODEL_FIELD& aField, int aSheet )
575 {
576 auto& r = add( CANONICAL_KIND::FIELD, aSheet, aField.name.text.ToStdString() );
577 r.properties["value"] = { aField.value.text.ToStdString() };
578 r.properties["visible"] = { aField.visible };
579 r.properties["font"] = { aField.presentation.font.text.ToStdString() };
580 r.geometry.points.push_back( point( aField.position ) );
581 r.geometry.angleTenths = canonicalAngle( aField.angle );
582 addSourceProperties( r, aField.properties );
583 };
584 auto& settings = add( CANONICAL_KIND::SETTINGS, -1 );
585 settings.properties["coordinate_units_per_mil"] = { aModel.settings.coordinateUnitsPerMil };
586 settings.properties["line_width_half_mils"] = { aModel.settings.defaultLineWidth };
587 settings.properties["bus_width_half_mils"] = { aModel.settings.defaultBusWidth };
588 settings.geometry.points.push_back( point( aModel.settings.pageSize ) );
589 addSourceProperties( settings, aModel.settings.properties );
590 for( const MODEL_SHEET& s : aModel.sheets )
591 {
592 auto& r = add( CANONICAL_KIND::SHEET, static_cast<int>( s.index ), s.name.text.ToStdString() );
593 r.properties["title"] = { s.title.text.ToStdString() };
594 r.properties["line_width_half_mils"] = { s.defaultLineWidth };
595 r.properties["bus_width_half_mils"] = { s.defaultBusWidth };
596 r.geometry.points.push_back( point( s.pageSize ) );
597 addSourceProperties( r, s.properties );
598 for( const auto& g : s.border )
599 graphic( g, static_cast<int>( s.index ) );
600 for( const auto& f : s.titleBlockFields )
601 field( f, static_cast<int>( s.index ) );
602 }
603 for( const auto& d : aModel.definitions )
604 {
605 auto& definition = add( CANONICAL_KIND::DEFINITION, d.source.sheet, d.name.text.ToStdString() );
606 addSourceProperties( definition, d.properties );
607 for( const auto& g : d.graphics )
608 graphic( g, d.source.sheet );
609 for( const auto& p : d.pins )
610 {
611 auto& r = add( CANONICAL_KIND::PIN, d.source.sheet, p.number.text.ToStdString() );
612 r.properties["name"] = { p.name.text.ToStdString() };
613 r.properties["type"] = canonicalPinType( p.electricalType );
614 r.properties["style"] = canonicalPinStyle( p.graphicStyle );
615 r.properties["length_half_mils"] = { p.length };
616 r.properties["font"] = { p.presentation.font.text.ToStdString() };
617 r.properties["visible"] = { p.presentation.visible };
618 r.geometry.points.push_back( point( p.position ) );
619 r.geometry.angleTenths = canonicalAngle( p.angle );
620 addSourceProperties( r, p.properties );
621 }
622 for( const auto& f : d.fields )
623 field( f, d.source.sheet );
624 }
625 for( const auto& p : aModel.partTypes )
626 {
627 auto& part = add( CANONICAL_KIND::PART_TYPE, p.source.sheet, p.name.text.ToStdString() );
628 addSourceProperties( part, p.properties );
629 for( const auto& g : p.gates )
630 {
631 auto& r = add( CANONICAL_KIND::GATE, g.source.sheet, std::to_string( g.unit ) );
632 auto definition = std::ranges::find_if( aModel.definitions,
633 [&]( const MODEL_SYMBOL_DEFINITION& aDefinition )
634 {
635 return aDefinition.id == g.definition.id;
636 } );
637 r.properties["definition"] = definition == aModel.definitions.end()
638 ? unknownEnum( g.definition.id.Value() )
639 : CANONICAL_PROPERTY{ definition->name.text.ToStdString() };
640 addSourceProperties( r, g.properties );
641 for( size_t i = 0; i < g.pins.size(); ++i )
642 {
643 auto& m = add( CANONICAL_KIND::GATE_PIN_MAPPING, g.source.sheet,
644 std::to_string( g.unit ) + ":" + std::to_string( i ) );
645 const MODEL_PIN_DEFINITION* pin = nullptr;
646
647 if( definition != aModel.definitions.end() )
648 {
649 auto resolved = std::ranges::find_if( definition->pins,
650 [&]( const MODEL_PIN_DEFINITION& aPin )
651 {
652 return aPin.id == g.pins[i].id;
653 } );
654
655 if( resolved != definition->pins.end() )
656 pin = &*resolved;
657 }
658
659 if( pin )
660 {
661 m.properties["pin_number"] = { pin->number.text.ToStdString() };
662 m.properties["pin_name"] = { pin->name.text.ToStdString() };
663 }
664 else
665 {
666 m.properties["pin_number"] = unknownEnum( g.pins[i].id.Value() );
667 m.properties["pin_name"] = unknownEnum( g.pins[i].id.Value() );
668 }
669 }
670 }
671 for( const auto& f : p.fields )
672 field( f, p.source.sheet );
673 }
674 for( const auto& p : aModel.placements )
675 {
676 auto& r = addOwned( CANONICAL_KIND::PLACEMENT, p.sheet, p.reference.text.ToStdString() );
677 r.properties["unit"] = { int64_t( p.unit ) };
678 r.properties["mirrored"] = { p.mirrored };
679 r.geometry.points.push_back( point( p.position ) );
680 r.geometry.angleTenths = canonicalAngle( p.angle );
681 addSourceProperties( r, p.properties );
682 int ownerSheet = r.sheet;
683 for( const auto& f : p.fields )
684 field( f, ownerSheet );
685 }
686 for( const auto& n : aModel.nets )
687 {
688 auto& net = addOwned( CANONICAL_KIND::NET, n.sheet, n.name.text.ToStdString() );
689 addSourceProperties( net, n.properties );
690 for( const auto& c : n.connections )
691 {
692 auto& r = addOwned( CANONICAL_KIND::CONNECTION, n.sheet );
693 r.properties["endpoint_count"] = { int64_t( c.endpoints.size() ) };
694 for( size_t i = 0; i < c.endpoints.size(); ++i )
695 {
696 const auto prefix = "endpoint_" + std::to_string( i ) + "_";
697 r.properties[prefix + "kind"] = canonicalEndpointKind( c.endpoints[i].kind );
698 addSourceProperties( r, c.endpoints[i].properties, prefix );
699
700 if( c.endpoints[i].placement )
701 {
702 auto placement = std::ranges::find_if( aModel.placements,
703 [&]( const MODEL_PLACEMENT& aPlacement )
704 {
705 return aPlacement.id == c.endpoints[i].placement->id;
706 } );
707 r.properties[prefix + "placement"] =
708 placement == aModel.placements.end()
709 ? unknownEnum( c.endpoints[i].placement->id.Value() )
710 : CANONICAL_PROPERTY{ placement->reference.text.ToStdString() };
711 }
712
713 if( c.endpoints[i].pin )
714 {
715 auto definition = std::ranges::find_if( aModel.definitions,
716 [&]( const MODEL_SYMBOL_DEFINITION& aDefinition )
717 {
718 return std::ranges::any_of(
719 aDefinition.pins,
720 [&]( const MODEL_PIN_DEFINITION& aPin )
721 {
722 return aPin.id == c.endpoints[i].pin->id;
723 } );
724 } );
725 r.properties[prefix + "pin_number"] =
726 definition == aModel.definitions.end()
727 ? unknownEnum( c.endpoints[i].pin->id.Value() )
728 : CANONICAL_PROPERTY{ std::ranges::find_if( definition->pins,
729 [&]( const MODEL_PIN_DEFINITION& aPin )
730 {
731 return aPin.id
732 == c.endpoints[i].pin->id;
733 } )
734 ->number.text.ToStdString() };
735 }
736 }
737 for( const auto& p : c.vertices )
738 r.geometry.points.push_back( point( p ) );
739 addSourceProperties( r, c.properties );
740 }
741 }
742 for( const auto& b : aModel.buses )
743 {
744 auto netName = [&]( NET_ID aId ) -> CANONICAL_PROPERTY
745 {
746 auto net = std::ranges::find_if( aModel.nets,
747 [&]( const MODEL_NET& aNet )
748 {
749 return aNet.id == aId;
750 } );
751
752 if( net == aModel.nets.end() )
753 return unknownEnum( aId.Value() );
754
755 return { net->name.text.ToStdString() };
756 };
757 auto& r = addOwned( CANONICAL_KIND::BUS, b.sheet, b.name.text.ToStdString() );
758 std::vector<std::string> aliases;
759 std::vector<std::string> members;
760
761 for( const SOURCE_STRING& alias : b.aliases )
762 aliases.push_back( alias.text.ToStdString() );
763
764 for( const NET_REFERENCE& member : b.memberNets )
765 members.push_back( std::get<std::string>( netName( member.id ).value ) );
766
767 r.properties["aliases"] = { aliases };
768 r.properties["member_nets"] = { members };
769 for( const auto& p : b.vertices )
770 r.geometry.points.push_back( point( p ) );
771 addSourceProperties( r, b.properties );
772 for( size_t i = 0; i < b.entries.size(); ++i )
773 {
774 auto& e = addOwned( CANONICAL_KIND::BUS_ENTRY, b.sheet, std::to_string( i ) );
775 e.properties["member_index"] = { int64_t( i ) };
776 e.properties["member_net"] = netName( b.entries[i].memberNet.id );
777 e.geometry.points.push_back( point( b.entries[i].position ) );
778 addSourceProperties( e, b.entries[i].properties );
779 }
780 for( const auto& a : b.aliases )
781 addOwned( CANONICAL_KIND::BUS_ALIAS, b.sheet, a.text.ToStdString() );
782 for( size_t i = 0; i < b.memberNets.size(); ++i )
783 {
784 auto& member = addOwned( CANONICAL_KIND::BUS_MEMBER, b.sheet, std::to_string( i ) );
785 member.properties["member_net"] = netName( b.memberNets[i].id );
786 }
787 }
788 for( const auto& l : aModel.labels )
789 {
790 auto& r = addOwned( CANONICAL_KIND::LABEL, l.sheet, l.text.text.ToStdString() );
791 r.properties["kind"] = canonicalLabelKind( l.kind );
792 std::vector<std::string> linkedSheets;
793
794 for( const SHEET_REFERENCE& linked : l.linkedSheets )
795 {
796 auto sheet = std::ranges::find( aModel.sheets, linked.id, &MODEL_SHEET::id );
797
798 if( sheet != aModel.sheets.end() )
799 linkedSheets.push_back( std::to_string( sheet->index ) );
800 }
801
802 std::ranges::sort( linkedSheets );
803 r.properties["linked_sheets"] = { linkedSheets };
804 r.geometry.points.push_back( point( l.position ) );
805 r.geometry.angleTenths = canonicalAngle( l.angle );
806 addSourceProperties( r, l.properties );
807 }
808 for( const auto& j : aModel.junctions )
809 {
810 auto& r = addOwned( CANONICAL_KIND::JUNCTION, j.sheet );
811 r.geometry.points.push_back( point( j.position ) );
812 addSourceProperties( r, j.properties );
813 }
814 for( const auto& t : aModel.texts )
815 {
816 auto& r = addOwned( CANONICAL_KIND::TEXT, t.sheet, t.text.text.ToStdString() );
817 r.properties["visible"] = { t.presentation.visible };
818 r.properties["font"] = { t.presentation.font.text.ToStdString() };
819 r.properties["bold"] = { t.presentation.bold };
820 r.properties["italic"] = { t.presentation.italic };
821 r.properties["underline"] = { t.presentation.underline };
822 r.properties["horizontal_justification"] = canonicalJustification( t.presentation.horizontalJustification );
823 r.properties["vertical_justification"] = canonicalVerticalJustification( t.presentation.verticalJustification );
824 r.geometry.points.push_back( point( t.position ) );
825 r.geometry.angleTenths = canonicalAngle( t.angle );
826 addSourceProperties( r, t.properties );
827 }
828 for( const MODEL_PAGE_GRAPHIC& pageGraphic : aModel.graphics )
829 {
830 auto& record = addOwned( CANONICAL_KIND::GRAPHIC, pageGraphic.sheet );
831 int sheet = record.sheet;
832 auto owner = record.properties.at( "owner_sheet" );
833 out.pop_back();
834 graphic( pageGraphic.graphic, sheet );
835 out.back().properties["owner_sheet"] = owner;
836 }
837 for( const MODEL_WORKSHEET& worksheet : aModel.worksheets )
838 {
839 auto& record = addOwned( CANONICAL_KIND::GRAPHIC, worksheet.sheet );
840 int sheet = record.sheet;
841 auto owner = record.properties.at( "owner_sheet" );
842 out.pop_back();
843
844 for( const MODEL_GRAPHIC& worksheetGraphic : worksheet.graphics )
845 {
846 graphic( worksheetGraphic, sheet );
847 out.back().properties["owner_sheet"] = owner;
848 }
849 }
850 return out;
851}
852
853static std::vector<CANONICAL_SEMANTIC_RECORD> normalizeAsciiModel( const PADS_SCH::PADS_SCH_PARSER& aParser )
854{
855 std::vector<CANONICAL_SEMANTIC_RECORD> out;
856 auto add = [&]( CANONICAL_KIND k, int s, std::string key = {} ) -> CANONICAL_SEMANTIC_RECORD&
857 {
858 out.push_back( { s, k, std::move( key ) } );
859 return out.back();
860 };
861 const auto& p = aParser.GetParameters();
862 auto addOwned = [&]( CANONICAL_KIND aKind, int aSheet, std::string aKey = {} ) -> CANONICAL_SEMANTIC_RECORD&
863 {
864 auto sheet = std::ranges::find_if( aParser.GetSheetHeaders(),
865 [&]( const PADS_SCH::SHEET_HEADER& aCandidate )
866 {
867 return aCandidate.sheet_num - 1 == aSheet;
868 } );
869 auto& record = add( aKind, aSheet, std::move( aKey ) );
870 record.properties["owner_sheet"] =
871 sheet == aParser.GetSheetHeaders().end()
872 ? CANONICAL_PROPERTY{ "unknown:" + std::to_string( aSheet ), PROPERTY_DISPOSITION::UNSUPPORTED }
873 : CANONICAL_PROPERTY{ sheet->sheet_name };
874 return record;
875 };
876 auto& settings = add( CANONICAL_KIND::SETTINGS, -1 );
877 settings.properties["coordinate_units_per_mil"] = { int64_t( 2 ) };
878 settings.properties["line_width_half_mils"] = { int64_t( std::llround( p.line_width * 2 ) ) };
879 settings.properties["bus_width_half_mils"] = { int64_t( p.bus_width * 2 ) };
880 settings.geometry.points.push_back( point( PADS_SCH::POINT{ p.sheet_size.width, p.sheet_size.height } ) );
881 auto addGraphic = [&]( const PADS_SCH::SYMBOL_GRAPHIC& aGraphic, int aSheet )
882 {
883 auto& r = add( CANONICAL_KIND::GRAPHIC, aSheet );
884 r.properties["type"] = canonicalGraphicType( aGraphic.type );
885 r.properties["line_style"] = canonicalLineStyle( aGraphic.line_style );
886 r.properties["stroke_half_mils"] = { int64_t( std::llround( aGraphic.line_width * 2 ) ) };
887 r.properties["fill"] = canonicalFill( aGraphic.filled );
888 r.properties["text"] = { std::string() };
889 r.properties["font"] = { std::string() };
890 r.properties["visible"] = { true };
891 r.properties["height_half_mils"] = { int64_t( 0 ) };
892 r.properties["width_half_mils"] = { int64_t( 0 ) };
893
894 for( const auto& q : aGraphic.points )
895 r.geometry.points.push_back( point( q.coord ) );
896
897 auto arcPoint = std::ranges::find_if( aGraphic.points,
898 []( const PADS_SCH::GRAPHIC_POINT& aPoint )
899 {
900 return aPoint.arc.has_value();
901 } );
902
903 if( arcPoint != aGraphic.points.end() )
904 {
905 const PADS_SCH::ARC_DATA& arc = *arcPoint->arc;
906 r.properties["arc_center_x_half_mils"] = { int64_t( std::llround( ( arc.bbox_x1 + arc.bbox_x2 ) ) ) };
907 r.properties["arc_center_y_half_mils"] = { int64_t( std::llround( ( arc.bbox_y1 + arc.bbox_y2 ) ) ) };
908 r.properties["arc_bounds_x1_half_mils"] = { int64_t( std::llround( arc.bbox_x1 * 2 ) ) };
909 r.properties["arc_bounds_y1_half_mils"] = { int64_t( std::llround( arc.bbox_y1 * 2 ) ) };
910 r.properties["arc_bounds_x2_half_mils"] = { int64_t( std::llround( arc.bbox_x2 * 2 ) ) };
911 r.properties["arc_bounds_y2_half_mils"] = { int64_t( std::llround( arc.bbox_y2 * 2 ) ) };
912 r.properties["arc_sweep_tenths"] = { int64_t( std::llround( std::abs( arc.bulge ) ) ) };
913 r.properties["arc_clockwise"] = { arc.angle < 0 };
914 }
915 };
916 for( const auto& s : aParser.GetSheetHeaders() )
917 {
918 auto& r = add( CANONICAL_KIND::SHEET, s.sheet_num - 1, s.sheet_name );
919 r.properties["title"] = { std::string() };
920 r.properties["line_width_half_mils"] = { int64_t( std::llround( p.line_width * 2 ) ) };
921 r.properties["bus_width_half_mils"] = { int64_t( p.bus_width * 2 ) };
922 r.geometry.points.push_back( point( PADS_SCH::POINT{ p.sheet_size.width, p.sheet_size.height } ) );
923 }
924 for( const auto& d : aParser.GetSymbolDefs() )
925 {
926 add( CANONICAL_KIND::DEFINITION, -1, d.name );
927 for( const auto& g : d.graphics )
928 addGraphic( g, -1 );
929 std::vector<PADS_SCH::SYMBOL_PIN> pins = d.pins;
930
931 for( const auto& [partName, part] : aParser.GetPartTypes() )
932 {
933 for( const PADS_SCH::GATE_DEF& gate : part.gates )
934 {
935 if( gate.decal_names.empty() || gate.decal_names.front() != d.name || gate.pins.size() != pins.size() )
936 continue;
937
938 for( size_t i = 0; i < pins.size(); ++i )
939 {
940 pins[i].number = gate.pins[i].pin_id;
941 pins[i].name = gate.pins[i].pin_name;
942 switch( gate.pins[i].pin_type )
943 {
944 case 'L': pins[i].type = PADS_SCH::PIN_TYPE::INPUT; break;
945 case 'S': pins[i].type = PADS_SCH::PIN_TYPE::OUTPUT; break;
946 case 'B': pins[i].type = PADS_SCH::PIN_TYPE::BIDIRECTIONAL; break;
947 case 'T': pins[i].type = PADS_SCH::PIN_TYPE::TRISTATE; break;
948 case 'C': pins[i].type = PADS_SCH::PIN_TYPE::OPEN_COLLECTOR; break;
949 case 'E': pins[i].type = PADS_SCH::PIN_TYPE::OPEN_EMITTER; break;
950 case 'P':
951 case 'G': pins[i].type = PADS_SCH::PIN_TYPE::POWER; break;
952 case 'U': pins[i].type = PADS_SCH::PIN_TYPE::PASSIVE; break;
953 default: pins[i].type = PADS_SCH::PIN_TYPE::UNSPECIFIED; break;
954 }
955 }
956
957 break;
958 }
959 }
960
961 for( const auto& pin : pins )
962 {
963 auto& r = add( CANONICAL_KIND::PIN, -1, pin.number );
964 r.properties["name"] = { pin.name };
965 r.properties["type"] = canonicalPinType( pin.type );
966 r.properties["style"] = canonicalPinStyle( pin.inverted, pin.clock );
967 r.properties["length_half_mils"] = { int64_t( std::llround( pin.length * 2 ) ) };
968 r.geometry.points.push_back( point( pin.position ) );
969 r.geometry.angleTenths = canonicalAngle( std::llround( pin.rotation * 10 ) );
970 }
971 for( const auto& t : d.texts )
972 {
973 auto& r = add( CANONICAL_KIND::GRAPHIC, -1 );
974 r.properties["type"] = { "text" };
975 r.properties["line_style"] = { "solid" };
976 r.properties["stroke_half_mils"] = { int64_t( 0 ) };
977 r.properties["fill"] = { "none" };
978 r.properties["text"] = { t.content };
979 r.properties["visible"] = { t.visible };
980 r.properties["font"] = { t.font_name };
981 r.properties["height_half_mils"] = { int64_t( std::llround( t.size ) ) };
982 r.properties["width_half_mils"] = { int64_t( t.width_factor ) };
983 r.geometry.points.push_back( point( t.position ) );
984 r.geometry.angleTenths = canonicalAngle( std::llround( t.rotation * 10 ) );
985 }
986 }
987 for( const auto& [name, part] : aParser.GetPartTypes() )
988 {
989 add( CANONICAL_KIND::PART_TYPE, -1, name );
990 for( size_t i = 0; i < part.gates.size(); ++i )
991 {
992 auto& gate = add( CANONICAL_KIND::GATE, -1, std::to_string( i + 1 ) );
993 gate.properties["definition"] = { part.gates[i].decal_names.empty() ? std::string()
994 : part.gates[i].decal_names.front() };
995 for( size_t j = 0; j < part.gates[i].pins.size(); ++j )
996 {
997 auto& r = add( CANONICAL_KIND::GATE_PIN_MAPPING, -1,
998 std::to_string( i + 1 ) + ":" + std::to_string( j ) );
999 r.properties["pin_number"] = { part.gates[i].pins[j].pin_id };
1000 r.properties["pin_name"] = { part.gates[i].pins[j].pin_name };
1001 }
1002 }
1003 }
1004 for( const auto& x : aParser.GetPartPlacements() )
1005 {
1006 auto& r = addOwned( CANONICAL_KIND::PLACEMENT, x.sheet_number - 1, x.reference );
1007 r.properties["unit"] = { int64_t( x.gate_number ) };
1008 r.properties["mirrored"] = { x.mirror_flags != 0 };
1009 r.geometry.points.push_back( point( x.position ) );
1010 r.geometry.angleTenths = canonicalAngle( std::llround( x.rotation * 10 ) );
1011 int ownerSheet = r.sheet;
1012 for( const auto& f : x.attributes )
1013 {
1014 auto& q = add( CANONICAL_KIND::FIELD, ownerSheet, f.name );
1015 q.properties["value"] = { f.value };
1016 q.properties["visible"] = { f.visible };
1017 q.properties["font"] = { f.font_name };
1018 q.geometry.points.push_back( point( f.position ) );
1019 q.geometry.angleTenths = canonicalAngle( std::llround( f.rotation * 10 ) );
1020 }
1021 }
1022 for( const auto& n : aParser.GetSignals() )
1023 {
1024 if( n.flags1 == 5 && n.flags2 == 3 )
1025 continue;
1026
1027 int netSheet = n.wires.empty() ? -1 : n.wires.front().sheet_number - 1;
1028 addOwned( CANONICAL_KIND::NET, netSheet, n.name );
1029 for( const auto& c : n.wires )
1030 {
1031 auto& r = addOwned( CANONICAL_KIND::CONNECTION, c.sheet_number - 1 );
1032 r.properties["endpoint_count"] = { int64_t( 2 ) };
1033
1034 const std::array<std::string, 2> endpoints = { c.endpoint_a, c.endpoint_b };
1035
1036 for( size_t endpoint = 0; endpoint < endpoints.size(); ++endpoint )
1037 {
1038 const std::string prefix = "endpoint_" + std::to_string( endpoint ) + "_";
1039 const std::string& token = endpoints[endpoint];
1040
1041 if( token.starts_with( "@@@" ) )
1042 {
1043 r.properties[prefix + "kind"] = canonicalEndpointKind( MODEL_ENDPOINT_KIND::POINT );
1044 }
1045 else
1046 {
1047 const size_t separator = token.rfind( '.' );
1048 r.properties[prefix + "kind"] = canonicalEndpointKind( MODEL_ENDPOINT_KIND::PIN );
1049 r.properties[prefix + "placement"] = { token.substr( 0, separator ) };
1050 r.properties[prefix + "pin_number"] = { token.substr( separator + 1 ) };
1051 }
1052 }
1053
1054 for( const auto& q : c.vertices )
1055 r.geometry.points.push_back( point( q ) );
1056 }
1057 }
1058 for( const auto& b : aParser.GetBuses() )
1059 {
1060 auto& r = addOwned( CANONICAL_KIND::BUS, b.sheet_number - 1, b.name );
1061 r.properties["aliases"] = { b.aliases };
1062 r.properties["member_nets"] = { b.member_nets };
1063 for( const auto& q : b.path )
1064 r.geometry.points.push_back( point( q ) );
1065 for( size_t i = 0; i < b.entries.size(); ++i )
1066 {
1067 auto& e = addOwned( CANONICAL_KIND::BUS_ENTRY, b.sheet_number - 1, std::to_string( i ) );
1068 e.properties["member_index"] = { int64_t( i ) };
1069 e.properties["member_net"] = { b.entries[i].member_net };
1070 e.geometry.points.push_back( point( b.entries[i].position ) );
1071 }
1072 for( const auto& a : b.aliases )
1073 addOwned( CANONICAL_KIND::BUS_ALIAS, b.sheet_number - 1, a );
1074 for( size_t i = 0; i < b.member_nets.size(); ++i )
1075 {
1076 auto& member = addOwned( CANONICAL_KIND::BUS_MEMBER, b.sheet_number - 1, std::to_string( i ) );
1077 member.properties["member_net"] = { b.member_nets[i] };
1078
1079 if( std::ranges::none_of( aParser.GetSignals(),
1080 [&]( const PADS_SCH::SCH_SIGNAL& aSignal )
1081 {
1082 return aSignal.name == b.member_nets[i];
1083 } ) )
1084 {
1085 addOwned( CANONICAL_KIND::NET, b.sheet_number - 1, b.member_nets[i] );
1086 }
1087 }
1088 }
1089 for( const auto& l : aParser.GetOffPageConnectors() )
1090 {
1091 if( l.symbol_lib.starts_with( "@@@B" ) )
1092 continue;
1093 auto& r = addOwned( CANONICAL_KIND::LABEL, l.source_sheet - 1, l.signal_name );
1094 r.properties["kind"] = { l.symbol_lib == "@TERM" ? "local"
1095 : l.symbol_lib.starts_with( "$OSR" ) ? "global"
1096 : l.symbol_lib.starts_with( "$GND" ) ? "ground"
1097 : l.symbol_lib.starts_with( "$PWR" ) ? "power"
1098 : "unsupported" };
1099 std::vector<std::string> linkedSheets;
1100
1101 if( l.symbol_lib.starts_with( "$OSR" ) )
1102 {
1103 for( const auto& peer : aParser.GetOffPageConnectors() )
1104 {
1105 if( peer.signal_name != l.signal_name || peer.source_sheet == l.source_sheet
1106 || !peer.symbol_lib.starts_with( "$OSR" ) )
1107 {
1108 continue;
1109 }
1110
1111 auto sheet = std::ranges::find_if( aParser.GetSheetHeaders(),
1112 [&]( const PADS_SCH::SHEET_HEADER& aHeader )
1113 {
1114 return aHeader.sheet_num == peer.source_sheet;
1115 } );
1116
1117 if( sheet != aParser.GetSheetHeaders().end() )
1118 linkedSheets.push_back( std::to_string( sheet->sheet_num - 1 ) );
1119 }
1120 }
1121
1122 std::ranges::sort( linkedSheets );
1123 r.properties["linked_sheets"] = { linkedSheets };
1124 r.geometry.points.push_back( point( l.position ) );
1125 r.geometry.angleTenths = canonicalAngle( l.rotation * 10 );
1126 }
1127 for( const auto& j : aParser.GetTiedDots() )
1128 addOwned( CANONICAL_KIND::JUNCTION, j.sheet_number - 1 ).geometry.points.push_back( point( j.position ) );
1129 for( const auto& t : aParser.GetTextItems() )
1130 {
1131 auto& r = addOwned( CANONICAL_KIND::TEXT, t.sheet_number - 1, t.content );
1132 r.properties["visible"] = { true };
1133 r.properties["bold"] = { false };
1134 r.properties["italic"] = { false };
1135 r.properties["underline"] = { false };
1136 r.geometry.points.push_back( point( t.position ) );
1137 r.geometry.angleTenths = canonicalAngle( t.rotation * 10 );
1138 }
1139 for( const auto& lines : aParser.GetLinesItems() )
1140 {
1141 for( const auto& g : lines.primitives )
1142 {
1143 auto& owner = addOwned( CANONICAL_KIND::GRAPHIC, lines.sheet_number - 1 );
1144 auto property = owner.properties.at( "owner_sheet" );
1145 out.pop_back();
1146 addGraphic( g, lines.sheet_number - 1 );
1147 out.back().properties["owner_sheet"] = property;
1148 out.back().properties["page_graphic_group"] = { lines.name };
1149 out.back().properties["type"] = canonicalGraphicType( g );
1150
1151 for( CANONICAL_POINT& graphicPoint : out.back().geometry.points )
1152 {
1153 graphicPoint.xHalfMils += std::llround( lines.origin.x * 2 );
1154 graphicPoint.yHalfMils += std::llround( lines.origin.y * 2 );
1155 }
1156
1157 for( const std::string& name :
1158 { "arc_center_x_half_mils", "arc_bounds_x1_half_mils", "arc_bounds_x2_half_mils" } )
1159 {
1160 auto property = out.back().properties.find( name );
1161
1162 if( property != out.back().properties.end() )
1163 std::get<int64_t>( property->second.value ) += std::llround( lines.origin.x * 2 );
1164 }
1165
1166 for( const std::string& name :
1167 { "arc_center_y_half_mils", "arc_bounds_y1_half_mils", "arc_bounds_y2_half_mils" } )
1168 {
1169 auto property = out.back().properties.find( name );
1170
1171 if( property != out.back().properties.end() )
1172 std::get<int64_t>( property->second.value ) += std::llround( lines.origin.y * 2 );
1173 }
1174
1175 if( g.type == PADS_SCH::GRAPHIC_TYPE::RECTANGLE )
1176 {
1177 out.back().properties["type"] = canonicalGraphicType( MODEL_GRAPHIC_KIND::POLYLINE );
1178
1179 if( out.back().geometry.points.size() == 2 )
1180 {
1181 const CANONICAL_POINT first = out.back().geometry.points.front();
1182 const CANONICAL_POINT last = out.back().geometry.points.back();
1183 out.back().geometry.points = { { last.xHalfMils, last.yHalfMils },
1184 { first.xHalfMils, last.yHalfMils },
1185 { first.xHalfMils, first.yHalfMils },
1186 { last.xHalfMils, first.yHalfMils },
1187 { last.xHalfMils, last.yHalfMils } };
1188 }
1189 }
1190
1191 out.back().geometry.angleTenths = 0;
1192 }
1193
1194 for( const auto& t : lines.texts )
1195 {
1196 auto& r = addOwned( CANONICAL_KIND::GRAPHIC, lines.sheet_number - 1 );
1197 r.properties["type"] = canonicalGraphicType( MODEL_GRAPHIC_KIND::TEXT );
1198 r.properties["line_style"] = canonicalLineStyle( MODEL_LINE_STYLE::DEFAULT );
1199 r.properties["stroke_half_mils"] = { int64_t( 0 ) };
1200 r.properties["fill"] = canonicalFill( MODEL_FILL_STYLE::NONE );
1201 r.properties["text"] = { t.content };
1202 r.properties["page_graphic_group"] = { lines.name };
1203 r.properties["visible"] = { true };
1204 r.properties["height_half_mils"] = { int64_t( t.height ) };
1205 r.properties["width_half_mils"] = { int64_t( t.width_factor ) };
1206 r.properties["font"] = { t.font_name };
1207 r.geometry.points.push_back( point( t.position ) );
1208 r.geometry.points.back().xHalfMils += std::llround( lines.origin.x * 2 );
1209 r.geometry.points.back().yHalfMils += std::llround( lines.origin.y * 2 );
1210 r.geometry.angleTenths = canonicalAngle( t.rotation * 10 );
1211 }
1212 }
1213 for( const auto& label : aParser.GetNetNameLabels() )
1214 {
1215 auto& r = addOwned( CANONICAL_KIND::LABEL, -1, label.net_name );
1216 r.properties["kind"] = { "local" };
1217 r.properties["visible"] = { true };
1218 r.properties["font"] = { label.font_name };
1219 r.properties["horizontal_justification"] = canonicalHorizontalJustification( label.justification );
1220 r.properties["vertical_justification"] = canonicalVerticalJustification( label.justification );
1221 r.geometry.points.push_back( point( PADS_SCH::POINT{ double( label.x_offset ), double( label.y_offset ) } ) );
1222 r.geometry.angleTenths = canonicalAngle( label.rotation * 10 );
1223 }
1224 return out;
1225}
1226
1227static bool snapshotsMatch( std::vector<CANONICAL_SEMANTIC_RECORD> aExpected,
1228 std::vector<CANONICAL_SEMANTIC_RECORD> aActual,
1229 const SNAPSHOT_ALLOWLIST& aAllowedDifferences = {} )
1230{
1231 auto strip = [&]( auto& records )
1232 {
1233 for( auto& record : records )
1234 std::erase_if( record.properties,
1235 [&]( const auto& p )
1236 {
1237 return aAllowedDifferences.contains( { p.first, p.second.disposition } );
1238 } );
1239 std::sort( records.begin(), records.end() );
1240 };
1241 strip( aExpected );
1242 strip( aActual );
1243 return aExpected == aActual;
1244}
1245
1246
1247static bool task10SourcePropertiesPresent( const std::vector<CANONICAL_SEMANTIC_RECORD>& aRecords )
1248{
1249 auto has =
1250 []( const CANONICAL_SEMANTIC_RECORD& aRecord, const std::string& aName, PROPERTY_DISPOSITION aDisposition )
1251 {
1252 auto property = aRecord.properties.find( aName );
1253 return property != aRecord.properties.end() && property->second.disposition == aDisposition;
1254 };
1255
1256 for( const CANONICAL_SEMANTIC_RECORD& record : aRecords )
1257 {
1258 if( record.kind == CANONICAL_KIND::NET
1259 && ( !has( record, "global_net_record", PROPERTY_DISPOSITION::EXACT )
1260 || !has( record, "preserved_net_identity", PROPERTY_DISPOSITION::PRESERVED )
1261 || !has( record, "preserved_net_relationship", PROPERTY_DISPOSITION::PRESERVED ) ) )
1262 {
1263 return false;
1264 }
1265
1266 if( record.kind == CANONICAL_KIND::CONNECTION
1267 && ( !has( record, "endpoint_0_raw_endpoint_handle", PROPERTY_DISPOSITION::EXACT )
1268 || !has( record, "endpoint_1_raw_endpoint_handle", PROPERTY_DISPOSITION::EXACT )
1269 || !has( record, "endpoint_0_raw_endpoint_relationship", PROPERTY_DISPOSITION::PRESERVED )
1270 || !has( record, "endpoint_1_raw_endpoint_relationship", PROPERTY_DISPOSITION::PRESERVED )
1271 || !has( record, "raw_connection_marker", PROPERTY_DISPOSITION::EXACT ) ) )
1272 {
1273 return false;
1274 }
1275
1276 if( record.kind == CANONICAL_KIND::LABEL && !has( record, "offpage_variant", PROPERTY_DISPOSITION::EXACT ) )
1277 {
1278 return false;
1279 }
1280
1281 if( record.kind == CANONICAL_KIND::JUNCTION
1282 && !has( record, "connection_record", PROPERTY_DISPOSITION::EXACT ) )
1283 {
1284 return false;
1285 }
1286
1287 if( record.kind == CANONICAL_KIND::BUS
1288 && ( !has( record, "preserved_net_identity", PROPERTY_DISPOSITION::PRESERVED )
1289 || !has( record, "preserved_net_relationship", PROPERTY_DISPOSITION::PRESERVED ) ) )
1290 {
1291 return false;
1292 }
1293 }
1294
1295 return true;
1296}
1297
1298} // namespace
1299
1300
1301BOOST_AUTO_TEST_SUITE( PadsSchBinaryParser )
1302
1303
1304BOOST_AUTO_TEST_CASE( EmbeddedOleImages )
1305{
1307 std::vector<uint8_t> bytes = loadBinaryFixture( "ole_images.sch" );
1308 PADS_SCH_MODEL model = parser.Parse( bytes, wxS( "ole_images.sch" ) );
1309
1310 BOOST_REQUIRE_EQUAL( model.images.size(), 2 );
1311 BOOST_CHECK_EQUAL( model.images[0].id.Value(), 0 );
1312 BOOST_CHECK_EQUAL( model.images[0].sheet.id.Value(), 1 );
1313 BOOST_CHECK( model.images[0].type == MODEL_EMBEDDED_IMAGE_TYPE::BMP );
1314 BOOST_CHECK_EQUAL( model.images[0].streamName, wxS( "\\x01Ole10Native" ) );
1315 BOOST_CHECK( ( model.images[0].extent == std::array<int32_t, 4>{ 30, 30, 350, 210 } ) );
1316 BOOST_CHECK( ( model.images[0].databaseBox == std::array<int32_t, 4>{ -15766, -10366, -13296, -11755 } ) );
1317 BOOST_CHECK_EQUAL( model.images[0].position.x, 5876 );
1318 BOOST_CHECK_EQUAL( model.images[0].position.y, 19758 );
1319 BOOST_CHECK_EQUAL( model.images[0].size.x, 9880 );
1320 BOOST_CHECK_EQUAL( model.images[0].size.y, 5556 );
1321 BOOST_CHECK_EQUAL( model.images[0].flags, 1 );
1322 BOOST_REQUIRE_GE( model.images[0].data.size(), 2 );
1323 BOOST_CHECK_EQUAL( model.images[0].data[0], 'B' );
1324 BOOST_CHECK_EQUAL( model.images[0].data[1], 'M' );
1325 BOOST_CHECK_EQUAL( model.images[0].source.objectClass, wxS( "embedded OLE image" ) );
1326 BOOST_CHECK_EQUAL( model.images[0].source.recordIndex, 0 );
1327 BOOST_CHECK_GT( model.images[0].source.absoluteOffset, 0 );
1328 BOOST_CHECK_GT( model.images[0].source.length, 512 );
1329
1330 BOOST_CHECK_EQUAL( model.images[1].id.Value(), 1 );
1331 BOOST_CHECK_EQUAL( model.images[1].sheet.id.Value(), 1 );
1332 BOOST_CHECK( model.images[1].type == MODEL_EMBEDDED_IMAGE_TYPE::WMF );
1333 BOOST_CHECK_EQUAL( model.images[1].streamName, wxS( "\\x01Ole10Native" ) );
1334 BOOST_CHECK( ( model.images[1].extent == std::array<int32_t, 4>{ 30, 30, 170, 84 } ) );
1335 BOOST_CHECK( ( model.images[1].databaseBox == std::array<int32_t, 4>{ -15766, -10366, -14686, -10782 } ) );
1336 BOOST_CHECK_EQUAL( model.images[1].position.x, 3096 );
1337 BOOST_CHECK_EQUAL( model.images[1].position.y, 21704 );
1338 BOOST_CHECK_EQUAL( model.images[1].size.x, 4320 );
1339 BOOST_CHECK_EQUAL( model.images[1].size.y, 1664 );
1340 BOOST_CHECK_EQUAL( model.images[1].flags, 1 );
1341 BOOST_REQUIRE_GE( model.images[1].data.size(), 4 );
1342 BOOST_CHECK_EQUAL( model.images[1].data[0], 0xD7 );
1343 BOOST_CHECK_EQUAL( model.images[1].data[1], 0xCD );
1344 BOOST_CHECK_EQUAL( model.images[1].data[2], 0xC6 );
1345 BOOST_CHECK_EQUAL( model.images[1].data[3], 0x9A );
1346
1347 PADS_SCH_SDB sdb;
1348 sdb.Load( bytes );
1349 BOOST_REQUIRE_EQUAL( sdb.OleItems().size(), 2u );
1350 std::vector<uint8_t> zeroWidth = bytes;
1351 writeU32( zeroWidth, sdb.OleItems()[0].boxOffset + 8, static_cast<uint32_t>( sdb.OleItems()[0].left ) );
1352 PADS_SCH_MODEL zeroWidthModel = parser.Parse( zeroWidth, wxS( "ole-zero-width.sch" ) );
1353 BOOST_REQUIRE_EQUAL( zeroWidthModel.images.size(), 2 );
1354 BOOST_CHECK( zeroWidthModel.images[0].type == MODEL_EMBEDDED_IMAGE_TYPE::UNSUPPORTED );
1355 BOOST_CHECK( std::ranges::any_of( zeroWidthModel.diagnostics,
1356 []( const PARSER_DIAGNOSTIC& aDiagnostic )
1357 {
1358 return aDiagnostic.source.objectClass
1359 == wxS( "embedded OLE image database box" )
1360 && aDiagnostic.message.Contains( wxS( "zero-size" ) );
1361 } ) );
1362}
1363
1364
1365BOOST_AUTO_TEST_CASE( GlobalsAndSheets )
1366{
1368 PADS_SCH_MODEL minimal = parser.Parse( loadBinaryFixture( "minimal_v13.sch" ), wxS( "minimal_v13.sch" ) );
1369
1370 BOOST_CHECK_EQUAL( minimal.version, 0x000D );
1371 BOOST_CHECK_EQUAL( minimal.subversion, 0 );
1372 BOOST_CHECK_EQUAL( minimal.settings.codePage, 1252 );
1374 BOOST_CHECK_EQUAL( minimal.settings.pageSize.x, 34000 );
1375 BOOST_CHECK_EQUAL( minimal.settings.pageSize.y, 22000 );
1378 BOOST_CHECK_EQUAL( minimal.settings.source.objectClass, wxS( "design settings" ) );
1381 BOOST_CHECK_EQUAL( minimal.settings.source.length, 400 );
1382 BOOST_CHECK_EQUAL( minimal.settings.source.version, 0x000D );
1383 BOOST_REQUIRE_EQUAL( minimal.sheets.size(), 1 );
1384 BOOST_CHECK_EQUAL( minimal.sheets[0].id.Value(), 1 );
1385 BOOST_CHECK_EQUAL( minimal.sheets[0].index, 0 );
1386 BOOST_CHECK_EQUAL( minimal.sheets[0].name.text, wxS( "$$$NONE" ) );
1387 BOOST_CHECK( !minimal.sheets[0].parent );
1388 BOOST_CHECK_EQUAL( minimal.sheets[0].pageSize.x, 34000 );
1389 BOOST_CHECK_EQUAL( minimal.sheets[0].defaultLineWidth, 20 );
1390 BOOST_REQUIRE_EQUAL( minimal.sheets[0].titleBlockFields.size(), 14 );
1391 BOOST_CHECK_EQUAL( minimal.sheets[0].titleBlockFields.front().name.text, wxS( "Drawn By" ) );
1392 BOOST_CHECK_EQUAL( minimal.sheets[0].titleBlockFields.back().name.text, wxS( "Scale" ) );
1393
1394 const std::array<wxString, 14> titleNames = { wxS( "Drawn By" ), wxS( "Checked By" ), wxS( "QC By" ),
1395 wxS( "Released By" ), wxS( "Drawn Date" ), wxS( "Checked Date" ),
1396 wxS( "QC Date" ), wxS( "Release Date" ), wxS( "Company Name" ),
1397 wxS( "Title" ), wxS( "Code" ), wxS( "Drawing Number" ),
1398 wxS( "Revision" ), wxS( "Scale" ) };
1399 const std::array<size_t, 14> titleOffsets = { 0x254, 0x264, 0x276, 0x283, 0x296, 0x2A8, 0x2BC,
1400 0x2CB, 0x2DF, 0x2F3, 0x300, 0x30C, 0x322, 0x332 };
1401 const std::array<size_t, 14> titleLengths = { 16, 18, 13, 19, 18, 20, 15, 20, 20, 13, 12, 22, 16, 13 };
1402
1403 for( size_t i = 0; i < titleNames.size(); ++i )
1404 {
1405 const MODEL_FIELD& field = minimal.sheets[0].titleBlockFields[i];
1406 const std::string expectedName = titleNames[i].ToStdString();
1407 BOOST_CHECK_EQUAL( field.name.text, titleNames[i] );
1408 BOOST_CHECK_EQUAL( field.value.text, wxString() );
1409 BOOST_CHECK_EQUAL_COLLECTIONS( field.name.raw.begin(), field.name.raw.end(), expectedName.begin(),
1410 expectedName.end() );
1411 BOOST_CHECK( field.value.raw.empty() );
1412 BOOST_CHECK_EQUAL( field.source.absoluteOffset, titleOffsets[i] );
1413 BOOST_CHECK_EQUAL( field.source.length, titleLengths[i] );
1415 BOOST_CHECK_EQUAL( field.source.version, 0x000D );
1416 BOOST_CHECK_EQUAL( field.name.source.absoluteOffset, titleOffsets[i] + 6 );
1417 BOOST_CHECK_EQUAL( field.name.source.length, expectedName.size() );
1419 BOOST_CHECK_EQUAL( field.name.source.version, 0x000D );
1420 BOOST_CHECK_EQUAL( field.value.source.absoluteOffset, titleOffsets[i] + titleLengths[i] - 1 );
1423 BOOST_CHECK_EQUAL( field.value.source.version, 0x000D );
1424 }
1425
1426 BOOST_CHECK_EQUAL( minimal.sheets[0].title.text, wxString() );
1427 PADS_SCH::PADS_SCH_PARSER minimalAscii;
1428 BOOST_REQUIRE( minimalAscii.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/minimal_v13.txt" ) );
1429 BOOST_REQUIRE_EQUAL( minimalAscii.GetParameters().fields.size(), titleNames.size() );
1430
1431 for( const MODEL_FIELD& field : minimal.sheets[0].titleBlockFields )
1432 {
1433 auto asciiField = minimalAscii.GetParameters().fields.find( field.name.text.ToStdString() );
1434 BOOST_REQUIRE( asciiField != minimalAscii.GetParameters().fields.end() );
1435 BOOST_CHECK_EQUAL( asciiField->second, field.value.text.ToStdString() );
1436 }
1437
1438 PADS_SCH_MODEL multisheet =
1439 parser.Parse( loadBinaryFixture( "multisheet_connectivity.sch" ), wxS( "multisheet_connectivity.sch" ) );
1440 BOOST_REQUIRE_EQUAL( multisheet.sheets.size(), 2 );
1441 BOOST_CHECK_EQUAL( multisheet.sheets[0].id.Value(), 1 );
1442 BOOST_CHECK_EQUAL( multisheet.sheets[1].id.Value(), 2 );
1443 BOOST_CHECK_EQUAL( multisheet.sheets[0].name.text, wxS( "[1]DUP/SAFE:*" ) );
1444 BOOST_CHECK_EQUAL( multisheet.sheets[1].name.text, wxS( "[2]DUP/SAFE:*" ) );
1445 BOOST_CHECK_LT( multisheet.sheets[0].source.absoluteOffset, multisheet.sheets[1].source.absoluteOffset );
1446 BOOST_CHECK( !multisheet.sheets[0].parent );
1447 BOOST_CHECK( !multisheet.sheets[1].parent );
1448
1449 for( const MODEL_SHEET& sheet : multisheet.sheets )
1450 {
1451 BOOST_REQUIRE_EQUAL( sheet.titleBlockFields.size(), 14 );
1452
1453 for( const MODEL_FIELD& field : sheet.titleBlockFields )
1454 {
1455 BOOST_CHECK_EQUAL( field.source.sheet, -1 );
1456 BOOST_CHECK_EQUAL( field.name.source.sheet, -1 );
1457 BOOST_CHECK_EQUAL( field.value.source.sheet, -1 );
1459 }
1460
1461 BOOST_CHECK_EQUAL( sheet.title.source.sheet, -1 );
1462 auto title = std::ranges::find_if( sheet.titleBlockFields,
1463 []( const MODEL_FIELD& aField )
1464 {
1465 return aField.name.text == wxS( "Title" );
1466 } );
1467 BOOST_REQUIRE( title != sheet.titleBlockFields.end() );
1468 BOOST_CHECK( sheet.title == title->value );
1469 }
1470}
1471
1472
1473BOOST_AUTO_TEST_CASE( VariableTitleFields )
1474{
1475 std::vector<uint8_t> bytes = loadBinaryFixture( "minimal_v13.sch" );
1476 const size_t poolOffset = outerControllerOffset( bytes, 1 );
1477 const size_t poolBytes = readU32( bytes, 0x20 + 1 * 28 + 12 );
1478 const std::array<std::pair<std::string, std::string>, 5> expected = {
1479 std::pair{ "Drawn By", "Alice" }, std::pair{ "Checked By", "Bob" }, std::pair{ "Title", "Variable Title" },
1480 std::pair{ "Revision", "C" }, std::pair{ "Scale", "2:1" }
1481 };
1482 std::vector<uint8_t> pool;
1483 std::array<size_t, expected.size()> offsets;
1484 std::array<size_t, expected.size()> lengths;
1485
1486 for( size_t i = 0; i < expected.size(); ++i )
1487 {
1488 offsets[i] = poolOffset + pool.size();
1489 const std::string slot = "Field\n" + expected[i].first + '\x01' + expected[i].second + '\0';
1490 lengths[i] = slot.size();
1491 pool.insert( pool.end(), slot.begin(), slot.end() );
1492 }
1493
1494 const size_t firstNonFieldOffset = poolOffset + pool.size();
1495 const std::string firstNonField( "Font Default\0", 13 );
1496 pool.insert( pool.end(), firstNonField.begin(), firstNonField.end() );
1497 BOOST_REQUIRE_LE( pool.size(), poolBytes );
1498 pool.resize( poolBytes, 0 );
1499 std::copy( pool.begin(), pool.end(), bytes.begin() + poolOffset );
1500
1502 PADS_SCH_MODEL model = parser.Parse( bytes, wxS( "variable-title.sch" ) );
1503 BOOST_REQUIRE_EQUAL( model.sheets.size(), 1 );
1504 BOOST_REQUIRE_EQUAL( model.sheets[0].titleBlockFields.size(), expected.size() );
1505
1506 for( size_t i = 0; i < expected.size(); ++i )
1507 {
1508 const MODEL_FIELD& field = model.sheets[0].titleBlockFields[i];
1509 BOOST_CHECK_EQUAL( field.name.text, wxString::FromUTF8( expected[i].first ) );
1510 BOOST_CHECK_EQUAL( field.value.text, wxString::FromUTF8( expected[i].second ) );
1511 BOOST_CHECK_EQUAL_COLLECTIONS( field.name.raw.begin(), field.name.raw.end(), expected[i].first.begin(),
1512 expected[i].first.end() );
1513 BOOST_CHECK_EQUAL_COLLECTIONS( field.value.raw.begin(), field.value.raw.end(), expected[i].second.begin(),
1514 expected[i].second.end() );
1515 BOOST_CHECK_EQUAL( field.source.absoluteOffset, offsets[i] );
1516 BOOST_CHECK_EQUAL( field.source.length, lengths[i] );
1519 BOOST_CHECK_EQUAL( field.source.version, 0x000D );
1520 BOOST_CHECK_EQUAL( field.source.sheet, -1 );
1521 BOOST_CHECK_EQUAL( field.source.objectClass, wxS( "title field" ) );
1522 BOOST_CHECK_EQUAL( field.name.source.absoluteOffset, offsets[i] + 6 );
1523 BOOST_CHECK_EQUAL( field.name.source.length, expected[i].first.size() );
1525 BOOST_CHECK_EQUAL( field.name.source.version, 0x000D );
1526 BOOST_CHECK_EQUAL( field.name.source.sheet, -1 );
1527 BOOST_CHECK_EQUAL( field.value.source.absoluteOffset, offsets[i] + 6 + expected[i].first.size() + 1 );
1528 BOOST_CHECK_EQUAL( field.value.source.length, expected[i].second.size() );
1530 BOOST_CHECK_EQUAL( field.value.source.version, 0x000D );
1531 BOOST_CHECK_EQUAL( field.value.source.sheet, -1 );
1532 }
1533
1534 BOOST_CHECK_EQUAL( model.sheets[0].title.text, wxS( "Variable Title" ) );
1535 BOOST_CHECK_EQUAL_COLLECTIONS( bytes.begin() + firstNonFieldOffset,
1536 bytes.begin() + firstNonFieldOffset + firstNonField.size(), firstNonField.begin(),
1537 firstNonField.end() );
1538 BOOST_CHECK_EQUAL( model.sheets[0].titleBlockFields.back().source.absoluteOffset
1539 + model.sheets[0].titleBlockFields.back().source.length,
1540 firstNonFieldOffset );
1541}
1542
1543
1545{
1547 PADS_SCH_MODEL model = parser.Parse( loadBinaryFixture( "text_encoding.sch" ), wxS( "text_encoding.sch" ) );
1548
1549 BOOST_REQUIRE_EQUAL( model.texts.size(), 1 );
1550 const MODEL_TEXT& text = model.texts.front();
1551 BOOST_CHECK_EQUAL( text.sheet.id.Value(), 1 );
1552 BOOST_CHECK_EQUAL( text.text.text, wxS( "ascii-text cafe=" ) );
1553 BOOST_CHECK_EQUAL( text.text.raw.size(), 16 );
1554 BOOST_CHECK( text.text.encoding == STRING_ENCODING_STATUS::CODE_PAGE );
1555 BOOST_CHECK_EQUAL( text.text.codePage, 1252 );
1556 BOOST_CHECK_EQUAL( text.position.x, 20200 );
1557 BOOST_CHECK_EQUAL( text.position.y, 14000 );
1558 BOOST_CHECK_EQUAL( text.angle, 0 );
1559 BOOST_CHECK_EQUAL( text.presentation.height, 97 );
1560 BOOST_CHECK_EQUAL( text.presentation.width, 10 );
1561 BOOST_CHECK( text.presentation.horizontalJustification == MODEL_JUSTIFICATION::LEFT );
1562 BOOST_CHECK( text.presentation.verticalJustification == MODEL_JUSTIFICATION::RIGHT );
1563 BOOST_CHECK( !text.presentation.bold );
1564 BOOST_CHECK( !text.presentation.italic );
1565 BOOST_CHECK( !text.presentation.underline );
1566 BOOST_CHECK( text.presentation.visible );
1567 BOOST_REQUIRE_EQUAL( text.properties.size(), 1 );
1568 BOOST_CHECK_EQUAL( text.properties[0].name.text, wxS( "controller_1_relationship_word_28" ) );
1569 BOOST_CHECK_EQUAL( text.properties[0].value.text, wxS( "16" ) );
1570 BOOST_CHECK_EQUAL( text.properties[0].value.raw.size(), 2 );
1571 BOOST_CHECK_EQUAL( text.properties[0].source.absoluteOffset, text.source.absoluteOffset + 28 );
1572 BOOST_CHECK_EQUAL( text.properties[0].source.length, 2 );
1573 BOOST_CHECK( text.properties[0].disposition == PROPERTY_DISPOSITION::PRESERVED );
1574 BOOST_CHECK_EQUAL( text.source.objectClass, wxS( "free text" ) );
1575 BOOST_CHECK_EQUAL( text.source.controller, 1 );
1576 BOOST_CHECK_EQUAL( text.text.source.controller, 2 );
1577 BOOST_CHECK_EQUAL( text.source.sheet, 0 );
1578
1579 std::vector<uint8_t> cp1252Bytes = loadBinaryFixture( "text_encoding.sch" );
1580 cp1252Bytes[text.text.source.absoluteOffset + text.text.source.length - 1] = 0xE9;
1581 PADS_SCH_MODEL cp1252 = parser.Parse( cp1252Bytes, wxS( "text_encoding.sch" ) );
1582 BOOST_REQUIRE_EQUAL( cp1252.texts.size(), 1 );
1583 BOOST_CHECK_EQUAL( cp1252.texts[0].text.raw.back(), 0xE9 );
1584 BOOST_CHECK_EQUAL( cp1252.texts[0].text.text.Last().GetValue(), 0x00E9 );
1585 BOOST_CHECK( cp1252.texts[0].text.encoding == STRING_ENCODING_STATUS::CODE_PAGE );
1586 BOOST_CHECK_EQUAL( cp1252.texts[0].text.codePage, 1252 );
1587 BOOST_CHECK_EQUAL( cp1252.texts[0].text.source.absoluteOffset, text.text.source.absoluteOffset );
1588 BOOST_CHECK_EQUAL( cp1252.texts[0].text.source.length, text.text.source.length );
1589 BOOST_CHECK_EQUAL( cp1252.texts[0].text.source.controller, 2 );
1590 BOOST_CHECK_EQUAL( cp1252.texts[0].text.source.version, 0x000D );
1591
1592 std::vector<uint8_t> invalidOffset = loadBinaryFixture( "text_encoding.sch" );
1593 writeU32( invalidOffset, text.source.absoluteOffset + 8, std::numeric_limits<uint32_t>::max() );
1594 BOOST_CHECK_EXCEPTION( parser.Parse( invalidOffset, wxS( "invalid-offset.sch" ) ), IO_ERROR,
1595 [&]( const IO_ERROR& aError )
1596 {
1597 return aError.What().Contains( wxString::Format( wxS( "offset 0x%zX" ),
1598 text.source.absoluteOffset + 8 ) )
1599 && aError.What().Contains( wxS( "string offset leaves controller 2" ) );
1600 } );
1601
1602 std::vector<uint8_t> invalidLength = loadBinaryFixture( "text_encoding.sch" );
1603 writeU16( invalidLength, text.source.absoluteOffset + 20, std::numeric_limits<uint16_t>::max() );
1604 BOOST_CHECK_EXCEPTION( parser.Parse( invalidLength, wxS( "invalid-length.sch" ) ), IO_ERROR,
1605 [&]( const IO_ERROR& aError )
1606 {
1607 return aError.What().Contains( wxString::Format( wxS( "offset 0x%zX" ),
1608 text.source.absoluteOffset + 20 ) )
1609 && aError.What().Contains( wxS( "string length leaves controller 2" ) );
1610 } );
1611
1612 std::vector<uint8_t> missingNul = loadBinaryFixture( "text_encoding.sch" );
1613 const size_t terminatorOffset = text.text.source.absoluteOffset + text.text.source.length;
1614 missingNul[terminatorOffset] = 'X';
1615 BOOST_CHECK_EXCEPTION( parser.Parse( missingNul, wxS( "missing-nul.sch" ) ), IO_ERROR,
1616 [&]( const IO_ERROR& aError )
1617 {
1618 return aError.What().Contains(
1619 wxString::Format( wxS( "offset 0x%zX" ), terminatorOffset ) )
1620 && aError.What().Contains( wxS( "not NUL terminated" ) )
1621 && aError.What().Contains( wxS( "controller 2" ) );
1622 } );
1623
1625 BOOST_REQUIRE( ascii.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/text_encoding.txt" ) );
1626 std::vector<CANONICAL_SEMANTIC_RECORD> expected = normalizeAsciiModel( ascii );
1627 std::vector<CANONICAL_SEMANTIC_RECORD> actual = normalizeBinaryModel( model );
1628 auto retainTask7Properties = []( std::vector<CANONICAL_SEMANTIC_RECORD>& aRecords )
1629 {
1630 std::erase_if( aRecords,
1631 []( const CANONICAL_SEMANTIC_RECORD& aRecord )
1632 {
1633 return aRecord.kind != CANONICAL_KIND::SETTINGS && aRecord.kind != CANONICAL_KIND::SHEET
1634 && ( aRecord.kind != CANONICAL_KIND::TEXT
1635 || aRecord.stableKey != "ascii-text cafe=" );
1636 } );
1637
1638 for( CANONICAL_SEMANTIC_RECORD& record : aRecords )
1639 {
1640 if( record.kind != CANONICAL_KIND::TEXT )
1641 continue;
1642
1643 std::erase_if( record.properties,
1644 []( const auto& aProperty )
1645 {
1646 return aProperty.first != "owner_sheet" && aProperty.first != "visible"
1647 && aProperty.first != "bold" && aProperty.first != "italic"
1648 && aProperty.first != "underline";
1649 } );
1650 }
1651 };
1652 retainTask7Properties( expected );
1653 retainTask7Properties( actual );
1654 BOOST_CHECK( snapshotsMatch( expected, actual ) );
1655
1656 std::vector<uint8_t> changedRelationship = loadBinaryFixture( "text_encoding.sch" );
1657 writeU16( changedRelationship, text.source.absoluteOffset + 28, 0xBEEF );
1658 PADS_SCH_MODEL changed = parser.Parse( changedRelationship, wxS( "text_encoding.sch" ) );
1659 BOOST_REQUIRE_EQUAL( changed.texts.size(), 1 );
1660 BOOST_CHECK_EQUAL( changed.texts[0].properties[0].value.text, wxS( "48879" ) );
1661 BOOST_CHECK( changed.texts[0].properties[0].disposition == PROPERTY_DISPOSITION::PRESERVED );
1662 BOOST_CHECK_EQUAL( changed.texts[0].presentation.bold, text.presentation.bold );
1663 BOOST_CHECK_EQUAL( changed.texts[0].presentation.italic, text.presentation.italic );
1664 BOOST_CHECK_EQUAL( changed.texts[0].presentation.underline, text.presentation.underline );
1665 BOOST_CHECK_EQUAL( changed.texts[0].presentation.visible, text.presentation.visible );
1666 BOOST_CHECK_EQUAL( changed.diagnostics.size(), model.diagnostics.size() );
1667
1668 struct JUSTIFICATION_CASE
1669 {
1670 uint16_t raw;
1671 MODEL_JUSTIFICATION horizontal;
1672 MODEL_JUSTIFICATION vertical;
1673 };
1674
1675 for( const JUSTIFICATION_CASE& expected :
1676 { JUSTIFICATION_CASE{ 5, MODEL_JUSTIFICATION::RIGHT, MODEL_JUSTIFICATION::LEFT },
1683 {
1684 std::vector<uint8_t> changedJustification = loadBinaryFixture( "text_encoding.sch" );
1685 writeU16( changedJustification, text.source.absoluteOffset + 18, expected.raw );
1686 PADS_SCH_MODEL justified = parser.Parse( changedJustification, wxS( "text_encoding.sch" ) );
1687 BOOST_REQUIRE_EQUAL( justified.texts.size(), 1 );
1688 BOOST_CHECK( justified.texts[0].presentation.horizontalJustification == expected.horizontal );
1689 BOOST_CHECK( justified.texts[0].presentation.verticalJustification == expected.vertical );
1690 BOOST_CHECK_EQUAL( justified.diagnostics.size(), model.diagnostics.size() );
1691 }
1692
1693 struct FONT_CASE
1694 {
1695 uint32_t style;
1696 bool bold;
1697 bool italic;
1698 };
1699
1700 for( const FONT_CASE& expected : { FONT_CASE{ 0, false, false }, FONT_CASE{ 1, false, true },
1701 FONT_CASE{ 2, true, false }, FONT_CASE{ 3, true, true } } )
1702 {
1703 std::vector<uint8_t> changedFont = loadBinaryFixture( "text_encoding.sch" );
1704 writeU16( changedFont, text.source.absoluteOffset, 0 );
1705 writeU32( changedFont, outerControllerOffset( changedFont, 19 ), expected.style );
1706 PADS_SCH_MODEL styled = parser.Parse( changedFont, wxS( "text_encoding.sch" ) );
1707 BOOST_REQUIRE_EQUAL( styled.texts.size(), 1 );
1708 BOOST_CHECK_EQUAL( styled.texts[0].presentation.bold, expected.bold );
1709 BOOST_CHECK_EQUAL( styled.texts[0].presentation.italic, expected.italic );
1710 BOOST_CHECK_EQUAL( styled.texts[0].presentation.width, 10 );
1711 }
1712
1713 std::vector<uint8_t> hiddenText = loadBinaryFixture( "text_encoding.sch" );
1714 hiddenText[text.source.absoluteOffset + 31] = 1;
1715 PADS_SCH_MODEL hidden = parser.Parse( hiddenText, wxS( "text_encoding.sch" ) );
1716 BOOST_REQUIRE_EQUAL( hidden.texts.size(), 1 );
1717 BOOST_CHECK( !hidden.texts[0].presentation.visible );
1718 BOOST_CHECK_EQUAL( hidden.texts[0].presentation.width, 10 );
1719}
1720
1721
1722BOOST_AUTO_TEST_CASE( TextOptionMatrix )
1723{
1725 PADS_SCH_MODEL model = parser.Parse( loadBinaryFixture( "text_options.sch" ), wxS( "text_options.sch" ) );
1726
1727 BOOST_REQUIRE_EQUAL( model.texts.size(), 26 );
1728
1729 auto textByName = [&]( const wxString& aName ) -> const MODEL_TEXT&
1730 {
1731 auto found = std::ranges::find_if( model.texts,
1732 [&]( const MODEL_TEXT& aText )
1733 {
1734 return aText.text.text == aName;
1735 } );
1736 BOOST_REQUIRE( found != model.texts.end() );
1737 return *found;
1738 };
1739
1740 const std::array<MODEL_JUSTIFICATION, 16> horizontal = { MODEL_JUSTIFICATION::LEFT, MODEL_JUSTIFICATION::RIGHT,
1748
1749 for( size_t ii = 0; ii < horizontal.size(); ++ii )
1750 {
1751 // The vertical half of the code splits at 2 and 8, the way PADS_COMMON::DecodeJustification
1752 // splits it for the ASCII importer
1753 const MODEL_JUSTIFICATION vertical = ii >= 8 ? MODEL_JUSTIFICATION::CENTER
1754 : ii >= 2 ? MODEL_JUSTIFICATION::LEFT
1756 const MODEL_TEXT& text = textByName( wxString::Format( wxS( "JUST_%02zu" ), ii ) );
1757 BOOST_CHECK( text.presentation.horizontalJustification == horizontal[ii] );
1758 BOOST_CHECK( text.presentation.verticalJustification == vertical );
1759 }
1760
1761 for( const auto& [name, angle] : { std::pair{ wxS( "ANGLE_000" ), 0 }, std::pair{ wxS( "ANGLE_090" ), 900 },
1762 std::pair{ wxS( "ANGLE_180" ), 1800 }, std::pair{ wxS( "ANGLE_270" ), 2700 } } )
1763 {
1764 BOOST_CHECK_EQUAL( textByName( name ).angle, angle );
1765 }
1766
1767 BOOST_CHECK( !textByName( wxS( "STYLE_REGULAR" ) ).presentation.bold );
1768 BOOST_CHECK( !textByName( wxS( "STYLE_REGULAR" ) ).presentation.italic );
1769 BOOST_CHECK( textByName( wxS( "STYLE_BOLD" ) ).presentation.bold );
1770 BOOST_CHECK( textByName( wxS( "STYLE_ITALIC" ) ).presentation.italic );
1771 BOOST_CHECK( textByName( wxS( "STYLE_BOLD_ITALIC" ) ).presentation.bold );
1772 BOOST_CHECK( !textByName( wxS( "STYLE_BOLD_ITALIC" ) ).presentation.italic );
1773
1774 BOOST_CHECK( textByName( wxS( "VISIBLE_0" ) ).presentation.visible );
1775 BOOST_CHECK( !textByName( wxS( "VISIBLE_1" ) ).presentation.visible );
1776 BOOST_CHECK_EQUAL( textByName( wxS( "VISIBLE_0" ) ).presentation.width, 10 );
1777 BOOST_CHECK_EQUAL( textByName( wxS( "VISIBLE_1" ) ).presentation.width, 10 );
1778}
1779
1780
1781BOOST_AUTO_TEST_CASE( VisibilityAndUnderlineOptionMatrix )
1782{
1783 PADS_SCH_MODEL model = PADS_SCH_BINARY_PARSER().Parse( loadBinaryFixture( "visibility_options.sch" ),
1784 wxS( "visibility_options.sch" ) );
1785
1786 auto underlined = std::ranges::find_if( model.texts,
1787 []( const MODEL_TEXT& aText )
1788 {
1789 return aText.text.text == wxS( "STYLE_UNDERLINE" );
1790 } );
1791 BOOST_REQUIRE( underlined != model.texts.end() );
1792 BOOST_CHECK( underlined->presentation.underline );
1793 BOOST_CHECK( !underlined->presentation.bold );
1794 BOOST_CHECK( !underlined->presentation.italic );
1795 BOOST_CHECK_EQUAL( underlined->presentation.font.text, wxS( "Verdana" ) );
1796
1797 BOOST_REQUIRE_EQUAL( model.placements.size(), 32 );
1798
1799 for( uint8_t flags = 0; flags < 32; ++flags )
1800 {
1801 const wxString reference = wxString::Format( wxS( "R%u" ), flags + 1 );
1802 auto placement = std::ranges::find( model.placements, reference,
1803 []( const MODEL_PLACEMENT& aPlacement )
1804 {
1805 return aPlacement.reference.text;
1806 } );
1807 BOOST_REQUIRE_MESSAGE( placement != model.placements.end(), reference );
1808 BOOST_CHECK_EQUAL( placement->itemVisibilityFlags, flags );
1809 BOOST_CHECK_EQUAL( placement->referenceVisible, ( flags & 0x01 ) == 0 );
1810 BOOST_CHECK_EQUAL( placement->partTypeVisible, ( flags & 0x02 ) == 0 );
1811 BOOST_CHECK_EQUAL( placement->pinNamesVisible, ( flags & 0x08 ) == 0 );
1812 BOOST_CHECK_EQUAL( placement->pinNumbersVisible, ( flags & 0x10 ) == 0 );
1813 }
1814}
1815
1816
1817BOOST_AUTO_TEST_CASE( TextEncodingWarnings )
1818{
1819 SOURCE_PROVENANCE source{ wxS( "text_encoding.sch" ), 0x000D, wxS( "free text" ), 2, 0, 0x6250, 2, 0 };
1820 std::vector<PARSER_DIAGNOSTIC> diagnostics;
1821 SOURCE_STRING unknown = PADS_SCH_BINARY_PARSER::DecodeString( { 0x41, 0xE9 }, 932, source, diagnostics );
1822 SOURCE_STRING invalid = PADS_SCH_BINARY_PARSER::DecodeString( { 0xC3, 0x28 }, 65001, source, diagnostics );
1823
1824 BOOST_CHECK( unknown.encoding == STRING_ENCODING_STATUS::UNKNOWN_CODE_PAGE );
1825 BOOST_CHECK_EQUAL( unknown.raw[1], 0xE9 );
1826 BOOST_CHECK_EQUAL( unknown.text[1].GetValue(), 0xFFFD );
1827 BOOST_CHECK( invalid.encoding == STRING_ENCODING_STATUS::INVALID_BYTES );
1828 BOOST_REQUIRE_EQUAL( diagnostics.size(), 2 );
1829 BOOST_CHECK_EQUAL( diagnostics[0].source.absoluteOffset, 0x6250 );
1830 BOOST_CHECK( diagnostics[0].message.Contains( wxS( "code page 932" ) ) );
1831 BOOST_CHECK( diagnostics[1].message.Contains( wxS( "invalid UTF-8" ) ) );
1832}
1833
1834
1835BOOST_AUTO_TEST_CASE( GlobalRecordCorruption )
1836{
1838 std::vector<uint8_t> reordered = loadBinaryFixture( "multisheet_connectivity.sch" );
1839 size_t sheetIndex = outerControllerOffset( reordered, 3 );
1840 writeU16( reordered, sheetIndex + 8, 2 );
1841 writeU16( reordered, sheetIndex + 48 + 8, 1 );
1842 PADS_SCH_MODEL reorderedModel = parser.Parse( reordered, wxS( "reordered.sch" ) );
1843 BOOST_REQUIRE_EQUAL( reorderedModel.sheets.size(), 2u );
1844 BOOST_CHECK_EQUAL( reorderedModel.sheets[0].id.Value(), 2u );
1845 BOOST_CHECK_EQUAL( reorderedModel.sheets[0].index, 1u );
1846 BOOST_CHECK_EQUAL( reorderedModel.sheets[0].source.recordIndex, 0u );
1847 BOOST_CHECK_EQUAL( reorderedModel.sheets[1].id.Value(), 1u );
1848 BOOST_CHECK_EQUAL( reorderedModel.sheets[1].index, 0u );
1849 BOOST_CHECK_EQUAL( reorderedModel.sheets[1].source.recordIndex, 1u );
1850
1851 std::vector<uint8_t> duplicate = loadBinaryFixture( "multisheet_connectivity.sch" );
1852 sheetIndex = outerControllerOffset( duplicate, 3 );
1853 writeU16( duplicate, sheetIndex + 48 + 8, 1 );
1854 BOOST_CHECK_EXCEPTION( parser.Parse( duplicate, wxS( "duplicate.sch" ) ), IO_ERROR,
1855 []( const IO_ERROR& aError )
1856 {
1857 return aError.What().Contains( wxS( "duplicate sheet ID 1" ) )
1858 && aError.What().Contains( wxS( "controller 3" ) );
1859 } );
1860
1861 std::vector<uint8_t> wrongClassBytes = loadMinimalV13();
1862 sheetIndex = outerControllerOffset( wrongClassBytes, 3 );
1863 writeU32( wrongClassBytes, sheetIndex, static_cast<uint32_t>( outerControllerOffset( wrongClassBytes, 5 ) ) );
1864 BOOST_CHECK_EXCEPTION( parser.Parse( wrongClassBytes, wxS( "wrong-class.sch" ) ), IO_ERROR,
1865 []( const IO_ERROR& aError )
1866 {
1867 return aError.What().Contains( wxS( "references the wrong SDB object class" ) )
1868 && aError.What().Contains( wxS( "controller 3" ) );
1869 } );
1870
1871 PADS_SCH_MODEL wrongClass = parser.Parse( loadMinimalV13(), wxS( "wrong-class.sch" ) );
1872 SOURCE_PROVENANCE refSource{ wxS( "wrong-class.sch" ), 0x000D, wxS( "free text" ), 1, 0, 0x100, 32, 0 };
1873 wrongClass.texts.push_back( { refSource, { SHEET_ID( 99 ), refSource } } );
1874 BOOST_CHECK_EXCEPTION( wrongClass.ValidateOrThrow(), IO_ERROR,
1875 []( const IO_ERROR& aError )
1876 {
1877 return aError.What().Contains( wxS( "unresolved text sheet reference" ) );
1878 } );
1879
1880 PADS_SCH_MODEL cycle = parser.Parse( loadMinimalV13(), wxS( "cycle.sch" ) );
1881 cycle.sheets[0].parent = SHEET_REFERENCE{ cycle.sheets[0].id, cycle.sheets[0].source };
1882 BOOST_CHECK_EXCEPTION( cycle.ValidateOrThrow(), IO_ERROR,
1883 []( const IO_ERROR& aError )
1884 {
1885 return aError.What().Contains( wxS( "cyclic sheet hierarchy" ) );
1886 } );
1887
1888 PADS_SCH_MODEL longHierarchy;
1889 longHierarchy.version = 0x000D;
1890 constexpr size_t hierarchySize = 4096;
1891 longHierarchy.sheets.reserve( hierarchySize );
1892
1893 for( size_t i = 0; i < hierarchySize; ++i )
1894 {
1895 SOURCE_PROVENANCE source{ wxS( "long-hierarchy.sch" ), 0x000D, wxS( "sheet" ), 3, i, 0x100 + i * 48, 48,
1896 static_cast<int>( i ) };
1897 MODEL_SHEET sheet;
1898 sheet.id = SHEET_ID( i + 1 );
1899 sheet.index = i;
1900 sheet.source = source;
1901
1902 if( i > 0 )
1903 sheet.parent = SHEET_REFERENCE{ SHEET_ID( i ), source };
1904
1905 longHierarchy.sheets.push_back( std::move( sheet ) );
1906 }
1907
1908 BOOST_CHECK_NO_THROW( longHierarchy.ValidateOrThrow() );
1909 longHierarchy.sheets[0].parent = SHEET_REFERENCE{ longHierarchy.sheets.back().id, longHierarchy.sheets[0].source };
1910 BOOST_CHECK_EXCEPTION( longHierarchy.ValidateOrThrow(), IO_ERROR,
1911 []( const IO_ERROR& aError )
1912 {
1913 return aError.What().Contains( wxS( "cyclic sheet hierarchy" ) )
1914 && aError.What().Contains( wxS( "controller 3" ) );
1915 } );
1916}
1917
1918
1919BOOST_AUTO_TEST_CASE( ModelContract )
1920{
1921 static_assert( !std::is_same_v<SHEET_ID, PLACEMENT_ID> );
1922 static_assert( !std::is_convertible_v<SHEET_ID, PLACEMENT_ID> );
1923
1924 const std::vector<uint8_t> bytes = loadMinimalV13();
1926 PADS_SCH_MODEL first = parser.Parse( bytes, wxS( "minimal_v13.sch" ) );
1927 PADS_SCH_MODEL second = parser.Parse( bytes, wxS( "minimal_v13.sch" ) );
1928
1929 BOOST_CHECK_EQUAL( first.version, 0x000D );
1930 BOOST_REQUIRE_EQUAL( first.sheets.size(), 1 );
1931 BOOST_CHECK_EQUAL( first.sheets[0].index, 0 );
1932 BOOST_CHECK_EQUAL( first.sheets[0].id.Value(), 1 );
1933 BOOST_CHECK_EQUAL( first.sheets[0].source.file, wxS( "minimal_v13.sch" ) );
1934 BOOST_CHECK_GT( first.sheets[0].source.length, 0 );
1935 BOOST_CHECK_EQUAL( first.sheets[0].source.version, 0x000D );
1936 BOOST_CHECK_EQUAL( first.sheets[0].source.objectClass, wxS( "sheet" ) );
1937 BOOST_CHECK_EQUAL( first.sheets[0].source.recordIndex, 0 );
1938 BOOST_CHECK_EQUAL( first.sheets[0].source.absoluteOffset, second.sheets[0].source.absoluteOffset );
1939 BOOST_CHECK_EQUAL( first.sheets[0].source.length, second.sheets[0].source.length );
1940 BOOST_CHECK_EQUAL( first.sheets[0].id.Value(), second.sheets[0].id.Value() );
1941 BOOST_CHECK_EQUAL( canonicalModel( first ), canonicalModel( second ) );
1942 BOOST_CHECK( first == second );
1943
1944 BOOST_CHECK_EQUAL( NormalizeAngle( -900 ), 2700 );
1945 BOOST_CHECK_EQUAL( NormalizeAngle( 4500 ), 900 );
1946 BOOST_CHECK_EQUAL( NormalizeCoordinate( -1234 ), -1234 );
1947
1948 SOURCE_STRING sourceString{
1949 { 0x41, 0xC3, 0xA9 }, wxString::FromUTF8( "A\xC3\xA9" ), STRING_ENCODING_STATUS::UTF8, first.sheets[0].source
1950 };
1951 BOOST_CHECK_EQUAL( sourceString.raw.size(), 3 );
1952 BOOST_CHECK_EQUAL( sourceString.text, wxString::FromUTF8( "A\xC3\xA9" ) );
1953 BOOST_CHECK( sourceString.encoding == STRING_ENCODING_STATUS::UTF8 );
1954
1955 first.diagnostics.push_back(
1956 { RPT_SEVERITY_WARNING, first.sheets[0].source, wxS( "unknown but structurally valid enum" ) } );
1957 BOOST_CHECK_EQUAL( first.diagnostics.back().source.absoluteOffset, first.sheets[0].source.absoluteOffset );
1958
1959 PADS_SCH_MODEL unresolved = second;
1960 unresolved.sheets[0].parent = SHEET_REFERENCE{ SHEET_ID( 99 ), unresolved.sheets[0].source };
1961 BOOST_CHECK_EXCEPTION( unresolved.ValidateOrThrow(), IO_ERROR,
1962 []( const IO_ERROR& aError )
1963 {
1964 return aError.What().Contains( wxS( "unresolved" ) );
1965 } );
1966
1967 PADS_SCH_MODEL cyclic = second;
1968 cyclic.sheets[0].parent = SHEET_REFERENCE{ cyclic.sheets[0].id, cyclic.sheets[0].source };
1969 BOOST_CHECK_EXCEPTION( cyclic.ValidateOrThrow(), IO_ERROR,
1970 []( const IO_ERROR& aError )
1971 {
1972 return aError.What().Contains( wxS( "cyclic" ) )
1973 && aError.What().Contains( wxS( "offset" ) );
1974 } );
1975
1976 SOURCE_PROVENANCE textSource{ wxS( "strings.sch" ), 0x000D, wxS( "text" ), 4, 7, 0x1234, 2, 0 };
1977 std::vector<PARSER_DIAGNOSTIC> diagnostics;
1978 SOURCE_STRING unknown = PADS_SCH_BINARY_PARSER::DecodeString( { 0x41 }, 99999, textSource, diagnostics );
1979 SOURCE_STRING invalid = PADS_SCH_BINARY_PARSER::DecodeString( { 0xC3, 0x28 }, 65001, textSource, diagnostics );
1980 PADS_SCH_BINARY_PARSER::RecordUnknownEnum( wxS( "line style" ), 17, textSource, diagnostics );
1981 BOOST_CHECK( unknown.encoding == STRING_ENCODING_STATUS::UNKNOWN_CODE_PAGE );
1982 BOOST_CHECK( invalid.encoding == STRING_ENCODING_STATUS::INVALID_BYTES );
1983 BOOST_REQUIRE_EQUAL( diagnostics.size(), 3 );
1984 BOOST_CHECK_EQUAL( diagnostics[0].source.absoluteOffset, 0x1234 );
1985 BOOST_CHECK_EQUAL( diagnostics[1].source.recordIndex, 7 );
1986 BOOST_CHECK( diagnostics[2].message.Contains( wxS( "line style 17" ) ) );
1987}
1988
1989
1990BOOST_AUTO_TEST_CASE( ConnectionEndpointValidation )
1991{
1992 SOURCE_PROVENANCE source{ wxS( "model.sch" ), 0x000D, wxS( "connection" ), 8, 3, 0x200, 16, 0 };
1994 model.version = 0x000D;
1995 model.source = source;
1996 model.sheets.push_back( { SHEET_ID( 0 ), 0, source } );
1997 model.definitions.push_back( { DEFINITION_ID( 1 ), source } );
1998 model.definitions[0].pins.push_back( { PIN_ID( 2 ), source } );
1999 model.partTypes.push_back( { PART_TYPE_ID( 3 ), source } );
2000 model.partTypes[0].gates.push_back(
2001 { GATE_ID( 4 ), source, { DEFINITION_ID( 1 ), source }, 1, { { PIN_ID( 2 ), source } } } );
2002 model.placements.push_back( { PLACEMENT_ID( 5 ),
2003 source,
2004 { SHEET_ID( 0 ), source },
2005 { PART_TYPE_ID( 3 ), source },
2006 GATE_REFERENCE{ GATE_ID( 4 ), source },
2007 { DEFINITION_ID( 1 ), source },
2008 { { PIN_ID( 2 ), source } } } );
2009 model.nets.push_back( { NET_ID( 6 ), source, { SHEET_ID( 0 ), source } } );
2010 model.nets[0].connections.push_back( { source } );
2011 model.nets[0].connections[0].endpoints.push_back( { MODEL_ENDPOINT_KIND::PIN, source,
2012 PLACEMENT_REFERENCE{ PLACEMENT_ID( 5 ), source },
2013 PIN_REFERENCE{ PIN_ID( 2 ), source } } );
2014 BOOST_CHECK_NO_THROW( model.ValidateOrThrow() );
2015
2016 PADS_SCH_MODEL wrongPin = model;
2017 wrongPin.nets[0].connections[0].endpoints[0].pin = PIN_REFERENCE{ PIN_ID( 99 ), source };
2018 BOOST_CHECK_THROW( wrongPin.ValidateOrThrow(), IO_ERROR );
2019
2020 PADS_SCH_MODEL wrongUnit = model;
2021 wrongUnit.placements[0].unit = 2;
2022 BOOST_CHECK_THROW( wrongUnit.ValidateOrThrow(), IO_ERROR );
2023
2025 empty.nets[0].connections[0].endpoints[0] = { MODEL_ENDPOINT_KIND::INVALID, source };
2026 BOOST_CHECK_THROW( empty.ValidateOrThrow(), IO_ERROR );
2027
2028 PADS_SCH_MODEL mixed = model;
2029 mixed.nets[0].connections[0].endpoints[0].kind = MODEL_ENDPOINT_KIND::POINT;
2030 BOOST_CHECK_THROW( mixed.ValidateOrThrow(), IO_ERROR );
2031
2032 PADS_SCH_MODEL point = model;
2033 point.nets[0].connections[0].endpoints[0] = { MODEL_ENDPOINT_KIND::POINT, source };
2034 BOOST_CHECK_NO_THROW( point.ValidateOrThrow() );
2035
2036 SOURCE_PROVENANCE topologySource{ wxS( "model.sch" ), 0x000D, wxS( "endpoint" ), 9, 4, 0x280, 16, 1 };
2037 PADS_SCH_MODEL crossSheet = model;
2038 crossSheet.sheets.push_back( { SHEET_ID( 1 ), 1, topologySource } );
2039 crossSheet.nets[0].sheet = { SHEET_ID( 1 ), topologySource };
2040 crossSheet.nets[0].connections[0].endpoints[0].source = topologySource;
2041 const wxString endpointError =
2042 FormatParserError( topologySource, wxS( "connection endpoint placement sheet does not match net sheet" ) );
2043 BOOST_CHECK_EXCEPTION( crossSheet.ValidateOrThrow(), IO_ERROR,
2044 [&]( const IO_ERROR& aError )
2045 {
2046 return aError.What().Contains( endpointError );
2047 } );
2048
2049 PADS_SCH_MODEL busModel = model;
2050 busModel.nets[0].connections.clear();
2051 busModel.buses.push_back( { BUS_ID( 7 ), source, { SHEET_ID( 0 ), source } } );
2052 busModel.buses[0].memberNets.push_back( { NET_ID( 6 ), source } );
2053 busModel.buses[0].entries.push_back( { topologySource, {}, { NET_ID( 6 ), topologySource } } );
2054 BOOST_CHECK_NO_THROW( busModel.ValidateOrThrow() );
2055
2056 PADS_SCH_MODEL missingBusMember = busModel;
2057 missingBusMember.buses[0].memberNets.clear();
2058 const wxString membershipError =
2059 FormatParserError( topologySource, wxS( "bus-entry net is absent from bus member nets" ) );
2060 BOOST_CHECK_EXCEPTION( missingBusMember.ValidateOrThrow(), IO_ERROR,
2061 [&]( const IO_ERROR& aError )
2062 {
2063 return aError.What().Contains( membershipError );
2064 } );
2065
2066 PADS_SCH_MODEL crossSheetBus = busModel;
2067 crossSheetBus.sheets.push_back( { SHEET_ID( 1 ), 1, topologySource } );
2068 crossSheetBus.buses[0].sheet = { SHEET_ID( 1 ), topologySource };
2069 const wxString busSheetError = FormatParserError( source, wxS( "bus member-net sheet does not match bus sheet" ) );
2070 BOOST_CHECK_EXCEPTION( crossSheetBus.ValidateOrThrow(), IO_ERROR,
2071 [&]( const IO_ERROR& aError )
2072 {
2073 return aError.What().Contains( busSheetError );
2074 } );
2075}
2076
2077
2078BOOST_AUTO_TEST_CASE( TypedPageGraphicOwnership )
2079{
2080 SOURCE_PROVENANCE source{ wxS( "graphics.sch" ), 0x000D, wxS( "page graphic" ), 5, 6, 0x420, 24, 0 };
2082 model.source = source;
2083 model.sheets.push_back( { SHEET_ID( 4 ), 0, source } );
2084 model.graphics.push_back( MODEL_PAGE_GRAPHIC{ source, { SHEET_ID( 4 ), source }, { source } } );
2085 BOOST_CHECK_NO_THROW( model.ValidateOrThrow() );
2086
2087 model.graphics[0].sheet.id = SHEET_ID( 99 );
2088 auto records = normalizeBinaryModel( model );
2089 auto record = std::ranges::find_if( records,
2090 []( const CANONICAL_SEMANTIC_RECORD& aRecord )
2091 {
2092 return aRecord.kind == CANONICAL_KIND::GRAPHIC;
2093 } );
2094 BOOST_REQUIRE( record != records.end() );
2095 BOOST_CHECK( record->properties.at( "owner_sheet" ).disposition == PROPERTY_DISPOSITION::UNSUPPORTED );
2096 const wxString error = FormatParserError( source, wxS( "unresolved page-graphic sheet reference" ) );
2097 BOOST_CHECK_EXCEPTION( model.ValidateOrThrow(), IO_ERROR,
2098 [&]( const IO_ERROR& aError )
2099 {
2100 return aError.What().Contains( error );
2101 } );
2102}
2103
2104
2105BOOST_AUTO_TEST_CASE( TypedIdDiagnosticProvenance )
2106{
2107 SOURCE_PROVENANCE firstSource{ wxS( "ids.sch" ), 0x000D, wxS( "sheet" ), 3, 1, 0x100, 48, 0 };
2108 SOURCE_PROVENANCE secondSource{ wxS( "ids.sch" ), 0x000D, wxS( "sheet" ), 3, 2, 0x200, 48, 1 };
2110 duplicate.source = { wxS( "ids.sch" ), 0x000D, wxS( "model" ), -1, 0, 0, 0x300, -1 };
2111 duplicate.sheets.push_back( { SHEET_ID( 7 ), 0, firstSource } );
2112 duplicate.sheets.push_back( { SHEET_ID( 7 ), 1, secondSource } );
2113
2114 const wxString duplicateDetail =
2115 wxS( "duplicate sheet ID 7; first at v0x000D sheet controller 3 record 1 sheet 0 offset 0x100" );
2116 const wxString duplicateError = FormatParserError( secondSource, duplicateDetail );
2117 BOOST_CHECK_EXCEPTION( duplicate.ValidateOrThrow(), IO_ERROR,
2118 [&]( const IO_ERROR& aError )
2119 {
2120 return aError.What().Contains( duplicateError );
2121 } );
2122
2123 PADS_SCH_MODEL invalid;
2124 invalid.source = duplicate.source;
2125 invalid.sheets.push_back( { SHEET_ID(), 0, secondSource } );
2126 const wxString invalidError = FormatParserError( secondSource, wxS( "invalid sheet ID" ) );
2127 BOOST_CHECK_EXCEPTION( invalid.ValidateOrThrow(), IO_ERROR,
2128 [&]( const IO_ERROR& aError )
2129 {
2130 return aError.What().Contains( invalidError );
2131 } );
2132}
2133
2134
2135BOOST_AUTO_TEST_CASE( SourceStringEncoding )
2136{
2137 SOURCE_PROVENANCE source{ wxS( "encoding.sch" ), 0x000D, wxS( "text" ), 4, 8, 0x300, 4, 0 };
2138 std::vector<PARSER_DIAGNOSTIC> diagnostics;
2139
2140 SOURCE_STRING utf8 = PADS_SCH_BINARY_PARSER::DecodeString( { 0x41, 0xC3, 0xA9 }, 65001, source, diagnostics );
2141 BOOST_CHECK_EQUAL( utf8.text, wxString::FromUTF8( "A\xC3\xA9" ) );
2142 BOOST_CHECK_EQUAL( utf8.codePage, 65001 );
2143 BOOST_CHECK_EQUAL( utf8.codePageName, wxS( "UTF-8" ) );
2144 BOOST_CHECK( utf8.encoding == STRING_ENCODING_STATUS::UTF8 );
2145 BOOST_CHECK( utf8.source == source );
2146
2147 SOURCE_STRING cp1252 =
2148 PADS_SCH_BINARY_PARSER::DecodeString( { 0x80, 0x93, 0xE9 }, 1252, source, diagnostics, wxS( "CP1252" ) );
2149 BOOST_REQUIRE_EQUAL( cp1252.text.length(), 3 );
2150 BOOST_CHECK_EQUAL( cp1252.text[0].GetValue(), 0x20AC );
2151 BOOST_CHECK_EQUAL( cp1252.text[1].GetValue(), 0x201C );
2152 BOOST_CHECK_EQUAL( cp1252.text[2].GetValue(), 0x00E9 );
2153 BOOST_CHECK_EQUAL( cp1252.codePage, 1252 );
2154 BOOST_CHECK_EQUAL( cp1252.codePageName, wxS( "CP1252" ) );
2155 BOOST_CHECK( cp1252.encoding == STRING_ENCODING_STATUS::CODE_PAGE );
2156
2158 PADS_SCH_BINARY_PARSER::DecodeString( { 0x41, 0xFE }, 99999, source, diagnostics, wxS( "vendor-foo" ) );
2159 BOOST_CHECK_EQUAL( unknown.raw[1], 0xFE );
2160 BOOST_REQUIRE_EQUAL( unknown.text.length(), 2 );
2161 BOOST_CHECK_EQUAL( unknown.text[0].GetValue(), 0x41 );
2162 BOOST_CHECK_EQUAL( unknown.text[1].GetValue(), 0xFFFD );
2163 BOOST_CHECK_EQUAL( unknown.codePage, 99999 );
2164 BOOST_CHECK_EQUAL( unknown.codePageName, wxS( "vendor-foo" ) );
2165 BOOST_CHECK( unknown.encoding == STRING_ENCODING_STATUS::UNKNOWN_CODE_PAGE );
2166
2167 SOURCE_STRING invalidUtf8 =
2168 PADS_SCH_BINARY_PARSER::DecodeString( { 0x41, 0xC3, 0x28, 0xFF }, 65001, source, diagnostics );
2169 BOOST_REQUIRE_EQUAL( invalidUtf8.text.length(), 4 );
2170 BOOST_CHECK_EQUAL( invalidUtf8.text[0].GetValue(), 0x41 );
2171 BOOST_CHECK_EQUAL( invalidUtf8.text[1].GetValue(), 0xFFFD );
2172 BOOST_CHECK_EQUAL( invalidUtf8.text[2].GetValue(), 0x28 );
2173 BOOST_CHECK_EQUAL( invalidUtf8.text[3].GetValue(), 0xFFFD );
2174 BOOST_CHECK( invalidUtf8.encoding == STRING_ENCODING_STATUS::INVALID_BYTES );
2175 BOOST_REQUIRE_EQUAL( diagnostics.size(), 2 );
2176 BOOST_CHECK_EQUAL( diagnostics[0].source.absoluteOffset, 0x300 );
2177 BOOST_CHECK( diagnostics[0].message.Contains( wxS( "99999" ) ) );
2178 BOOST_CHECK_EQUAL( diagnostics[1].source.recordIndex, 8 );
2179 BOOST_CHECK( diagnostics[1].message.Contains( wxS( "UTF-8" ) ) );
2180
2181 SOURCE_STRING emptyUnknown = PADS_SCH_BINARY_PARSER::DecodeString( {}, 932, source, diagnostics );
2182 BOOST_CHECK( emptyUnknown.encoding == STRING_ENCODING_STATUS::UNKNOWN_CODE_PAGE );
2183 BOOST_REQUIRE_EQUAL( diagnostics.size(), 3 );
2184 BOOST_CHECK( diagnostics[2].message.Contains( wxS( "932" ) ) );
2185}
2186
2187
2188BOOST_AUTO_TEST_CASE( SemanticSnapshotAdapterContract )
2189{
2190 SOURCE_PROVENANCE source{ wxS( "snapshot.sch" ), 0x000D, wxS( "sheet" ), 3, 0, 0x250, 48, 0 };
2191 PADS_SCH_MODEL binary;
2192 binary.sheets.push_back( { SHEET_ID( 1 ),
2193 0,
2194 source,
2195 { { 'M', 'a', 'i', 'n' }, wxS( "Main" ), STRING_ENCODING_STATUS::UTF8, source } } );
2196 binary.sheets.push_back( { SHEET_ID( 2 ),
2197 1,
2198 source,
2199 { { 'M', 'a', 'i', 'n' }, wxS( "Main" ), STRING_ENCODING_STATUS::UTF8, source } } );
2200 BOOST_CHECK_NE( binary.sheets[0].id.Value(), binary.sheets[1].id.Value() );
2201 BOOST_CHECK( normalizeBinaryModel( binary ) == normalizeBinaryModel( binary ) );
2202 binary.sheets[0].properties.push_back( { { {}, wxS( "value" ), STRING_ENCODING_STATUS::UTF8, source },
2203 { {}, wxS( "10k" ), STRING_ENCODING_STATUS::UTF8, source },
2205 source } );
2206 binary.sheets[0].properties.push_back( { { {}, wxS( "font" ), STRING_ENCODING_STATUS::UTF8, source },
2207 { {}, wxS( "PADS stroke" ), STRING_ENCODING_STATUS::UTF8, source },
2209 source } );
2210 binary.sheets[0].properties.push_back( { { {}, wxS( "color" ), STRING_ENCODING_STATUS::UTF8, source },
2211 { {}, wxS( "green" ), STRING_ENCODING_STATUS::UTF8, source },
2213 source } );
2214 binary.sheets[1].properties.push_back( { { {}, wxS( "value" ), STRING_ENCODING_STATUS::UTF8, source },
2215 { {}, wxS( "22k" ), STRING_ENCODING_STATUS::UTF8, source },
2217 source } );
2218
2219 std::vector<CANONICAL_SEMANTIC_RECORD> expected = normalizeBinaryModel( binary );
2220 std::vector<CANONICAL_SEMANTIC_RECORD> actual = expected;
2221 std::reverse( actual.begin(), actual.end() );
2222
2223 for( CANONICAL_SEMANTIC_RECORD& item : actual )
2224 {
2225 if( item.properties.contains( "font" ) )
2226 item.properties.at( "font" ).value = std::string( "Arial" );
2227
2228 if( item.properties.contains( "color" ) )
2229 item.properties.at( "color" ).value = std::string( "red" );
2230 }
2231
2232 BOOST_CHECK( !snapshotsMatch( expected, actual ) );
2233 BOOST_CHECK( !snapshotsMatch( expected, actual, { { "font", PROPERTY_DISPOSITION::APPROXIMATE } } ) );
2234 BOOST_CHECK( snapshotsMatch(
2237
2238 actual.pop_back();
2239 BOOST_CHECK( !snapshotsMatch(
2242}
2243
2244
2245BOOST_AUTO_TEST_CASE( SnapshotAdaptersCoverSemanticVocabulary )
2246{
2247 SOURCE_PROVENANCE source{ wxS( "snapshot.sch" ), 0x000D, wxS( "synthetic" ), 1, 0, 0x250, 16, 0 };
2248 auto string = [&]( const wxString& aText )
2249 {
2251 result.text = aText;
2252 result.source = source;
2253 return result;
2254 };
2255
2257 model.settings.source = source;
2258 model.sheets.push_back( { SHEET_ID( 1 ), 0, source, string( wxS( "Main" ) ) } );
2259
2260 MODEL_SYMBOL_DEFINITION definition;
2261 definition.id = DEFINITION_ID( 2 );
2262 definition.source = source;
2263 definition.name = string( wxS( "SYM" ) );
2264 definition.graphics.push_back( { source } );
2265 definition.pins.push_back( { PIN_ID( 3 ), source, string( wxS( "1" ) ) } );
2266 definition.fields.push_back( { FIELD_ID( 10 ), source, string( wxS( "Value" ) ), string( wxS( "R" ) ) } );
2267 model.definitions.push_back( definition );
2268
2269 MODEL_PART_TYPE partType;
2270 partType.id = PART_TYPE_ID( 4 );
2271 partType.source = source;
2272 partType.name = string( wxS( "RES" ) );
2273 partType.gates.push_back(
2274 { GATE_ID( 5 ), source, { DEFINITION_ID( 2 ), source }, 1, { { PIN_ID( 3 ), source } } } );
2275 partType.fields.push_back( { FIELD_ID( 11 ), source, string( wxS( "Tolerance" ) ), string( wxS( "1%" ) ) } );
2276 model.partTypes.push_back( partType );
2277
2278 MODEL_PLACEMENT placement;
2279 placement.id = PLACEMENT_ID( 6 );
2280 placement.source = source;
2281 placement.reference = string( wxS( "R1" ) );
2282 placement.fields.push_back( { FIELD_ID( 12 ), source, string( wxS( "Value" ) ), string( wxS( "10k" ) ) } );
2283 model.placements.push_back( placement );
2284
2285 MODEL_NET net;
2286 net.id = NET_ID( 7 );
2287 net.source = source;
2288 net.name = string( wxS( "N" ) );
2289 net.connections.push_back( { source } );
2290 model.nets.push_back( net );
2291
2292 MODEL_BUS bus;
2293 bus.id = BUS_ID( 8 );
2294 bus.source = source;
2295 bus.name = string( wxS( "D[0..0]" ) );
2296 bus.entries.push_back( { source, {}, { NET_ID( 7 ), source } } );
2297 bus.aliases.push_back( string( wxS( "DATA" ) ) );
2298 bus.memberNets.push_back( { NET_ID( 7 ), source } );
2299 model.buses.push_back( bus );
2300 model.labels.push_back( { source, {}, MODEL_LABEL_KIND::LOCAL, string( wxS( "N" ) ) } );
2301 model.junctions.push_back( { source } );
2302 model.texts.push_back( { source, {}, string( wxS( "note" ) ) } );
2303 model.graphics.push_back( { source, { SHEET_ID( 1 ), source }, { source } } );
2304
2305 std::vector<CANONICAL_SEMANTIC_RECORD> binaryRecords = normalizeBinaryModel( model );
2306 std::set<CANONICAL_KIND> binaryKinds;
2307
2308 for( const CANONICAL_SEMANTIC_RECORD& item : binaryRecords )
2309 binaryKinds.insert( item.kind );
2310
2311 const std::set<CANONICAL_KIND> expectedKinds{
2312 CANONICAL_KIND::SETTINGS, CANONICAL_KIND::SHEET, CANONICAL_KIND::DEFINITION,
2313 CANONICAL_KIND::GRAPHIC, CANONICAL_KIND::PIN, CANONICAL_KIND::FIELD,
2314 CANONICAL_KIND::PART_TYPE, CANONICAL_KIND::GATE, CANONICAL_KIND::GATE_PIN_MAPPING,
2315 CANONICAL_KIND::PLACEMENT, CANONICAL_KIND::NET, CANONICAL_KIND::CONNECTION,
2316 CANONICAL_KIND::BUS, CANONICAL_KIND::BUS_ENTRY, CANONICAL_KIND::BUS_ALIAS,
2317 CANONICAL_KIND::BUS_MEMBER, CANONICAL_KIND::LABEL, CANONICAL_KIND::JUNCTION,
2318 CANONICAL_KIND::TEXT
2319 };
2320 BOOST_CHECK( binaryKinds == expectedKinds );
2321
2322 const std::string fixtureRoot = KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/";
2323 PADS_SCH::PADS_SCH_PARSER asciiParser;
2324 BOOST_REQUIRE( asciiParser.Parse( fixtureRoot + "minimal_v13.txt" ) );
2325 std::vector<CANONICAL_SEMANTIC_RECORD> asciiRecords = normalizeAsciiModel( asciiParser );
2326 BOOST_CHECK( std::ranges::any_of( asciiRecords,
2327 []( const CANONICAL_SEMANTIC_RECORD& aItem )
2328 {
2329 return aItem.kind == CANONICAL_KIND::SETTINGS;
2330 } ) );
2331 BOOST_CHECK( std::ranges::any_of( asciiRecords,
2332 []( const CANONICAL_SEMANTIC_RECORD& aItem )
2333 {
2334 return aItem.kind == CANONICAL_KIND::SHEET;
2335 } ) );
2336
2337 PADS_SCH_BINARY_PARSER binaryParser;
2338 std::vector<CANONICAL_SEMANTIC_RECORD> minimalBinary =
2339 normalizeBinaryModel( binaryParser.Parse( loadMinimalV13(), wxS( "minimal_v13.sch" ) ) );
2340 auto countKind = []( const std::vector<CANONICAL_SEMANTIC_RECORD>& aItems, CANONICAL_KIND aKind )
2341 {
2342 return std::ranges::count_if( aItems,
2343 [&]( const CANONICAL_SEMANTIC_RECORD& aItem )
2344 {
2345 return aItem.kind == aKind;
2346 } );
2347 };
2348 BOOST_CHECK_EQUAL( countKind( minimalBinary, CANONICAL_KIND::SETTINGS ),
2349 countKind( asciiRecords, CANONICAL_KIND::SETTINGS ) );
2350 BOOST_CHECK_EQUAL( countKind( minimalBinary, CANONICAL_KIND::SHEET ),
2351 countKind( asciiRecords, CANONICAL_KIND::SHEET ) );
2352}
2353
2354
2355BOOST_AUTO_TEST_CASE( HandbuiltBusAdapterParity )
2356{
2357 const std::string fixture = KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/buses.txt";
2358 PADS_SCH::PADS_SCH_PARSER asciiParser;
2359 BOOST_REQUIRE( asciiParser.Parse( fixture ) );
2360
2361 SOURCE_PROVENANCE source{ wxS( "buses.sch" ), 0x000D, wxS( "synthetic" ), 1, 0, 0, 0, 0 };
2362 auto string = [&]( const wxString& aText )
2363 {
2365 result.text = aText;
2366 result.source = source;
2367 return result;
2368 };
2369
2370 PADS_SCH_MODEL binary;
2372 binary.settings.pageSize = { 34000, 22000, source };
2373 binary.settings.defaultLineWidth = 20;
2374 binary.settings.defaultBusWidth = 50;
2375
2376 MODEL_SHEET sheet{ SHEET_ID( 1 ), 0, source, string( wxS( "Main" ) ) };
2377 sheet.pageSize = binary.settings.pageSize;
2378 sheet.defaultLineWidth = 20;
2379 sheet.defaultBusWidth = 50;
2380 binary.sheets.push_back( sheet );
2381
2382 for( int i = 0; i < 3; ++i )
2383 {
2384 MODEL_NET net;
2385 net.id = NET_ID( i + 1 );
2386 net.source = source;
2387 net.sheet = { SHEET_ID( 1 ), source };
2388 net.name = string( wxString::Format( wxS( "DATA%d" ), i ) );
2389 binary.nets.push_back( net );
2390 }
2391
2392 MODEL_BUS bus;
2393 bus.id = BUS_ID( 2 );
2394 bus.source = source;
2395 bus.sheet = { SHEET_ID( 1 ), source };
2396 bus.name = string( wxS( "DATA[0..2]" ) );
2397 bus.vertices = { { 6000, 14000, source }, { 14000, 14000, source }, { 14000, 10000, source } };
2398 bus.aliases.push_back( string( wxS( "DATA[0..2]" ) ) );
2399
2400 for( int i = 0; i < 3; ++i )
2401 {
2402 bus.entries.push_back( { source, { 8000 + i * 2000, 14000, source }, { NET_ID( i + 1 ), source } } );
2403 bus.memberNets.push_back( { NET_ID( i + 1 ), source } );
2404 }
2405
2406 binary.buses.push_back( bus );
2407 BOOST_CHECK( snapshotsMatch( normalizeBinaryModel( binary ), normalizeAsciiModel( asciiParser ) ) );
2408
2409 std::vector<CANONICAL_SEMANTIC_RECORD> records = normalizeBinaryModel( binary );
2410 auto entry = std::ranges::find_if( records,
2411 []( const CANONICAL_SEMANTIC_RECORD& aRecord )
2412 {
2413 return aRecord.kind == CANONICAL_KIND::BUS_ENTRY;
2414 } );
2415 BOOST_REQUIRE( entry != records.end() );
2416 BOOST_CHECK( entry->properties.at( "member_net" ).value == CANONICAL_VALUE( std::string( "DATA0" ) ) );
2417 auto busRecord = std::ranges::find_if( records,
2418 []( const CANONICAL_SEMANTIC_RECORD& aRecord )
2419 {
2420 return aRecord.kind == CANONICAL_KIND::BUS;
2421 } );
2422 BOOST_REQUIRE( busRecord != records.end() );
2423 BOOST_CHECK( busRecord->properties.at( "member_nets" ).value
2424 == CANONICAL_VALUE( std::vector<std::string>{ "DATA0", "DATA1", "DATA2" } ) );
2425
2426 PADS_SCH_MODEL wrongMapping = binary;
2427 wrongMapping.buses[0].entries[0].memberNet.id = NET_ID( 2 );
2428 BOOST_CHECK( !snapshotsMatch( normalizeBinaryModel( wrongMapping ), normalizeAsciiModel( asciiParser ) ) );
2429
2430 PADS_SCH_MODEL missingMember = binary;
2431 missingMember.buses[0].memberNets.pop_back();
2432 BOOST_CHECK( !snapshotsMatch( normalizeBinaryModel( missingMember ), normalizeAsciiModel( asciiParser ) ) );
2433}
2434
2435
2436BOOST_AUTO_TEST_CASE( CanonicalEnumMappings )
2437{
2438 BOOST_CHECK( canonicalGraphicType( MODEL_GRAPHIC_KIND::RECTANGLE ).value
2439 == CANONICAL_VALUE( std::string( "rectangle" ) ) );
2440 BOOST_CHECK( canonicalGraphicType( MODEL_GRAPHIC_KIND::CIRCLE ).value
2441 == CANONICAL_VALUE( std::string( "circle" ) ) );
2442 BOOST_CHECK( canonicalGraphicType( MODEL_GRAPHIC_KIND::ARC ).value == CANONICAL_VALUE( std::string( "arc" ) ) );
2443 BOOST_CHECK( canonicalGraphicType( MODEL_GRAPHIC_KIND::POLYLINE ).value
2444 == CANONICAL_VALUE( std::string( "polyline" ) ) );
2445 BOOST_CHECK( canonicalGraphicType( PADS_SCH::GRAPHIC_TYPE::RECTANGLE ).value
2446 == CANONICAL_VALUE( std::string( "rectangle" ) ) );
2447 BOOST_CHECK( canonicalGraphicType( PADS_SCH::GRAPHIC_TYPE::CIRCLE ).value
2448 == CANONICAL_VALUE( std::string( "circle" ) ) );
2449 BOOST_CHECK( canonicalGraphicType( PADS_SCH::GRAPHIC_TYPE::ARC ).value == CANONICAL_VALUE( std::string( "arc" ) ) );
2450 BOOST_CHECK( canonicalGraphicType( PADS_SCH::GRAPHIC_TYPE::POLYLINE ).value
2451 == CANONICAL_VALUE( std::string( "polyline" ) ) );
2452 BOOST_CHECK( canonicalLineStyle( MODEL_LINE_STYLE::DEFAULT ).value == CANONICAL_VALUE( std::string( "solid" ) ) );
2453 BOOST_CHECK( canonicalLineStyle( 255 ).value == CANONICAL_VALUE( std::string( "solid" ) ) );
2454 BOOST_CHECK( canonicalLineStyle( 0 ).value == CANONICAL_VALUE( std::string( "dash" ) ) );
2455 BOOST_CHECK( canonicalLineStyle( 42 ).disposition == PROPERTY_DISPOSITION::UNSUPPORTED );
2456 BOOST_CHECK( canonicalFill( MODEL_FILL_STYLE::HATCHED ).value == CANONICAL_VALUE( std::string( "hatched" ) ) );
2457 BOOST_CHECK( canonicalFill( true ).value == CANONICAL_VALUE( std::string( "filled" ) ) );
2458 BOOST_CHECK( canonicalPinType( uint32_t( 3 ) ).value == CANONICAL_VALUE( std::string( "bidirectional" ) ) );
2459 BOOST_CHECK( canonicalPinType( PADS_SCH::PIN_TYPE::BIDIRECTIONAL ).value
2460 == CANONICAL_VALUE( std::string( "bidirectional" ) ) );
2461 BOOST_CHECK( canonicalPinStyle( 3 ).value == CANONICAL_VALUE( std::string( "inverted_clock" ) ) );
2462 BOOST_CHECK( canonicalPinStyle( true, true ).value == CANONICAL_VALUE( std::string( "inverted_clock" ) ) );
2463 BOOST_CHECK( canonicalJustification( MODEL_JUSTIFICATION::CENTER ).value
2464 == CANONICAL_VALUE( std::string( "center" ) ) );
2465 BOOST_CHECK( canonicalHorizontalJustification( 12 ).value == CANONICAL_VALUE( std::string( "center" ) ) );
2466 BOOST_CHECK( canonicalVerticalJustification( 12 ).value == CANONICAL_VALUE( std::string( "center" ) ) );
2467 BOOST_CHECK( canonicalLabelKind( MODEL_LABEL_KIND::HIERARCHICAL ).value
2468 == CANONICAL_VALUE( std::string( "hierarchical" ) ) );
2469 BOOST_CHECK( canonicalEndpointKind( MODEL_ENDPOINT_KIND::PIN ).value == CANONICAL_VALUE( std::string( "pin" ) ) );
2470
2472 BOOST_REQUIRE( parser.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/multigate.txt" ) );
2473 const PADS_SCH::SYMBOL_GRAPHIC* serializedGraphic = nullptr;
2474
2475 for( const PADS_SCH::SYMBOL_DEF& definition : parser.GetSymbolDefs() )
2476 {
2477 auto graphic = std::ranges::find_if( definition.graphics,
2478 []( const PADS_SCH::SYMBOL_GRAPHIC& aGraphic )
2479 {
2480 return aGraphic.line_style == 255;
2481 } );
2482
2483 if( graphic != definition.graphics.end() )
2484 {
2485 serializedGraphic = &*graphic;
2486 break;
2487 }
2488 }
2489
2490 BOOST_REQUIRE( serializedGraphic );
2491 BOOST_CHECK( canonicalLineStyle( serializedGraphic->line_style )
2492 == canonicalLineStyle( MODEL_LINE_STYLE::DEFAULT ) );
2493}
2494
2495
2496BOOST_AUTO_TEST_CASE( GatePinMappingsUseSemanticPinIdentity )
2497{
2498 SOURCE_PROVENANCE source{ wxS( "mapping.sch" ), 0x000D, wxS( "synthetic" ), 1, 0, 0, 0, 0 };
2499 auto string = [&]( const wxString& aText )
2500 {
2502 result.text = aText;
2503 result.source = source;
2504 return result;
2505 };
2506
2508 MODEL_SYMBOL_DEFINITION definition;
2509 definition.id = DEFINITION_ID( 1 );
2510 definition.source = source;
2511 definition.pins.push_back( { PIN_ID( 91 ), source, string( wxS( "A1" ) ), string( wxS( "CLK" ) ) } );
2512 model.definitions.push_back( definition );
2513
2514 MODEL_PART_TYPE part;
2515 part.source = source;
2516 part.gates.push_back( { GATE_ID( 2 ), source, { definition.id, source }, 1, { { PIN_ID( 91 ), source } } } );
2517 model.partTypes.push_back( part );
2518
2519 const auto records = normalizeBinaryModel( model );
2520 auto mapping = std::ranges::find_if( records,
2521 []( const CANONICAL_SEMANTIC_RECORD& aRecord )
2522 {
2523 return aRecord.kind == CANONICAL_KIND::GATE_PIN_MAPPING;
2524 } );
2525 BOOST_REQUIRE( mapping != records.end() );
2526 BOOST_CHECK( mapping->properties.at( "pin_number" ).value == CANONICAL_VALUE( std::string( "A1" ) ) );
2527 BOOST_CHECK( mapping->properties.at( "pin_name" ).value == CANONICAL_VALUE( std::string( "CLK" ) ) );
2528 BOOST_CHECK( !mapping->properties.contains( "pin_id" ) );
2529}
2530
2531
2532BOOST_AUTO_TEST_CASE( SymbolPrimitives )
2533{
2535 PADS_SCH_MODEL model = parser.Parse( loadBinaryFixture( "symbol_primitives.sch" ), wxS( "symbol_primitives.sch" ) );
2536 const MODEL_SYMBOL_DEFINITION& definition = itemNamed( model.definitions, wxS( "BATCHB_PRIMITIVES" ) );
2537
2538 BOOST_REQUIRE_EQUAL( definition.graphics.size(), 6 );
2539 BOOST_CHECK( definition.graphics[0].kind == MODEL_GRAPHIC_KIND::LINE );
2540 BOOST_REQUIRE_EQUAL( definition.graphics[0].points.size(), 2 );
2541 BOOST_CHECK_EQUAL( definition.graphics[0].points[0].x, 200 );
2542 BOOST_CHECK_EQUAL( definition.graphics[0].points[0].y, 200 );
2543 BOOST_CHECK_EQUAL( definition.graphics[0].points[1].x, 800 );
2544 BOOST_CHECK_EQUAL( definition.graphics[0].points[1].y, 200 );
2545 BOOST_CHECK( definition.graphics[1].kind == MODEL_GRAPHIC_KIND::POLYLINE );
2546 BOOST_CHECK( definition.graphics[2].kind == MODEL_GRAPHIC_KIND::CIRCLE );
2547 BOOST_CHECK( definition.graphics[3].kind == MODEL_GRAPHIC_KIND::ARC );
2548 BOOST_CHECK_EQUAL( definition.graphics[3].strokeWidth, 20 );
2549 BOOST_CHECK( definition.graphics[3].lineStyle == MODEL_LINE_STYLE::SOLID );
2550 BOOST_CHECK_EQUAL( definition.graphics[3].arcSweepAngle, 1800 );
2551 BOOST_CHECK( definition.graphics[3].arcClockwise );
2552 BOOST_CHECK_EQUAL( definition.graphics[3].arcCenter.x, 1300 );
2553 BOOST_CHECK_EQUAL( definition.graphics[3].arcCenter.y, 1100 );
2554 BOOST_CHECK_EQUAL( definition.graphics[3].arcBoundsStart.x, 1000 );
2555 BOOST_CHECK_EQUAL( definition.graphics[3].arcBoundsStart.y, 800 );
2556 BOOST_CHECK_EQUAL( definition.graphics[3].arcBoundsEnd.x, 1600 );
2557 BOOST_CHECK_EQUAL( definition.graphics[3].arcBoundsEnd.y, 1400 );
2558 BOOST_CHECK( definition.graphics[4].fill == MODEL_FILL_STYLE::FILLED );
2559 BOOST_CHECK( definition.graphics[5].kind == MODEL_GRAPHIC_KIND::TEXT );
2560 BOOST_CHECK_EQUAL( definition.graphics[5].text.text, wxS( "EMBEDDED_TEXT" ) );
2561 BOOST_CHECK_EQUAL( definition.graphics[5].presentation.font.text, wxS( "Default Font" ) );
2562 BOOST_REQUIRE_EQUAL( definition.graphics[5].points.size(), 1 );
2563 BOOST_CHECK_EQUAL( definition.graphics[5].points[0].x, 1000 );
2564 BOOST_CHECK_EQUAL( definition.graphics[5].points[0].y, 1600 );
2565 BOOST_CHECK_EQUAL( definition.graphics[5].presentation.height, 100 );
2566 BOOST_CHECK_EQUAL( definition.graphics[5].presentation.width, 10 );
2567 BOOST_CHECK_EQUAL( definition.graphics[5].angle, 0 );
2568 BOOST_REQUIRE_EQUAL( definition.pins.size(), 2 );
2569 BOOST_CHECK_EQUAL( definition.pins[0].position.x, 0 );
2570 BOOST_CHECK_EQUAL( definition.pins[0].position.y, 600 );
2571 BOOST_CHECK_EQUAL( definition.pins[1].position.x, 2400 );
2572 BOOST_CHECK_EQUAL( definition.pins[1].position.y, 600 );
2573
2574 std::vector<uint8_t> privateStroke = loadBinaryFixture( "symbol_primitives.sch" );
2575 const size_t privatePiece =
2576 sheetControllerOffset( privateStroke, 4 ) + definition.graphics[0].source.recordIndex * 6;
2577 writeU16( privateStroke, privatePiece + 4, 0xEF03 );
2578 PADS_SCH_MODEL preservedStroke = parser.Parse( privateStroke, wxS( "private-symbol-stroke.sch" ) );
2579 const MODEL_SYMBOL_DEFINITION& preservedDefinition =
2580 itemNamed( preservedStroke.definitions, wxS( "BATCHB_PRIMITIVES" ) );
2581 auto privateGraphic = std::ranges::find_if( preservedDefinition.graphics,
2582 [&]( const MODEL_GRAPHIC& aGraphic )
2583 {
2584 return aGraphic.source.controller == 4
2585 && aGraphic.source.recordIndex
2586 == definition.graphics[0].source.recordIndex;
2587 } );
2588 BOOST_REQUIRE( privateGraphic != preservedDefinition.graphics.end() );
2589 BOOST_CHECK_EQUAL( privateGraphic->strokeWidth, 0 );
2590 auto rawStroke = std::ranges::find_if( privateGraphic->properties,
2591 []( const SOURCE_PROPERTY& aProperty )
2592 {
2593 return aProperty.name.text == wxS( "unsupported_graphic_stroke_width" );
2594 } );
2595 BOOST_REQUIRE( rawStroke != privateGraphic->properties.end() );
2596 BOOST_CHECK_EQUAL( rawStroke->value.text, wxS( "3" ) );
2597 BOOST_CHECK( rawStroke->disposition == PROPERTY_DISPOSITION::UNSUPPORTED );
2598 BOOST_CHECK_EQUAL( rawStroke->source.absoluteOffset, privatePiece + 4 );
2599 BOOST_CHECK_EQUAL( rawStroke->source.length, 1 );
2600 BOOST_CHECK_EQUAL( propertyValue( privateGraphic->properties, wxS( "preserved_graphic_presentation" ) ),
2601 wxS( "239" ) );
2602}
2603
2604
2605BOOST_AUTO_TEST_CASE( TerminalOffsetPresentation )
2606{
2608 std::vector<uint8_t> fixture = loadBinaryFixture( "pin_styles.sch" );
2609 PADS_SCH_MODEL baseline = parser.Parse( fixture, wxS( "pin_styles.sch" ) );
2610 const MODEL_PIN_DEFINITION& baselinePin =
2611 itemNamed( baseline.definitions, wxS( "BATCHB_PIN_STYLES" ) ).pins[0];
2612 const size_t visibilityOffset = baselinePin.source.absoluteOffset + 24;
2613
2614 writeU16( fixture, visibilityOffset, 0x8040 );
2615 PADS_SCH_MODEL visibilityOnly = parser.Parse( fixture, wxS( "terminal-visibility-only.sch" ) );
2616 const MODEL_PIN_DEFINITION& visibilityOnlyPin =
2617 itemNamed( visibilityOnly.definitions, wxS( "BATCHB_PIN_STYLES" ) ).pins[0];
2618
2619 BOOST_CHECK_EQUAL( visibilityOnlyPin.visibilityFlags, 0x80 );
2620 BOOST_CHECK( std::ranges::none_of( visibilityOnly.diagnostics,
2621 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
2622 {
2623 return aDiagnostic.message.Contains(
2624 wxS( "terminal number-offset presentation" ) )
2625 && aDiagnostic.source.controller == 8
2626 && aDiagnostic.source.recordIndex
2627 == baselinePin.source.recordIndex;
2628 } ) );
2629
2630 writeU16( fixture, visibilityOffset, 0x0D40 );
2631 PADS_SCH_MODEL pairedPresentation = parser.Parse( fixture, wxS( "terminal-presentation-0d.sch" ) );
2632 const MODEL_PIN_DEFINITION& pairedPin =
2633 itemNamed( pairedPresentation.definitions, wxS( "BATCHB_PIN_STYLES" ) ).pins[0];
2634
2636 BOOST_CHECK_EQUAL( pairedPin.nameOffsetAngle, 900 );
2638 BOOST_CHECK_EQUAL( pairedPin.numberOffsetAngle, 0 );
2640 BOOST_CHECK( std::ranges::none_of( pairedPresentation.diagnostics,
2641 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
2642 {
2643 return ( aDiagnostic.message.Contains(
2644 wxS( "terminal name-offset presentation" ) )
2645 || aDiagnostic.message.Contains(
2646 wxS( "terminal number-offset presentation" ) ) )
2647 && aDiagnostic.source.controller == 8
2648 && aDiagnostic.source.recordIndex
2649 == baselinePin.source.recordIndex;
2650 } ) );
2651}
2652
2653
2654BOOST_AUTO_TEST_CASE( PartPinsAndGates )
2655{
2657 PADS_SCH_MODEL pins = parser.Parse( loadBinaryFixture( "pin_styles.sch" ), wxS( "pin_styles.sch" ) );
2658 const MODEL_PART_TYPE& pinPart = itemNamed( pins.partTypes, wxS( "BATCHB-PIN-STYLES" ) );
2659 BOOST_REQUIRE_EQUAL( pinPart.gates.size(), 1 );
2660 BOOST_REQUIRE_EQUAL( pinPart.gates[0].pins.size(), 7 );
2661
2662 const MODEL_SYMBOL_DEFINITION& pinDefinition = itemNamed( pins.definitions, wxS( "BATCHB_PIN_STYLES" ) );
2663 BOOST_REQUIRE_EQUAL( pinDefinition.pins.size(), 7 );
2664 BOOST_CHECK_EQUAL( pinDefinition.pins[0].number.text, wxS( "1" ) );
2665 BOOST_CHECK_EQUAL( pinDefinition.pins[0].name.text, wxS( "INPUT_PIN" ) );
2666 BOOST_CHECK_EQUAL( pinDefinition.pins[0].electricalType, 1 );
2667 BOOST_CHECK_EQUAL( pinDefinition.pins[1].electricalType, 2 );
2668 BOOST_CHECK_EQUAL( pinDefinition.pins[4].graphicStyle, 1 );
2669 BOOST_CHECK_EQUAL( pinDefinition.pins[5].graphicStyle, 2 );
2670 BOOST_CHECK( !pinDefinition.pins[6].presentation.visible );
2671 BOOST_CHECK_EQUAL( pinDefinition.pins[4].side, 1 );
2672 BOOST_CHECK_EQUAL( pinDefinition.pins[4].angle, 0 );
2673 BOOST_CHECK_EQUAL( pinDefinition.pins[4].length, 280 );
2674 BOOST_CHECK_EQUAL( propertyValue( pinDefinition.pins[0].properties, wxS( "pin_name_height_half_mils" ) ),
2675 wxS( "100" ) );
2676 BOOST_CHECK_EQUAL( propertyValue( pinDefinition.pins[0].properties, wxS( "pin_number_height_half_mils" ) ),
2677 wxS( "100" ) );
2678 BOOST_CHECK_EQUAL( pinDefinition.pins[0].namePresentation.width, 10 );
2679 BOOST_CHECK_EQUAL( pinDefinition.pins[0].numberPresentation.width, 10 );
2680 BOOST_CHECK_EQUAL( pinDefinition.pins[0].nameOffset.x, -200 );
2681 BOOST_CHECK_EQUAL( pinDefinition.pins[0].nameOffset.y, -100 );
2682 BOOST_CHECK_EQUAL( pinDefinition.pins[0].numberOffset.x, 500 );
2683 BOOST_CHECK_EQUAL( pinDefinition.pins[0].numberOffset.y, -100 );
2684 BOOST_CHECK( pinDefinition.pins[2].presentation.visible );
2685 BOOST_CHECK( !pinDefinition.pins[2].namePresentation.visible );
2686 BOOST_CHECK( !pinDefinition.pins[6].namePresentation.visible );
2687 BOOST_CHECK( pinDefinition.pins[6].numberPresentation.visible );
2688 BOOST_REQUIRE_EQUAL( pinPart.signalPins.size(), 1 );
2689 BOOST_CHECK_EQUAL( pinPart.signalPins[0].number.text, wxS( "8" ) );
2690 BOOST_CHECK_EQUAL( pinPart.signalPins[0].name.text, wxS( "VCC_HIDDEN" ) );
2691
2692 PADS_SCH_MODEL multi = parser.Parse( loadBinaryFixture( "multigate.sch" ), wxS( "multigate.sch" ) );
2693 const MODEL_PART_TYPE& multiPart = itemNamed( multi.partTypes, wxS( "BATCHD-MULTIGATE" ) );
2694 BOOST_REQUIRE_EQUAL( multiPart.gates.size(), 2 );
2695 BOOST_CHECK_EQUAL( multiPart.gates[0].unit, 1 );
2696 BOOST_CHECK_EQUAL( multiPart.gates[0].pins.size(), 7 );
2697 BOOST_CHECK_EQUAL( multiPart.gates[1].unit, 2 );
2698 BOOST_CHECK_EQUAL( multiPart.gates[1].pins.size(), 2 );
2699 BOOST_CHECK_EQUAL( multiPart.gates[0].properties.front().name.text, wxS( "swap_group" ) );
2700 const MODEL_PART_TYPE& alternatePart = itemNamed( multi.partTypes, wxS( "RES-RESN1" ) );
2701 BOOST_REQUIRE_EQUAL( alternatePart.gates[0].alternateDefinitions.size(), 3 );
2702 BOOST_REQUIRE_EQUAL( multiPart.signalPins.size(), 1 );
2703 BOOST_CHECK_EQUAL( multiPart.signalPins[0].number.text, wxS( "15" ) );
2704
2705 PADS_SCH_MODEL connectorModel = parser.Parse( loadBinaryFixture( "connectors.sch" ), wxS( "connectors.sch" ) );
2706 const MODEL_PART_TYPE& connector = itemNamed( connectorModel.partTypes, wxS( "CON-26P-ED" ) );
2707 BOOST_REQUIRE_EQUAL( connector.gates.size(), 1 );
2708 BOOST_REQUIRE_EQUAL( connector.gates[0].decalGroupMembers.size(), 5 );
2709 BOOST_REQUIRE_EQUAL( connector.gates[0].connectorPins.size(), 26 );
2710
2711 for( const DEFINITION_REFERENCE& member : connector.gates[0].decalGroupMembers )
2712 {
2713 BOOST_CHECK( member.id.IsValid() );
2714 BOOST_CHECK_EQUAL( member.source.controller, 10 );
2715 }
2716
2717 for( size_t pin = 0; pin < connector.gates[0].connectorPins.size(); ++pin )
2718 {
2719 const MODEL_CONNECTOR_PIN& connectorPin = connector.gates[0].connectorPins[pin];
2720 BOOST_CHECK_EQUAL( connectorPin.number.text, wxString::Format( wxS( "%llu" ), pin + 1 ) );
2721 BOOST_CHECK_EQUAL( connectorPin.name.text, wxString() );
2722 BOOST_CHECK_EQUAL( connectorPin.electricalType, 0 );
2723 BOOST_CHECK_EQUAL( connectorPin.swapGroup, 0 );
2724 BOOST_CHECK_EQUAL( connectorPin.flags, 0 );
2725 BOOST_CHECK_EQUAL( connectorPin.source.controller, 11 );
2726 BOOST_CHECK_EQUAL( connectorPin.source.length, 24 );
2727 BOOST_CHECK_EQUAL( connectorPin.number.source.controller, 11 );
2728 BOOST_CHECK_EQUAL( connectorPin.number.source.absoluteOffset, connectorPin.source.absoluteOffset + 4 );
2729 BOOST_CHECK_EQUAL( connectorPin.number.source.length, 16 );
2730 }
2731}
2732
2733
2734BOOST_AUTO_TEST_CASE( DefinitionFields )
2735{
2736 const FIELD_ID controller7Record11214 = MakeFieldId( FIELD_ID_DOMAIN::DEFINITION, 11214, 0 );
2737 const FIELD_ID controller7Record12125 = MakeFieldId( FIELD_ID_DOMAIN::DEFINITION, 12125, 0 );
2738 BOOST_CHECK( controller7Record11214.IsValid() );
2739 BOOST_CHECK( controller7Record12125.IsValid() );
2740 BOOST_CHECK_NE( controller7Record11214.Value(), controller7Record12125.Value() );
2741
2743 PADS_SCH_MODEL model = parser.Parse( loadBinaryFixture( "fields.sch" ), wxS( "fields.sch" ) );
2744 const MODEL_SYMBOL_DEFINITION& definition = itemNamed( model.definitions, wxS( "RESZ-H" ) );
2745 BOOST_REQUIRE_EQUAL( definition.fields.size(), 4 );
2746 std::set<uint64_t> fieldIds;
2747
2748 for( size_t ordinal = 0; ordinal < definition.fields.size(); ++ordinal )
2749 {
2750 const MODEL_FIELD& field = definition.fields[ordinal];
2751 BOOST_CHECK( field.id.IsValid() );
2752 BOOST_CHECK_EQUAL( field.id.Value(),
2753 MakeFieldId( FIELD_ID_DOMAIN::DEFINITION, definition.id.Value(), ordinal ).Value() );
2754 BOOST_CHECK( fieldIds.insert( field.id.Value() ).second );
2755 }
2756 BOOST_CHECK_EQUAL( definition.fields[0].name.text, wxS( "REF-DES" ) );
2757 BOOST_CHECK_EQUAL( definition.fields[1].name.text, wxS( "PART-TYPE" ) );
2758 BOOST_CHECK_EQUAL( definition.fields[2].name.text, wxS( "VALUE" ) );
2759 BOOST_CHECK_EQUAL( definition.fields[3].name.text, wxS( "*" ) );
2760 BOOST_CHECK_EQUAL( definition.fields[0].presentation.font.text, wxS( "Default Font" ) );
2761 BOOST_CHECK_EQUAL( definition.fields[0].presentation.height, 100 );
2762 BOOST_CHECK_EQUAL( definition.fields[0].presentation.width, 10 );
2763 BOOST_CHECK_EQUAL( definition.fields[0].position.x, 600 );
2764 BOOST_CHECK_EQUAL( definition.fields[0].position.y, 200 );
2765 BOOST_CHECK_EQUAL( definition.fields[1].position.x, 620 );
2766 BOOST_CHECK_EQUAL( definition.fields[1].position.y, 400 );
2767 BOOST_CHECK( definition.fields[0].value.text.empty() );
2768 BOOST_CHECK_EQUAL( propertyValue( definition.fields[0].presentation.properties, wxS( "font_handle" ) ),
2769 wxS( "-1" ) );
2770 BOOST_CHECK_EQUAL( propertyValue( definition.fields[1].presentation.properties, wxS( "font_handle" ) ),
2771 wxS( "-1" ) );
2772 BOOST_CHECK_EQUAL( propertyValue( definition.fields[2].presentation.properties, wxS( "font_handle" ) ),
2773 wxS( "-4" ) );
2774 BOOST_CHECK_EQUAL( propertyValue( definition.fields[3].presentation.properties, wxS( "font_handle" ) ),
2775 wxS( "-4" ) );
2776 BOOST_CHECK( definition.fields[0].presentation.horizontalJustification == MODEL_JUSTIFICATION::LEFT );
2777 BOOST_CHECK( definition.fields[0].presentation.verticalJustification == MODEL_JUSTIFICATION::LEFT );
2778 BOOST_CHECK( definition.fields[2].presentation.horizontalJustification == MODEL_JUSTIFICATION::CENTER );
2779 BOOST_CHECK( definition.fields[2].presentation.verticalJustification == MODEL_JUSTIFICATION::LEFT );
2780
2781 const MODEL_PART_TYPE& partType = itemNamed( model.partTypes, wxS( "RES-RESN1" ) );
2782 BOOST_REQUIRE_EQUAL( partType.fields.size(), 4 );
2783
2784 for( size_t i = 0; i < partType.fields.size(); ++i )
2785 {
2786 BOOST_CHECK_EQUAL( partType.fields[i].name.text, definition.fields[i].name.text );
2787 BOOST_CHECK_EQUAL( partType.fields[i].value.text, definition.fields[i].value.text );
2788 BOOST_CHECK( partType.fields[i].presentation == definition.fields[i].presentation );
2789 BOOST_CHECK( partType.fields[i].id.IsValid() );
2790 BOOST_CHECK_EQUAL( partType.fields[i].id.Value(),
2791 MakeFieldId( FIELD_ID_DOMAIN::PART_TYPE, partType.id.Value(), i ).Value() );
2792 BOOST_CHECK( fieldIds.insert( partType.fields[i].id.Value() ).second );
2793 }
2794
2795 PADS_SCH_MODEL repeated = parser.Parse( loadBinaryFixture( "fields.sch" ), wxS( "fields.sch" ) );
2796 BOOST_CHECK( repeated.definitions == model.definitions );
2797 BOOST_CHECK( repeated.partTypes == model.partTypes );
2798}
2799
2800
2801BOOST_AUTO_TEST_CASE( PlacementTransforms )
2802{
2805 parser.Parse( loadBinaryFixture( "placement_transform.sch" ), wxS( "placement_transform.sch" ) );
2806 BOOST_REQUIRE_EQUAL( model.placements.size(), 5 );
2807
2808 const std::array<wxString, 5> references = { wxS( "R1" ), wxS( "R2" ), wxS( "R3" ), wxS( "R4" ), wxS( "R5" ) };
2809 const std::array<SOURCE_POINT, 5> positions = { SOURCE_POINT{ -4800, 18200 }, SOURCE_POINT{ -200, 14800 },
2810 SOURCE_POINT{ 2200, 14000 }, SOURCE_POINT{ 3400, 13000 },
2811 SOURCE_POINT{ 6000, 10800 } };
2812 const std::array<int, 5> angles = { 0, 900, 0, 900, 900 };
2813 const std::array<bool, 5> mirrored = { false, false, true, true, true };
2814 const std::array<uint16_t, 5> mirrorFlags = { 0, 0, 3, 3, 2 };
2815 const std::array<std::pair<uint16_t, int>, 4> orthogonalAngles = {
2816 std::pair<uint16_t, int>{ 0, 0 }, { 900, 900 }, { 1800, 1800 }, { 2700, 2700 }
2817 };
2818
2819 for( const auto& [raw, normalized] : orthogonalAngles )
2820 {
2821 std::vector<uint8_t> transform = loadBinaryFixture( "placement_transform.sch" );
2822 writeU16( transform, sheetControllerOffset( transform, 15 ) + 0x24, raw );
2823 PADS_SCH_MODEL transformed = parser.Parse( transform, wxS( "placement_transform.sch" ) );
2824 BOOST_CHECK_EQUAL( transformed.placements[0].angle, normalized );
2825 BOOST_CHECK_EQUAL( propertyValue( transformed.placements[0].properties, wxS( "raw_angle" ) ),
2826 wxString::Format( wxS( "%u" ), raw ) );
2827 }
2828
2829 for( size_t i = 0; i < model.placements.size(); ++i )
2830 {
2831 const MODEL_PLACEMENT& placement = model.placements[i];
2832 BOOST_CHECK_EQUAL( placement.reference.text, references[i] );
2833 BOOST_CHECK_EQUAL( placement.position.x, positions[i].x );
2834 BOOST_CHECK_EQUAL( placement.position.y, positions[i].y );
2835 BOOST_CHECK_EQUAL( placement.angle, angles[i] );
2836 BOOST_CHECK_EQUAL( placement.mirrored, mirrored[i] );
2837 BOOST_CHECK_EQUAL( placement.mirrorFlags, mirrorFlags[i] );
2838 BOOST_CHECK_EQUAL( propertyValue( placement.properties, wxS( "raw_angle" ) ),
2839 wxString::Format( wxS( "%d" ), angles[i] ) );
2840 BOOST_CHECK_EQUAL( propertyValue( placement.properties, wxS( "raw_mirror" ) ),
2841 wxString::Format( wxS( "%u" ), mirrorFlags[i] ) );
2842 auto rawAngle = std::ranges::find_if( placement.properties,
2843 []( const SOURCE_PROPERTY& aProperty )
2844 {
2845 return aProperty.name.text == wxS( "raw_angle" );
2846 } );
2847 auto rawMirror = std::ranges::find_if( placement.properties,
2848 []( const SOURCE_PROPERTY& aProperty )
2849 {
2850 return aProperty.name.text == wxS( "raw_mirror" );
2851 } );
2852 BOOST_REQUIRE( rawAngle != placement.properties.end() );
2853 BOOST_REQUIRE( rawMirror != placement.properties.end() );
2854 BOOST_CHECK_EQUAL( rawAngle->source.absoluteOffset, placement.source.absoluteOffset + 0x24 );
2855 BOOST_CHECK_EQUAL( rawMirror->source.absoluteOffset, placement.source.absoluteOffset + 0x26 );
2856 BOOST_CHECK_EQUAL( rawAngle->source.length, 2 );
2857 BOOST_CHECK_EQUAL( rawMirror->source.length, 2 );
2858 BOOST_CHECK_EQUAL( placement.sheet.id.Value(), model.sheets[0].id.Value() );
2859 BOOST_REQUIRE( placement.gate.has_value() );
2860 BOOST_CHECK( placement.partType.id.IsValid() );
2861 BOOST_CHECK( placement.gate->id.IsValid() );
2862 BOOST_CHECK( placement.definition.id.IsValid() );
2863 BOOST_CHECK_EQUAL( placement.pins.size(), 2 );
2864 BOOST_CHECK_GE( placement.fields.size(), 3 );
2865 BOOST_CHECK_EQUAL( placement.source.controller, 15 );
2866 }
2867
2868 BOOST_CHECK_EQUAL( model.placements[0].pins[0].numberOffset.x, 34 );
2869 BOOST_CHECK_EQUAL( model.placements[0].pins[0].numberOffset.y, 16 );
2870 BOOST_CHECK_EQUAL( model.placements[0].pins[0].numberAngle, 0 );
2871 BOOST_CHECK_EQUAL( model.placements[0].pins[0].numberJustification, 0 );
2872 BOOST_CHECK_EQUAL( model.placements[0].pins[0].numberPresentationFlags, 0x0100 );
2873 BOOST_CHECK_EQUAL( model.placements[0].pins[1].numberOffset.x, 0 );
2874 BOOST_CHECK_EQUAL( model.placements[0].pins[1].numberOffset.y, 20 );
2875 BOOST_CHECK_EQUAL( model.placements[0].pins[1].numberPresentationFlags, 0 );
2876
2877 auto placementNamed = []( const PADS_SCH_MODEL& aModel, const wxString& aReference ) -> const MODEL_PLACEMENT&
2878 {
2879 auto placement = std::ranges::find_if( aModel.placements,
2880 [&]( const MODEL_PLACEMENT& aPlacement )
2881 {
2882 return aPlacement.reference.text == aReference;
2883 } );
2884 BOOST_REQUIRE( placement != aModel.placements.end() );
2885 return *placement;
2886 };
2887
2888 PADS_SCH_MODEL multi = parser.Parse( loadBinaryFixture( "multigate.sch" ), wxS( "multigate.sch" ) );
2889 const MODEL_PLACEMENT& gateA = placementNamed( multi, wxS( "U3-A" ) );
2890 const MODEL_PLACEMENT& gateB = placementNamed( multi, wxS( "U3-B" ) );
2891 BOOST_CHECK_EQUAL( gateA.unit, 1 );
2892 BOOST_CHECK_EQUAL( gateB.unit, 2 );
2893 BOOST_CHECK( gateA.definition.id != gateB.definition.id );
2894 BOOST_CHECK_EQUAL( gateA.pins.size(), 7 );
2895 BOOST_CHECK_EQUAL( gateB.pins.size(), 2 );
2896
2897 PADS_SCH_MODEL connectors = parser.Parse( loadBinaryFixture( "connectors.sch" ), wxS( "connectors.sch" ) );
2898 const MODEL_PLACEMENT& connector = placementNamed( connectors, wxS( "P1-1" ) );
2899 BOOST_CHECK_EQUAL( connector.unit, 1 );
2900 BOOST_CHECK_GE( connector.fields.size(), 4 );
2901 BOOST_CHECK_EQUAL( connector.fields[2].name.text, wxS( "*" ) );
2902 BOOST_CHECK( connector.fields[2].value.text.empty() );
2903 BOOST_CHECK_EQUAL( connector.fields[3].name.text, wxS( "*" ) );
2904 BOOST_CHECK( connector.fields[3].value.text.empty() );
2905}
2906
2907
2908BOOST_AUTO_TEST_CASE( PlacementInstanceFields )
2909{
2911 PADS_SCH_MODEL model = parser.Parse( loadBinaryFixture( "fields.sch" ), wxS( "fields.sch" ) );
2912 BOOST_REQUIRE_EQUAL( model.placements.size(), 1 );
2913 const MODEL_PLACEMENT& placement = model.placements[0];
2914 BOOST_REQUIRE_GE( placement.fields.size(), 3 );
2915
2916 BOOST_CHECK_EQUAL( placement.fields[0].name.text, wxS( "REF-DES" ) );
2917 BOOST_CHECK_EQUAL( placement.fields[0].value.text, wxS( "R1" ) );
2918 BOOST_CHECK_EQUAL( placement.fields[0].position.x, 600 );
2919 BOOST_CHECK_EQUAL( placement.fields[0].position.y, 200 );
2920 BOOST_CHECK_EQUAL( placement.fields[0].presentation.font.text, wxS( "Default Font" ) );
2921 BOOST_CHECK_EQUAL( placement.fields[1].name.text, wxS( "PART-TYPE" ) );
2922 BOOST_CHECK_EQUAL( placement.fields[1].value.text, wxS( "RES-RESN1" ) );
2923 BOOST_CHECK_EQUAL( placement.fields[1].position.x, 620 );
2924 BOOST_CHECK_EQUAL( placement.fields[1].position.y, 400 );
2925 BOOST_CHECK_EQUAL( placement.fields[1].angle, 900 );
2926 BOOST_CHECK_EQUAL( placement.fields[1].presentation.height, 194 );
2927 BOOST_CHECK_EQUAL( placement.fields[1].presentation.width, 10 );
2928 BOOST_CHECK_EQUAL( placement.fields[1].presentation.font.text, wxS( "Bold Verdana" ) );
2929 BOOST_CHECK_EQUAL( placement.fields[2].name.text, wxS( "userfield" ) );
2930 BOOST_CHECK_EQUAL( placement.fields[2].value.text, wxS( "override-value" ) );
2931 BOOST_CHECK_EQUAL( placement.fields[2].position.x, 600 );
2932 BOOST_CHECK_EQUAL( placement.fields[2].position.y, -400 );
2933 BOOST_CHECK_EQUAL( placement.fields[2].presentation.height, 194 );
2934 BOOST_CHECK_EQUAL( placement.fields[2].presentation.width, 10 );
2935 BOOST_CHECK_EQUAL( placement.fields[2].presentation.visible, false );
2936 BOOST_CHECK_EQUAL( placement.fields[2].presentation.font.text, wxS( "Bold Verdana" ) );
2937 BOOST_CHECK_EQUAL( placement.fields[2].source.controller, 17 );
2938 BOOST_CHECK_EQUAL( propertyValue( placement.fields[2].properties, wxS( "component_attribute_index" ) ),
2939 wxS( "10" ) );
2940 auto attributeIndex = std::ranges::find_if( placement.fields[2].properties,
2941 []( const SOURCE_PROPERTY& aProperty )
2942 {
2943 return aProperty.name.text == wxS( "component_attribute_index" );
2944 } );
2945 BOOST_REQUIRE( attributeIndex != placement.fields[2].properties.end() );
2946 BOOST_CHECK_EQUAL( attributeIndex->source.controller, 17 );
2947 BOOST_CHECK_EQUAL( attributeIndex->source.absoluteOffset, placement.fields[2].source.absoluteOffset + 16 );
2948 BOOST_CHECK_EQUAL( attributeIndex->source.length, 2 );
2949
2950 for( size_t i = 0; i < placement.fields.size(); ++i )
2951 {
2952 BOOST_CHECK_EQUAL( placement.fields[i].id.Value(),
2953 MakeFieldId( FIELD_ID_DOMAIN::PLACEMENT, placement.id.Value(), i ).Value() );
2954 }
2955}
2956
2957
2958BOOST_AUTO_TEST_CASE( PlacementHandleErrors )
2959{
2962 parser.Parse( loadBinaryFixture( "placement_transform.sch" ), wxS( "duplicate-placement.sch" ) );
2963 duplicate.placements[1].id = duplicate.placements[0].id;
2964 BOOST_CHECK_EXCEPTION( duplicate.ValidateOrThrow(), IO_ERROR,
2965 []( const IO_ERROR& aError )
2966 {
2967 return aError.What().Contains( wxS( "duplicate placement ID" ) )
2968 && aError.What().Contains( wxS( "controller 15" ) )
2969 && aError.What().Contains( wxS( "first at" ) );
2970 } );
2971
2972 std::vector<uint8_t> unresolvedPart = loadBinaryFixture( "placement_transform.sch" );
2973 writeU16( unresolvedPart, sheetControllerOffset( unresolvedPart, 15 ) + 0x42, 20 );
2974 BOOST_CHECK_EXCEPTION( parser.Parse( unresolvedPart, wxS( "placement-part.sch" ) ), IO_ERROR,
2975 []( const IO_ERROR& aError )
2976 {
2977 return aError.What().Contains( wxS( "targets definition object class" ) )
2978 && aError.What().Contains( wxS( "controller 15" ) );
2979 } );
2980
2981 std::vector<uint8_t> wrongGroup = loadBinaryFixture( "placement_transform.sch" );
2982 writeU32( wrongGroup, sheetControllerOffset( wrongGroup, 15 ) + 0x1C, 0xFFFFFFFF );
2983 BOOST_CHECK_EXCEPTION( parser.Parse( wrongGroup, wxS( "placement-group.sch" ) ), IO_ERROR,
2984 []( const IO_ERROR& aError )
2985 {
2986 return aError.What().Contains( wxS( "component-group handle" ) );
2987 } );
2988
2989 std::vector<uint8_t> wrongDecal = loadBinaryFixture( "placement_transform.sch" );
2990 writeU16( wrongDecal, sheetControllerOffset( wrongDecal, 15 ) + 0x44, 0 );
2991 BOOST_CHECK_EXCEPTION( parser.Parse( wrongDecal, wxS( "placement-decal.sch" ) ), IO_ERROR,
2992 []( const IO_ERROR& aError )
2993 {
2994 return aError.What().Contains( wxS( "different object classes" ) );
2995 } );
2996
2997 std::vector<uint8_t> wrongGate = loadBinaryFixture( "multigate.sch" );
2998 writeU16( wrongGate, sheetControllerOffset( wrongGate, 15 ) + 136 + 0x4A, 7 );
2999 BOOST_CHECK_EXCEPTION( parser.Parse( wrongGate, wxS( "placement-gate.sch" ) ), IO_ERROR,
3000 []( const IO_ERROR& aError )
3001 {
3002 return aError.What().Contains( wxS( "gate handle targets definition object class" ) );
3003 } );
3004
3005 std::vector<uint8_t> wrongPin = loadBinaryFixture( "placement_transform.sch" );
3006 writeU16( wrongPin, sheetControllerOffset( wrongPin, 16 ) + 4, 0xFFFF );
3007 BOOST_CHECK_EXCEPTION( parser.Parse( wrongPin, wxS( "placement-pin.sch" ) ), IO_ERROR,
3008 []( const IO_ERROR& aError )
3009 {
3010 return aError.What().Contains( wxS( "placed-pin handle" ) );
3011 } );
3012
3013 std::vector<uint8_t> wrongField = loadBinaryFixture( "fields.sch" );
3014 BOOST_REQUIRE_GT( readU32( wrongField, 0x20 + 7 * 28 + 8 ), 10 );
3015 BOOST_REQUIRE_LE( readU32( wrongField, 0x20 + 19 * 28 + 8 ), 10 );
3016 writeU16( wrongField, sheetControllerOffset( wrongField, 17 ), 10 );
3017 BOOST_CHECK_EXCEPTION( parser.Parse( wrongField, wxS( "placement-field.sch" ) ), IO_ERROR,
3018 []( const IO_ERROR& aError )
3019 {
3020 return aError.What().Contains( wxS( "placement font handle" ) );
3021 } );
3022
3023 std::vector<uint8_t> wrongAttribute = loadBinaryFixture( "fields.sch" );
3024 writeU16( wrongAttribute, sheetControllerOffset( wrongAttribute, 17 ) + 16, 0xFFFE );
3025 BOOST_CHECK_EXCEPTION( parser.Parse( wrongAttribute, wxS( "placement-field-attribute.sch" ) ), IO_ERROR,
3026 []( const IO_ERROR& aError )
3027 {
3028 return aError.What().Contains( wxS( "attribute index leaves component group" ) )
3029 && aError.What().Contains( wxS( "controller 17" ) );
3030 } );
3031
3032 PADS_SCH_MODEL wrongSheet =
3033 parser.Parse( loadBinaryFixture( "placement_transform.sch" ), wxS( "placement-sheet.sch" ) );
3034 const uint32_t wrongClassSheet = wrongSheet.definitions.back().id.Value();
3035 BOOST_REQUIRE( std::ranges::none_of( wrongSheet.sheets,
3036 [&]( const MODEL_SHEET& aSheet )
3037 {
3038 return aSheet.id.Value() == wrongClassSheet;
3039 } ) );
3040 wrongSheet.placements[0].sheet = { SHEET_ID( wrongClassSheet ), wrongSheet.placements[0].source };
3041 BOOST_CHECK_EXCEPTION( wrongSheet.ValidateOrThrow(), IO_ERROR,
3042 []( const IO_ERROR& aError )
3043 {
3044 return aError.What().Contains( wxS( "unresolved placement sheet reference" ) );
3045 } );
3046
3047 std::vector<uint8_t> unknownTransform = loadBinaryFixture( "placement_transform.sch" );
3048 writeU16( unknownTransform, sheetControllerOffset( unknownTransform, 15 ) + 0x24, 123 );
3049 writeU16( unknownTransform, sheetControllerOffset( unknownTransform, 15 ) + 0x26, 9 );
3050 PADS_SCH_MODEL unknown = parser.Parse( unknownTransform, wxS( "placement-transform-enum.sch" ) );
3051 BOOST_CHECK_EQUAL( propertyValue( unknown.placements[0].properties, wxS( "raw_angle" ) ), wxS( "123" ) );
3052 BOOST_CHECK_EQUAL( propertyValue( unknown.placements[0].properties, wxS( "raw_mirror" ) ), wxS( "9" ) );
3053 BOOST_CHECK_EQUAL( std::ranges::count_if( unknown.diagnostics,
3054 []( const PARSER_DIAGNOSTIC& aDiagnostic )
3055 {
3056 return aDiagnostic.message.Contains(
3057 wxS( "unknown placement transform" ) );
3058 } ),
3059 2 );
3060
3061 std::vector<uint8_t> boundaryTransform = loadBinaryFixture( "placement_transform.sch" );
3062 writeU16( boundaryTransform, sheetControllerOffset( boundaryTransform, 15 ) + 0x24, 3600 );
3063 writeU16( boundaryTransform, sheetControllerOffset( boundaryTransform, 15 ) + 0x26, 1 );
3064 PADS_SCH_MODEL boundary = parser.Parse( boundaryTransform, wxS( "placement-transform-boundary.sch" ) );
3065 BOOST_CHECK_EQUAL( boundary.placements[0].angle, 0 );
3066 BOOST_CHECK_EQUAL( boundary.placements[0].mirrored, true );
3067 BOOST_CHECK_EQUAL( propertyValue( boundary.placements[0].properties, wxS( "raw_angle" ) ), wxS( "3600" ) );
3068 BOOST_CHECK_EQUAL( propertyValue( boundary.placements[0].properties, wxS( "raw_mirror" ) ), wxS( "1" ) );
3069 BOOST_CHECK_EQUAL( std::ranges::count_if( boundary.diagnostics,
3070 []( const PARSER_DIAGNOSTIC& aDiagnostic )
3071 {
3072 return aDiagnostic.message.Contains(
3073 wxS( "unknown placement transform" ) );
3074 } ),
3075 1 );
3076}
3077
3078
3079BOOST_AUTO_TEST_CASE( PlacementTransformOptions )
3080{
3082 PADS_SCH_MODEL model = parser.Parse( loadBinaryFixture( "placement_transform_options.sch" ),
3083 wxS( "placement_transform_options.sch" ) );
3084
3085 BOOST_REQUIRE_EQUAL( model.placements.size(), 8 );
3086
3087 const std::array<int, 4> angles = { 0, 900, 1800, 2700 };
3088
3089 for( size_t i = 0; i < model.placements.size(); ++i )
3090 {
3091 const MODEL_PLACEMENT& placement = model.placements[i];
3092 const uint16_t expectedMirror = i / angles.size();
3093
3094 BOOST_CHECK_EQUAL( placement.reference.text, wxString::Format( wxS( "R%zu" ), i + 1 ) );
3095 BOOST_CHECK_EQUAL( placement.angle, angles[i % angles.size()] );
3096 BOOST_CHECK_EQUAL( placement.mirrorFlags, expectedMirror );
3097 BOOST_CHECK_EQUAL( placement.mirrored, expectedMirror != 0 );
3098 BOOST_CHECK_EQUAL( propertyValue( placement.properties, wxS( "raw_angle" ) ),
3099 wxString::Format( wxS( "%d" ), angles[i % angles.size()] ) );
3100 BOOST_CHECK_EQUAL( propertyValue( placement.properties, wxS( "raw_mirror" ) ),
3101 wxString::Format( wxS( "%u" ), expectedMirror ) );
3102 }
3103}
3104
3105
3106BOOST_AUTO_TEST_CASE( PlacementSemanticSnapshot )
3107{
3108 PADS_SCH_BINARY_PARSER binaryParser;
3109 const std::array<std::string, 7> fixtures = {
3110 "placement_transform", "placement_transform_options", "fields", "connectors",
3111 "multigate", "field_justification", "pin_justification"
3112 };
3113
3114 for( const std::string& fixture : fixtures )
3115 {
3116 PADS_SCH_MODEL binary =
3117 binaryParser.Parse( loadBinaryFixture( fixture + ".sch" ), wxString::FromUTF8( fixture + ".sch" ) );
3119 BOOST_REQUIRE( ascii.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/" + fixture + ".txt" ) );
3120 BOOST_REQUIRE_EQUAL( binary.placements.size(), ascii.GetPartPlacements().size() );
3121
3122 for( size_t placementIndex = 0; placementIndex < binary.placements.size(); ++placementIndex )
3123 {
3124 const MODEL_PLACEMENT& binaryPlacement = binary.placements[placementIndex];
3125 const PADS_SCH::PART_PLACEMENT& asciiPlacement = ascii.GetPartPlacements()[placementIndex];
3126 BOOST_CHECK_EQUAL( binaryPlacement.reference.text, wxString::FromUTF8( asciiPlacement.reference ) );
3127 BOOST_CHECK_EQUAL( binaryPlacement.position.x, asciiPlacement.position.x * 2 );
3128 BOOST_CHECK_EQUAL( binaryPlacement.position.y, asciiPlacement.position.y * 2 );
3129 BOOST_CHECK_EQUAL( binaryPlacement.angle, std::lround( asciiPlacement.rotation * 10 ) );
3130 BOOST_CHECK_EQUAL( binaryPlacement.mirrorFlags, asciiPlacement.mirror_flags );
3131 BOOST_CHECK_EQUAL( binaryPlacement.sheet.id.Value(), binary.sheets[0].id.Value() );
3132 auto part = std::ranges::find_if( binary.partTypes,
3133 [&]( const MODEL_PART_TYPE& aPart )
3134 {
3135 return aPart.id == binaryPlacement.partType.id;
3136 } );
3137 BOOST_REQUIRE( part != binary.partTypes.end() );
3138 BOOST_CHECK_EQUAL( part->name.text, wxString::FromUTF8( asciiPlacement.part_type ) );
3139 BOOST_REQUIRE( binaryPlacement.gate.has_value() );
3140 auto gate = std::ranges::find_if( part->gates,
3141 [&]( const MODEL_GATE& aGate )
3142 {
3143 return aGate.id == binaryPlacement.gate->id;
3144 } );
3145 BOOST_REQUIRE( gate != part->gates.end() );
3146 BOOST_CHECK_EQUAL( binaryPlacement.unit, static_cast<uint32_t>( asciiPlacement.gate_index + 1 ) );
3147 auto definition = std::ranges::find_if( binary.definitions,
3148 [&]( const MODEL_SYMBOL_DEFINITION& aDefinition )
3149 {
3150 return aDefinition.id == binaryPlacement.definition.id;
3151 } );
3152 BOOST_REQUIRE( definition != binary.definitions.end() );
3153 BOOST_CHECK_EQUAL( definition->name.text, wxString::FromUTF8( asciiPlacement.decal_name ) );
3154 BOOST_CHECK( !propertyValue( binaryPlacement.properties, wxS( "decal_handle" ) ).empty() );
3155 BOOST_REQUIRE_GE( binaryPlacement.fields.size(), asciiPlacement.attributes.size() );
3156
3157 if( !asciiPlacement.pin_overrides.empty() )
3158 {
3159 BOOST_REQUIRE_EQUAL( binaryPlacement.pins.size(), asciiPlacement.pin_overrides.size() );
3160
3161 for( size_t pinIndex = 0; pinIndex < binaryPlacement.pins.size(); ++pinIndex )
3162 {
3163 BOOST_CHECK_EQUAL( binaryPlacement.pins[pinIndex].numberAngle,
3164 asciiPlacement.pin_overrides[pinIndex].angle * 10 );
3165 BOOST_CHECK_EQUAL( binaryPlacement.pins[pinIndex].numberJustification,
3166 asciiPlacement.pin_overrides[pinIndex].justification );
3167 }
3168 }
3169
3170 for( size_t fieldIndex = 0; fieldIndex < asciiPlacement.attributes.size(); ++fieldIndex )
3171 {
3172 const MODEL_FIELD& binaryField = binaryPlacement.fields[fieldIndex];
3173 const PADS_SCH::PART_ATTRIBUTE& asciiField = asciiPlacement.attributes[fieldIndex];
3174 BOOST_CHECK_EQUAL( binaryField.name.text, wxString::FromUTF8( asciiField.name ) );
3175 const wxString asciiValue = fieldIndex == 0 ? wxString::FromUTF8( asciiPlacement.reference )
3176 : fieldIndex == 1 ? wxString::FromUTF8( asciiPlacement.part_type )
3177 : wxString::FromUTF8( asciiField.value );
3178 BOOST_CHECK_EQUAL( binaryField.value.text, asciiValue );
3179 BOOST_CHECK_EQUAL( binaryField.position.x, asciiField.position.x * 2 );
3180 BOOST_CHECK_EQUAL( binaryField.position.y, asciiField.position.y * 2 );
3181 BOOST_CHECK_EQUAL( binaryField.angle, std::lround( asciiField.rotation * 10 ) );
3182 BOOST_CHECK_EQUAL( binaryField.presentation.height, asciiField.height );
3183 BOOST_CHECK_EQUAL( binaryField.presentation.width, asciiField.width );
3184 const bool expectedVisible = fieldIndex == 0 ? binaryPlacement.referenceVisible
3185 : fieldIndex == 1 ? binaryPlacement.partTypeVisible
3186 : asciiField.visible;
3187 BOOST_CHECK_EQUAL( binaryField.presentation.visible, expectedVisible );
3188 BOOST_CHECK_EQUAL( binaryField.presentation.font.text, wxString::FromUTF8( asciiField.font_name ) );
3189 BOOST_CHECK_EQUAL( binaryField.presentation.bold,
3190 wxString::FromUTF8( asciiField.font_name ).StartsWith( wxS( "Bold " ) ) );
3192 wxString::FromUTF8( asciiField.font_name ).StartsWith( wxS( "Italic " ) ) );
3193
3194 const uint16_t justification = asciiField.justification;
3195 const uint16_t horizontal = justification >= 8 ? justification - 8
3196 : justification >= 2 ? justification - 2
3197 : justification;
3198 const MODEL_JUSTIFICATION expectedHorizontal = horizontal == 1 ? MODEL_JUSTIFICATION::RIGHT
3199 : horizontal == 4 ? MODEL_JUSTIFICATION::CENTER
3201 const MODEL_JUSTIFICATION expectedVertical = asciiField.justification >= 8 ? MODEL_JUSTIFICATION::CENTER
3202 : asciiField.justification >= 2
3205 BOOST_CHECK( binaryField.presentation.horizontalJustification == expectedHorizontal );
3206 BOOST_CHECK( binaryField.presentation.verticalJustification == expectedVertical );
3207
3208 if( fieldIndex >= 2 )
3209 {
3210 BOOST_CHECK_EQUAL( propertyValue( binaryField.properties, wxS( "display_flags" ) ),
3211 wxString::Format( wxS( "%d" ), asciiField.visibility ) );
3212 const wxString expectedAttributeIndex = fixture == "fields" || fixture == "field_justification"
3213 || fixture == "placement_transform_options"
3214 ? wxS( "10" )
3215 : wxS( "65535" );
3216 BOOST_CHECK_EQUAL( propertyValue( binaryField.properties, wxS( "component_attribute_index" ) ),
3217 expectedAttributeIndex );
3218 }
3219 }
3220 }
3221 }
3222}
3223
3224
3226{
3229 parser.Parse( loadBinaryFixture( "connectivity_topology.sch" ), wxS( "connectivity_topology.sch" ) );
3230
3231 BOOST_REQUIRE_EQUAL( model.nets.size(), 9 );
3232 BOOST_CHECK_EQUAL( itemNamed( model.nets, wxS( "CROSS_H" ) ).connections.size(), 1 );
3233 BOOST_CHECK_EQUAL( itemNamed( model.nets, wxS( "CROSS_V" ) ).connections.size(), 1 );
3234 BOOST_REQUIRE_EQUAL( itemNamed( model.nets, wxS( "T_NET" ) ).connections.size(), 3 );
3235
3236 const MODEL_CONNECTION& horizontal = itemNamed( model.nets, wxS( "CROSS_H" ) ).connections.front();
3237 BOOST_REQUIRE_EQUAL( horizontal.vertices.size(), 2 );
3238 BOOST_REQUIRE_EQUAL( horizontal.endpoints.size(), 2 );
3239 BOOST_CHECK_EQUAL( horizontal.vertices[0].x, 6000 );
3240 BOOST_CHECK_EQUAL( horizontal.vertices[0].y, 6000 );
3241 BOOST_CHECK_EQUAL( horizontal.vertices[1].x, 14000 );
3242 BOOST_CHECK_EQUAL( horizontal.vertices[1].y, 6000 );
3243 BOOST_CHECK( horizontal.endpoints[0].kind == MODEL_ENDPOINT_KIND::POINT );
3244 BOOST_CHECK( horizontal.endpoints[1].kind == MODEL_ENDPOINT_KIND::POINT );
3245
3246 PADS_SCH_MODEL minimal = parser.Parse( loadMinimalV13(), wxS( "minimal_v13.sch" ) );
3247 BOOST_REQUIRE_EQUAL( minimal.nets.size(), 1 );
3248 BOOST_REQUIRE_EQUAL( minimal.nets[0].connections.size(), 1 );
3249 BOOST_CHECK( minimal.nets[0].connections[0].endpoints[0].kind == MODEL_ENDPOINT_KIND::PIN );
3250 BOOST_REQUIRE( minimal.nets[0].connections[0].endpoints[0].placement );
3251 BOOST_REQUIRE( minimal.nets[0].connections[0].endpoints[0].pin );
3252 BOOST_CHECK_EQUAL( minimal.nets[0].connections[0].endpoints[0].placement->id.Value(),
3253 minimal.placements[0].id.Value() );
3254 BOOST_CHECK_EQUAL( minimal.nets[0].connections[0].endpoints[0].pin->id.Value(),
3255 minimal.placements[0].pins[0].id.Value() );
3256}
3257
3258
3259BOOST_AUTO_TEST_CASE( LabelsAndPower )
3260{
3263 parser.Parse( loadBinaryFixture( "connectivity_topology.sch" ), wxS( "connectivity_topology.sch" ) );
3264
3265 BOOST_REQUIRE_EQUAL( model.labels.size(), 16 );
3266 BOOST_CHECK_EQUAL( std::ranges::count( model.labels, MODEL_LABEL_KIND::LOCAL, &MODEL_LABEL::kind ), 13 );
3267 BOOST_CHECK_EQUAL( std::ranges::count( model.labels, MODEL_LABEL_KIND::GROUND, &MODEL_LABEL::kind ), 1 );
3268 BOOST_CHECK_EQUAL( std::ranges::count( model.labels, MODEL_LABEL_KIND::POWER, &MODEL_LABEL::kind ), 1 );
3269 BOOST_CHECK_EQUAL( std::ranges::count( model.labels, MODEL_LABEL_KIND::GLOBAL, &MODEL_LABEL::kind ), 1 );
3270
3271 auto global = std::ranges::find_if( model.labels,
3272 []( const MODEL_LABEL& aLabel )
3273 {
3274 return aLabel.kind == MODEL_LABEL_KIND::GLOBAL;
3275 } );
3276 BOOST_REQUIRE( global != model.labels.end() );
3277 BOOST_CHECK_EQUAL( global->text.text, wxS( "GLOBAL_LINK" ) );
3278 BOOST_CHECK_EQUAL( global->position.x, 16000 );
3279 BOOST_CHECK_EQUAL( global->position.y, 12000 );
3280 BOOST_CHECK_EQUAL( global->angle, 0 );
3281
3282 PADS_SCH_MODEL multisheet =
3283 parser.Parse( loadBinaryFixture( "multisheet_connectivity.sch" ), wxS( "multisheet_connectivity.sch" ) );
3284 BOOST_CHECK_EQUAL( std::ranges::count_if( multisheet.labels,
3285 []( const MODEL_LABEL& aLabel )
3286 {
3287 return aLabel.kind == MODEL_LABEL_KIND::GLOBAL
3288 && aLabel.text.text == wxS( "CROSS_SHEET" );
3289 } ),
3290 2 );
3291 std::vector<const MODEL_LABEL*> crossSheetLabels;
3292
3293 for( const MODEL_LABEL& label : multisheet.labels )
3294 {
3295 if( label.kind == MODEL_LABEL_KIND::GLOBAL && label.text.text == wxS( "CROSS_SHEET" ) )
3296 crossSheetLabels.push_back( &label );
3297 }
3298
3299 BOOST_REQUIRE_EQUAL( crossSheetLabels.size(), 2 );
3300 std::ranges::sort( crossSheetLabels,
3301 []( const MODEL_LABEL* aLeft, const MODEL_LABEL* aRight )
3302 {
3303 return aLeft->source.sheet < aRight->source.sheet;
3304 } );
3305 BOOST_REQUIRE_EQUAL( crossSheetLabels[0]->linkedSheets.size(), 1 );
3306 BOOST_REQUIRE_EQUAL( crossSheetLabels[1]->linkedSheets.size(), 1 );
3307 BOOST_CHECK_EQUAL( crossSheetLabels[0]->source.sheet, 0 );
3308 BOOST_CHECK_EQUAL( crossSheetLabels[1]->source.sheet, 1 );
3309 BOOST_CHECK_EQUAL( crossSheetLabels[0]->linkedSheets[0].id.Value(), multisheet.sheets[1].id.Value() );
3310 BOOST_CHECK_EQUAL( crossSheetLabels[1]->linkedSheets[0].id.Value(), multisheet.sheets[0].id.Value() );
3311}
3312
3313
3315{
3318 parser.Parse( loadBinaryFixture( "connectivity_topology.sch" ), wxS( "connectivity_topology.sch" ) );
3319
3320 BOOST_REQUIRE_EQUAL( model.buses.size(), 1 );
3321 const MODEL_BUS& bus = model.buses.front();
3322 BOOST_CHECK_EQUAL( bus.name.text, wxS( "BATCHC_BUS_ALIAS" ) );
3323 BOOST_REQUIRE_EQUAL( bus.vertices.size(), 3 );
3324 BOOST_CHECK_EQUAL( bus.vertices[0].x, 6000 );
3325 BOOST_CHECK_EQUAL( bus.vertices[0].y, 14000 );
3326 BOOST_CHECK_EQUAL( bus.vertices[2].x, 14000 );
3327 BOOST_CHECK_EQUAL( bus.vertices[2].y, 10000 );
3328 BOOST_REQUIRE_EQUAL( bus.entries.size(), 3 );
3329 BOOST_REQUIRE_EQUAL( bus.aliases.size(), 1 );
3330 BOOST_REQUIRE_EQUAL( bus.memberNets.size(), 3 );
3331 BOOST_CHECK_EQUAL( bus.aliases[0].text, wxS( "BATCHC_BUS_ALIAS" ) );
3332 BOOST_CHECK_EQUAL( bus.entries[0].position.x, 8000 );
3333 BOOST_CHECK_EQUAL( bus.entries[1].position.x, 10000 );
3334 BOOST_CHECK_EQUAL( bus.entries[2].position.x, 12000 );
3335 BOOST_CHECK_EQUAL( itemNamed( model.nets, wxS( "BUS0" ) ).id.Value(), bus.memberNets[0].id.Value() );
3336 BOOST_CHECK_EQUAL( itemNamed( model.nets, wxS( "BUS1" ) ).id.Value(), bus.memberNets[1].id.Value() );
3337 BOOST_CHECK_EQUAL( itemNamed( model.nets, wxS( "BUS2" ) ).id.Value(), bus.memberNets[2].id.Value() );
3338}
3339
3340
3342{
3344 PADS_SCH_MODEL model = parser.Parse( loadBinaryFixture( "page_graphics.sch" ), wxS( "page_graphics.sch" ) );
3345
3346 BOOST_REQUIRE_EQUAL( model.sheets.size(), 1 );
3347 BOOST_REQUIRE_EQUAL( model.worksheets.size(), 1u );
3348 BOOST_REQUIRE_EQUAL( model.worksheets[0].graphics.size(), 86u );
3349 BOOST_REQUIRE_EQUAL( model.graphics.size(), 6u );
3350 BOOST_CHECK_EQUAL( std::ranges::count_if( model.worksheets[0].graphics,
3351 []( const MODEL_GRAPHIC& aGraphic )
3352 {
3353 return aGraphic.kind == MODEL_GRAPHIC_KIND::LINE;
3354 } ),
3355 31 );
3356 BOOST_CHECK_GE( std::ranges::count_if( model.worksheets[0].graphics,
3357 []( const MODEL_GRAPHIC& aGraphic )
3358 {
3359 return aGraphic.kind == MODEL_GRAPHIC_KIND::POLYLINE;
3360 } ),
3361 1 );
3362 BOOST_CHECK_EQUAL( std::ranges::count_if( model.worksheets[0].graphics,
3363 []( const MODEL_GRAPHIC& aGraphic )
3364 {
3365 return aGraphic.kind == MODEL_GRAPHIC_KIND::TEXT;
3366 } ),
3367 52 );
3368 auto titleText = std::ranges::find_if( model.worksheets[0].graphics,
3369 []( const MODEL_GRAPHIC& aGraphic )
3370 {
3371 return aGraphic.kind == MODEL_GRAPHIC_KIND::TEXT
3372 && aGraphic.text.text == wxS( "TITLE:" );
3373 } );
3374 BOOST_REQUIRE( titleText != model.worksheets[0].graphics.end() );
3375 BOOST_CHECK_EQUAL( titleText->presentation.height, 100 );
3376 BOOST_CHECK_EQUAL( titleText->presentation.width, 10 );
3377 auto titleGroup = std::ranges::find_if( titleText->properties,
3378 []( const SOURCE_PROPERTY& aProperty )
3379 {
3380 return aProperty.name.text == wxS( "page_graphic_group" );
3381 } );
3382 BOOST_REQUIRE( titleGroup != titleText->properties.end() );
3383 const uint16_t titleTextCount =
3384 readU16( loadBinaryFixture( "page_graphics.sch" ), titleGroup->source.absoluteOffset + 64 );
3385 BOOST_REQUIRE_GT( titleTextCount, 1u );
3386
3387 std::vector<uint8_t> repeatedPredecessor = loadBinaryFixture( "page_graphics.sch" );
3388 const uint16_t lastTextRecord = readU16( repeatedPredecessor, titleGroup->source.absoluteOffset + 66 );
3389 writeU16( repeatedPredecessor, sheetControllerOffset( repeatedPredecessor, 1 ) + lastTextRecord * 32 + 24,
3390 lastTextRecord );
3391 BOOST_CHECK_EXCEPTION( parser.Parse( repeatedPredecessor, wxS( "page-text-cycle.sch" ) ), IO_ERROR,
3392 []( const IO_ERROR& aError )
3393 {
3394 return aError.What().Contains(
3395 wxS( "page-text predecessor repeats controller 1 record" ) );
3396 } );
3397
3398 std::vector<uint8_t> noncontiguousPredecessor = loadBinaryFixture( "page_graphics.sch" );
3399 const size_t textController = sheetControllerOffset( noncontiguousPredecessor, 1 );
3400 const uint16_t predecessor1 = readU16( noncontiguousPredecessor, textController + lastTextRecord * 32 + 24 );
3401 const uint16_t predecessor2 = readU16( noncontiguousPredecessor, textController + predecessor1 * 32 + 24 );
3402 const uint16_t predecessor3 = readU16( noncontiguousPredecessor, textController + predecessor2 * 32 + 24 );
3403 writeU16( noncontiguousPredecessor, textController + lastTextRecord * 32 + 24, predecessor2 );
3404 writeU16( noncontiguousPredecessor, textController + predecessor2 * 32 + 24, predecessor1 );
3405 writeU16( noncontiguousPredecessor, textController + predecessor1 * 32 + 24, predecessor3 );
3406 PADS_SCH_MODEL noncontiguous = parser.Parse( noncontiguousPredecessor, wxS( "page-text-noncontiguous.sch" ) );
3407 std::vector<size_t> recoveredRecords;
3408
3409 const MODEL_WORKSHEET& noncontiguousWorksheet = itemNamed( noncontiguous.worksheets, wxS( "SIZEB" ) );
3410
3411 for( const MODEL_GRAPHIC& graphic : noncontiguousWorksheet.graphics )
3412 {
3413 if( graphic.kind == MODEL_GRAPHIC_KIND::TEXT
3414 && propertyValue( graphic.properties, wxS( "page_graphic_group" ) ) == titleGroup->value.text )
3415 {
3416 recoveredRecords.push_back( graphic.source.recordIndex );
3417 }
3418 }
3419
3420 BOOST_REQUIRE_EQUAL( recoveredRecords.size(), titleTextCount );
3421 BOOST_CHECK_EQUAL( recoveredRecords[recoveredRecords.size() - 3], predecessor1 );
3422 BOOST_CHECK_EQUAL( recoveredRecords[recoveredRecords.size() - 2], predecessor2 );
3423 BOOST_CHECK_EQUAL( recoveredRecords.back(), lastTextRecord );
3424
3425 std::vector<const MODEL_PAGE_GRAPHIC*> custom;
3426
3427 for( const MODEL_PAGE_GRAPHIC& graphic : model.graphics )
3428 {
3429 if( propertyValue( graphic.graphic.properties, wxS( "page_graphic_group" ) ) == wxS( "BATCHB_PAGE_GRAPHICS" ) )
3430 {
3431 custom.push_back( &graphic );
3432 }
3433 }
3434
3435 BOOST_REQUIRE_EQUAL( custom.size(), 6 );
3436 auto customKind = [&]( MODEL_GRAPHIC_KIND aKind )
3437 {
3438 return std::ranges::find_if( custom,
3439 [&]( const MODEL_PAGE_GRAPHIC* aGraphic )
3440 {
3441 return aGraphic->graphic.kind == aKind;
3442 } );
3443 };
3444 auto line = customKind( MODEL_GRAPHIC_KIND::LINE );
3445 BOOST_REQUIRE( line != custom.end() );
3446 BOOST_REQUIRE_EQUAL( ( *line )->graphic.points.size(), 2 );
3447 BOOST_CHECK_EQUAL( ( *line )->graphic.points[0].x, 12000 );
3448 BOOST_CHECK_EQUAL( ( *line )->graphic.points[1].x, 13600 );
3449 BOOST_CHECK_EQUAL( ( *line )->graphic.strokeWidth, 20 );
3450 BOOST_CHECK( ( *line )->graphic.lineStyle == MODEL_LINE_STYLE::SOLID );
3451 std::vector<uint8_t> unprovedLineStyle = loadBinaryFixture( "page_graphics.sch" );
3452 unprovedLineStyle[( *line )->graphic.source.absoluteOffset + 1] = 2;
3453 PADS_SCH_MODEL unprovedStyleModel = parser.Parse( unprovedLineStyle, wxS( "unproved-line-style.sch" ) );
3454 auto unprovedStyleGraphic = std::ranges::find_if( unprovedStyleModel.graphics,
3455 [&]( const MODEL_PAGE_GRAPHIC& aGraphic )
3456 {
3457 return aGraphic.graphic.source.recordIndex
3458 == ( *line )->graphic.source.recordIndex;
3459 } );
3460 BOOST_REQUIRE( unprovedStyleGraphic != unprovedStyleModel.graphics.end() );
3461 BOOST_CHECK( unprovedStyleGraphic->graphic.lineStyle == MODEL_LINE_STYLE::DEFAULT );
3462 BOOST_CHECK_EQUAL( std::ranges::count_if( unprovedStyleModel.diagnostics,
3463 []( const PARSER_DIAGNOSTIC& aDiagnostic )
3464 {
3465 return aDiagnostic.message.Contains(
3466 wxS( "page graphic line style 2" ) );
3467 } ),
3468 1 );
3469 auto circle = customKind( MODEL_GRAPHIC_KIND::CIRCLE );
3470 BOOST_REQUIRE( circle != custom.end() );
3471 BOOST_CHECK_EQUAL( ( *circle )->graphic.points[0].x, 15200 );
3472 BOOST_CHECK_EQUAL( ( *circle )->graphic.points[1].y, 12800 );
3473 auto arc = customKind( MODEL_GRAPHIC_KIND::ARC );
3474 BOOST_REQUIRE( arc != custom.end() );
3475 BOOST_CHECK_EQUAL( ( *arc )->graphic.arcSweepAngle, 1800 );
3476 BOOST_CHECK( ( *arc )->graphic.arcClockwise );
3477 BOOST_CHECK_EQUAL( ( *arc )->graphic.arcCenter.x, 16800 );
3478 BOOST_CHECK_EQUAL( ( *arc )->graphic.arcCenter.y, 12400 );
3479 auto text = customKind( MODEL_GRAPHIC_KIND::TEXT );
3480 BOOST_REQUIRE( text != custom.end() );
3481 BOOST_CHECK_EQUAL( ( *text )->graphic.text.text, wxS( "EMBEDDED PAGE TEXT" ) );
3482 BOOST_CHECK_EQUAL( ( *text )->graphic.points[0].y, 13200 );
3483 BOOST_CHECK_EQUAL( ( *text )->graphic.presentation.font.text, wxS( "Default Font" ) );
3484 BOOST_CHECK_EQUAL( ( *text )->graphic.presentation.height, 150 );
3485 BOOST_CHECK_EQUAL( ( *text )->graphic.presentation.width, 10 );
3487 BOOST_REQUIRE( ascii.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/page_graphics.txt" ) );
3488 auto pageRecords = []( std::vector<CANONICAL_SEMANTIC_RECORD> aRecords )
3489 {
3490 std::erase_if( aRecords,
3491 []( const CANONICAL_SEMANTIC_RECORD& aRecord )
3492 {
3493 auto group = aRecord.properties.find( "page_graphic_group" );
3494 return aRecord.kind != CANONICAL_KIND::GRAPHIC || group == aRecord.properties.end()
3495 || group->second.value != CANONICAL_VALUE( std::string( "BATCHB_PAGE_GRAPHICS" ) );
3496 } );
3497 return aRecords;
3498 };
3499 auto expectedPage = pageRecords( normalizeAsciiModel( ascii ) );
3500 auto actualPage = pageRecords( normalizeBinaryModel( model ) );
3501 BOOST_CHECK( snapshotsMatch( expectedPage, actualPage,
3502 { { "preserved_graphic_presentation", PROPERTY_DISPOSITION::PRESERVED } } ) );
3503
3504 std::vector<uint8_t> privateStroke = loadBinaryFixture( "page_graphics.sch" );
3505 const size_t privatePiece = sheetControllerOffset( privateStroke, 4 ) + 81 * 6;
3506 writeU16( privateStroke, privatePiece + 4, 0xEF03 );
3507 PADS_SCH_MODEL preservedStroke = parser.Parse( privateStroke, wxS( "private-stroke.sch" ) );
3508 auto privateGraphic = std::ranges::find_if( preservedStroke.graphics,
3509 []( const MODEL_PAGE_GRAPHIC& aGraphic )
3510 {
3511 return aGraphic.graphic.source.controller == 4
3512 && aGraphic.graphic.source.recordIndex == 81;
3513 } );
3514 BOOST_REQUIRE( privateGraphic != preservedStroke.graphics.end() );
3515 BOOST_CHECK_EQUAL( privateGraphic->graphic.strokeWidth, 0 );
3516 auto rawStroke = std::ranges::find_if( privateGraphic->graphic.properties,
3517 []( const SOURCE_PROPERTY& aProperty )
3518 {
3519 return aProperty.name.text == wxS( "unsupported_graphic_stroke_width" );
3520 } );
3521 BOOST_REQUIRE( rawStroke != privateGraphic->graphic.properties.end() );
3522 BOOST_CHECK_EQUAL( rawStroke->value.text, wxS( "3" ) );
3523 BOOST_CHECK( rawStroke->disposition == PROPERTY_DISPOSITION::UNSUPPORTED );
3524 BOOST_CHECK_EQUAL( rawStroke->source.controller, 4 );
3525 BOOST_CHECK_EQUAL( rawStroke->source.recordIndex, 81 );
3526 BOOST_CHECK_EQUAL( rawStroke->source.absoluteOffset, privatePiece + 4 );
3527 BOOST_CHECK_EQUAL( rawStroke->source.length, 1 );
3528 BOOST_CHECK_EQUAL( propertyValue( privateGraphic->graphic.properties, wxS( "preserved_graphic_presentation" ) ),
3529 wxS( "239" ) );
3530 BOOST_CHECK_EQUAL( std::ranges::count_if( preservedStroke.diagnostics,
3531 [&]( const PARSER_DIAGNOSTIC& aDiagnostic )
3532 {
3533 return aDiagnostic.source == rawStroke->source && aDiagnostic.property
3534 && aDiagnostic.property->name == rawStroke->name.text
3535 && aDiagnostic.property->disposition == rawStroke->disposition;
3536 } ),
3537 1u );
3538}
3539
3540
3541BOOST_AUTO_TEST_CASE( NativeWorksheetRecognition )
3542{
3544 const PADS_SCH_MODEL minimal = parser.Parse( loadBinaryFixture( "minimal_v13.sch" ), wxS( "minimal_v13.sch" ) );
3545
3546 BOOST_REQUIRE_EQUAL( minimal.worksheets.size(), 1u );
3547 BOOST_CHECK_EQUAL( minimal.worksheets[0].name.text, wxS( "SIZEB" ) );
3548 BOOST_CHECK_EQUAL( minimal.worksheets[0].graphics.size(), 86u );
3549 BOOST_CHECK( minimal.graphics.empty() );
3550
3551 const PADS_SCH_MODEL page = parser.Parse( loadBinaryFixture( "page_graphics.sch" ), wxS( "page_graphics.sch" ) );
3552 BOOST_REQUIRE_EQUAL( page.worksheets.size(), 1u );
3553 BOOST_CHECK_EQUAL( page.worksheets[0].name.text, wxS( "SIZEB" ) );
3554 BOOST_CHECK_EQUAL( page.worksheets[0].graphics.size(), 86u );
3555 BOOST_REQUIRE_EQUAL( page.graphics.size(), 6u );
3556 BOOST_CHECK( std::ranges::all_of( page.graphics,
3557 []( const MODEL_PAGE_GRAPHIC& aGraphic )
3558 {
3559 return propertyValue( aGraphic.graphic.properties,
3560 wxS( "page_graphic_group" ) )
3561 == wxS( "BATCHB_PAGE_GRAPHICS" );
3562 } ) );
3563
3564 std::vector<uint8_t> distinct = loadBinaryFixture( "page_graphics.sch" );
3565 const std::string duplicateName = "BATCHB_SIZEB";
3566 auto duplicateRecord = std::search( distinct.begin(), distinct.end(), duplicateName.begin(), duplicateName.end() );
3567 BOOST_REQUIRE( duplicateRecord != distinct.end() );
3568 const size_t duplicateOffset = std::distance( distinct.begin(), duplicateRecord );
3569 const size_t firstVertex = readU32( distinct, duplicateOffset + 0x34 );
3570 const size_t vertexOffset = sheetControllerOffset( distinct, 5 ) + firstVertex * 8;
3571 writeU16( distinct, vertexOffset, readU16( distinct, vertexOffset ) + 2 );
3572
3573 const PADS_SCH_MODEL distinctPage = parser.Parse( distinct, wxS( "distinct-page-graphics.sch" ) );
3574 BOOST_REQUIRE_EQUAL( distinctPage.worksheets.size(), 1u );
3575 BOOST_CHECK_EQUAL( distinctPage.worksheets[0].name.text, wxS( "SIZEB" ) );
3576 BOOST_CHECK_EQUAL( distinctPage.graphics.size(), 92u );
3577 BOOST_CHECK_EQUAL( std::ranges::count_if( distinctPage.graphics,
3578 []( const MODEL_PAGE_GRAPHIC& aGraphic )
3579 {
3580 return propertyValue( aGraphic.graphic.properties,
3581 wxS( "page_graphic_group" ) )
3582 == wxS( "BATCHB_SIZEB" );
3583 } ),
3584 86u );
3585 BOOST_CHECK_EQUAL( std::ranges::count_if( distinctPage.diagnostics,
3586 []( const PARSER_DIAGNOSTIC& aDiagnostic )
3587 {
3588 return aDiagnostic.message.Contains(
3589 wxS( "distinct worksheet layout preserved" ) );
3590 } ),
3591 1u );
3592}
3593
3594
3595BOOST_AUTO_TEST_CASE( ConnectivityHandleErrors )
3596{
3598 std::vector<uint8_t> wrongEndpoint = loadBinaryFixture( "connectivity_topology.sch" );
3599 const size_t connection = sheetControllerOffset( wrongEndpoint, 21 );
3600 writeU16( wrongEndpoint, connection + 12, 0x1000 );
3601 BOOST_CHECK_EXCEPTION( parser.Parse( wrongEndpoint, wxS( "wrong-endpoint.sch" ) ), IO_ERROR,
3602 [&]( const IO_ERROR& aError )
3603 {
3604 return aError.What().Contains( wxS( "controller 21" ) )
3605 && aError.What().Contains( wxS( "wrong endpoint object class" ) );
3606 } );
3607
3608 std::vector<uint8_t> unresolvedEndpoint = loadBinaryFixture( "connectivity_topology.sch" );
3609 writeU16( unresolvedEndpoint, connection + 12, 0x2FFF );
3610 BOOST_CHECK_EXCEPTION( parser.Parse( unresolvedEndpoint, wxS( "unresolved-endpoint.sch" ) ), IO_ERROR,
3611 [&]( const IO_ERROR& aError )
3612 {
3613 return aError.What().Contains( wxS( "controller 21" ) )
3614 && aError.What().Contains( wxS( "unresolved off-page endpoint" ) );
3615 } );
3616
3617 std::vector<uint8_t> unresolvedPlacement = loadBinaryFixture( "minimal_v13.sch" );
3618 const size_t minimalConnection = sheetControllerOffset( unresolvedPlacement, 21 );
3619 BOOST_REQUIRE_EQUAL( readU16( unresolvedPlacement, minimalConnection + 12 ), 0u );
3620 writeU16( unresolvedPlacement, minimalConnection + 12, 0x0FFF );
3621 BOOST_CHECK_EXCEPTION( parser.Parse( unresolvedPlacement, wxS( "unresolved-placement.sch" ) ), IO_ERROR,
3622 []( const IO_ERROR& aError )
3623 {
3624 return aError.What().Contains( wxS( "controller 21" ) )
3625 && aError.What().Contains( wxS( "unresolved placement endpoint" ) );
3626 } );
3627
3628 std::vector<uint8_t> duplicateMembership = loadBinaryFixture( "connectivity_topology.sch" );
3629 const size_t globalNets = outerControllerOffset( duplicateMembership, 8 );
3630 writeU32( duplicateMembership, globalNets + 2 * 88 + 4, 1 );
3631 BOOST_CHECK_EXCEPTION( parser.Parse( duplicateMembership, wxS( "duplicate-membership.sch" ) ), IO_ERROR,
3632 []( const IO_ERROR& aError )
3633 {
3634 return aError.What().Contains( wxS( "controller 8" ) )
3635 && aError.What().Contains( wxS( "duplicate net sheet-membership" ) );
3636 } );
3637
3638 std::vector<uint8_t> wrongNetClass = loadBinaryFixture( "connectivity_topology.sch" );
3639 const size_t wrongNetConnection = sheetControllerOffset( wrongNetClass, 21 );
3640 writeU32( wrongNetClass, wrongNetConnection + 8, 0 );
3641 BOOST_CHECK_EXCEPTION( parser.Parse( wrongNetClass, wxS( "wrong-net-class.sch" ) ), IO_ERROR,
3642 []( const IO_ERROR& aError )
3643 {
3644 return aError.What().Contains( wxS( "controller 21" ) )
3645 && aError.What().Contains( wxS( "wrong or unresolved object class" ) );
3646 } );
3647
3648 std::vector<uint8_t> wrongBusClass = loadBinaryFixture( "connectivity_topology.sch" );
3649 const size_t wrongBus = sheetControllerOffset( wrongBusClass, 18 );
3650 writeU32( wrongBusClass, wrongBus + 8, 1 );
3651 BOOST_CHECK_EXCEPTION( parser.Parse( wrongBusClass, wxS( "wrong-bus-class.sch" ) ), IO_ERROR,
3652 []( const IO_ERROR& aError )
3653 {
3654 return aError.What().Contains( wxS( "controller 18" ) )
3655 && aError.What().Contains( wxS( "wrong or unresolved object class" ) );
3656 } );
3657
3658 std::vector<uint8_t> wrongSheet = loadBinaryFixture( "multisheet_connectivity.sch" );
3659 const size_t sheetMembership = outerControllerOffset( wrongSheet, 4 );
3660 writeU16( wrongSheet, sheetMembership + 2, 2 );
3661 BOOST_CHECK_EXCEPTION( parser.Parse( wrongSheet, wxS( "wrong-net-sheet.sch" ) ), IO_ERROR,
3662 []( const IO_ERROR& aError )
3663 {
3664 return aError.What().Contains( wxS( "controller 8" ) )
3665 && aError.What().Contains( wxS( "wrong sheet object class" ) );
3666 } );
3667
3668 std::vector<uint8_t> busCycle = loadBinaryFixture( "connectivity_topology.sch" );
3669 const size_t bus = sheetControllerOffset( busCycle, 18 );
3670 const size_t offpage = sheetControllerOffset( busCycle, 20 );
3671 const uint16_t tailHandle = readU16( busCycle, bus + 24 );
3672 BOOST_REQUIRE_EQUAL( tailHandle >> 12, 2 );
3673 const uint16_t tailRecord = tailHandle & 0x0FFF;
3674 writeU16( busCycle, offpage + tailRecord * 32 + 4, tailHandle );
3675 BOOST_CHECK_EXCEPTION( parser.Parse( busCycle, wxS( "bus-entry-cycle.sch" ) ), IO_ERROR,
3676 []( const IO_ERROR& aError )
3677 {
3678 return aError.What().Contains( wxS( "controller 18" ) )
3679 && aError.What().Contains( wxS( "cyclic bus-entry" ) );
3680 } );
3681
3682 std::vector<uint8_t> unresolvedLabelConnection = loadBinaryFixture( "connectivity_topology.sch" );
3683 const size_t label = sheetControllerOffset( unresolvedLabelConnection, 20 );
3684 writeU16( unresolvedLabelConnection, label + 8, 0xFFFF );
3685 BOOST_CHECK_EXCEPTION( parser.Parse( unresolvedLabelConnection, wxS( "unresolved-label-connection.sch" ) ),
3686 IO_ERROR,
3687 []( const IO_ERROR& aError )
3688 {
3689 return aError.What().Contains( wxS( "controller 20" ) )
3690 && aError.What().Contains( wxS( "off-page net handle leaves controller 21" ) );
3691 } );
3692
3693 std::vector<uint8_t> wrongJunctionOwner = loadBinaryFixture( "connectivity_topology.sch" );
3694 const size_t junction = sheetControllerOffset( wrongJunctionOwner, 19 );
3695 const uint16_t junctionOwner = readU16( wrongJunctionOwner, junction + 8 );
3696 BOOST_REQUIRE_GT( sheetControllerCount( wrongJunctionOwner, 21 ), 1 );
3697 writeU16( wrongJunctionOwner, junction + 8, junctionOwner == 0 ? 1 : 0 );
3698 const wxString junctionOwnerOffset =
3699 wxString::Format( wxS( "offset 0x%llX" ), static_cast<unsigned long long>( junction + 8 ) );
3700 BOOST_CHECK_EXCEPTION( parser.Parse( wrongJunctionOwner, wxS( "wrong-junction-owner.sch" ) ), IO_ERROR,
3701 [&]( const IO_ERROR& aError )
3702 {
3703 return aError.What().Contains( wxS( "controller 19" ) )
3704 && aError.What().Contains( wxS( "does not point back" ) )
3705 && aError.What().Contains( junctionOwnerOffset );
3706 } );
3707
3708 std::vector<uint8_t> crossNetJunction = loadBinaryFixture( "connectivity_topology.sch" );
3709 const size_t crossNetJunctions = sheetControllerOffset( crossNetJunction, 19 );
3710 const size_t crossNetConnections = sheetControllerOffset( crossNetJunction, 21 );
3711 const uint16_t tiedDotHandle = 0x3000;
3712 const uint16_t tiedDotOwner = readU16( crossNetJunction, crossNetJunctions + 8 );
3713 size_t sharedConnection = std::numeric_limits<size_t>::max();
3714 uint32_t differentNet = std::numeric_limits<uint32_t>::max();
3715 const uint32_t ownerNet = readU32( crossNetJunction, crossNetConnections + tiedDotOwner * 40 + 8 );
3716
3717 for( size_t record = 0; record < sheetControllerCount( crossNetJunction, 21 ); ++record )
3718 {
3719 const size_t offset = crossNetConnections + record * 40;
3720
3721 if( record != tiedDotOwner
3722 && ( readU16( crossNetJunction, offset + 12 ) == tiedDotHandle
3723 || readU16( crossNetJunction, offset + 14 ) == tiedDotHandle ) )
3724 {
3725 sharedConnection = record;
3726 }
3727
3728 if( readU32( crossNetJunction, offset + 8 ) != ownerNet )
3729 differentNet = readU32( crossNetJunction, offset + 8 );
3730 }
3731
3732 BOOST_REQUIRE_NE( sharedConnection, std::numeric_limits<size_t>::max() );
3733 BOOST_REQUIRE_NE( differentNet, std::numeric_limits<uint32_t>::max() );
3734 writeU32( crossNetJunction, crossNetConnections + sharedConnection * 40 + 8, differentNet );
3735 const wxString crossNetOffset =
3736 wxString::Format( wxS( "offset 0x%llX" ), static_cast<unsigned long long>( crossNetJunctions + 8 ) );
3737 BOOST_CHECK_EXCEPTION( parser.Parse( crossNetJunction, wxS( "cross-net-junction.sch" ) ), IO_ERROR,
3738 [&]( const IO_ERROR& aError )
3739 {
3740 return aError.What().Contains( wxS( "controller 19" ) )
3741 && aError.What().Contains( wxS( "shared across different nets" ) )
3742 && aError.What().Contains( crossNetOffset );
3743 } );
3744
3745 std::vector<uint8_t> wrongOffpageOwner = loadBinaryFixture( "connectivity_topology.sch" );
3746 const size_t offpageOwner = sheetControllerOffset( wrongOffpageOwner, 20 );
3747 const uint16_t connectionOwner = readU16( wrongOffpageOwner, offpageOwner + 8 );
3748 writeU16( wrongOffpageOwner, offpageOwner + 8, connectionOwner == 0 ? 1 : 0 );
3749 const wxString offpageOwnerOffset =
3750 wxString::Format( wxS( "offset 0x%llX" ), static_cast<unsigned long long>( offpageOwner + 8 ) );
3751 BOOST_CHECK_EXCEPTION( parser.Parse( wrongOffpageOwner, wxS( "wrong-offpage-owner.sch" ) ), IO_ERROR,
3752 [&]( const IO_ERROR& aError )
3753 {
3754 return aError.What().Contains( wxS( "controller 20" ) )
3755 && aError.What().Contains( wxS( "does not point back" ) )
3756 && aError.What().Contains( offpageOwnerOffset );
3757 } );
3758
3759 std::vector<uint8_t> crossNetOffpage = loadBinaryFixture( "connectivity_topology.sch" );
3760 const size_t crossNetOffpages = sheetControllerOffset( crossNetOffpage, 20 );
3761 const size_t crossNetOffpageConnections = sheetControllerOffset( crossNetOffpage, 21 );
3762 const size_t crossNetVertices = sheetControllerOffset( crossNetOffpage, 22 );
3763 const uint16_t offpageConnection = readU16( crossNetOffpage, crossNetOffpages + 8 );
3764 const uint32_t offpageNet = readU32( crossNetOffpage, crossNetOffpageConnections + offpageConnection * 40 + 8 );
3765 size_t foreignConnection = std::numeric_limits<size_t>::max();
3766
3767 for( size_t record = 0; record < sheetControllerCount( crossNetOffpage, 21 ); ++record )
3768 {
3769 if( readU32( crossNetOffpage, crossNetOffpageConnections + record * 40 + 8 ) != offpageNet )
3770 {
3771 foreignConnection = record;
3772 break;
3773 }
3774 }
3775
3776 BOOST_REQUIRE_NE( foreignConnection, std::numeric_limits<size_t>::max() );
3777 const size_t foreignOffset = crossNetOffpageConnections + foreignConnection * 40;
3778 const size_t firstVertex = crossNetVertices + readU32( crossNetOffpage, foreignOffset + 4 ) * 8;
3779 writeU16( crossNetOffpage, foreignOffset + 12, 0x2000 );
3780 writeU16( crossNetOffpage, firstVertex + 4, readU16( crossNetOffpage, crossNetOffpages + 22 ) );
3781 writeU16( crossNetOffpage, firstVertex + 6, readU16( crossNetOffpage, crossNetOffpages + 24 ) );
3782 BOOST_CHECK_EXCEPTION( parser.Parse( crossNetOffpage, wxS( "cross-net-offpage.sch" ) ), IO_ERROR,
3783 []( const IO_ERROR& aError )
3784 {
3785 return aError.What().Contains( wxS( "controller 20" ) )
3786 && aError.What().Contains( wxS( "shared across different nets" ) );
3787 } );
3788}
3789
3790
3791BOOST_AUTO_TEST_CASE( CorpusSemanticSnapshot )
3792{
3793 PADS_SCH_BINARY_PARSER binaryParser;
3794 std::vector<CANONICAL_SEMANTIC_RECORD> allActual;
3795
3796 for( const std::string& fixture :
3797 { "minimal_v13", "placement_transform", "fields", "connectors", "text_encoding", "page_graphics",
3798 "connectivity_topology", "multisheet_connectivity", "symbol_primitives", "pin_styles", "multigate" } )
3799 {
3800 PADS_SCH_MODEL binary =
3801 binaryParser.Parse( loadBinaryFixture( fixture + ".sch" ), wxString::FromUTF8( fixture + ".sch" ) );
3803 BOOST_REQUIRE( ascii.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/" + fixture + ".txt" ) );
3804
3805 auto task10 = []( std::vector<CANONICAL_SEMANTIC_RECORD> aRecords )
3806 {
3807 std::erase_if( aRecords,
3808 []( const CANONICAL_SEMANTIC_RECORD& aRecord )
3809 {
3810 return aRecord.kind != CANONICAL_KIND::NET && aRecord.kind != CANONICAL_KIND::CONNECTION
3811 && aRecord.kind != CANONICAL_KIND::BUS
3812 && aRecord.kind != CANONICAL_KIND::BUS_ENTRY
3813 && aRecord.kind != CANONICAL_KIND::BUS_ALIAS
3814 && aRecord.kind != CANONICAL_KIND::BUS_MEMBER
3815 && aRecord.kind != CANONICAL_KIND::LABEL
3816 && aRecord.kind != CANONICAL_KIND::JUNCTION
3817 && ( aRecord.kind != CANONICAL_KIND::GRAPHIC
3818 || !aRecord.properties.contains( "page_graphic_group" )
3819 || aRecord.properties.at( "page_graphic_group" ).value
3820 != CANONICAL_VALUE( std::string( "BATCHB_PAGE_GRAPHICS" ) ) );
3821 } );
3822
3823 return aRecords;
3824 };
3825
3826 const SNAPSHOT_ALLOWLIST sourceProperties = {
3827 { "global_net_record", PROPERTY_DISPOSITION::EXACT },
3828 { "preserved_net_identity", PROPERTY_DISPOSITION::PRESERVED },
3829 { "preserved_net_relationship", PROPERTY_DISPOSITION::PRESERVED },
3830 { "endpoint_0_raw_endpoint_handle", PROPERTY_DISPOSITION::EXACT },
3831 { "endpoint_1_raw_endpoint_handle", PROPERTY_DISPOSITION::EXACT },
3832 { "endpoint_0_raw_endpoint_relationship", PROPERTY_DISPOSITION::PRESERVED },
3833 { "endpoint_1_raw_endpoint_relationship", PROPERTY_DISPOSITION::PRESERVED },
3834 { "raw_connection_marker", PROPERTY_DISPOSITION::EXACT },
3835 { "offpage_variant", PROPERTY_DISPOSITION::EXACT },
3836 { "connection_record", PROPERTY_DISPOSITION::EXACT },
3837 { "preserved_drawing_text_relationship", PROPERTY_DISPOSITION::UNSUPPORTED },
3838 { "preserved_graphic_presentation", PROPERTY_DISPOSITION::PRESERVED },
3839 };
3840
3841 BOOST_TEST_CONTEXT( fixture )
3842 {
3843 for( const MODEL_NET& net : binary.nets )
3844 {
3845 for( const MODEL_CONNECTION& connection : net.connections )
3846 {
3847 BOOST_REQUIRE_GE( connection.vertices.size(), 2 );
3848 BOOST_REQUIRE_EQUAL( connection.endpoints.size(), 2 );
3849 BOOST_CHECK_EQUAL( connection.endpoints[0].point.x, connection.vertices.front().x );
3850 BOOST_CHECK_EQUAL( connection.endpoints[0].point.y, connection.vertices.front().y );
3851 BOOST_CHECK_EQUAL( connection.endpoints[1].point.x, connection.vertices.back().x );
3852 BOOST_CHECK_EQUAL( connection.endpoints[1].point.y, connection.vertices.back().y );
3853 BOOST_CHECK( connection.vertices.front().x != connection.vertices.back().x
3854 || connection.vertices.front().y != connection.vertices.back().y );
3855 }
3856 }
3857
3858 auto expected = task10( normalizeAsciiModel( ascii ) );
3859 auto actual = task10( normalizeBinaryModel( binary ) );
3860 BOOST_CHECK( snapshotsMatch( expected, actual, sourceProperties ) );
3861 allActual.insert( allActual.end(), actual.begin(), actual.end() );
3862 }
3863 }
3864
3865 BOOST_CHECK( task10SourcePropertiesPresent( allActual ) );
3866 auto missingRawKind = allActual;
3867
3868 auto label = std::ranges::find( missingRawKind, CANONICAL_KIND::LABEL, &CANONICAL_SEMANTIC_RECORD::kind );
3869 BOOST_REQUIRE( label != missingRawKind.end() );
3870 label->properties.erase( "offpage_variant" );
3871
3872 BOOST_CHECK( !task10SourcePropertiesPresent( missingRawKind ) );
3873
3874 for( const std::string& endpoint :
3875 { "endpoint_0_raw_endpoint_handle", "endpoint_1_raw_endpoint_handle", "endpoint_0_raw_endpoint_relationship",
3876 "endpoint_1_raw_endpoint_relationship" } )
3877 {
3878 auto missingEndpoint = allActual;
3879 auto connection =
3880 std::ranges::find( missingEndpoint, CANONICAL_KIND::CONNECTION, &CANONICAL_SEMANTIC_RECORD::kind );
3881 BOOST_REQUIRE( connection != missingEndpoint.end() );
3882 connection->properties.erase( endpoint );
3883 BOOST_CHECK( !task10SourcePropertiesPresent( missingEndpoint ) );
3884 }
3885}
3886
3887
3888BOOST_AUTO_TEST_CASE( SymbolHandleErrors )
3889{
3891 std::vector<uint8_t> wrongClass = loadBinaryFixture( "symbol_primitives.sch" );
3892 size_t usedDecals = sheetControllerOffset( wrongClass, 7 );
3893 writeU32( wrongClass, usedDecals + 104 * 108 + 48, 8 );
3894 BOOST_CHECK_EXCEPTION( parser.Parse( wrongClass, wxS( "wrong-class.sch" ) ), IO_ERROR,
3895 []( const IO_ERROR& aError )
3896 {
3897 wxString message = aError.What();
3898 return message.Contains( wxS( "wrong object class" ) )
3899 && message.Contains( wxS( "controller 7" ) )
3900 && message.Contains( wxS( "record 104" ) );
3901 } );
3902
3903 std::vector<uint8_t> unresolved = loadBinaryFixture( "multigate.sch" );
3904 size_t gates = sheetControllerOffset( unresolved, 10 );
3905 writeU16( unresolved, gates + 12, 0xFFFF );
3906 BOOST_CHECK_EXCEPTION( parser.Parse( unresolved, wxS( "unresolved.sch" ) ), IO_ERROR,
3907 []( const IO_ERROR& aError )
3908 {
3909 return aError.What().Contains( wxS( "unresolved symbol definition reference" ) );
3910 } );
3911
3912 std::vector<uint8_t> badPinHandle = loadBinaryFixture( "symbol_primitives.sch" );
3913 size_t terminals = sheetControllerOffset( badPinHandle, 8 );
3914 writeU16( badPinHandle, terminals + 8 * 26, 0xFFFE );
3915 BOOST_CHECK_EXCEPTION( parser.Parse( badPinHandle, wxS( "bad-pin-handle.sch" ) ), IO_ERROR,
3916 []( const IO_ERROR& aError )
3917 {
3918 return aError.What().Contains( wxS( "pin-decal handle" ) )
3919 && aError.What().Contains( wxS( "controller 8" ) );
3920 } );
3921
3922 std::vector<uint8_t> gateCount = loadBinaryFixture( "multigate.sch" );
3923 size_t partTypes = sheetControllerOffset( gateCount, 9 );
3924 writeU16( gateCount, partTypes + 76 + 68, 3 );
3925 BOOST_CHECK_EXCEPTION( parser.Parse( gateCount, wxS( "gate-count.sch" ) ), IO_ERROR,
3926 []( const IO_ERROR& aError )
3927 {
3928 return aError.What().Contains( wxS( "gate count" ) )
3929 && aError.What().Contains( wxS( "controller 9" ) );
3930 } );
3931
3932 std::vector<uint8_t> fieldHandle = loadBinaryFixture( "symbol_primitives.sch" );
3933 size_t fieldDecals = sheetControllerOffset( fieldHandle, 7 );
3934 writeU32( fieldHandle, fieldDecals + 104 * 108 + 52, 132 );
3935 BOOST_CHECK_EXCEPTION( parser.Parse( fieldHandle, wxS( "field-handle.sch" ) ), IO_ERROR,
3936 []( const IO_ERROR& aError )
3937 {
3938 return aError.What().Contains( wxS( "field" ) )
3939 && aError.What().Contains( wxS( "controller 7" ) );
3940 } );
3941
3942 std::vector<uint8_t> alternateHandle = loadBinaryFixture( "multigate.sch" );
3943 size_t alternateGates = sheetControllerOffset( alternateHandle, 10 );
3944 writeU16( alternateHandle, alternateGates + 2, 0xFFFE );
3945 BOOST_CHECK_EXCEPTION( parser.Parse( alternateHandle, wxS( "alternate-handle.sch" ) ), IO_ERROR,
3946 []( const IO_ERROR& aError )
3947 {
3948 return aError.What().Contains( wxS( "alternate symbol definition" ) )
3949 && aError.What().Contains( wxS( "controller 10" ) );
3950 } );
3951
3952 std::vector<uint8_t> binaryCycle = loadBinaryFixture( "multigate.sch" );
3953 const size_t cyclePartTypes = sheetControllerOffset( binaryCycle, 9 );
3954 const size_t cycleGates = sheetControllerOffset( binaryCycle, 10 );
3955 const uint32_t firstGateRecord = readU32( binaryCycle, cyclePartTypes + 76 + 44 );
3956 const uint16_t firstDefinition = readU16( binaryCycle, cycleGates + firstGateRecord * 12 );
3957 const uint16_t secondDefinition = readU16( binaryCycle, cycleGates + ( firstGateRecord + 1 ) * 12 );
3958 writeU16( binaryCycle, cycleGates + firstGateRecord * 12 + 2, secondDefinition );
3959 writeU16( binaryCycle, cycleGates + ( firstGateRecord + 1 ) * 12 + 2, firstDefinition );
3960 BOOST_CHECK_EXCEPTION( parser.Parse( binaryCycle, wxS( "definition-cycle.sch" ) ), IO_ERROR,
3961 []( const IO_ERROR& aError )
3962 {
3963 return aError.What().Contains( wxS( "cyclic symbol definition reference" ) )
3964 && aError.What().Contains( wxS( "controller 10" ) );
3965 } );
3966
3967 std::vector<uint8_t> unknownSide = loadBinaryFixture( "pin_styles.sch" );
3968 const size_t unknownSideTerminals = sheetControllerOffset( unknownSide, 8 );
3969 writeU16( unknownSide, unknownSideTerminals + 22, 8 );
3970 PADS_SCH_MODEL unknownSideModel = parser.Parse( unknownSide, wxS( "unknown-side.sch" ) );
3971 BOOST_CHECK( std::ranges::any_of( unknownSideModel.diagnostics,
3972 []( const PARSER_DIAGNOSTIC& aDiagnostic )
3973 {
3974 return aDiagnostic.message.Contains( wxS( "terminal side" ) )
3975 && aDiagnostic.source.controller == 8
3976 && aDiagnostic.source.recordIndex == 0;
3977 } ) );
3978
3979 std::vector<uint8_t> unknownLength = loadBinaryFixture( "pin_styles.sch" );
3980 const PADS_SCH_MODEL unknownLengthBaseline = parser.Parse( unknownLength, wxS( "known-length.sch" ) );
3981 const size_t unknownLengthTerminalRecord = static_cast<size_t>(
3982 itemNamed( unknownLengthBaseline.definitions, wxS( "BATCHB_PIN_STYLES" ) ).pins[0].source.recordIndex );
3983 const size_t unknownLengthTerminals = sheetControllerOffset( unknownLength, 8 );
3984 const uint16_t pinDecalHandle = readU16( unknownLength, unknownLengthTerminals + unknownLengthTerminalRecord * 26 );
3985 const size_t unknownLengthDecals = sheetControllerOffset( unknownLength, 7 );
3986 const uint32_t pinDecalDefinition = readU32( unknownLength, unknownLengthDecals + pinDecalHandle * 108 + 48 );
3987 const size_t unknownLengthDefinitions = sheetControllerOffset( unknownLength, 3 );
3988 const uint32_t pinDecalVertex = readU32( unknownLength, unknownLengthDefinitions + pinDecalDefinition * 80 + 0x34 );
3989 const uint32_t pinDecalVertexEnd =
3990 readU32( unknownLength, unknownLengthDefinitions + ( pinDecalDefinition + 1 ) * 80 + 0x34 );
3991 const size_t unknownLengthVertices = sheetControllerOffset( unknownLength, 5 );
3992
3993 for( uint32_t vertex = pinDecalVertex; vertex < pinDecalVertexEnd; ++vertex )
3994 {
3995 const size_t vertexOffset = unknownLengthVertices + vertex * 6;
3996
3997 if( readU16( unknownLength, vertexOffset ) == 0 && readU16( unknownLength, vertexOffset + 2 ) == 0 )
3998 writeU16( unknownLength, vertexOffset, 1 );
3999 }
4000 PADS_SCH_MODEL unknownLengthModel = parser.Parse( unknownLength, wxS( "unknown-length.sch" ) );
4001 const MODEL_PIN_DEFINITION& unknownLengthPin =
4002 itemNamed( unknownLengthModel.definitions, wxS( "BATCHB_PIN_STYLES" ) ).pins[0];
4003 BOOST_CHECK_EQUAL( unknownLengthPin.length, 0 );
4004 BOOST_CHECK( std::ranges::any_of( unknownLengthPin.properties,
4005 []( const SOURCE_PROPERTY& aProperty )
4006 {
4007 return aProperty.name.text == wxS( "pin_length" )
4008 && aProperty.disposition == PROPERTY_DISPOSITION::UNSUPPORTED;
4009 } ) );
4010
4011 std::vector<uint8_t> pieceCount = loadBinaryFixture( "symbol_primitives.sch" );
4012 size_t definitions = sheetControllerOffset( pieceCount, 3 );
4013 pieceCount[definitions + 14 * 80 + 43] = 1;
4014 BOOST_CHECK_EXCEPTION( parser.Parse( pieceCount, wxS( "piece-count.sch" ) ), IO_ERROR,
4015 []( const IO_ERROR& aError )
4016 {
4017 return aError.What().Contains( wxS( "graphic-piece counts" ) )
4018 && aError.What().Contains( wxS( "controller 3" ) );
4019 } );
4020
4021 std::vector<uint8_t> ownership = loadBinaryFixture( "symbol_primitives.sch" );
4022 size_t ownershipDefinitions = sheetControllerOffset( ownership, 3 );
4023 const size_t vertexPrefix = ownershipDefinitions + 14 * 80 + 0x34;
4024 writeU32( ownership, vertexPrefix, readU32( ownership, vertexPrefix ) + 1 );
4025 BOOST_CHECK_EXCEPTION( parser.Parse( ownership, wxS( "piece-ownership.sch" ) ), IO_ERROR,
4026 []( const IO_ERROR& aError )
4027 {
4028 return aError.What().Contains( wxS( "ownership mismatch" ) )
4029 && aError.What().Contains( wxS( "controller 3" ) );
4030 } );
4031
4032 auto duplicateModel = [&]()
4033 {
4034 return parser.Parse( loadBinaryFixture( "multigate.sch" ), wxS( "duplicate-id.sch" ) );
4035 };
4036 PADS_SCH_MODEL duplicateDefinition = duplicateModel();
4037 duplicateDefinition.definitions[1].id = duplicateDefinition.definitions[0].id;
4038 BOOST_CHECK_EXCEPTION( duplicateDefinition.ValidateOrThrow(), IO_ERROR,
4039 []( const IO_ERROR& aError )
4040 {
4041 return aError.What().Contains( wxS( "duplicate definition ID" ) );
4042 } );
4043 PADS_SCH_MODEL duplicatePart = duplicateModel();
4044 duplicatePart.partTypes[1].id = duplicatePart.partTypes[0].id;
4045 BOOST_CHECK_EXCEPTION( duplicatePart.ValidateOrThrow(), IO_ERROR,
4046 []( const IO_ERROR& aError )
4047 {
4048 return aError.What().Contains( wxS( "duplicate part type ID" ) );
4049 } );
4050 PADS_SCH_MODEL duplicateGate = duplicateModel();
4051 duplicateGate.partTypes[1].gates[1].id = duplicateGate.partTypes[1].gates[0].id;
4052 BOOST_CHECK_EXCEPTION( duplicateGate.ValidateOrThrow(), IO_ERROR,
4053 []( const IO_ERROR& aError )
4054 {
4055 return aError.What().Contains( wxS( "duplicate gate ID" ) );
4056 } );
4057 PADS_SCH_MODEL duplicatePin = duplicateModel();
4058 const MODEL_SYMBOL_DEFINITION& pinOwner = itemNamed( duplicatePin.definitions, wxS( "BATCHB_PIN_STYLES" ) );
4059 size_t pinOwnerIndex = &pinOwner - duplicatePin.definitions.data();
4060 duplicatePin.definitions[pinOwnerIndex].pins[1].id = duplicatePin.definitions[pinOwnerIndex].pins[0].id;
4061 BOOST_CHECK_EXCEPTION( duplicatePin.ValidateOrThrow(), IO_ERROR,
4062 []( const IO_ERROR& aError )
4063 {
4064 return aError.What().Contains( wxS( "duplicate pin ID" ) );
4065 } );
4066
4067 PADS_SCH_MODEL duplicateField = duplicateModel();
4068 duplicateField.definitions[0].fields.push_back( duplicateField.definitions[1].fields[0] );
4069 duplicateField.definitions[0].fields.back().source = duplicateField.definitions[0].source;
4070 BOOST_CHECK_EXCEPTION( duplicateField.ValidateOrThrow(), IO_ERROR,
4071 []( const IO_ERROR& aError )
4072 {
4073 return aError.What().Contains( wxS( "duplicate field ID" ) )
4074 && aError.What().Contains( wxS( "first at" ) );
4075 } );
4076
4077 std::vector<uint8_t> duplicateBinaryField = loadBinaryFixture( "fields.sch" );
4078 const size_t duplicateFieldDecals = sheetControllerOffset( duplicateBinaryField, 7 );
4079 const uint32_t textCount = sheetControllerCount( duplicateBinaryField, 1 );
4080 std::vector<size_t> fieldRecords;
4081
4082 for( size_t record = 0; record < sheetControllerCount( duplicateBinaryField, 7 ); ++record )
4083 {
4084 if( readU32( duplicateBinaryField, duplicateFieldDecals + record * 108 + 52 ) < textCount )
4085 fieldRecords.push_back( record );
4086 }
4087
4088 BOOST_REQUIRE_GE( fieldRecords.size(), 2 );
4089 writeU32( duplicateBinaryField, duplicateFieldDecals + fieldRecords[1] * 108 + 52,
4090 readU32( duplicateBinaryField, duplicateFieldDecals + fieldRecords[0] * 108 + 52 ) );
4091 BOOST_CHECK_EXCEPTION( parser.Parse( duplicateBinaryField, wxS( "duplicate-field.sch" ) ), IO_ERROR,
4092 []( const IO_ERROR& aError )
4093 {
4094 return aError.What().Contains( wxS( "duplicate field ID" ) )
4095 && aError.What().Contains( wxS( "controller 7" ) )
4096 && aError.What().Contains( wxS( "first at" ) );
4097 } );
4098
4099 std::vector<uint8_t> connectorPinName = loadBinaryFixture( "connectors.sch" );
4100 const size_t connectorParts = sheetControllerOffset( connectorPinName, 9 );
4101 const size_t connectorPins = sheetControllerOffset( connectorPinName, 11 );
4102 const uint32_t connectorPinStart = readU32( connectorPinName, connectorParts + 76 + 48 );
4103 writeU32( connectorPinName, connectorPins + connectorPinStart * 24, 0xFFFFFFFE );
4104 BOOST_CHECK_EXCEPTION( parser.Parse( connectorPinName, wxS( "connector-pin-name.sch" ) ), IO_ERROR,
4105 []( const IO_ERROR& aError )
4106 {
4107 return aError.What().Contains( wxS( "pin-name offset leaves controller 14" ) )
4108 && aError.What().Contains( wxS( "controller 11" ) );
4109 } );
4110}
4111
4112
4113BOOST_AUTO_TEST_CASE( SymbolDefinitionSemanticSnapshot )
4114{
4115 PADS_SCH_BINARY_PARSER binaryParser;
4116 PADS_SCH_MODEL binary = binaryParser.Parse( loadBinaryFixture( "pin_styles.sch" ), wxS( "pin_styles.sch" ) );
4117 PADS_SCH::PADS_SCH_PARSER asciiParser;
4118 BOOST_REQUIRE( asciiParser.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/pin_styles.txt" ) );
4119
4120 const MODEL_SYMBOL_DEFINITION& binaryDefinition = itemNamed( binary.definitions, wxS( "BATCHB_PIN_STYLES" ) );
4121 auto asciiDefinition = std::ranges::find_if( asciiParser.GetSymbolDefs(),
4122 []( const PADS_SCH::SYMBOL_DEF& aDefinition )
4123 {
4124 return aDefinition.name == "BATCHB_PIN_STYLES";
4125 } );
4126 BOOST_REQUIRE( asciiDefinition != asciiParser.GetSymbolDefs().end() );
4127 BOOST_CHECK_EQUAL( binaryDefinition.graphics.size(), asciiDefinition->graphics.size() );
4128 BOOST_CHECK_EQUAL( binaryDefinition.pins.size(), asciiDefinition->pins.size() );
4129 BOOST_CHECK_EQUAL( binaryDefinition.pins[0].position.x,
4130 static_cast<int64_t>( std::llround( asciiDefinition->pins[0].position.x * 2 ) ) );
4131 BOOST_CHECK_EQUAL( binaryDefinition.pins[4].graphicStyle,
4132 ( asciiDefinition->pins[4].inverted ? 1U : 0U ) | ( asciiDefinition->pins[4].clock ? 2U : 0U ) );
4133
4134 BOOST_REQUIRE_EQUAL( binaryDefinition.graphics.size(), asciiDefinition->graphics.size() );
4135
4136 for( size_t i = 0; i < binaryDefinition.graphics.size(); ++i )
4137 {
4138 BOOST_CHECK( canonicalGraphicType( binaryDefinition.graphics[i].kind )
4139 == canonicalGraphicType( asciiDefinition->graphics[i].type ) );
4140 BOOST_CHECK_EQUAL( binaryDefinition.graphics[i].strokeWidth,
4141 std::llround( asciiDefinition->graphics[i].line_width * 2 ) );
4142 BOOST_CHECK( canonicalFill( binaryDefinition.graphics[i].fill )
4143 == canonicalFill( asciiDefinition->graphics[i].filled ) );
4144 BOOST_CHECK_EQUAL( binaryDefinition.graphics[i].points.size(), asciiDefinition->graphics[i].points.size() );
4145 }
4146
4147 BOOST_REQUIRE_EQUAL( binaryDefinition.pins.size(), asciiDefinition->pins.size() );
4148 const PADS_SCH::GATE_DEF& asciiGate = asciiParser.GetPartTypes().at( "BATCHB-PIN-STYLES" ).gates[0];
4149 BOOST_REQUIRE_EQUAL( asciiGate.pins.size(), binaryDefinition.pins.size() );
4150
4151 auto checkPinPresentation = []( const MODEL_PIN_DEFINITION& aBinaryPin, const PADS_SCH::SYMBOL_PIN& aAsciiPin )
4152 {
4153 BOOST_CHECK( canonicalPinStyle( aBinaryPin.graphicStyle )
4154 == canonicalPinStyle( aAsciiPin.inverted, aAsciiPin.clock ) );
4155 BOOST_CHECK_EQUAL( aBinaryPin.length, std::llround( aAsciiPin.length * 2 ) );
4156 BOOST_CHECK_EQUAL( aBinaryPin.position.x, std::llround( aAsciiPin.position.x * 2 ) );
4157 BOOST_CHECK_EQUAL( aBinaryPin.position.y, std::llround( aAsciiPin.position.y * 2 ) );
4158 BOOST_CHECK_EQUAL( aBinaryPin.side, aAsciiPin.side );
4159 BOOST_CHECK_EQUAL( aBinaryPin.angle, std::llround( aAsciiPin.rotation * 10 ) );
4160 BOOST_CHECK_EQUAL( aBinaryPin.namePresentation.height, aAsciiPin.pn_h );
4161 BOOST_CHECK_EQUAL( aBinaryPin.namePresentation.width, aAsciiPin.pn_w );
4162 BOOST_CHECK_EQUAL( aBinaryPin.numberPresentation.height, aAsciiPin.pl_h );
4163 BOOST_CHECK_EQUAL( aBinaryPin.numberPresentation.width, aAsciiPin.pl_w );
4164 BOOST_CHECK_EQUAL( aBinaryPin.nameOffset.x, aAsciiPin.pn_offset.x * 2 );
4165 BOOST_CHECK_EQUAL( aBinaryPin.nameOffset.y, aAsciiPin.pn_offset.y * 2 );
4166 BOOST_CHECK_EQUAL( aBinaryPin.numberOffset.x, aAsciiPin.pl_offset.x * 2 );
4167 BOOST_CHECK_EQUAL( aBinaryPin.numberOffset.y, aAsciiPin.pl_offset.y * 2 );
4168 BOOST_CHECK_EQUAL( aBinaryPin.nameAngle, aAsciiPin.pn_angle * 10 );
4169 BOOST_CHECK_EQUAL( aBinaryPin.numberAngle, aAsciiPin.pl_angle * 10 );
4170 BOOST_CHECK_EQUAL( aBinaryPin.nameJustification, aAsciiPin.pn_just );
4171 BOOST_CHECK_EQUAL( aBinaryPin.numberJustification, aAsciiPin.pl_just );
4172 BOOST_CHECK_EQUAL( aBinaryPin.nameOffsetAngle, aAsciiPin.pn_off_angle * 10 );
4173 BOOST_CHECK_EQUAL( aBinaryPin.numberOffsetAngle, aAsciiPin.pl_off_angle * 10 );
4174 BOOST_CHECK_EQUAL( aBinaryPin.nameOffsetJustification, aAsciiPin.pn_off_just );
4175 BOOST_CHECK_EQUAL( aBinaryPin.numberOffsetJustification, aAsciiPin.pl_off_just );
4176 BOOST_CHECK_EQUAL( aBinaryPin.visibilityFlags, aAsciiPin.p_flags );
4178 aAsciiPin.pn_h != 0 && ( aAsciiPin.p_flags & 128 ) == 0 );
4179 BOOST_CHECK_EQUAL( aBinaryPin.numberPresentation.visible, aAsciiPin.pl_h != 0 );
4180 };
4181
4182 for( size_t i = 0; i < binaryDefinition.pins.size(); ++i )
4183 {
4184 BOOST_CHECK_EQUAL( binaryDefinition.pins[i].number.text, wxString::FromUTF8( asciiGate.pins[i].pin_id ) );
4185 BOOST_CHECK_EQUAL( binaryDefinition.pins[i].name.text, wxString::FromUTF8( asciiGate.pins[i].pin_name ) );
4186 BOOST_CHECK( canonicalPinType( binaryDefinition.pins[i].electricalType )
4187 == canonicalPinType( asciiGate.pins[i].pin_type ) );
4188 checkPinPresentation( binaryDefinition.pins[i], asciiDefinition->pins[i] );
4189 }
4190
4191 PADS_SCH_MODEL primitiveBinary =
4192 binaryParser.Parse( loadBinaryFixture( "symbol_primitives.sch" ), wxS( "symbol_primitives.sch" ) );
4193 PADS_SCH::PADS_SCH_PARSER primitiveAscii;
4195 primitiveAscii.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/symbol_primitives.txt" ) );
4196 const MODEL_SYMBOL_DEFINITION& primitiveDefinition =
4197 itemNamed( primitiveBinary.definitions, wxS( "BATCHB_PRIMITIVES" ) );
4198 const PADS_SCH::SYMBOL_DEF* asciiPrimitive = primitiveAscii.GetSymbolDef( "BATCHB_PRIMITIVES" );
4199 BOOST_REQUIRE( asciiPrimitive );
4200 BOOST_REQUIRE_EQUAL( primitiveDefinition.graphics.size(),
4201 asciiPrimitive->graphics.size() + asciiPrimitive->texts.size() );
4202
4203 for( size_t graphic = 0; graphic < asciiPrimitive->graphics.size(); ++graphic )
4204 {
4205 BOOST_CHECK( canonicalGraphicType( primitiveDefinition.graphics[graphic].kind )
4206 == canonicalGraphicType( asciiPrimitive->graphics[graphic] ) );
4207 BOOST_CHECK_EQUAL( primitiveDefinition.graphics[graphic].strokeWidth,
4208 std::llround( asciiPrimitive->graphics[graphic].line_width * 2 ) );
4209 BOOST_CHECK( primitiveDefinition.graphics[graphic].lineStyle == MODEL_LINE_STYLE::SOLID );
4210 BOOST_CHECK( canonicalFill( primitiveDefinition.graphics[graphic].fill )
4211 == canonicalFill( asciiPrimitive->graphics[graphic].filled ) );
4212 BOOST_REQUIRE_EQUAL( primitiveDefinition.graphics[graphic].points.size(),
4213 asciiPrimitive->graphics[graphic].points.size() );
4214
4215 for( size_t pointIndex = 0; pointIndex < asciiPrimitive->graphics[graphic].points.size(); ++pointIndex )
4216 {
4217 BOOST_CHECK_EQUAL( primitiveDefinition.graphics[graphic].points[pointIndex].x,
4218 std::llround( asciiPrimitive->graphics[graphic].points[pointIndex].coord.x * 2 ) );
4219 BOOST_CHECK_EQUAL( primitiveDefinition.graphics[graphic].points[pointIndex].y,
4220 std::llround( asciiPrimitive->graphics[graphic].points[pointIndex].coord.y * 2 ) );
4221 }
4222 }
4223
4224 const MODEL_GRAPHIC& binaryText = primitiveDefinition.graphics.back();
4225 BOOST_REQUIRE_EQUAL( asciiPrimitive->texts.size(), 1 );
4226 BOOST_CHECK_EQUAL( binaryText.text.text, wxString::FromUTF8( asciiPrimitive->texts[0].content ) );
4227 BOOST_CHECK_EQUAL( binaryText.points[0].x, std::llround( asciiPrimitive->texts[0].position.x * 2 ) );
4228 BOOST_CHECK_EQUAL( binaryText.points[0].y, std::llround( asciiPrimitive->texts[0].position.y * 2 ) );
4229 BOOST_CHECK_EQUAL( binaryText.presentation.height, std::llround( asciiPrimitive->texts[0].size ) );
4230 BOOST_CHECK_EQUAL( binaryText.presentation.width, asciiPrimitive->texts[0].width_factor );
4231 BOOST_CHECK_EQUAL( binaryText.presentation.font.text, wxString::FromUTF8( asciiPrimitive->font1 ) );
4232 BOOST_CHECK_EQUAL( binaryText.angle, std::llround( asciiPrimitive->texts[0].rotation * 10 ) );
4233 BOOST_CHECK_EQUAL( binaryText.presentation.visible, asciiPrimitive->texts[0].visible );
4234 BOOST_CHECK( canonicalJustification( binaryText.presentation.horizontalJustification ).value
4235 == canonicalHorizontalJustification( asciiPrimitive->texts[0].justification ).value );
4236 BOOST_CHECK( canonicalVerticalJustification( binaryText.presentation.verticalJustification ).value
4237 == canonicalVerticalJustification( asciiPrimitive->texts[0].justification ).value );
4238
4239 PADS_SCH_MODEL fieldsBinary = binaryParser.Parse( loadBinaryFixture( "fields.sch" ), wxS( "fields.sch" ) );
4240 PADS_SCH::PADS_SCH_PARSER fieldsAscii;
4241 BOOST_REQUIRE( fieldsAscii.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/fields.txt" ) );
4242 const MODEL_SYMBOL_DEFINITION& fieldsDefinition = itemNamed( fieldsBinary.definitions, wxS( "RESZ-H" ) );
4243 const PADS_SCH::SYMBOL_DEF* asciiFields = fieldsAscii.GetSymbolDef( "RESZ-H" );
4244 BOOST_REQUIRE( asciiFields );
4245 BOOST_REQUIRE_EQUAL( fieldsDefinition.fields.size(), asciiFields->attrs.size() );
4246
4247 for( size_t field = 0; field < asciiFields->attrs.size(); ++field )
4248 {
4249 BOOST_TEST_CONTEXT( "field " << field )
4250 {
4251 BOOST_CHECK_EQUAL( fieldsDefinition.fields[field].name.text,
4252 wxString::FromUTF8( asciiFields->attrs[field].attr_name ) );
4253 BOOST_CHECK( fieldsDefinition.fields[field].value.text.empty() );
4254 BOOST_CHECK_EQUAL( fieldsDefinition.fields[field].position.x,
4255 std::llround( asciiFields->attrs[field].position.x * 2 ) );
4256 BOOST_CHECK_EQUAL( fieldsDefinition.fields[field].position.y,
4257 std::llround( asciiFields->attrs[field].position.y * 2 ) );
4258 BOOST_CHECK_EQUAL( fieldsDefinition.fields[field].angle, asciiFields->attrs[field].angle * 10 );
4259 BOOST_CHECK_EQUAL( fieldsDefinition.fields[field].presentation.height, asciiFields->attrs[field].height );
4260 BOOST_CHECK_EQUAL( fieldsDefinition.fields[field].presentation.width, asciiFields->attrs[field].width );
4261 BOOST_CHECK_EQUAL( fieldsDefinition.fields[field].presentation.font.text,
4262 wxString::FromUTF8( asciiFields->attrs[field].font_name ) );
4263 BOOST_CHECK(
4264 canonicalJustification( fieldsDefinition.fields[field].presentation.horizontalJustification ).value
4265 == canonicalHorizontalJustification( asciiFields->attrs[field].justification ).value );
4266 BOOST_CHECK(
4267 canonicalVerticalJustification( fieldsDefinition.fields[field].presentation.verticalJustification )
4268 .value
4269 == canonicalVerticalJustification( asciiFields->attrs[field].justification ).value );
4270 }
4271 }
4272
4273 const MODEL_PART_TYPE& fieldsPart = itemNamed( fieldsBinary.partTypes, wxS( "RES-RESN1" ) );
4274 BOOST_REQUIRE_EQUAL( fieldsPart.fields.size(), fieldsDefinition.fields.size() );
4275
4276 for( size_t field = 0; field < fieldsPart.fields.size(); ++field )
4277 {
4278 BOOST_CHECK_EQUAL( fieldsPart.fields[field].name.text, fieldsDefinition.fields[field].name.text );
4279 BOOST_CHECK_EQUAL( fieldsPart.fields[field].value.text, fieldsDefinition.fields[field].value.text );
4280 BOOST_CHECK( fieldsPart.fields[field].presentation == fieldsDefinition.fields[field].presentation );
4281 }
4282
4283 PADS_SCH_MODEL multiBinary = binaryParser.Parse( loadBinaryFixture( "multigate.sch" ), wxS( "multigate.sch" ) );
4284 PADS_SCH::PADS_SCH_PARSER multiAscii;
4285 BOOST_REQUIRE( multiAscii.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/multigate.txt" ) );
4286 const MODEL_PART_TYPE& multiPart = itemNamed( multiBinary.partTypes, wxS( "BATCHD-MULTIGATE" ) );
4287 const PADS_SCH::PARTTYPE_DEF& asciiMulti = multiAscii.GetPartTypes().at( "BATCHD-MULTIGATE" );
4288 BOOST_REQUIRE_EQUAL( multiPart.gates.size(), asciiMulti.gates.size() );
4289 BOOST_REQUIRE_EQUAL( multiPart.signalPins.size(), asciiMulti.sigpins.size() );
4290
4291 auto definitionName = [&]( const PADS_SCH_MODEL& aModel, const DEFINITION_REFERENCE& aReference )
4292 {
4293 auto definition = std::ranges::find_if( aModel.definitions,
4294 [&]( const MODEL_SYMBOL_DEFINITION& aDefinition )
4295 {
4296 return aDefinition.id == aReference.id;
4297 } );
4298 BOOST_REQUIRE( definition != aModel.definitions.end() );
4299 return definition->name.text;
4300 };
4301
4302 auto pinDefinition = []( const PADS_SCH_MODEL& aModel,
4303 const PIN_REFERENCE& aReference ) -> const MODEL_PIN_DEFINITION&
4304 {
4305 for( const MODEL_SYMBOL_DEFINITION& definition : aModel.definitions )
4306 {
4307 auto pin = std::ranges::find_if( definition.pins,
4308 [&]( const MODEL_PIN_DEFINITION& aPin )
4309 {
4310 return aPin.id == aReference.id;
4311 } );
4312
4313 if( pin != definition.pins.end() )
4314 return *pin;
4315 }
4316
4317 BOOST_FAIL( "unresolved pin reference" );
4318 throw std::logic_error( "unreachable" );
4319 };
4320
4321 for( size_t gate = 0; gate < multiPart.gates.size(); ++gate )
4322 {
4323 BOOST_CHECK_EQUAL( definitionName( multiBinary, multiPart.gates[gate].definition ),
4324 wxString::FromUTF8( asciiMulti.gates[gate].decal_names.front() ) );
4325 BOOST_CHECK_EQUAL( propertyValue( multiPart.gates[gate].properties, wxS( "swap_group" ) ),
4326 wxString::Format( wxS( "%d" ), asciiMulti.gates[gate].swap_flag ) );
4327 BOOST_CHECK_EQUAL( multiPart.gates[gate].pins.size(), asciiMulti.gates[gate].pins.size() );
4328
4329 for( size_t pin = 0; pin < multiPart.gates[gate].pins.size(); ++pin )
4330 {
4331 const MODEL_PIN_DEFINITION& binaryPin = pinDefinition( multiBinary, multiPart.gates[gate].pins[pin] );
4332 BOOST_CHECK_EQUAL( binaryPin.number.text, wxString::FromUTF8( asciiMulti.gates[gate].pins[pin].pin_id ) );
4333 BOOST_CHECK_EQUAL( binaryPin.name.text, wxString::FromUTF8( asciiMulti.gates[gate].pins[pin].pin_name ) );
4334 BOOST_CHECK_EQUAL( propertyValue( binaryPin.properties, wxS( "swap_group" ) ),
4335 wxString::Format( wxS( "%d" ), asciiMulti.gates[gate].pins[pin].swap_group ) );
4336 }
4337 }
4338
4339 const MODEL_PART_TYPE& alternatePart = itemNamed( multiBinary.partTypes, wxS( "RES-RESN1" ) );
4340 const PADS_SCH::GATE_DEF& asciiAlternates = multiAscii.GetPartTypes().at( "RES-RESN1" ).gates[0];
4341 BOOST_REQUIRE_EQUAL( alternatePart.gates[0].alternateDefinitions.size() + 1, asciiAlternates.decal_names.size() );
4342 BOOST_CHECK_EQUAL( definitionName( multiBinary, alternatePart.gates[0].definition ),
4343 wxString::FromUTF8( asciiAlternates.decal_names[0] ) );
4344
4345 for( size_t alternate = 0; alternate < alternatePart.gates[0].alternateDefinitions.size(); ++alternate )
4346 {
4347 BOOST_CHECK_EQUAL( definitionName( multiBinary, alternatePart.gates[0].alternateDefinitions[alternate] ),
4348 wxString::FromUTF8( asciiAlternates.decal_names[alternate + 1] ) );
4349 }
4350
4351 for( size_t signal = 0; signal < multiPart.signalPins.size(); ++signal )
4352 {
4353 BOOST_CHECK_EQUAL( multiPart.signalPins[signal].number.text,
4354 wxString::FromUTF8( asciiMulti.sigpins[signal].pin_number ) );
4355 BOOST_CHECK_EQUAL( multiPart.signalPins[signal].name.text,
4356 wxString::FromUTF8( asciiMulti.sigpins[signal].net_name ) );
4357 }
4358
4359 PADS_SCH_MODEL connectorsBinary =
4360 binaryParser.Parse( loadBinaryFixture( "connectors.sch" ), wxS( "connectors.sch" ) );
4361 PADS_SCH::PADS_SCH_PARSER connectorsAscii;
4362 BOOST_REQUIRE( connectorsAscii.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/connectors.txt" ) );
4363 const MODEL_PART_TYPE& connectorPart = itemNamed( connectorsBinary.partTypes, wxS( "CON-26P-ED" ) );
4364 const PADS_SCH::GATE_DEF& asciiConnector = connectorsAscii.GetPartTypes().at( "CON-26P-ED" ).gates[0];
4365 BOOST_REQUIRE_EQUAL( connectorPart.gates[0].decalGroupMembers.size(), asciiConnector.decal_names.size() );
4366
4367 for( size_t member = 0; member < asciiConnector.decal_names.size(); ++member )
4368 {
4369 BOOST_CHECK_EQUAL( definitionName( connectorsBinary, connectorPart.gates[0].decalGroupMembers[member] ),
4370 wxString::FromUTF8( asciiConnector.decal_names[member] ) );
4371 }
4372
4373 const std::array<std::string, 11> pairedFixtures = { "minimal_v13",
4374 "placement_transform",
4375 "fields",
4376 "connectors",
4377 "text_encoding",
4378 "page_graphics",
4379 "connectivity_topology",
4380 "multisheet_connectivity",
4381 "symbol_primitives",
4382 "pin_styles",
4383 "multigate" };
4384
4385 for( const std::string& fixture : pairedFixtures )
4386 {
4387 PADS_SCH_MODEL fixtureBinary =
4388 binaryParser.Parse( loadBinaryFixture( fixture + ".sch" ), wxString::FromUTF8( fixture ) );
4389 PADS_SCH::PADS_SCH_PARSER fixtureAscii;
4391 fixtureAscii.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/" + fixture + ".txt" ) );
4392
4393 for( const MODEL_SYMBOL_DEFINITION& binarySymbol : fixtureBinary.definitions )
4394 {
4395 if( binarySymbol.pins.empty() )
4396 continue;
4397
4398 const PADS_SCH::SYMBOL_DEF* asciiSymbol = fixtureAscii.GetSymbolDef( binarySymbol.name.text.ToStdString() );
4399 BOOST_REQUIRE_MESSAGE( asciiSymbol, fixture + ": " + binarySymbol.name.text.ToStdString() );
4400 BOOST_REQUIRE_EQUAL( binarySymbol.pins.size(), asciiSymbol->pins.size() );
4401
4402 for( size_t pin = 0; pin < binarySymbol.pins.size(); ++pin )
4403 {
4404 BOOST_TEST_CONTEXT( fixture << ": " << binarySymbol.name.text << " pin " << pin << " flags "
4405 << binarySymbol.pins[pin].presentationFlags << "/"
4406 << binarySymbol.pins[pin].visibilityAndNumberPresentationFlags )
4407 {
4408 checkPinPresentation( binarySymbol.pins[pin], asciiSymbol->pins[pin] );
4409 }
4410 }
4411 }
4412
4413 for( const MODEL_PART_TYPE& binaryPart : fixtureBinary.partTypes )
4414 {
4415 const auto asciiPart = fixtureAscii.GetPartTypes().find( binaryPart.name.text.ToStdString() );
4416 BOOST_REQUIRE_MESSAGE( asciiPart != fixtureAscii.GetPartTypes().end(),
4417 fixture + ": " + binaryPart.name.text.ToStdString() );
4418
4419 if( asciiPart->second.gates.empty() )
4420 continue;
4421
4422 BOOST_REQUIRE_EQUAL( binaryPart.gates.size(), asciiPart->second.gates.size() );
4423
4424 for( size_t gate = 0; gate < binaryPart.gates.size(); ++gate )
4425 {
4426 const MODEL_GATE& binaryGate = binaryPart.gates[gate];
4427 const PADS_SCH::GATE_DEF& asciiGateForPart = asciiPart->second.gates[gate];
4428
4429 if( !binaryGate.decalGroupMembers.empty() )
4430 {
4431 BOOST_REQUIRE_EQUAL( binaryGate.connectorPins.size(), asciiGateForPart.pins.size() );
4432
4433 for( size_t pin = 0; pin < binaryGate.connectorPins.size(); ++pin )
4434 {
4435 const MODEL_CONNECTOR_PIN& binaryPin = binaryGate.connectorPins[pin];
4436 BOOST_TEST_CONTEXT( fixture << ": " << binaryPart.name.text << " connector pin " << pin )
4437 {
4438 BOOST_CHECK_EQUAL( binaryPin.number.text,
4439 wxString::FromUTF8( asciiGateForPart.pins[pin].pin_id ) );
4440 BOOST_CHECK_EQUAL( binaryPin.name.text,
4441 wxString::FromUTF8( asciiGateForPart.pins[pin].pin_name ) );
4442 BOOST_CHECK( canonicalPinType( binaryPin.electricalType )
4443 == canonicalPinType( asciiGateForPart.pins[pin].pin_type ) );
4444 }
4445 }
4446
4447 continue;
4448 }
4449
4450 BOOST_REQUIRE_EQUAL( binaryGate.pins.size(), asciiGateForPart.pins.size() );
4451 BOOST_REQUIRE_EQUAL( binaryGate.logicalPins.size(), asciiGateForPart.pins.size() );
4452
4453 for( size_t pin = 0; pin < binaryGate.pins.size(); ++pin )
4454 {
4455 const MODEL_GATE_PIN& binaryPin = binaryGate.logicalPins[pin];
4456 BOOST_TEST_CONTEXT( fixture << ": " << binaryPart.name.text << " gate " << gate << " pin " << pin )
4457 {
4458 BOOST_CHECK_EQUAL( binaryPin.number.text,
4459 wxString::FromUTF8( asciiGateForPart.pins[pin].pin_id ) );
4460 BOOST_CHECK_EQUAL( binaryPin.name.text,
4461 wxString::FromUTF8( asciiGateForPart.pins[pin].pin_name ) );
4462 BOOST_CHECK( canonicalPinType( binaryPin.electricalType )
4463 == canonicalPinType( asciiGateForPart.pins[pin].pin_type ) );
4464 }
4465 }
4466 }
4467 }
4468 }
4469}
4470
4471
4472BOOST_AUTO_TEST_CASE( TerminalJustificationCodes )
4473{
4474 static constexpr std::array<const char*, 6> fixtures = { "terminal_justification_0_6",
4475 "terminal_justification_7_13",
4476 "terminal_justification_14_15",
4477 "terminal_justification_90_0_6",
4478 "terminal_justification_90_7_13",
4479 "terminal_justification_90_14_15" };
4480
4481 PADS_SCH_BINARY_PARSER binaryParser;
4482
4483 for( const char* fixture : fixtures )
4484 {
4485 const std::string binaryName = std::string( fixture ) + ".sch";
4486 const std::string asciiName = std::string( fixture ) + ".txt";
4487 PADS_SCH_MODEL binary = binaryParser.Parse( loadBinaryFixture( binaryName ), wxString::FromUTF8( binaryName ) );
4488 PADS_SCH::PADS_SCH_PARSER asciiParser;
4489 BOOST_REQUIRE( asciiParser.Parse( KI_TEST::GetEeschemaTestDataDir() + "/plugins/pads/binary/" + asciiName ) );
4490
4491 const MODEL_SYMBOL_DEFINITION& binaryDefinition = itemNamed( binary.definitions, wxS( "BATCHB_PIN_STYLES" ) );
4492 const PADS_SCH::SYMBOL_DEF* asciiDefinition = asciiParser.GetSymbolDef( "BATCHB_PIN_STYLES" );
4493 BOOST_REQUIRE( asciiDefinition );
4494 BOOST_REQUIRE_EQUAL( binaryDefinition.pins.size(), asciiDefinition->pins.size() );
4495
4496 for( size_t pin = 0; pin < binaryDefinition.pins.size(); ++pin )
4497 {
4498 BOOST_CHECK_EQUAL( binaryDefinition.pins[pin].nameAngle,
4499 asciiDefinition->pins[pin].pn_angle * 10 );
4500 BOOST_CHECK_EQUAL( binaryDefinition.pins[pin].numberAngle,
4501 asciiDefinition->pins[pin].pl_angle * 10 );
4502 BOOST_CHECK_EQUAL( binaryDefinition.pins[pin].nameJustification, asciiDefinition->pins[pin].pn_just );
4503 BOOST_CHECK_EQUAL( binaryDefinition.pins[pin].numberJustification, asciiDefinition->pins[pin].pl_just );
4504 }
4505 }
4506}
4507
4508// The free-text justification word is the same code space every other PADS record uses, so the
4509// vertical half must decode the same way PADS_COMMON::DecodeJustification splits it.
4510BOOST_AUTO_TEST_CASE( FreeTextVerticalJustification )
4511{
4513 PADS_SCH_MODEL model = parser.Parse( loadBinaryFixture( "text_encoding.sch" ), wxS( "text_encoding.sch" ) );
4514
4515 BOOST_REQUIRE_EQUAL( model.texts.size(), 1 );
4516 const size_t justificationOffset = model.texts.front().source.absoluteOffset + 18;
4517
4518 for( uint16_t code : { 0, 1, 2, 3, 4, 6, 8, 9, 12 } )
4519 {
4520 BOOST_TEST_CONTEXT( "justification " << code )
4521 {
4522 std::vector<uint8_t> bytes = loadBinaryFixture( "text_encoding.sch" );
4523 writeU16( bytes, justificationOffset, code );
4524
4525 PADS_SCH_MODEL patched = parser.Parse( bytes, wxS( "text_encoding.sch" ) );
4526 BOOST_REQUIRE_EQUAL( patched.texts.size(), 1 );
4527
4528 GR_TEXT_H_ALIGN_T expectedHorizontal = GR_TEXT_H_ALIGN_LEFT;
4529 GR_TEXT_V_ALIGN_T expectedVertical = GR_TEXT_V_ALIGN_CENTER;
4530 PADS_COMMON::DecodeJustification( code, expectedHorizontal, expectedVertical );
4531
4532 MODEL_JUSTIFICATION vertical = patched.texts.front().presentation.verticalJustification;
4534 : expectedVertical == GR_TEXT_V_ALIGN_TOP ? MODEL_JUSTIFICATION::LEFT
4536
4537 BOOST_CHECK( vertical == expected );
4538 }
4539 }
4540}
4541
4542
const char * name
bool operator==(const wxAuiPaneInfo &aLhs, const wxAuiPaneInfo &aRhs)
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()
Parser for PADS Logic ASCII schematic export files.
const std::vector< TEXT_ITEM > & GetTextItems() const
const std::vector< TIED_DOT > & GetTiedDots() const
const std::vector< OFF_PAGE_CONNECTOR > & GetOffPageConnectors() const
bool Parse(const std::string &aFileName)
const std::vector< SHEET_HEADER > & GetSheetHeaders() const
const std::vector< NETNAME_LABEL > & GetNetNameLabels() const
const std::vector< BUS_DEF > & GetBuses() const
const std::vector< SCH_SIGNAL > & GetSignals() const
const std::vector< LINES_ITEM > & GetLinesItems() const
const std::vector< SYMBOL_DEF > & GetSymbolDefs() const
const std::vector< PART_PLACEMENT > & GetPartPlacements() const
const std::map< std::string, PARTTYPE_DEF > & GetPartTypes() const
const SYMBOL_DEF * GetSymbolDef(const std::string &aName) const
const PARAMETERS & GetParameters() const
constexpr ValueType Value() const
PADS_SCH_MODEL Parse(const std::vector< uint8_t > &aBytes, const wxString &aSourceName={}) const
static SOURCE_STRING DecodeString(const std::vector< uint8_t > &aBytes, uint32_t aCodePage, const SOURCE_PROVENANCE &aSource, std::vector< PARSER_DIAGNOSTIC > &aDiagnostics, const wxString &aRecordedCodePageName={})
static void RecordUnknownEnum(const wxString &aEnumName, uint32_t aValue, const SOURCE_PROVENANCE &aSource, std::vector< PARSER_DIAGNOSTIC > &aDiagnostics)
void Load(std::vector< uint8_t > aBytes)
const std::vector< SCH_SDB_OLE_ITEM > & OleItems() const
static bool empty(const wxTextEntryBase *aCtrl)
static std::string ToStdString(const wxString &aStr)
bool operator<(const FOOTPRINT_INFO &lhs, const FOOTPRINT_INFO &rhs)
@ BUS
the source net is carried by a bus
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
void DecodeJustification(int aJustification, GR_TEXT_H_ALIGN_T &aHJustify, GR_TEXT_V_ALIGN_T &aVJustify)
Decode a PADS text justification code into KiCad horizontal and vertical alignment.
constexpr int64_t NormalizeCoordinate(int64_t aCoordinate)
CONTROLLER_REFERENCE< DEFINITION_ID > DEFINITION_REFERENCE
constexpr int NormalizeAngle(int aAngle)
CONTROLLER_ID< NET_ID_TAG > NET_ID
CONTROLLER_ID< DEFINITION_ID_TAG > DEFINITION_ID
CONTROLLER_REFERENCE< PLACEMENT_ID > PLACEMENT_REFERENCE
CONTROLLER_REFERENCE< SHEET_ID > SHEET_REFERENCE
CONTROLLER_ID< SHEET_ID_TAG > SHEET_ID
CONTROLLER_ID< PIN_ID_TAG > PIN_ID
CONTROLLER_ID< FIELD_ID_TAG, uint64_t > FIELD_ID
constexpr FIELD_ID MakeFieldId(FIELD_ID_DOMAIN aDomain, uint32_t aOwner, uint32_t aOrdinal)
CONTROLLER_ID< PART_TYPE_ID_TAG > PART_TYPE_ID
wxString FormatParserError(const SOURCE_PROVENANCE &aSource, const wxString &aMessage)
CONTROLLER_ID< PLACEMENT_ID_TAG > PLACEMENT_ID
CONTROLLER_ID< GATE_ID_TAG > GATE_ID
CONTROLLER_REFERENCE< NET_ID > NET_REFERENCE
CONTROLLER_REFERENCE< GATE_ID > GATE_REFERENCE
CONTROLLER_ID< BUS_ID_TAG > BUS_ID
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
@ RPT_SEVERITY_WARNING
@ NET
This item represents a net.
std::vector< std::string > decal_names
std::vector< PARTTYPE_PIN > pins
std::map< std::string, std::string > fields
std::vector< GATE_DEF > gates
std::vector< SIGPIN > sigpins
std::string decal_name
CAEDECAL the placed gate draws, empty when unresolved.
std::vector< PART_ATTRIBUTE > attributes
std::vector< PIN_OVERRIDE > pin_overrides
std::vector< SYMBOL_GRAPHIC > graphics
std::vector< SYMBOL_PIN > pins
std::vector< CAEDECAL_ATTR > attrs
std::vector< SYMBOL_TEXT > texts
std::vector< GRAPHIC_POINT > points
std::vector< SOURCE_PROPERTY > properties
std::vector< MODEL_BUS_ENTRY > entries
std::vector< SOURCE_STRING > aliases
std::vector< NET_REFERENCE > memberNets
std::vector< SOURCE_POINT > vertices
std::vector< MODEL_CONNECTION_ENDPOINT > endpoints
std::vector< SOURCE_POINT > vertices
MODEL_TEXT_PRESENTATION presentation
std::vector< SOURCE_PROPERTY > properties
std::vector< DEFINITION_REFERENCE > decalGroupMembers
std::vector< MODEL_CONNECTOR_PIN > connectorPins
std::vector< PIN_REFERENCE > pins
std::vector< MODEL_GATE_PIN > logicalPins
std::vector< SOURCE_PROPERTY > properties
std::vector< SOURCE_POINT > points
std::vector< MODEL_CONNECTION > connections
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::optional< SHEET_REFERENCE > parent
std::vector< SOURCE_PROPERTY > properties
std::vector< MODEL_FIELD > titleBlockFields
std::vector< MODEL_GRAPHIC > border
std::vector< MODEL_PIN_DEFINITION > pins
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_EMBEDDED_IMAGE > images
std::vector< MODEL_PART_TYPE > partTypes
std::vector< MODEL_SHEET > sheets
std::vector< MODEL_PLACEMENT > placements
std::vector< PARSER_DIAGNOSTIC > diagnostics
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_CHECK_EQUAL_COLLECTIONS(mixed.begin(), mixed.end(), expMixed.begin(), expMixed.end())
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
KIBIS_MODEL * model
KIBIS_PIN * pin
VECTOR3I expected(15, 30, 45)
BOOST_AUTO_TEST_CASE(EmbeddedOleImages)
BOOST_TEST_CONTEXT("Test Clearance")
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
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_LEFT
GR_TEXT_V_ALIGN_T
This is API surface mapped to common.types.VertialAlignment.
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP