KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_orcad_sch_import.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
22#include <richio.h>
23
30#include <sch_io/ole_image.h>
32#include <sch_io/ole_image.h>
33
34#include <schematic.h>
35#include <import_net_map.h>
36#include <connection_graph.h>
37#include <sch_screen.h>
38#include <sch_sheet.h>
39#include <sch_sheet_pin.h>
40#include <sch_sheet_path.h>
41#include <sch_symbol.h>
42#include <sch_label.h>
43#include <sch_line.h>
44#include <sch_junction.h>
45#include <sch_no_connect.h>
46#include <sch_bitmap.h>
47#include <sch_shape.h>
48#include <sch_text.h>
49#include <sch_textbox.h>
50#include <sch_pin.h>
51#include <lib_symbol.h>
53#include <reporter.h>
55#include <bitmap_base.h>
56
57#include <wx/ffile.h>
58#include <wx/filefn.h>
59#include <wx/filename.h>
60
61#include <algorithm>
62#include <filesystem>
63#include <fstream>
64#include <map>
65#include <memory>
66#include <set>
67#include <sstream>
68#include <string>
69
70
71namespace
72{
73
74static void appendLe32( std::vector<uint8_t>& aBytes, uint32_t aValue )
75{
76 for( int shift = 0; shift < 32; shift += 8 )
77 aBytes.push_back( static_cast<uint8_t>( aValue >> shift ) );
78}
79
80
81static void appendLe16( std::vector<uint8_t>& aBytes, uint16_t aValue )
82{
83 aBytes.push_back( static_cast<uint8_t>( aValue ) );
84 aBytes.push_back( static_cast<uint8_t>( aValue >> 8 ) );
85}
86
87
88static void appendLzt( std::vector<uint8_t>& aBytes, const std::string& aValue )
89{
90 appendLe16( aBytes, static_cast<uint16_t>( aValue.size() ) );
91 aBytes.insert( aBytes.end(), aValue.begin(), aValue.end() );
92 aBytes.push_back( 0 );
93}
94
95
96// aLongPrefixCount long prefixes, one short prefix with no properties, preamble, empty trailer.
97static void appendFramedHeader( std::vector<uint8_t>& aBytes, uint8_t aType, size_t aLongPrefixCount )
98{
99 size_t start = aBytes.size();
100 size_t end = start + 9 * aLongPrefixCount + 3 + 8;
101
102 for( size_t i = 0; i < aLongPrefixCount; ++i )
103 {
104 aBytes.push_back( aType );
105 appendLe32( aBytes, static_cast<uint32_t>( end - ( start + 9 * i + 9 ) ) );
106 appendLe32( aBytes, 0 );
107 }
108
109 aBytes.push_back( aType );
110 appendLe16( aBytes, 0 );
111 aBytes.insert( aBytes.end(), { 0xFF, 0xE4, 0x5C, 0x39 } );
112 appendLe32( aBytes, 0 );
113}
114
115
116// Smallest Library stream that reaches the string table: header, one font slot, the eight part
117// field names and the 156-byte page settings block.
118static std::vector<char> makeLibraryStream( uint16_t aVersionMajor, const std::vector<std::string>& aStrings )
119{
120 std::vector<uint8_t> bytes( 32, 0 );
121 const std::string introduction = "OrCAD Windows Design";
122 std::copy( introduction.begin(), introduction.end(), bytes.begin() );
123
124 appendLe16( bytes, aVersionMajor );
125 appendLe16( bytes, 0 );
126 bytes.resize( bytes.size() + 12, 0 );
127 appendLe16( bytes, 1 );
128
129 appendLe16( bytes, 1 );
130 appendLe16( bytes, 0 );
131 bytes.resize( bytes.size() + 8, 0 );
132
133 for( int i = 0; i < 8; ++i )
134 appendLzt( bytes, "" );
135
136 bytes.resize( bytes.size() + 156, 0 );
137
138 if( aVersionMajor < 3 )
139 appendLe16( bytes, static_cast<uint16_t>( aStrings.size() ) );
140 else
141 appendLe32( bytes, static_cast<uint32_t>( aStrings.size() ) );
142
143 for( const std::string& text : aStrings )
144 appendLzt( bytes, text );
145
146 appendLe16( bytes, 0 );
147 bytes.resize( bytes.size() + 8, 0 );
148 appendLzt( bytes, "SCHEMATIC1" );
149
150 return std::vector<char>( bytes.begin(), bytes.end() );
151}
152
153
154static std::vector<char> cisFramed( const std::vector<uint8_t>& aPayload )
155{
156 std::vector<char> bytes;
157 uint32_t length = static_cast<uint32_t>( aPayload.size() );
158
159 for( int shift = 0; shift < 32; shift += 8 )
160 bytes.push_back( static_cast<char>( length >> shift ) );
161
162 bytes.insert( bytes.end(), aPayload.begin(), aPayload.end() );
163 return bytes;
164}
165
166
167static void writeLe16( std::vector<uint8_t>& aBytes, size_t aOffset, uint16_t aValue )
168{
169 aBytes[aOffset] = static_cast<uint8_t>( aValue );
170 aBytes[aOffset + 1] = static_cast<uint8_t>( aValue >> 8 );
171}
172
173
174static void writeLe32( std::vector<uint8_t>& aBytes, size_t aOffset, uint32_t aValue )
175{
176 for( int shift = 0; shift < 32; shift += 8 )
177 aBytes[aOffset++] = static_cast<uint8_t>( aValue >> shift );
178}
179
180
181static std::vector<uint8_t> makeGlyphIndexWmf()
182{
183 std::vector<uint8_t> bytes;
184 appendLe16( bytes, 1 );
185 appendLe16( bytes, 9 );
186 appendLe16( bytes, 0x0300 );
187 appendLe32( bytes, 0 );
188 appendLe16( bytes, 1 );
189 appendLe32( bytes, 10 );
190 appendLe16( bytes, 0 );
191
192 auto record = [&]( uint16_t aFunction, std::initializer_list<uint16_t> aParams )
193 {
194 appendLe32( bytes, static_cast<uint32_t>( aParams.size() + 3 ) );
195 appendLe16( bytes, aFunction );
196
197 for( uint16_t param : aParams )
198 appendLe16( bytes, param );
199 };
200
201 record( 0x0103, { 8 } );
202 record( 0x020B, { 0, 0 } );
203 record( 0x020C, { 100, 100 } );
204 record( 0x012E, { 0 } );
205 record( 0x0102, { 1 } );
206 record( 0x041B, { 90, 90, 10, 10 } );
207 record( 0x0A32, { 50, 50, 2, 0x0010, 0x4241, 8, 8 } );
208 record( 0x02FC, { 0, 0xFFFF, 0x00FF, 0 } );
209 record( 0x012D, { 0 } );
210 record( 0x0940, { 0x0029, 0x00AA, 0, 0, 0, 100, 100, 0, 0 } );
211 record( 0, {} );
212 writeLe32( bytes, 6, static_cast<uint32_t>( bytes.size() / 2 ) );
213 return bytes;
214}
215
216
217static std::vector<uint8_t> makeFlippedDibWmf()
218{
219 std::vector<uint8_t> bytes;
220 appendLe16( bytes, 1 );
221 appendLe16( bytes, 9 );
222 appendLe16( bytes, 0x0300 );
223 appendLe32( bytes, 0 );
224 appendLe16( bytes, 0 );
225 appendLe32( bytes, 0 );
226 appendLe16( bytes, 0 );
227
228 auto record = [&]( uint16_t aFunction, std::initializer_list<uint16_t> aParams )
229 {
230 appendLe32( bytes, static_cast<uint32_t>( aParams.size() + 3 ) );
231 appendLe16( bytes, aFunction );
232
233 for( uint16_t param : aParams )
234 appendLe16( bytes, param );
235 };
236
237 record( 0x0103, { 8 } );
238 record( 0x020B, { 0, 0 } );
239 record( 0x020C, { 100, 100 } );
240
241 const size_t dibRecord = bytes.size();
242 appendLe32( bytes, 0 );
243 appendLe16( bytes, 0x0B41 );
244 appendLe16( bytes, 0x0020 );
245 appendLe16( bytes, 0x00CC );
246 appendLe16( bytes, 2 );
247 appendLe16( bytes, 2 );
248 appendLe16( bytes, 0 );
249 appendLe16( bytes, 0 );
250 appendLe16( bytes, static_cast<uint16_t>( -80 ) );
251 appendLe16( bytes, 80 );
252 appendLe16( bytes, 90 );
253 appendLe16( bytes, 10 );
254
255 appendLe32( bytes, 40 );
256 appendLe32( bytes, 2 );
257 appendLe32( bytes, 2 );
258 appendLe16( bytes, 1 );
259 appendLe16( bytes, 24 );
260 appendLe32( bytes, 0 );
261 appendLe32( bytes, 16 );
262 appendLe32( bytes, 0 );
263 appendLe32( bytes, 0 );
264 appendLe32( bytes, 0 );
265 appendLe32( bytes, 0 );
266
267 bytes.insert( bytes.end(), { 0, 0, 255, 0, 255, 0, 0, 0 } );
268 bytes.insert( bytes.end(), { 255, 0, 0, 255, 255, 255, 0, 0 } );
269 writeLe32( bytes, dibRecord, static_cast<uint32_t>( ( bytes.size() - dibRecord ) / 2 ) );
270
271 record( 0, {} );
272 writeLe32( bytes, 6, static_cast<uint32_t>( bytes.size() / 2 ) );
273 writeLe32( bytes, 12, static_cast<uint32_t>( ( bytes.size() - dibRecord ) / 2 ) );
274 return bytes;
275}
276
277
278static std::vector<uint8_t> makeEmbeddedEmfWmf( const std::vector<uint8_t>& aEmf = {} )
279{
280 std::vector<uint8_t> emf = aEmf;
281
282 if( emf.empty() )
283 {
284 emf.resize( 100, 0 );
285 writeLe32( emf, 0, 1 );
286 writeLe32( emf, 4, 88 );
287 writeLe32( emf, 40, 0x464D4520 );
288 writeLe32( emf, 48, emf.size() );
289 }
290
291 std::vector<uint8_t> wmf;
292 appendLe16( wmf, 1 );
293 appendLe16( wmf, 9 );
294 appendLe16( wmf, 0x0300 );
295 appendLe32( wmf, 0 );
296 appendLe16( wmf, 0 );
297 appendLe32( wmf, 0 );
298 appendLe16( wmf, 0 );
299
300 size_t sourceOffset = 0;
301 const size_t chunkSizes[] = { emf.size() / 2, emf.size() - emf.size() / 2 };
302
303 for( size_t chunkSize : chunkSizes )
304 {
305 size_t recordStart = wmf.size();
306 appendLe32( wmf, 0 );
307 appendLe16( wmf, 0x0626 );
308 appendLe16( wmf, 0x000F );
309 appendLe16( wmf, static_cast<uint16_t>( 34 + chunkSize ) );
310 appendLe32( wmf, 0x43464D57 );
311 appendLe32( wmf, 1 );
312 appendLe32( wmf, 0x00010000 );
313 appendLe16( wmf, 0 );
314 appendLe32( wmf, 0 );
315 appendLe32( wmf, std::size( chunkSizes ) );
316 appendLe32( wmf, chunkSize );
317 appendLe32( wmf, emf.size() - sourceOffset - chunkSize );
318 appendLe32( wmf, emf.size() );
319 wmf.insert( wmf.end(), emf.begin() + sourceOffset, emf.begin() + sourceOffset + chunkSize );
320
321 if( wmf.size() % 2 )
322 wmf.push_back( 0 );
323
324 writeLe32( wmf, recordStart, ( wmf.size() - recordStart ) / 2 );
325 sourceOffset += chunkSize;
326 }
327
328 appendLe32( wmf, 3 );
329 appendLe16( wmf, 0 );
330 writeLe32( wmf, 6, wmf.size() / 2 );
331 return wmf;
332}
333
334
335static std::vector<uint8_t> makeRenderableEmf()
336{
337 std::vector<uint8_t> emf( 108, 0 );
338 writeLe32( emf, 0, 1 );
339 writeLe32( emf, 4, 108 );
340 writeLe32( emf, 16, 100 );
341 writeLe32( emf, 20, 100 );
342 writeLe32( emf, 32, 2646 );
343 writeLe32( emf, 36, 2646 );
344 writeLe32( emf, 40, 0x464D4520 );
345 writeLe32( emf, 44, 0x00010000 );
346 writeLe32( emf, 52, 23 );
347 writeLe16( emf, 56, 4 );
348 writeLe32( emf, 72, 100 );
349 writeLe32( emf, 76, 100 );
350 writeLe32( emf, 80, 26 );
351 writeLe32( emf, 84, 26 );
352 writeLe32( emf, 100, 26000 );
353 writeLe32( emf, 104, 26000 );
354
355 auto pointRecord = [&]( uint32_t aType, int32_t aX, int32_t aY )
356 {
357 appendLe32( emf, aType );
358 appendLe32( emf, 16 );
359 appendLe32( emf, static_cast<uint32_t>( aX ) );
360 appendLe32( emf, static_cast<uint32_t>( aY ) );
361 };
362
363 pointRecord( 27, 10, 10 );
364 pointRecord( 54, 90, 10 );
365
366 appendLe32( emf, 82 );
367 appendLe32( emf, 104 );
368 appendLe32( emf, 1 );
369 appendLe32( emf, static_cast<uint32_t>( -20 ) );
370 appendLe32( emf, 0 );
371 appendLe32( emf, 900 );
372 appendLe32( emf, 900 );
373 appendLe32( emf, 400 );
374 emf.insert( emf.end(), 8, 0 );
375 const std::u16string face = u"Source Sans Pro";
376
377 for( size_t i = 0; i < 32; ++i )
378 appendLe16( emf, i < face.size() ? face[i] : 0 );
379
380 appendLe32( emf, 37 );
381 appendLe32( emf, 12 );
382 appendLe32( emf, 1 );
383 appendLe32( emf, 24 );
384 appendLe32( emf, 12 );
385 appendLe32( emf, 0x00CC0000 );
386 appendLe32( emf, 22 );
387 appendLe32( emf, 12 );
388 appendLe32( emf, 0x18 );
389
390 appendLe32( emf, 84 );
391 appendLe32( emf, 88 );
392 emf.insert( emf.end(), 16, 0 );
393 appendLe32( emf, 1 );
394 appendLe32( emf, 0x3F800000 );
395 appendLe32( emf, 0x3F800000 );
396 appendLe32( emf, 40 );
397 appendLe32( emf, 70 );
398 appendLe32( emf, 2 );
399 appendLe32( emf, 76 );
400 appendLe32( emf, 0 );
401 emf.insert( emf.end(), 16, 0 );
402 appendLe32( emf, 80 );
403 appendLe16( emf, 'H' );
404 appendLe16( emf, 'i' );
405 appendLe32( emf, 12 );
406 appendLe32( emf, 12 );
407
408 appendLe32( emf, 82 );
409 appendLe32( emf, 104 );
410 appendLe32( emf, 2 );
411 appendLe32( emf, static_cast<uint32_t>( -20 ) );
412 appendLe32( emf, 0 );
413 appendLe32( emf, 0 );
414 appendLe32( emf, 0 );
415 appendLe32( emf, 400 );
416 emf.insert( emf.end(), 8, 0 );
417
418 for( size_t i = 0; i < 32; ++i )
419 appendLe16( emf, i < face.size() ? face[i] : 0 );
420
421 appendLe32( emf, 37 );
422 appendLe32( emf, 12 );
423 appendLe32( emf, 2 );
424
425 appendLe32( emf, 84 );
426 appendLe32( emf, 88 );
427 emf.insert( emf.end(), 16, 0 );
428 appendLe32( emf, 1 );
429 appendLe32( emf, 0x3F800000 );
430 appendLe32( emf, 0x3F800000 );
431 appendLe32( emf, 70 );
432 appendLe32( emf, 30 );
433 appendLe32( emf, 2 );
434 appendLe32( emf, 76 );
435 appendLe32( emf, 0x10 );
436 emf.insert( emf.end(), 16, 0 );
437 appendLe32( emf, 80 );
438 appendLe16( emf, 1211 );
439 appendLe16( emf, 3 );
440 appendLe32( emf, 20 );
441 appendLe32( emf, 5 );
442
443 appendLe32( emf, 82 );
444 appendLe32( emf, 104 );
445 appendLe32( emf, 3 );
446 appendLe32( emf, static_cast<uint32_t>( -20 ) );
447 appendLe32( emf, 0 );
448 appendLe32( emf, 0 );
449 appendLe32( emf, 0 );
450 appendLe32( emf, 400 );
451 emf.insert( emf.end(), 8, 0 );
452 const std::u16string calibri = u"Calibri";
453
454 for( size_t i = 0; i < 32; ++i )
455 appendLe16( emf, i < calibri.size() ? calibri[i] : 0 );
456
457 appendLe32( emf, 37 );
458 appendLe32( emf, 12 );
459 appendLe32( emf, 3 );
460 appendLe32( emf, 84 );
461 appendLe32( emf, 84 );
462 emf.insert( emf.end(), 16, 0 );
463 appendLe32( emf, 1 );
464 appendLe32( emf, 0x3F800000 );
465 appendLe32( emf, 0x3F800000 );
466 appendLe32( emf, 5 );
467 appendLe32( emf, 95 );
468 appendLe32( emf, 1 );
469 appendLe32( emf, 76 );
470 appendLe32( emf, 0x10 );
471 emf.insert( emf.end(), 16, 0 );
472 appendLe32( emf, 80 );
473 appendLe16( emf, 3 );
474 appendLe16( emf, 0 );
475 appendLe32( emf, 10 );
476
477 appendLe32( emf, 85 );
478 appendLe32( emf, 44 );
479 emf.insert( emf.end(), 16, 0 );
480 appendLe32( emf, 4 );
481
482 for( const std::pair<uint16_t, uint16_t>& point :
483 { std::pair<uint16_t, uint16_t>{ 10, 40 }, { 20, 20 }, { 30, 60 }, { 40, 40 } } )
484 {
485 appendLe16( emf, point.first );
486 appendLe16( emf, point.second );
487 }
488
489 appendLe32( emf, 59 );
490 appendLe32( emf, 8 );
491 pointRecord( 27, 10, 90 );
492 pointRecord( 54, 90, 90 );
493 appendLe32( emf, 60 );
494 appendLe32( emf, 8 );
495 appendLe32( emf, 64 );
496 appendLe32( emf, 24 );
497 emf.insert( emf.end(), 16, 0 );
498
499 appendLe32( emf, 91 );
500 appendLe32( emf, 52 );
501 emf.insert( emf.end(), 16, 0 );
502 appendLe32( emf, 1 );
503 appendLe32( emf, 4 );
504 appendLe32( emf, 4 );
505
506 for( const std::pair<uint16_t, uint16_t>& point :
507 { std::pair<uint16_t, uint16_t>{ 60, 60 }, { 70, 50 }, { 80, 60 }, { 70, 70 } } )
508 {
509 appendLe16( emf, point.first );
510 appendLe16( emf, point.second );
511 }
512
513 appendLe32( emf, 58 );
514 appendLe32( emf, 12 );
515 appendLe32( emf, 0x40400000 );
516
517 appendLe32( emf, 14 );
518 appendLe32( emf, 20 );
519 emf.insert( emf.end(), 12, 0 );
520 writeLe32( emf, 48, emf.size() );
521 return emf;
522}
523
524
525static std::vector<uint8_t> makeCalibriMetricEmf( const std::u16string& aText = u"UMC" )
526{
527 std::vector<uint8_t> emf( 108, 0 );
528 writeLe32( emf, 0, 1 );
529 writeLe32( emf, 4, 108 );
530 writeLe32( emf, 16, 100 );
531 writeLe32( emf, 20, 50 );
532 writeLe32( emf, 32, 2646 );
533 writeLe32( emf, 36, 1323 );
534 writeLe32( emf, 40, 0x464D4520 );
535 writeLe32( emf, 44, 0x00010000 );
536 writeLe32( emf, 52, 6 );
537 writeLe16( emf, 56, 2 );
538 writeLe32( emf, 72, 100 );
539 writeLe32( emf, 76, 50 );
540 writeLe32( emf, 80, 26 );
541 writeLe32( emf, 84, 13 );
542 writeLe32( emf, 100, 26000 );
543 writeLe32( emf, 104, 13000 );
544
545 appendLe32( emf, 82 );
546 appendLe32( emf, 104 );
547 appendLe32( emf, 1 );
548 appendLe32( emf, static_cast<uint32_t>( -20 ) );
549 appendLe32( emf, 0 );
550 appendLe32( emf, 0 );
551 appendLe32( emf, 0 );
552 appendLe32( emf, 400 );
553 emf.insert( emf.end(), 8, 0 );
554 const std::u16string calibri = u"Calibri";
555
556 for( size_t i = 0; i < 32; ++i )
557 appendLe16( emf, i < calibri.size() ? calibri[i] : 0 );
558
559 appendLe32( emf, 37 );
560 appendLe32( emf, 12 );
561 appendLe32( emf, 1 );
562 appendLe32( emf, 22 );
563 appendLe32( emf, 12 );
564 appendLe32( emf, 0x18 );
565
566 size_t stringBytes = ( aText.size() * 2 + 3 ) & ~size_t( 3 );
567 appendLe32( emf, 84 );
568 appendLe32( emf, 76 + stringBytes + aText.size() * 4 );
569 emf.insert( emf.end(), 16, 0 );
570 appendLe32( emf, 1 );
571 appendLe32( emf, 0x3F800000 );
572 appendLe32( emf, 0x3F800000 );
573 appendLe32( emf, 5 );
574 appendLe32( emf, 5 );
575 appendLe32( emf, aText.size() );
576 appendLe32( emf, 76 );
577 appendLe32( emf, 0 );
578 emf.insert( emf.end(), 16, 0 );
579 appendLe32( emf, 76 + stringBytes );
580
581 for( char16_t c : aText )
582 appendLe16( emf, c );
583
584 emf.insert( emf.end(), stringBytes - aText.size() * 2, 0 );
585
586 for( size_t i = 0; i < aText.size(); ++i )
587 appendLe32( emf, i == 1 ? 16 : i == 2 ? 10 : 12 );
588
589 appendLe32( emf, 14 );
590 appendLe32( emf, 20 );
591 emf.insert( emf.end(), 12, 0 );
592 writeLe32( emf, 48, emf.size() );
593 return emf;
594}
595
596
597// Wrap a compound file in the 26-byte prologue an OrCAD OLE payload carries: the compound
598// file's length plus 22 at offset 0, and the length itself at offset 22.
599static std::vector<uint8_t> makeOlePayload( const std::vector<uint8_t>& aCfb, uint32_t aDeclaredLength )
600{
601 std::vector<uint8_t> payload( 26, 0 );
602 writeLe32( payload, 0, aDeclaredLength + 22 );
603 writeLe32( payload, 22, aDeclaredLength );
604 payload.insert( payload.end(), aCfb.begin(), aCfb.end() );
605
606 return payload;
607}
608
609
610static std::vector<uint8_t> makeOlePreviewCfb( const std::vector<uint16_t>& aName, const std::vector<uint8_t>& aStream )
611{
612 constexpr uint32_t FREE_SECTOR = 0xFFFFFFFF;
613 constexpr uint32_t END_OF_CHAIN = 0xFFFFFFFE;
614 constexpr uint32_t FAT_SECTOR = 0xFFFFFFFD;
615 constexpr size_t SECTOR_SIZE = 512;
616 constexpr size_t STREAM_SECTORS = 8;
617
618 std::vector<uint8_t> cfb( SECTOR_SIZE * ( 3 + STREAM_SECTORS ), 0 );
619 const uint8_t magic[] = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
620 std::copy( std::begin( magic ), std::end( magic ), cfb.begin() );
621 writeLe16( cfb, 24, 0x003E );
622 writeLe16( cfb, 26, 3 );
623 writeLe16( cfb, 28, 0xFFFE );
624 writeLe16( cfb, 30, 9 );
625 writeLe16( cfb, 32, 6 );
626 writeLe32( cfb, 44, 1 );
627 writeLe32( cfb, 48, 1 );
628 writeLe32( cfb, 56, 4096 );
629 writeLe32( cfb, 60, END_OF_CHAIN );
630 writeLe32( cfb, 68, END_OF_CHAIN );
631
632 for( size_t i = 0; i < 109; ++i )
633 writeLe32( cfb, 76 + 4 * i, i == 0 ? 0 : FREE_SECTOR );
634
635 size_t fat = SECTOR_SIZE;
636 writeLe32( cfb, fat, FAT_SECTOR );
637 writeLe32( cfb, fat + 4, END_OF_CHAIN );
638
639 for( size_t i = 0; i < STREAM_SECTORS; ++i )
640 writeLe32( cfb, fat + 4 * ( 2 + i ), i + 1 == STREAM_SECTORS ? END_OF_CHAIN : 3 + i );
641
642 for( size_t i = 2 + STREAM_SECTORS; i < SECTOR_SIZE / 4; ++i )
643 writeLe32( cfb, fat + 4 * i, FREE_SECTOR );
644
645 size_t root = 2 * SECTOR_SIZE;
646 writeLe16( cfb, root, 'R' );
647 writeLe16( cfb, root + 2, 0 );
648 writeLe16( cfb, root + 64, 4 );
649 cfb[root + 66] = 5;
650 writeLe32( cfb, root + 68, FREE_SECTOR );
651 writeLe32( cfb, root + 72, FREE_SECTOR );
652 writeLe32( cfb, root + 76, 1 );
653 writeLe32( cfb, root + 116, END_OF_CHAIN );
654
655 size_t entry = root + 128;
656
657 for( size_t i = 0; i < aName.size(); ++i )
658 writeLe16( cfb, entry + 2 * i, aName[i] );
659
660 writeLe16( cfb, entry + 64, static_cast<uint16_t>( 2 * aName.size() ) );
661 cfb[entry + 66] = 2;
662 writeLe32( cfb, entry + 68, FREE_SECTOR );
663 writeLe32( cfb, entry + 72, FREE_SECTOR );
664 writeLe32( cfb, entry + 76, FREE_SECTOR );
665 writeLe32( cfb, entry + 116, 2 );
666 writeLe32( cfb, entry + 120, STREAM_SECTORS * SECTOR_SIZE );
667
668 std::copy_n( aStream.begin(), std::min( aStream.size(), STREAM_SECTORS * SECTOR_SIZE ),
669 cfb.begin() + 3 * SECTOR_SIZE );
670 return cfb;
671}
672
673
674static std::vector<uint8_t> makeOleWmfPreview( const std::vector<uint8_t>& aWmf )
675{
676 std::vector<uint8_t> presentation( 40, 0 );
677 writeLe32( presentation, 4, 14 );
678 presentation.insert( presentation.end(), aWmf.begin(), aWmf.end() );
679
680 const std::vector<uint16_t> name = { 2, 'O', 'l', 'e', 'P', 'r', 'e', 's', '0', '0', '0', 0 };
681 std::vector<uint8_t> cfb = makeOlePreviewCfb( name, presentation );
682 return makeOlePayload( cfb, cfb.size() );
683}
684
686struct TEMP_TEST_FILE
687{
688 TEMP_TEST_FILE( const wxString& aFileName, const wxString& aContents ) :
689 m_path( wxFileName( wxFileName::GetTempDir(), aFileName ).GetFullPath() )
690 {
691 wxFFile file( m_path, wxS( "w" ) );
692
693 if( file.IsOpened() )
694 file.Write( aContents );
695 }
696
697 ~TEMP_TEST_FILE() { wxRemoveFile( m_path ); }
698
699 wxString m_path;
700};
701
702} // namespace
703
704
706{
708 m_schematic( new SCHEMATIC( nullptr ) )
709 {
710 m_manager.LoadProject( "" );
711 m_schematic->SetProject( &m_manager.Prj() );
712 m_schematic->CurrentSheet().clear();
713 m_schematic->CurrentSheet().push_back( &m_schematic->Root() );
714 }
715
717
718 std::string dataPath( const std::string& aRelPath ) const
719 {
720 return KI_TEST::GetEeschemaTestDataDir() + "io/orcad/" + aRelPath;
721 }
722
723 SCH_SHEET* LoadOrcadSchematic( const std::string& aRelPath )
724 {
725 return m_plugin.LoadSchematicFile( dataPath( aRelPath ), m_schematic.get() );
726 }
727
729 std::unique_ptr<SCHEMATIC> m_schematic;
731};
732
733
734BOOST_FIXTURE_TEST_SUITE( OrcadSchImport, ORCAD_SCH_IMPORT_FIXTURE )
735
736
737BOOST_AUTO_TEST_CASE( CisVariantFallbackSortsBomNamesBytewise )
738{
739 std::vector<uint8_t> payload = { '2', 0xF9, '5', 'V', 0xF9, '1', '2', 'V' };
740 std::vector<std::string> names = OrcadCisParseCountedList( cisFramed( payload ), 0xF9 );
741
742 BOOST_REQUIRE_EQUAL( names.size(), 2u );
743 BOOST_CHECK_EQUAL( OrcadCisSelectVariant( names, std::nullopt ), "12V" );
744 BOOST_CHECK_EQUAL( OrcadCisSelectVariant( names, std::optional<std::string>( "5V" ) ), "5V" );
745 BOOST_CHECK_THROW( OrcadCisSelectVariant( names, std::optional<std::string>( "9V" ) ), IO_ERROR );
746}
747
748
749BOOST_AUTO_TEST_CASE( CisLegacyEmptyVariantListHasNoSelection )
750{
751 std::vector<std::string> names = OrcadCisParseCountedList( cisFramed( { 0xFB } ), 0xF9 );
752
753 BOOST_CHECK( names.empty() );
754 BOOST_CHECK( OrcadCisSelectVariant( names, std::nullopt ).empty() );
755 BOOST_CHECK_THROW( OrcadCisParseCountedList( cisFramed( {} ), 0xF9 ), IO_ERROR );
756}
757
758
759BOOST_AUTO_TEST_CASE( CisVariantPropertyUpdatesParseRealRecordShape )
760{
761 std::vector<uint8_t> payload;
762
763 auto appendText = [&]( const std::string& aText ) { payload.insert( payload.end(), aText.begin(), aText.end() ); };
764 auto separator = [&]( uint8_t aByte ) { payload.push_back( aByte ); };
765
766 appendText( "2835" );
767 separator( 0xB0 );
768 appendText( "SiLabsPN^Value^Voltage" );
769 separator( 0xC0 );
770 appendText( "NVMFS5C680NLT1G^NVMFS5C680NLT1G^60V~2829" );
771 separator( 0xB0 );
772 appendText( "Part Number^Value" );
773 separator( 0xC0 );
774 appendText( "PDS5100H-13^PDS5100~" );
775
776 auto updates = OrcadCisParsePropertyUpdates( cisFramed( payload ) );
777
778 BOOST_REQUIRE_EQUAL( updates.size(), 2u );
779 BOOST_CHECK_EQUAL( updates.at( 2835 ).at( "Value" ), "NVMFS5C680NLT1G" );
780 BOOST_CHECK_EQUAL( updates.at( 2835 ).at( "Voltage" ), "60V" );
781 BOOST_CHECK_EQUAL( updates.at( 2829 ).at( "Value" ), "PDS5100" );
782}
783
784
785BOOST_AUTO_TEST_CASE( CisVariantMembershipRetainsInstalledState )
786{
787 std::vector<uint8_t> payload = { '1', 0xB0, '2', '8', '3', '5', '~',
788 '0', 0xB0, '2', '2', '1', '2', '~' };
789 auto memberships = OrcadCisParseMemberships( cisFramed( payload ) );
790
791 BOOST_REQUIRE_EQUAL( memberships.size(), 2u );
792 BOOST_CHECK( memberships.at( 2835 ) );
793 BOOST_CHECK( !memberships.at( 2212 ) );
794}
795
796
797BOOST_AUTO_TEST_CASE( CisSchematicInfoMapsVariantPropertiesToPageDatabaseIds )
798{
799 std::vector<uint8_t> bytes;
800 appendLe32( bytes, 1 );
801 appendLe32( bytes, 1656123 );
802 appendLe32( bytes, 2 );
803 appendLzt( bytes, "Output Voltage-12V" );
804 appendLzt( bytes, "Output Voltage-5V" );
805 appendLe32( bytes, 2 );
806 appendLzt( bytes, "1" );
807 appendLzt( bytes, "1" );
808 appendLe32( bytes, 2 );
809
810 for( const auto& [value, voltage] :
811 { std::pair<std::string, std::string>( "NVMFS5C680NLT1G", "60V" ),
812 std::pair<std::string, std::string>( "SiR422DP-T1", "40V" ) } )
813 {
814 appendLe32( bytes, 2 );
815 appendLzt( bytes, "Value" );
816 appendLzt( bytes, "Voltage" );
817 appendLe32( bytes, 2 );
818 appendLzt( bytes, value );
819 appendLzt( bytes, voltage );
820 }
821
822 appendLe32( bytes, 1 );
823 std::vector<char> data( bytes.begin(), bytes.end() );
824 auto groups = OrcadCisParseSchematicInfo( data );
825
826 BOOST_CHECK_EQUAL( groups.at( "Output Voltage-12V" ).at( 1656123 ).at( "Value" ),
827 "NVMFS5C680NLT1G" );
828 BOOST_CHECK_EQUAL( groups.at( "Output Voltage-5V" ).at( 1656123 ).at( "Voltage" ), "40V" );
829}
830
831
832BOOST_AUTO_TEST_CASE( StructurePrefixDepthIsTypeOwned )
833{
834 const std::map<int, int> expected = { { 9, 1 }, { 10, 2 }, { 2, 3 }, { 13, 4 }, { 24, 5 },
835 { 66, 1 }, { 16, 2 }, { 12, 3 }, { 64, 4 } };
836
837 for( const auto& [type, depth] : expected )
838 {
839 std::optional<size_t> actual = OrcadLongPrefixCount( type );
840 BOOST_REQUIRE_MESSAGE( actual, "type " << type );
841 BOOST_CHECK_EQUAL( *actual, static_cast<size_t>( depth ) );
842 }
843
844 BOOST_CHECK( !OrcadLongPrefixCount( 0 ) );
845 BOOST_CHECK( !OrcadLongPrefixCount( 255 ) );
846}
847
848
849BOOST_AUTO_TEST_CASE( StructurePrefixDepthDoesNotRetry )
850{
851 // A LibraryPart chain still carries its 41 short-prefix property pairs at the registered depth.
852 std::vector<uint8_t> bytes;
853 std::vector<size_t> lengthOffsets;
854
855 for( int i = 0; i < 5; ++i )
856 {
857 bytes.push_back( ORCAD_ST_LIBRARY_PART );
858 lengthOffsets.push_back( bytes.size() );
859 appendLe32( bytes, 0 );
860 appendLe32( bytes, 0 );
861 }
862
863 bytes.push_back( ORCAD_ST_LIBRARY_PART );
864 appendLe16( bytes, 41 );
865
866 for( int i = 0; i < 41; ++i )
867 {
868 appendLe32( bytes, 0 );
869 appendLe32( bytes, 0 );
870 }
871
872 bytes.insert( bytes.end(), { 0xFF, 0xE4, 0x5C, 0x39 } );
873 appendLe32( bytes, 0 );
874
875 for( size_t offset : lengthOffsets )
876 writeLe32( bytes, offset, static_cast<uint32_t>( bytes.size() - offset - 8 ) );
877
878 ORCAD_STREAM stream( bytes.data(), bytes.size() );
879 ORCAD_STRUCT_READER reader( stream );
881
882 BOOST_CHECK_EQUAL( prefixes.bodyLens.size(), 5u );
883 BOOST_CHECK_EQUAL( prefixes.props.size(), 41u );
884 BOOST_CHECK_EQUAL( stream.GetOffset(), bytes.size() );
885
886 // A chain one prefix deeper than the type owns must not be accepted by probing.
887 std::vector<uint8_t> tooDeep;
888 appendFramedHeader( tooDeep, ORCAD_ST_DRAWN_INSTANCE, 4 );
889 ORCAD_STREAM tooDeepStream( tooDeep.data(), tooDeep.size() );
890 ORCAD_STRUCT_READER tooDeepReader( tooDeepStream );
891
892 BOOST_CHECK_THROW( tooDeepReader.ReadPrefixes( ORCAD_ST_DRAWN_INSTANCE ), IO_ERROR );
893
894 std::vector<uint8_t> correct;
895 appendFramedHeader( correct, ORCAD_ST_DRAWN_INSTANCE, 3 );
896 ORCAD_STREAM correctStream( correct.data(), correct.size() );
897 ORCAD_STRUCT_READER correctReader( correctStream );
898
899 BOOST_CHECK_EQUAL( correctReader.ReadPrefixes( ORCAD_ST_DRAWN_INSTANCE ).bodyLens.size(), 3u );
900}
901
902
903BOOST_AUTO_TEST_CASE( StructureBodyLimitIsEnforced )
904{
905 const uint8_t bytes[] = { 1, 2, 3, 4 };
906 ORCAD_STREAM stream( bytes, sizeof( bytes ) );
907
908 {
909 ORCAD_STREAM::LIMIT_GUARD limit( stream, 2 );
910 BOOST_CHECK_THROW( stream.ReadU32(), IO_ERROR );
911 }
912
913 BOOST_CHECK_EQUAL( stream.ReadU32(), 0x04030201u );
914}
915
916
917BOOST_AUTO_TEST_CASE( CacheEmptyStreamIsMarkerPlusFourZeroSections )
918{
919 // The ten-byte cache seen in the wild is not a special case: it is the u16 marker and the
920 // four section counts, all zero, consumed exactly.
921 std::vector<char> data( 10, 0 );
922 std::map<std::string, ORCAD_SYMBOL_DEF> symbols;
923 std::map<std::string, ORCAD_PACKAGE> packages;
924 std::vector<wxString> warnings;
925
926 ORCAD_WARN_FN warn = [&]( const wxString& aMsg )
927 {
928 warnings.push_back( aMsg );
929 };
930
931 BOOST_CHECK_NO_THROW( OrcadParseCache( data, {}, warn, symbols, packages ) );
932 BOOST_CHECK( warnings.empty() );
933 BOOST_CHECK( symbols.empty() );
934 BOOST_CHECK( packages.empty() );
935}
936
937
938BOOST_AUTO_TEST_CASE( CacheTrailingBytesAfterFourSectionsAreReported )
939{
940 // Exactly four sections are consumed. Anything after them means the walk lost alignment,
941 // which must be reported rather than ignored or thrown out of the import.
942 std::vector<char> data( 11, 0 );
943 std::map<std::string, ORCAD_SYMBOL_DEF> symbols;
944 std::map<std::string, ORCAD_PACKAGE> packages;
945 std::vector<wxString> warnings;
946
947 ORCAD_WARN_FN warn = [&]( const wxString& aMsg )
948 {
949 warnings.push_back( aMsg );
950 };
951
952 BOOST_CHECK_NO_THROW( OrcadParseCache( data, {}, warn, symbols, packages ) );
953 BOOST_CHECK_EQUAL( warnings.size(), 1u );
954 BOOST_CHECK( symbols.empty() );
955}
956
957
958BOOST_AUTO_TEST_CASE( CacheSectionRejectsAForeignStructureType )
959{
960 // Section 0 holds loose symbols. A Package there means the section counts no longer
961 // describe the stream, so the walk stops instead of decoding a record at the wrong offset.
962 std::vector<uint8_t> bytes;
963 appendLe16( bytes, 0 ); // marker
964 appendLe16( bytes, 1 ); // section 0 group count
965 appendLzt( bytes, "GROUP" );
966 appendLe16( bytes, 1 ); // variant count
967 appendLzt( bytes, "SOURCE.OLB" );
968 appendLe32( bytes, 0 ); // created
969 appendLe32( bytes, 0 ); // modified
970 bytes.push_back( ORCAD_ST_PACKAGE );
971 bytes.push_back( 0 );
972
973 std::vector<char> data( bytes.begin(), bytes.end() );
974 std::map<std::string, ORCAD_SYMBOL_DEF> symbols;
975 std::map<std::string, ORCAD_PACKAGE> packages;
976 std::vector<wxString> warnings;
977
978 ORCAD_WARN_FN warn = [&]( const wxString& aMsg )
979 {
980 warnings.push_back( aMsg );
981 };
982
983 BOOST_CHECK_NO_THROW( OrcadParseCache( data, {}, warn, symbols, packages ) );
984 BOOST_CHECK_EQUAL( warnings.size(), 1u );
985 BOOST_CHECK( symbols.empty() );
986 BOOST_CHECK( packages.empty() );
987}
988
989
990BOOST_AUTO_TEST_CASE( LibraryStringTableCountWidthFollowsVersion )
991{
992 ORCAD_LIBRARY_INFO legacy = OrcadParseLibrary( makeLibraryStream( 2, { "SIZE", "N/A" } ) );
993 BOOST_REQUIRE_EQUAL( legacy.strings.size(), 2u );
994 BOOST_CHECK_EQUAL( legacy.strings[1], "N/A" );
995
996 ORCAD_LIBRARY_INFO modern = OrcadParseLibrary( makeLibraryStream( 3, { "SIZE", "N/A" } ) );
997 BOOST_REQUIRE_EQUAL( modern.strings.size(), 2u );
998 BOOST_CHECK_EQUAL( modern.strings[1], "N/A" );
999
1000 // A u16 table under a version-3 header is rejected rather than retried at the other width.
1001 std::vector<char> mislabelled = makeLibraryStream( 2, { "SIZE", "N/A" } );
1002 mislabelled[32] = 3;
1003
1004 BOOST_CHECK_THROW( OrcadParseLibrary( mislabelled ), IO_ERROR );
1005}
1006
1007
1008BOOST_AUTO_TEST_CASE( PrimitiveRecordLengthConventionIsChecked )
1009{
1010 // The legacy convention excludes the u32 byteLength and its pad from the stored count.
1011 std::vector<uint8_t> legacy = { ORCAD_PRIM_LINE, ORCAD_PRIM_LINE };
1012 appendLe32( legacy, 24 );
1013 appendLe32( legacy, 0 );
1014
1015 for( uint32_t value : { 10u, 20u, 30u, 40u, 1u, 7u } )
1016 appendLe32( legacy, value );
1017
1018 ORCAD_STREAM legacyStream( legacy.data(), legacy.size() );
1019 std::optional<ORCAD_PRIMITIVE> primitive = OrcadReadPrimitive( legacyStream );
1020
1021 BOOST_REQUIRE( primitive );
1022 BOOST_CHECK_EQUAL( primitive->lineWidth, 7 );
1023 BOOST_CHECK_EQUAL( legacyStream.GetOffset(), legacy.size() );
1024
1025 // Neither convention explains a byteLength of 25, so the record is refused.
1026 std::vector<uint8_t> mismatched = legacy;
1027 mismatched.resize( mismatched.size() + 8, 0 );
1028 writeLe32( mismatched, 2, 25 );
1029
1030 ORCAD_STREAM mismatchedStream( mismatched.data(), mismatched.size() );
1031 BOOST_CHECK_THROW( OrcadReadPrimitive( mismatchedStream ), IO_ERROR );
1032}
1033
1034
1035BOOST_AUTO_TEST_CASE( CommentTextLengthOwnsItsRecordExtent )
1036{
1037 // CommentText carries padding past the string; the exclusive byteLength ends the record.
1038 std::vector<uint8_t> bytes = { ORCAD_PRIM_COMMENT_TEXT, ORCAD_PRIM_COMMENT_TEXT };
1039 size_t lengthOffset = bytes.size();
1040 appendLe32( bytes, 0 );
1041 appendLe32( bytes, 0 );
1042
1043 for( uint32_t value : { 10u, 20u, 30u, 40u, 10u, 20u } )
1044 appendLe32( bytes, value );
1045
1046 appendLe16( bytes, 1 );
1047 appendLe16( bytes, 0 );
1048 appendLzt( bytes, "NOTE" );
1049
1050 bytes.resize( bytes.size() + 20, 0 );
1051 writeLe32( bytes, lengthOffset, static_cast<uint32_t>( bytes.size() - 2 - 8 ) );
1052
1053 ORCAD_STREAM stream( bytes.data(), bytes.size() );
1054 std::optional<ORCAD_PRIMITIVE> primitive = OrcadReadPrimitive( stream );
1055
1056 BOOST_REQUIRE( primitive );
1057 BOOST_CHECK( primitive->kind == ORCAD_PRIM_KIND::TEXT );
1058 BOOST_CHECK_EQUAL( primitive->text, "NOTE" );
1059 BOOST_CHECK_EQUAL( stream.GetOffset(), bytes.size() );
1060}
1061
1062
1063BOOST_AUTO_TEST_CASE( PrimitiveLineWidth )
1064{
1065 std::vector<uint8_t> bytes = { ORCAD_PRIM_LINE, ORCAD_PRIM_LINE };
1066 appendLe32( bytes, 32 );
1067 appendLe32( bytes, 0 );
1068 appendLe32( bytes, 10 );
1069 appendLe32( bytes, 20 );
1070 appendLe32( bytes, 30 );
1071 appendLe32( bytes, 40 );
1072 appendLe32( bytes, 1 );
1073 appendLe32( bytes, 2 );
1074
1075 ORCAD_STREAM stream( bytes.data(), bytes.size() );
1076 std::optional<ORCAD_PRIMITIVE> primitive = OrcadReadPrimitive( stream );
1077
1078 BOOST_REQUIRE( primitive );
1079 BOOST_CHECK( primitive->kind == ORCAD_PRIM_KIND::LINE );
1080 BOOST_CHECK_EQUAL( primitive->lineWidth, 2 );
1081}
1082
1083
1084BOOST_AUTO_TEST_CASE( LibraryPartGeneralPropertiesPreserveImplementationPath )
1085{
1086 std::vector<uint8_t> bytes;
1087 appendLzt( bytes, "JMB582QH.Normal" );
1088 appendLzt( bytes, "IC.OLB" );
1089 appendLe32( bytes, 48 );
1090 appendLe16( bytes, 0 );
1091
1092 for( int coordinate : { 0, 0, 100, 100 } )
1093 appendLe16( bytes, static_cast<uint16_t>( coordinate ) );
1094
1095 size_t bboxEnd = bytes.size();
1096 appendLe16( bytes, 0 );
1097 appendLe16( bytes, 0 );
1098
1099 // The fifth prefix bounds GeneralProperties; the innermost stop ends the symbol body.
1100 size_t tailStart = bytes.size();
1101 appendLzt( bytes, "JMB582" );
1102 appendLzt( bytes, "" );
1103 appendLzt( bytes, "U" );
1104 appendLzt( bytes, "JMB582QH" );
1105 appendLe16( bytes, 3 );
1106
1107 ORCAD_STREAM stream( bytes.data(), bytes.size() );
1108 ORCAD_STRUCT_READER reader( stream );
1109 ORCAD_PREFIXES prefixes;
1110 prefixes.typeId = ORCAD_ST_LIBRARY_PART;
1111 prefixes.stops = { bytes.size(), tailStart, bboxEnd };
1112 prefixes.end = bytes.size();
1113
1114 ORCAD_SYMBOL_DEF symbol = OrcadReadSymbolDef( reader, prefixes, true );
1115
1116 BOOST_CHECK_EQUAL( symbol.props.at( "Implementation Path" ), "JMB582" );
1117 BOOST_CHECK_EQUAL( symbol.generalFlags, 3 );
1118}
1119
1120
1121BOOST_AUTO_TEST_CASE( ModernSymbolPinRetainsDisplayOverride )
1122{
1123 auto framed = []( uint8_t aType, const std::vector<uint8_t>& aBody )
1124 {
1125 std::vector<uint8_t> bytes;
1126 std::vector<size_t> lengthOffsets;
1127
1128 for( int i = 0; i < 2; ++i )
1129 {
1130 bytes.push_back( aType );
1131 lengthOffsets.push_back( bytes.size() );
1132 appendLe32( bytes, 0 );
1133 appendLe32( bytes, 0 );
1134 }
1135
1136 bytes.push_back( aType );
1137 appendLe16( bytes, 0xFFFF );
1138 bytes.insert( bytes.end(), { 0xFF, 0xE4, 0x5C, 0x39 } );
1139 appendLe32( bytes, 0 );
1140 bytes.insert( bytes.end(), aBody.begin(), aBody.end() );
1141
1142 for( size_t offset : lengthOffsets )
1143 writeLe32( bytes, offset, static_cast<uint32_t>( bytes.size() - offset - 8 ) );
1144
1145 return bytes;
1146 };
1147
1148 std::vector<uint8_t> displayBody;
1149 appendLe32( displayBody, 0 );
1150 appendLe16( displayBody, 20 );
1151 appendLe16( displayBody, 0 );
1152 appendLe16( displayBody, 0 );
1153 displayBody.push_back( 48 );
1154 appendLe16( displayBody, 0x01E9 );
1155 displayBody.push_back( 0 );
1156 std::vector<uint8_t> display = framed( ORCAD_ST_SYMBOL_DISPLAY_PROP, displayBody );
1157
1158 std::vector<uint8_t> pinBody;
1159 appendLzt( pinBody, "F1" );
1160
1161 for( int coordinate : { 20, 0, 20, -10 } )
1162 appendLe32( pinBody, static_cast<uint32_t>( coordinate ) );
1163
1164 appendLe16( pinBody, 0x21 );
1165 appendLe16( pinBody, 0 );
1166 appendLe32( pinBody, static_cast<uint32_t>( ORCAD_PORT_TYPE::PASSIVE ) );
1167 pinBody.push_back( ORCAD_ST_SYMBOL_PIN_SCALAR );
1168 pinBody.insert( pinBody.end(), 3, 0 );
1169 appendLe16( pinBody, 1 );
1170 pinBody.insert( pinBody.end(), display.begin(), display.end() );
1171 std::vector<uint8_t> bytes = framed( ORCAD_ST_SYMBOL_PIN_SCALAR, pinBody );
1172 std::vector<std::string> strings = { "Pin Name" };
1173 ORCAD_STREAM stream( bytes.data(), bytes.size() );
1174 ORCAD_STRUCT_READER reader( stream, &strings );
1175 std::optional<ORCAD_SYMBOL_PIN> pin = OrcadReadSymbolPin( reader );
1176
1177 BOOST_REQUIRE( pin );
1178 BOOST_REQUIRE_EQUAL( pin->displayProps.size(), 1u );
1179 BOOST_CHECK_EQUAL( pin->displayProps.front().name, "Pin Name" );
1180 BOOST_CHECK_EQUAL( pin->displayProps.front().x, 20 );
1181 BOOST_CHECK_EQUAL( pin->displayProps.front().rotation, 0 );
1182}
1183
1184
1185BOOST_AUTO_TEST_CASE( BusEntryCoordinatesPrecedeReservedWords )
1186{
1187 std::vector<uint8_t> bytes;
1188 appendLe32( bytes, 6 );
1189 appendLe32( bytes, 10 );
1190 appendLe32( bytes, 20 );
1191 appendLe32( bytes, 30 );
1192 appendLe32( bytes, 40 );
1193 appendLe32( bytes, 0 );
1194 appendLe32( bytes, 0 );
1195
1196 ORCAD_STREAM stream( bytes.data(), bytes.size() );
1197 ORCAD_BUS_ENTRY entry = OrcadReadBusEntryBody( stream );
1198
1199 BOOST_CHECK_EQUAL( entry.color, 6 );
1200 BOOST_CHECK_EQUAL( entry.x1, 10 );
1201 BOOST_CHECK_EQUAL( entry.y1, 20 );
1202 BOOST_CHECK_EQUAL( entry.x2, 30 );
1203 BOOST_CHECK_EQUAL( entry.y2, 40 );
1204}
1205
1206
1207BOOST_AUTO_TEST_CASE( S593487_V2CommentTextRetainsBoundingBox )
1208{
1209 // A legacy Cache is a u16 zero marker then four counted sections: loose symbols,
1210 // LibraryParts, PartCells, Packages. A group names the definition and counts its variants.
1211 std::vector<uint8_t> cache;
1212 appendLe16( cache, 0 ); // marker
1213 appendLe16( cache, 0 ); // section 0, loose symbols
1214 appendLe16( cache, 1 ); // section 1, LibraryParts
1215 appendLzt( cache, "THERMISTOR_2.Normal" );
1216 appendLe16( cache, 2 ); // two variants of the same definition
1217 appendLzt( cache, "SOURCE.OLB" );
1218 appendLe32( cache, 0 );
1219 appendLe32( cache, 0 );
1220 cache.push_back( ORCAD_ST_LIBRARY_PART );
1221 cache.push_back( 0 );
1222 cache.push_back( ORCAD_ST_LIBRARY_PART );
1223 appendLe16( cache, 0 );
1224 appendLzt( cache, "THERMISTOR_2.Normal" );
1225 appendLzt( cache, "SOURCE.OLB" );
1226 appendLe32( cache, 48 );
1227 appendLe16( cache, 1 );
1228 cache.push_back( ORCAD_PRIM_COMMENT_TEXT );
1229
1230 for( int coordinate : { 12, -8, 30, 7, 12, -8 } )
1231 appendLe32( cache, static_cast<uint32_t>( coordinate ) );
1232
1233 appendLe16( cache, 27 );
1234 appendLe16( cache, 0 );
1235 appendLzt( cache, "t" );
1236
1237 for( int coordinate : { 0, 0, 20, 30 } )
1238 appendLe16( cache, static_cast<uint16_t>( coordinate ) );
1239
1240 appendLe16( cache, 0 );
1241 appendLe16( cache, 0 );
1242 appendLzt( cache, "" );
1243 appendLzt( cache, "" );
1244 appendLzt( cache, "Q" );
1245 appendLzt( cache, "" );
1246 appendLe16( cache, 6 );
1247
1248 // Second variant, with its own header. Only the first variant of a group is preceded by
1249 // the group name and count; every later one starts straight at its source library.
1250 appendLzt( cache, "SOURCE.OLB" );
1251 appendLe32( cache, 0 );
1252 appendLe32( cache, 0 );
1253 cache.push_back( ORCAD_ST_LIBRARY_PART );
1254 cache.push_back( 0 );
1255 cache.push_back( ORCAD_ST_LIBRARY_PART );
1256 appendLe16( cache, 0 );
1257 appendLzt( cache, "THERMISTOR_2.Normal" );
1258 appendLzt( cache, "SOURCE.OLB" );
1259 appendLe32( cache, 48 );
1260 appendLe16( cache, 0 );
1261
1262 for( int coordinate : { 0, 0, 30, 30 } )
1263 appendLe16( cache, static_cast<uint16_t>( coordinate ) );
1264
1265 appendLe16( cache, 0 );
1266 appendLe16( cache, 0 );
1267 appendLzt( cache, "" );
1268 appendLzt( cache, "" );
1269 appendLzt( cache, "Q" );
1270 appendLzt( cache, "" );
1271 appendLe16( cache, 6 );
1272
1273 appendLe16( cache, 0 ); // section 2, PartCells
1274 appendLe16( cache, 0 ); // section 3, Packages
1275
1276 std::map<std::string, ORCAD_SYMBOL_DEF> symbols;
1277 std::map<std::string, ORCAD_PACKAGE> packages;
1279 std::vector<char>( cache.begin(), cache.end() ), {},
1280 []( const wxString& )
1281 {
1282 },
1283 symbols, packages );
1284
1285 BOOST_REQUIRE_EQUAL( symbols.size(), 1u );
1286 BOOST_REQUIRE_EQUAL( symbols.begin()->second.primitives.size(), 1u );
1287 const ORCAD_PRIMITIVE& text = symbols.begin()->second.primitives.front();
1288 BOOST_CHECK_EQUAL( text.x1, 12 );
1289 BOOST_CHECK_EQUAL( text.y1, -8 );
1290 BOOST_CHECK_EQUAL( text.x2, 30 );
1291 BOOST_CHECK_EQUAL( text.y2, 7 );
1292 BOOST_CHECK_EQUAL( symbols.begin()->second.generalFlags, 6 );
1293 BOOST_REQUIRE_EQUAL( symbols.begin()->second.variants.size(), 1u );
1294 BOOST_CHECK_EQUAL( symbols.begin()->second.variants.front().bbox->x2, 30 );
1295 BOOST_CHECK_EQUAL( symbols.begin()->second.variants.front().generalFlags, 6 );
1296}
1297
1298
1299BOOST_AUTO_TEST_CASE( CaptureColorPalette )
1300{
1301 BOOST_CHECK( OrcadColor( 8 ) == KIGFX::COLOR4D( 1.0, 0.0, 0.0, 1.0 ) );
1302 BOOST_CHECK( OrcadColor( 18 ) == KIGFX::COLOR4D( 0.0, 1.0, 0.0, 1.0 ) );
1303 BOOST_CHECK( OrcadColor( 28 ) == KIGFX::COLOR4D( 0.0, 0.0, 1.0, 1.0 ) );
1304 BOOST_CHECK( OrcadColor( 40 ) == KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 ) );
1305 BOOST_CHECK( OrcadColor( 47 ) == KIGFX::COLOR4D( 1.0, 1.0, 1.0, 1.0 ) );
1306 BOOST_CHECK( OrcadColor( 48 ) == KIGFX::COLOR4D::UNSPECIFIED );
1307 BOOST_CHECK( OrcadColor( 0 ) == KIGFX::COLOR4D::UNSPECIFIED );
1308}
1309
1310
1311BOOST_AUTO_TEST_CASE( PlacedPinSignedIndexCarriesNoConnectFlag )
1312{
1314
1315 pin.pinIndex = 7;
1316 BOOST_CHECK( !pin.IsNoConnect() );
1317
1318 pin.pinIndex = -7;
1319 BOOST_CHECK( pin.IsNoConnect() );
1320}
1321
1322
1323BOOST_AUTO_TEST_CASE( CaptureCompoundFileName )
1324{
1325 BOOST_CHECK_EQUAL( OrcadNormalizeCfbName( std::string( "Serial I" ) + '\x02' + "O" ), "Serial I/O" );
1326 BOOST_CHECK_EQUAL( OrcadNormalizeCfbName( std::string( "Sch 2" ) + '\x03' + " PCI Connector" ),
1327 "Sch 2: PCI Connector" );
1328}
1329
1330
1331BOOST_AUTO_TEST_CASE( CaptureStrokeAndFillSemantics )
1332{
1333 BOOST_CHECK_EQUAL( OrcadLineWidthIu( 0 ), schIUScale.MilsToIU( 10 ) );
1334 BOOST_CHECK_EQUAL( OrcadLineWidthIu( 1 ), schIUScale.MilsToIU( 30 ) );
1335 BOOST_CHECK_EQUAL( OrcadLineWidthIu( 2 ), schIUScale.MilsToIU( 50 ) );
1336 BOOST_CHECK_EQUAL( OrcadLineWidthIu( 3 ), schIUScale.MilsToIU( 10 ) );
1339
1340 BOOST_CHECK( OrcadLineStyle( 0 ) == LINE_STYLE::SOLID );
1341 BOOST_CHECK( OrcadLineStyle( 4 ) == LINE_STYLE::DASHDOTDOT );
1342 BOOST_CHECK( OrcadLineStyle( 5 ) == LINE_STYLE::DEFAULT );
1343
1344 BOOST_CHECK_EQUAL( OrcadDashRatios( 2 ).first, 67.0 );
1345 BOOST_CHECK_EQUAL( OrcadDashRatios( 2 ).second, 21.0 );
1346 BOOST_CHECK_EQUAL( OrcadDashRatios( 3 ).first, 3.0 );
1347 BOOST_CHECK_EQUAL( OrcadDashRatios( 3 ).second, 1.0 );
1348
1349 BOOST_CHECK( OrcadFillType( 0, 0 ) == FILL_T::FILLED_SHAPE );
1350 BOOST_CHECK( OrcadFillType( 1, 0 ) == FILL_T::NO_FILL );
1351 BOOST_CHECK( OrcadFillType( 2, 0 ) == FILL_T::HATCH );
1352 BOOST_CHECK( OrcadFillType( 2, 3 ) == FILL_T::REVERSE_HATCH );
1353 BOOST_CHECK( OrcadFillType( 2, 4 ) == FILL_T::CROSS_HATCH );
1354 BOOST_CHECK( OrcadFillType( 2, 5 ) == FILL_T::CROSS_HATCH );
1355 constexpr uint32_t legacyModified = 0x56A2631C;
1356 constexpr uint32_t currentModified = 0x652CEBA8;
1357 BOOST_CHECK_EQUAL( OrcadHatchPitchIu( legacyModified ), schIUScale.MilsToIU( 25 ) );
1358 BOOST_CHECK_EQUAL( OrcadHatchPitchIu( currentModified ), schIUScale.MilsToIU( 80 ) );
1359 BOOST_CHECK_EQUAL( OrcadHatchLineWidthIu( legacyModified ), schIUScale.MilsToIU( 3 ) );
1360 BOOST_CHECK_EQUAL( OrcadHatchLineWidthIu( currentModified ), schIUScale.MilsToIU( 10 ) );
1361
1363 rect.SetPosition( VECTOR2I( 0, 0 ) );
1364 rect.SetEnd( VECTOR2I( schIUScale.MilsToIU( 600 ), schIUScale.MilsToIU( 500 ) ) );
1365
1366 std::vector<SEG> reverseDiagonal = OrcadHatchLines( rect, 3, OrcadHatchPitchIu( legacyModified ) );
1367 BOOST_CHECK_GT( reverseDiagonal.size(), 40u );
1368 BOOST_CHECK_LT( reverseDiagonal.size(), 48u );
1369 BOOST_CHECK( std::all_of( reverseDiagonal.begin(), reverseDiagonal.end(),
1370 []( const SEG& aLine )
1371 {
1372 return static_cast<int64_t>( aLine.B.x - aLine.A.x )
1373 * ( aLine.B.y - aLine.A.y )
1374 < 0;
1375 } ) );
1376
1377 std::vector<SEG> diagonalCross = OrcadHatchLines( rect, 5, OrcadHatchPitchIu( legacyModified ) );
1378 BOOST_CHECK_GT( diagonalCross.size(), 80u );
1379 BOOST_CHECK_LT( diagonalCross.size(), 96u );
1380
1381 std::vector<SEG> orthogonalCross = OrcadHatchLines( rect, 4, OrcadHatchPitchIu( legacyModified ) );
1382 BOOST_CHECK_GT( orthogonalCross.size(), 40u );
1383 BOOST_CHECK_LT( orthogonalCross.size(), 48u );
1384 BOOST_CHECK( std::any_of( orthogonalCross.begin(), orthogonalCross.end(),
1385 []( const SEG& aLine ) { return aLine.A.x == aLine.B.x; } ) );
1386 BOOST_CHECK( std::any_of( orthogonalCross.begin(), orthogonalCross.end(),
1387 []( const SEG& aLine ) { return aLine.A.y == aLine.B.y; } ) );
1388
1389 std::vector<SEG> currentCross = OrcadHatchLines( rect, 5, OrcadHatchPitchIu( currentModified ) );
1390 BOOST_CHECK_GT( currentCross.size(), 20u );
1391 BOOST_CHECK_LT( currentCross.size(), 40u );
1392}
1393
1394
1395BOOST_AUTO_TEST_CASE( CapturePageOrder )
1396{
1397 wxString dashed = wxS( "03 - CAN" );
1398 wxString dotted = wxS( "02.uC" );
1399 wxString colon = wxS( "13:IMU" );
1400 wxString folder = wxS( "Sch 7: CAN Drivers" );
1401 wxString pager = wxS( "PAGER 8" );
1402 wxString underscored = wxS( "PAGE_03_HSMC CONNECTOR" );
1403 wxString compact = wxS( "PAGE01 OVERALL BLOCK DIAGRAM" );
1404 wxString plain = wxS( "Overview" );
1405
1406 BOOST_CHECK_EQUAL( OrcadPageOrder( dashed ), 3 );
1407 BOOST_CHECK_EQUAL( dashed, wxS( "CAN" ) );
1408 BOOST_CHECK_EQUAL( OrcadPageOrder( dotted ), 2 );
1409 BOOST_CHECK_EQUAL( dotted, wxS( "02.uC" ) );
1410 BOOST_CHECK_EQUAL( OrcadPageOrder( colon ), 13 );
1411 BOOST_CHECK_EQUAL( colon, wxS( "13:IMU" ) );
1412 BOOST_CHECK_EQUAL( OrcadPageOrder( folder ), 7 );
1413 BOOST_CHECK_EQUAL( folder, wxS( "Sch 7: CAN Drivers" ) );
1414 BOOST_CHECK_EQUAL( OrcadPageOrder( pager ), -1 );
1415 BOOST_CHECK_EQUAL( pager, wxS( "PAGER 8" ) );
1416 BOOST_CHECK_EQUAL( OrcadPageOrder( underscored ), 3 );
1417 BOOST_CHECK_EQUAL( underscored, wxS( "PAGE_03_HSMC CONNECTOR" ) );
1418 BOOST_CHECK_EQUAL( OrcadPageOrder( compact ), 1 );
1419 BOOST_CHECK_EQUAL( compact, wxS( "PAGE01 OVERALL BLOCK DIAGRAM" ) );
1420 BOOST_CHECK_EQUAL( OrcadPageOrder( plain ), -1 );
1421 BOOST_CHECK_EQUAL( plain, wxS( "Overview" ) );
1422}
1423
1424
1425BOOST_AUTO_TEST_CASE( ModernSchematicStreamReversesStoredPageOrder )
1426{
1427 std::vector<uint8_t> bytes;
1428 appendFramedHeader( bytes, ORCAD_ST_SCH_LIB, 1 );
1429 appendLzt( bytes, "SCHEMATIC1" );
1430 appendLe32( bytes, 0 );
1431 appendLe16( bytes, 3 );
1432 appendLzt( bytes, "Block Diagram" );
1433 appendLzt( bytes, "I2C-USB" );
1434 appendLzt( bytes, "Street Fighter" );
1435
1436 std::vector<char> stream( bytes.begin(), bytes.end() );
1437 std::vector<std::string> pages = OrcadParsePageOrder( stream );
1438
1439 BOOST_REQUIRE_EQUAL( pages.size(), 3u );
1440 BOOST_CHECK_EQUAL( pages[0], "Street Fighter" );
1441 BOOST_CHECK_EQUAL( pages[1], "I2C-USB" );
1442 BOOST_CHECK_EQUAL( pages[2], "Block Diagram" );
1443}
1444
1445
1446BOOST_AUTO_TEST_CASE( V2SchematicStreamReversesStoredPageOrder )
1447{
1448 std::vector<uint8_t> bytes = { ORCAD_ST_PAGE, 0, 0 };
1449 appendLzt( bytes, "Low Power" );
1450 appendLe32( bytes, 0 );
1451 appendLe16( bytes, 3 );
1452 appendLzt( bytes, "Pre Regulator 100V" );
1453 appendLzt( bytes, "ZVS Flyback" );
1454 appendLzt( bytes, "Pre Regulator 12V" );
1455
1456 std::vector<char> stream( bytes.begin(), bytes.end() );
1457 std::vector<std::string> pages = OrcadParsePageOrderV2( stream, {} );
1458
1459 BOOST_REQUIRE_EQUAL( pages.size(), 3u );
1460 BOOST_CHECK_EQUAL( pages[0], "Pre Regulator 12V" );
1461 BOOST_CHECK_EQUAL( pages[1], "ZVS Flyback" );
1462 BOOST_CHECK_EQUAL( pages[2], "Pre Regulator 100V" );
1463}
1464
1465
1466BOOST_AUTO_TEST_CASE( V2OccurrenceReferencesUseTargetObjectIds )
1467{
1468 auto appendPrefix = []( std::vector<uint8_t>& aBytes, uint8_t aType )
1469 {
1470 aBytes.push_back( aType );
1471 appendLe16( aBytes, 0 );
1472 };
1473 auto appendEmptyScope = []( std::vector<uint8_t>& aBytes )
1474 {
1475 appendLe16( aBytes, 0 );
1476 appendLe16( aBytes, 0 );
1477 appendLe16( aBytes, 0 );
1478 };
1479
1480 std::vector<uint8_t> bytes;
1481 appendLzt( bytes, "SCHEMATIC1" );
1482 bytes.insert( bytes.end(), 5, 0 );
1483 appendLe16( bytes, 0 );
1484 appendLe16( bytes, 0 );
1485 appendLe16( bytes, 0 );
1486 appendLe16( bytes, 2 );
1487
1488 appendPrefix( bytes, 0x42 );
1489 appendLe32( bytes, 0x12345678 );
1490 appendLe32( bytes, 0x00001234 );
1491 appendLzt( bytes, "" );
1492 appendLzt( bytes, "C1" );
1493 appendLe16( bytes, 0xFFFF );
1494 appendLe16( bytes, 0 );
1495 appendEmptyScope( bytes );
1496
1497 appendPrefix( bytes, 0x42 );
1498 appendLe32( bytes, 0x87654321 );
1499 appendLe32( bytes, 0x00004321 );
1500 appendLzt( bytes, "CHILD" );
1501 appendLzt( bytes, "" );
1502 appendLe16( bytes, 0xFFFF );
1503 appendLe16( bytes, 0 );
1504 appendLe16( bytes, 0 );
1505 appendLe16( bytes, 0 );
1506 appendLe16( bytes, 1 );
1507 appendPrefix( bytes, 0x42 );
1508 appendLe32( bytes, 0xABCDEF01 );
1509 appendLe32( bytes, 0x00005678 );
1510 appendLzt( bytes, "" );
1511 appendLzt( bytes, "R1" );
1512 appendLe16( bytes, 0xFFFF );
1513 appendLe16( bytes, 0 );
1514 appendEmptyScope( bytes );
1515
1516 std::vector<char> stream( bytes.begin(), bytes.end() );
1517 ORCAD_OCC_SCOPE root = OrcadReadOccurrenceTreeV2( stream, {} );
1518
1519 BOOST_REQUIRE_EQUAL( root.partRefs.size(), 1u );
1520 BOOST_CHECK_EQUAL( root.partRefs.at( 0x00001234 ), "C1" );
1521 BOOST_REQUIRE_EQUAL( root.blocks.size(), 1u );
1522 BOOST_CHECK_EQUAL( root.blocks[0].targetDbId, 0x00004321 );
1523 BOOST_REQUIRE_EQUAL( root.blocks[0].scope.partRefs.size(), 1u );
1524 BOOST_CHECK_EQUAL( root.blocks[0].scope.partRefs.at( 0x00005678 ), "R1" );
1525}
1526
1527
1528BOOST_AUTO_TEST_CASE( V2OccurrencePropertiesSurviveBlankReference )
1529{
1530 auto appendPrefix = []( std::vector<uint8_t>& aBytes, uint8_t aType,
1531 const std::vector<std::pair<uint16_t, uint16_t>>& aProperties )
1532 {
1533 aBytes.push_back( aType );
1534 appendLe16( aBytes, static_cast<uint16_t>( aProperties.size() ) );
1535
1536 for( const auto& [name, value] : aProperties )
1537 {
1538 appendLe16( aBytes, name );
1539 appendLe16( aBytes, value );
1540 }
1541 };
1542
1543 std::vector<uint8_t> bytes;
1544 appendLzt( bytes, "SCHEMATIC1" );
1545 bytes.insert( bytes.end(), 5, 0 );
1546 appendLe16( bytes, 0 );
1547 appendLe16( bytes, 0 );
1548 appendLe16( bytes, 0 );
1549 appendLe16( bytes, 1 );
1550
1551 appendPrefix( bytes, 0x42, { { 0, 1 }, { 2, 3 }, { 4, 0xFFFF } } );
1552 appendLe32( bytes, 0x12345678 );
1553 appendLe32( bytes, 0x00001234 );
1554 appendLzt( bytes, "" );
1555 appendLzt( bytes, "" );
1556 appendLe16( bytes, 0xFFFF );
1557 appendLe16( bytes, 0 );
1558 appendLe16( bytes, 0 );
1559 appendLe16( bytes, 0 );
1560 appendLe16( bytes, 0 );
1561
1562 std::vector<std::string> strings = { "Value", "434 123 050 816", "Manufacturer",
1563 "Wurth Electronics Inc", "Clear Me" };
1564 std::vector<char> stream( bytes.begin(), bytes.end() );
1565 ORCAD_OCC_SCOPE root = OrcadReadOccurrenceTreeV2( stream, strings );
1566
1567 BOOST_CHECK( root.partRefs.empty() );
1568 BOOST_REQUIRE_EQUAL( root.partProps.size(), 1u );
1569 BOOST_CHECK_EQUAL( root.partProps.at( 0x00001234 ).at( "Value" ), "434 123 050 816" );
1570 BOOST_CHECK_EQUAL( root.partProps.at( 0x00001234 ).at( "Manufacturer" ), "Wurth Electronics Inc" );
1571 BOOST_CHECK_EQUAL( root.partProps.at( 0x00001234 ).at( "Clear Me" ), "" );
1572}
1573
1574
1575BOOST_AUTO_TEST_CASE( ModernOccurrencePropertiesSurviveBlankReference )
1576{
1577 std::vector<uint8_t> bytes;
1578 bytes.push_back( 0x42 );
1579 size_t rootLengthOffset = bytes.size();
1580 appendLe32( bytes, 0 );
1581 appendLe32( bytes, 0 );
1582 appendLzt( bytes, "SCHEMATIC1" );
1583 bytes.insert( bytes.end(), 7, 0 );
1584 appendLe16( bytes, 0 );
1585 appendLe16( bytes, 0 );
1586 appendLe16( bytes, 0 );
1587 appendLe32( bytes, 0 );
1588 appendLe16( bytes, 1 );
1589
1590 size_t occurrenceStart = bytes.size();
1591 bytes.push_back( 0x42 );
1592 size_t occurrenceLengthOffset = bytes.size();
1593 appendLe32( bytes, 0 );
1594 appendLe32( bytes, 0 );
1595 bytes.push_back( 0x42 );
1596 appendLe16( bytes, 3 );
1597 appendLe32( bytes, 0 );
1598 appendLe32( bytes, 1 );
1599 appendLe32( bytes, 2 );
1600 appendLe32( bytes, 3 );
1601 appendLe32( bytes, 4 );
1602 appendLe32( bytes, 0xFFFFFFFF );
1603 bytes.insert( bytes.end(), { 0xFF, 0xE4, 0x5C, 0x39 } );
1604 appendLe32( bytes, 0 );
1605 appendLe32( bytes, 0x12345678 );
1606 appendLe32( bytes, 0x00001234 );
1607 bytes.push_back( 0x42 );
1608 appendLe32( bytes, 0 );
1609 appendLe32( bytes, 0 );
1610 appendLzt( bytes, "" );
1611 appendLzt( bytes, "" );
1612 appendLe32( bytes, 0xFFFFFFFF );
1613 appendLe16( bytes, 0 );
1614 appendLe16( bytes, 0 );
1615 appendLe16( bytes, 0 );
1616 appendLe32( bytes, 0 );
1617 appendLe16( bytes, 0 );
1618
1619 writeLe32( bytes, occurrenceLengthOffset,
1620 static_cast<uint32_t>( bytes.size() - occurrenceStart - 9 ) );
1621 writeLe32( bytes, rootLengthOffset, static_cast<uint32_t>( bytes.size() - 9 ) );
1622
1623 std::vector<std::string> strings = { "Value", "434 123 050 816", "Manufacturer",
1624 "Wurth Electronics Inc", "Clear Me" };
1625 std::vector<char> stream( bytes.begin(), bytes.end() );
1626 ORCAD_OCC_SCOPE root = OrcadReadOccurrenceTree( stream, strings, []( const wxString& ) {} );
1627
1628 BOOST_CHECK( root.partRefs.empty() );
1629 BOOST_REQUIRE_EQUAL( root.partProps.size(), 1u );
1630 BOOST_CHECK_EQUAL( root.partProps.at( 0x00001234 ).at( "Value" ), "434 123 050 816" );
1631 BOOST_CHECK_EQUAL( root.partProps.at( 0x00001234 ).at( "Manufacturer" ), "Wurth Electronics Inc" );
1632 BOOST_CHECK_EQUAL( root.partProps.at( 0x00001234 ).at( "Clear Me" ), "" );
1633}
1634
1635
1636BOOST_AUTO_TEST_CASE( HierarchyLinksComeFromSequentialOccurrenceRecords )
1637{
1638 std::vector<uint8_t> bytes;
1639 bytes.push_back( 0x42 );
1640 size_t rootLengthOffset = bytes.size();
1641 appendLe32( bytes, 0 );
1642 appendLe32( bytes, 0 );
1643 appendLzt( bytes, "SCHEMATIC1" );
1644 bytes.insert( bytes.end(), 7, 0 );
1645 appendLe16( bytes, 0 );
1646 appendLe16( bytes, 0 );
1647 appendLe16( bytes, 0 );
1648 appendLe32( bytes, 0 );
1649 appendLe16( bytes, 1 );
1650
1651 size_t occurrenceStart = bytes.size();
1652 bytes.push_back( 0x42 );
1653 size_t occurrenceLengthOffset = bytes.size();
1654 appendLe32( bytes, 0 );
1655 appendLe32( bytes, 0 );
1656 bytes.push_back( 0x42 );
1657 appendLe16( bytes, 0 );
1658 bytes.insert( bytes.end(), { 0xFF, 0xE4, 0x5C, 0x39 } );
1659 appendLe32( bytes, 0 );
1660 appendLe32( bytes, 0x12345678 );
1661 appendLe32( bytes, 0x0000BEEF );
1662 bytes.push_back( 0x42 );
1663 appendLe32( bytes, 0 );
1664 appendLe32( bytes, 0 );
1665 appendLzt( bytes, "PAG_2" );
1666 appendLzt( bytes, "" );
1667 appendLe32( bytes, 0xFFFFFFFF );
1668 appendLe16( bytes, 0 );
1669 appendLe16( bytes, 0 );
1670 appendLe16( bytes, 0 );
1671 appendLe32( bytes, 0 );
1672 appendLe16( bytes, 0 );
1673
1674 writeLe32( bytes, occurrenceLengthOffset, static_cast<uint32_t>( bytes.size() - occurrenceStart - 9 ) );
1675 writeLe32( bytes, rootLengthOffset, static_cast<uint32_t>( bytes.size() - 9 ) );
1676
1677 std::vector<char> stream( bytes.begin(), bytes.end() );
1678 ORCAD_OCC_SCOPE root = OrcadReadOccurrenceTree( stream, {}, []( const wxString& ) {} );
1679
1680 BOOST_REQUIRE_EQUAL( root.blocks.size(), 1u );
1681 BOOST_CHECK_EQUAL( root.blocks[0].targetDbId, 0x0000BEEFu );
1682 BOOST_CHECK_EQUAL( root.blocks[0].childFolder, "PAG_2" );
1683}
1684
1685
1686BOOST_AUTO_TEST_CASE( V2SymbolStreamRetainsDefinitionProperties )
1687{
1688 std::vector<uint8_t> bytes;
1689 bytes.push_back( ORCAD_ST_TITLEBLOCK_SYMBOL );
1690 appendLe16( bytes, 1 );
1691 appendLe16( bytes, 0 );
1692 appendLe16( bytes, 1 );
1693 appendLzt( bytes, "TITLEBLK" );
1694 appendLzt( bytes, "" );
1695 appendLe32( bytes, 0 );
1696 appendLe16( bytes, 0 );
1697 appendLe16( bytes, 0 );
1698 appendLe16( bytes, 0 );
1699 appendLe16( bytes, 100 );
1700 appendLe16( bytes, 50 );
1701 appendLe16( bytes, 0 );
1702 appendLe16( bytes, 0 );
1703
1704 std::map<std::string, ORCAD_SYMBOL_DEF> symbols;
1705 OrcadParseOlbSymbolStreamV2( std::vector<char>( bytes.begin(), bytes.end() ), { "SIZE", "N/A" }, symbols );
1706
1707 BOOST_REQUIRE_EQUAL( symbols.size(), 1u );
1708 BOOST_CHECK_EQUAL( symbols.at( "TITLEBLK" ).props.at( "SIZE" ), "N/A" );
1709}
1710
1711
1712BOOST_AUTO_TEST_CASE( ViewsDirectoryLimitsImportedSchematicFolders )
1713{
1714 std::vector<uint8_t> bytes;
1715 appendLe32( bytes, 0x5351BBF2 );
1716 appendLe16( bytes, 2 );
1717
1718 for( const std::string& name : { "DC1987A", "SCHEMATIC1" } )
1719 {
1720 appendLzt( bytes, name );
1721 appendLe16( bytes, 9 );
1722 bytes.insert( bytes.end(), 20, 0 );
1723 }
1724
1725 std::vector<char> stream( bytes.begin(), bytes.end() );
1726 std::vector<std::string> folders = OrcadParseSchematicFolderOrder( stream );
1727
1728 BOOST_REQUIRE_EQUAL( folders.size(), 2u );
1729 BOOST_CHECK_EQUAL( folders[0], "DC1987A" );
1730 BOOST_CHECK_EQUAL( folders[1], "SCHEMATIC1" );
1731}
1732
1733
1734BOOST_AUTO_TEST_CASE( PrimitiveStrokeAndFillStyles )
1735{
1736 std::vector<uint8_t> bytes = { ORCAD_PRIM_RECT, ORCAD_PRIM_RECT };
1737 appendLe32( bytes, 40 );
1738 appendLe32( bytes, 0 );
1739 appendLe32( bytes, 10 );
1740 appendLe32( bytes, 20 );
1741 appendLe32( bytes, 30 );
1742 appendLe32( bytes, 40 );
1743 appendLe32( bytes, 2 );
1744 appendLe32( bytes, 3 );
1745 appendLe32( bytes, 2 );
1746 appendLe32( bytes, 5 );
1747
1748 ORCAD_STREAM stream( bytes.data(), bytes.size() );
1749 std::optional<ORCAD_PRIMITIVE> primitive = OrcadReadPrimitive( stream );
1750
1751 BOOST_REQUIRE( primitive );
1752 BOOST_CHECK_EQUAL( primitive->lineStyle, 2 );
1753 BOOST_CHECK_EQUAL( primitive->lineWidth, 3 );
1754 BOOST_CHECK_EQUAL( primitive->fillStyle, 2 );
1755 BOOST_CHECK_EQUAL( primitive->hatchStyle, 5 );
1756}
1757
1758
1759BOOST_AUTO_TEST_CASE( DesignTemplatePinFonts )
1760{
1761 std::vector<uint8_t> bytes( 32, 0 );
1762 const std::string introduction = "OrCAD Windows Design";
1763 std::copy( introduction.begin(), introduction.end(), bytes.begin() );
1764 appendLe16( bytes, 3 );
1765 appendLe16( bytes, 2 );
1766 bytes.resize( bytes.size() + 12, 0 );
1767 appendLe16( bytes, 3 );
1768
1769 for( int height : { -9, -12 } )
1770 {
1771 size_t offset = bytes.size();
1772 bytes.resize( offset + 60, 0 );
1773 writeLe32( bytes, offset, static_cast<uint32_t>( height ) );
1774 writeLe32( bytes, offset + 8, height == -9 ? 900 : 0 );
1775 writeLe32( bytes, offset + 12, height == -9 ? 0 : 1800 );
1776 std::copy_n( "Arial", 5, bytes.begin() + offset + 28 );
1777 }
1778
1779 appendLe16( bytes, 24 );
1780
1781 for( int i = 0; i < 24; ++i )
1782 appendLe16( bytes, i == 10 ? 1 : i == 11 ? 2 : 0 );
1783
1784 bytes.resize( bytes.size() + 8, 0 );
1785
1786 for( int i = 0; i < 8; ++i )
1787 appendLzt( bytes, "" );
1788
1789 bytes.resize( bytes.size() + 156, 0 );
1790 appendLe32( bytes, 0 );
1791 appendLe16( bytes, 0 );
1792
1793 writeLe32( bytes, 36, 0x53A88A14 );
1794 writeLe32( bytes, 40, 0x56A2631C );
1795
1796 ORCAD_LIBRARY_INFO library = OrcadParseLibrary( std::vector<char>( bytes.begin(), bytes.end() ) );
1797 BOOST_REQUIRE_EQUAL( library.templateFonts.size(), 24u );
1798 BOOST_CHECK_EQUAL( library.templateFonts[10], 1 );
1799 BOOST_CHECK_EQUAL( library.templateFonts[11], 2 );
1800 BOOST_CHECK_EQUAL( library.pinNameFont, 10 );
1801 BOOST_CHECK_EQUAL( library.pinNumberFont, 11 );
1802 BOOST_REQUIRE_EQUAL( library.fonts.size(), 2u );
1803 BOOST_CHECK_EQUAL( library.fonts[0].escapement, 900 );
1804 BOOST_CHECK_EQUAL( library.fonts[0].orientation, 0 );
1805 BOOST_CHECK_EQUAL( library.fonts[1].escapement, 0 );
1806 BOOST_CHECK_EQUAL( library.fonts[1].orientation, 1800 );
1807 BOOST_CHECK_EQUAL( library.createTimestamp, 0x53A88A14 );
1808 BOOST_CHECK_EQUAL( library.modifyTimestamp, 0x56A2631C );
1809}
1810
1811
1812BOOST_AUTO_TEST_CASE( PageSettingsRetainPrintableFrameConfiguration )
1813{
1814 std::vector<uint8_t> bytes( 156, 0 );
1815 writeLe32( bytes, 24, 9700 );
1816 writeLe32( bytes, 28, 7200 );
1817 writeLe32( bytes, 32, 100 );
1818 writeLe16( bytes, 38, 5 );
1819 writeLe16( bytes, 40, 4 );
1820 writeLe32( bytes, 44, 100 );
1821 writeLe32( bytes, 48, 100 );
1822 writeLe32( bytes, 112, 1 );
1823
1824 for( size_t offset = 128; offset < 156; offset += 4 )
1825 writeLe32( bytes, offset, 1 );
1826
1827 ORCAD_STREAM stream( bytes.data(), bytes.size() );
1828 ORCAD_PAGE_SETTINGS settings = OrcadParsePageSettings( stream );
1829 BOOST_CHECK_EQUAL( settings.horizontalCount, 5 );
1830 BOOST_CHECK_EQUAL( settings.verticalCount, 4 );
1831 BOOST_CHECK_EQUAL( settings.horizontalWidth, 100 );
1832 BOOST_CHECK_EQUAL( settings.verticalWidth, 100 );
1833 BOOST_CHECK( !settings.horizontalChar );
1834 BOOST_CHECK( !settings.horizontalAscending );
1835 BOOST_CHECK( settings.verticalChar );
1836 BOOST_CHECK( !settings.verticalAscending );
1837 BOOST_CHECK( !settings.isMetric );
1838 BOOST_CHECK( settings.borderPrinted );
1839 BOOST_CHECK( settings.gridRefPrinted );
1840 BOOST_CHECK( settings.titleblockPrinted );
1841 BOOST_CHECK( settings.ansiGridRefs );
1842}
1843
1844
1845BOOST_AUTO_TEST_CASE( PrimitivePolygonStylesBeforePoints )
1846{
1847 std::vector<uint8_t> bytes = { ORCAD_PRIM_POLYGON, ORCAD_PRIM_POLYGON };
1848 appendLe32( bytes, 34 );
1849 appendLe32( bytes, 0 );
1850 appendLe32( bytes, 1 );
1851 appendLe32( bytes, 2 );
1852 appendLe32( bytes, 2 );
1853 appendLe32( bytes, 4 );
1854 appendLe16( bytes, 2 );
1855 appendLe16( bytes, 20 );
1856 appendLe16( bytes, 10 );
1857 appendLe16( bytes, 40 );
1858 appendLe16( bytes, 30 );
1859
1860 ORCAD_STREAM stream( bytes.data(), bytes.size() );
1861 std::optional<ORCAD_PRIMITIVE> primitive = OrcadReadPrimitive( stream );
1862
1863 BOOST_REQUIRE( primitive );
1864 BOOST_CHECK_EQUAL( primitive->lineStyle, 1 );
1865 BOOST_CHECK_EQUAL( primitive->lineWidth, 2 );
1866 BOOST_CHECK_EQUAL( primitive->fillStyle, 2 );
1867 BOOST_CHECK_EQUAL( primitive->hatchStyle, 4 );
1868 BOOST_REQUIRE_EQUAL( primitive->points.size(), 2u );
1869 BOOST_CHECK( ( primitive->points[0] == ORCAD_POINT{ 10, 20 } ) );
1870 BOOST_CHECK( ( primitive->points[1] == ORCAD_POINT{ 30, 40 } ) );
1871}
1872
1873
1874BOOST_AUTO_TEST_CASE( PrimitiveSymbolVectorContents )
1875{
1876 std::vector<uint8_t> bytes = { ORCAD_PRIM_SYMBOL_VECTOR, ORCAD_PRIM_SYMBOL_VECTOR };
1877 appendLe32( bytes, 63 );
1878 appendLe32( bytes, 0 );
1879 bytes.push_back( ORCAD_PRIM_SYMBOL_VECTOR );
1880 appendLe16( bytes, 0 );
1881 bytes.insert( bytes.end(), std::begin( ORCAD_STREAM::PREAMBLE ), std::end( ORCAD_STREAM::PREAMBLE ) );
1882 appendLe32( bytes, 0 );
1883 appendLe16( bytes, 12 );
1884 appendLe16( bytes, 26 );
1885 appendLe16( bytes, 1 );
1886 bytes.insert( bytes.end(), { ORCAD_PRIM_POLYLINE, 0, ORCAD_PRIM_POLYLINE } );
1887 appendLe32( bytes, 30 );
1888 appendLe32( bytes, 0 );
1889 appendLe32( bytes, 0 );
1890 appendLe32( bytes, 0 );
1891 appendLe16( bytes, 3 );
1892 appendLe16( bytes, 8 );
1893 appendLe16( bytes, 4 );
1894 appendLe16( bytes, 0 );
1895 appendLe16( bytes, 4 );
1896 appendLe16( bytes, 0 );
1897 appendLe16( bytes, 16 );
1898 appendLe16( bytes, 10 );
1899 bytes.insert( bytes.end(), { 'H', 'y', 's', 't', 'e', 'r', 'e', 's', 'i', 's', 0 } );
1900
1901 ORCAD_STREAM stream( bytes.data(), bytes.size() );
1902 std::optional<ORCAD_PRIMITIVE> primitive = OrcadReadPrimitive( stream );
1903
1904 BOOST_REQUIRE( primitive );
1905 BOOST_CHECK( primitive->kind == ORCAD_PRIM_KIND::GROUP_PRIM );
1906 BOOST_CHECK_EQUAL( primitive->x1, 12 );
1907 BOOST_CHECK_EQUAL( primitive->y1, 26 );
1908 BOOST_REQUIRE_EQUAL( primitive->children.size(), 1u );
1909 BOOST_CHECK( primitive->children[0].kind == ORCAD_PRIM_KIND::POLYLINE );
1910 BOOST_REQUIRE_EQUAL( primitive->children[0].points.size(), 3u );
1911 BOOST_CHECK( ( primitive->children[0].points[2] == ORCAD_POINT{ 16, 0 } ) );
1912}
1913
1914
1915BOOST_AUTO_TEST_CASE( OleMetafilePreviewExtraction )
1916{
1917 std::vector<uint8_t> presentation( 4096, 0 );
1918 writeLe32( presentation, 4, 14 );
1919 presentation[40] = 1;
1920 presentation[41] = 0;
1921 presentation[42] = 9;
1922 presentation[43] = 0;
1923
1924 std::vector<uint16_t> name = { 2, 'O', 'l', 'e', 'P', 'r', 'e', 's', '0', '0', '0', 0 };
1925 std::vector<uint8_t> cfb = makeOlePreviewCfb( name, presentation );
1926 OLE_IMAGE_PAYLOAD preview = ExtractOleImageFromPayload( makeOlePayload( cfb, cfb.size() ) );
1927
1928 BOOST_CHECK( preview.type == OLE_IMAGE_TYPE::WMF );
1929 BOOST_REQUIRE_EQUAL( preview.data.size(), presentation.size() - 40 );
1930 BOOST_CHECK_EQUAL( preview.data[0], 1 );
1931 BOOST_CHECK_EQUAL( preview.data[2], 9 );
1932}
1933
1934
1935BOOST_AUTO_TEST_CASE( TruncatedEmbeddedOleContainerIsPadded )
1936{
1937 std::vector<uint8_t> presentation( 4096, 0 );
1938 writeLe32( presentation, 4, 14 );
1939 presentation[40] = 1;
1940 presentation[42] = 9;
1941
1942 std::vector<uint16_t> name = { 2, 'O', 'l', 'e', 'P', 'r', 'e', 's', '0', '0', '0', 0 };
1943 std::vector<uint8_t> cfb = makeOlePreviewCfb( name, presentation );
1944 uint32_t declared = static_cast<uint32_t>( cfb.size() );
1945 cfb.resize( cfb.size() - 200 );
1946
1947 OLE_IMAGE_PAYLOAD preview = ExtractOleImageFromPayload( makeOlePayload( cfb, declared ) );
1948
1949 BOOST_CHECK( preview.type == OLE_IMAGE_TYPE::WMF );
1950 BOOST_REQUIRE_EQUAL( preview.data.size(), presentation.size() - 40 );
1951 BOOST_CHECK_EQUAL( preview.data[0], 1 );
1952 BOOST_CHECK_EQUAL( preview.data[2], 9 );
1953}
1954
1955
1956BOOST_AUTO_TEST_CASE( OleNativeBitmapExtraction )
1957{
1958 // The stream states its own payload length, which real files set to the stream size less
1959 // the length word itself.
1960 std::vector<uint8_t> native( 4096, 0 );
1961 writeLe32( native, 0, static_cast<uint32_t>( native.size() - 4 ) );
1962 native[4] = 'B';
1963 native[5] = 'M';
1964 writeLe32( native, 6, 58 );
1965
1966 std::vector<uint16_t> name = { 1, 'O', 'l', 'e', '1', '0', 'N', 'a', 't', 'i', 'v', 'e', 0 };
1967 std::vector<uint8_t> cfb = makeOlePreviewCfb( name, native );
1968 OLE_IMAGE_PAYLOAD preview = ExtractOleImageFromPayload( makeOlePayload( cfb, cfb.size() ) );
1969
1970 BOOST_CHECK( preview.type == OLE_IMAGE_TYPE::BMP );
1971 BOOST_REQUIRE_EQUAL( preview.data.size(), native.size() - 4 );
1972 BOOST_CHECK_EQUAL( preview.data[0], 'B' );
1973 BOOST_CHECK_EQUAL( preview.data[1], 'M' );
1974
1975 for( const std::vector<uint8_t>& signature :
1976 { std::vector<uint8_t>{ 0x89, 'P', 'N', 'G' }, std::vector<uint8_t>{ 0xFF, 0xD8, 0xFF } } )
1977 {
1978 std::copy( signature.begin(), signature.end(), native.begin() + 4 );
1979 cfb = makeOlePreviewCfb( name, native );
1980 preview = ExtractOleImageFromPayload( makeOlePayload( cfb, cfb.size() ) );
1981
1982 BOOST_CHECK( preview.type == OLE_IMAGE_TYPE::BMP );
1983 BOOST_CHECK_EQUAL_COLLECTIONS( preview.data.begin(), preview.data.end(), native.begin() + 4, native.end() );
1984 }
1985}
1986
1987
1988BOOST_AUTO_TEST_CASE( SharedOleContentsBitmapExtraction )
1989{
1990 std::vector<uint8_t> contents( 4096, 0 );
1991 contents[0] = 'B';
1992 contents[1] = 'M';
1993 writeLe32( contents, 2, 58 );
1994
1995 std::vector<uint16_t> name = { 'C', 'O', 'N', 'T', 'E', 'N', 'T', 'S', 0 };
1996 OLE_IMAGE_PAYLOAD image = ExtractOleImage( makeOlePreviewCfb( name, contents ) );
1997
1998 BOOST_CHECK( image.type == OLE_IMAGE_TYPE::BMP );
1999 BOOST_CHECK_EQUAL( image.streamName, "CONTENTS" );
2000 BOOST_REQUIRE_EQUAL( image.data.size(), contents.size() );
2001 BOOST_CHECK_EQUAL( image.data[0], 'B' );
2002 BOOST_CHECK_EQUAL( image.data[1], 'M' );
2003}
2004
2005
2006// A directory entry that refers to itself must terminate without recursion.
2007BOOST_AUTO_TEST_CASE( OleDirectoryCycleTerminates )
2008{
2009 constexpr size_t SECTOR_SIZE = 512;
2010 constexpr size_t RIGHT_SIBLING_OFFSET = 72;
2011
2012 std::vector<uint16_t> name = { 'C', 'O', 'N', 'T', 'E', 'N', 'T', 'S', 0 };
2013 std::vector<uint8_t> contents( 64, 0 );
2014
2015 contents[0] = 'B';
2016 contents[1] = 'M';
2017
2018 std::vector<uint8_t> cfb = makeOlePreviewCfb( name, contents );
2019
2020 // The stream entry is directory index 1, immediately after the root
2021 const size_t streamEntry = 2 * SECTOR_SIZE + 128;
2022
2023 writeLe32( cfb, streamEntry + RIGHT_SIBLING_OFFSET, 1 );
2024
2025 OLE_IMAGE_PAYLOAD image = ExtractOleImage( cfb.data(), cfb.size() );
2026
2027 // The walk must terminate AND still reach the stream, so a visited set that pruned too much
2028 // would fail here rather than pass quietly
2029 BOOST_CHECK( image.type == OLE_IMAGE_TYPE::BMP );
2030}
2031
2032
2033// A header size alone must not identify a DIB and hide a valid OlePres000 preview.
2034BOOST_AUTO_TEST_CASE( OleContentsRejectsBogusDib )
2035{
2036 std::vector<uint16_t> name = { 'C', 'O', 'N', 'T', 'E', 'N', 'T', 'S', 0 };
2037 std::vector<uint8_t> contents( 64, 0 );
2038
2039 writeLe32( contents, 0, 40 ); // biSize, the weak signature
2040 writeLe32( contents, 4, 16 ); // biWidth
2041 writeLe32( contents, 8, 16 ); // biHeight
2042 writeLe16( contents, 12, 7 ); // biPlanes, only 1 is ever valid
2043 writeLe16( contents, 14, 24 ); // biBitCount
2044
2045 OLE_IMAGE_PAYLOAD image = ExtractOleImage( makeOlePreviewCfb( name, contents ) );
2046
2047 BOOST_CHECK( image.type == OLE_IMAGE_TYPE::NONE );
2048}
2049
2050
2051BOOST_AUTO_TEST_CASE( CiImageFullRasterExtraction )
2052{
2053 // The payload opens with the preview DIB, and the marker follows it. Capture writes a
2054 // 55x15 monochrome placeholder: 40-byte header, two palette entries, 8-byte rows.
2055 std::vector<uint8_t> payload( 40 + 2 * 4 + 8 * 15, 0 );
2056 writeLe32( payload, 0, 40 ); // biSize
2057 writeLe32( payload, 4, 55 ); // biWidth
2058 writeLe32( payload, 8, 15 ); // biHeight
2059 writeLe16( payload, 12, 1 ); // biPlanes
2060 writeLe16( payload, 14, 1 ); // biBitCount
2061
2062 const std::string marker = "~~CI_IMAGE~~";
2063 payload.insert( payload.end(), marker.begin(), marker.end() );
2064 payload.insert( payload.end(), { 0, 2, '1', '1' } );
2065
2066 const std::vector<uint8_t> png = { 0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A, 1, 2, 3 };
2067 payload.insert( payload.end(), png.begin(), png.end() );
2068
2069 BOOST_CHECK( OleExtractCiImage( payload ) == png );
2070
2071 payload.resize( 40 );
2072 BOOST_CHECK( OleExtractCiImage( payload ).empty() );
2073
2074 writeLe32( payload, 4, 0x7FFFFFFF );
2075 writeLe32( payload, 8, 0x80000000 );
2076 writeLe16( payload, 14, 32 );
2077 BOOST_CHECK( OleExtractCiImage( payload ).empty() );
2078}
2079
2080
2081BOOST_AUTO_TEST_CASE( CiImageScannerDoesNotConsumeOleNativeCache )
2082{
2083 std::vector<uint8_t> payload;
2084 constexpr char marker[] = "~~CI_IMAGE~~";
2085 payload.insert( payload.end(), marker, marker + sizeof( marker ) - 1 );
2086 payload.insert( payload.end(), { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 } );
2087 payload.insert( payload.end(), { 0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A } );
2088
2089 BOOST_CHECK( OleExtractCiImage( payload ).empty() );
2090}
2091
2092
2093BOOST_AUTO_TEST_CASE( EmbeddedImageFillsNonSquareSourceBox )
2094{
2095 BOOST_CHECK_EQUAL( OrcadStretchedImageSize( 718, 720, 830, 360 ), VECTOR2I( 1660, 720 ) );
2096 BOOST_CHECK_EQUAL( OrcadStretchedImageSize( 1702, 528, 1702, 528 ), VECTOR2I( 1702, 528 ) );
2097 BOOST_CHECK_EQUAL( OrcadStretchedImageSize( 800, 400, 300, 600 ), VECTOR2I( 800, 1600 ) );
2098}
2099
2100
2101BOOST_AUTO_TEST_CASE( WmfRenderUsesEmbeddedBoxAspectBeforeRasterization )
2102{
2103 BOOST_CHECK_EQUAL( OleWmfRenderSize( 718, 720, 4096, 720, 830.0 / 360.0 ), VECTOR2I( 1660, 720 ) );
2104 BOOST_CHECK_EQUAL( OleWmfRenderSize( 1702, 528, 2048, 2048, 0.0 ), VECTOR2I( 1702, 528 ) );
2105 BOOST_CHECK_EQUAL( OleWmfRenderSize( 1702, 528, 1000, 1000, 0.0 ), VECTOR2I( 1000, 310 ) );
2106}
2107
2108
2109BOOST_AUTO_TEST_CASE( WmfGlyphIndexTextAndDestinationCopyRender )
2110{
2111 wxImage image;
2112
2113 BOOST_REQUIRE( OleRenderWmf( makeGlyphIndexWmf(), 200, 200, image ) );
2114
2115 const unsigned char* pixels = image.GetData();
2116 size_t nonWhite = 0;
2117
2118 for( size_t i = 0; i < static_cast<size_t>( image.GetWidth() ) * image.GetHeight(); ++i )
2119 {
2120 if( pixels[3 * i] != 255 || pixels[3 * i + 1] != 255 || pixels[3 * i + 2] != 255 )
2121 ++nonWhite;
2122 }
2123
2124 BOOST_CHECK_GT( nonWhite, 0u );
2125}
2126
2127
2128BOOST_AUTO_TEST_CASE( WmfNegativeDestinationHeightFlipsDibWithoutClipping )
2129{
2130 wxImage image;
2131
2132 BOOST_REQUIRE( OleRenderWmf( makeFlippedDibWmf(), 200, 200, image ) );
2133 BOOST_CHECK_LE( image.GetWidth(), 200 );
2134 BOOST_CHECK_LE( image.GetHeight(), 200 );
2135 BOOST_CHECK_GE( image.GetWidth(), 50 );
2136 BOOST_CHECK_GE( image.GetHeight(), 50 );
2137
2138 const int left = image.GetWidth() / 4;
2139 const int top = image.GetHeight() / 4;
2140 const int bottom = image.GetHeight() * 3 / 4;
2141 const int topRed = image.GetRed( left, top );
2142 const int topBlue = image.GetBlue( left, top );
2143 const int bottomRed = image.GetRed( left, bottom );
2144 const int bottomBlue = image.GetBlue( left, bottom );
2145
2146 BOOST_CHECK_GT( topRed, topBlue + 50 );
2147 BOOST_CHECK_GT( bottomBlue, bottomRed + 50 );
2148}
2149
2150
2151BOOST_AUTO_TEST_CASE( EmbeddedEmfChunksExcludeWmfFraming )
2152{
2153 std::vector<uint8_t> emf = OleExtractEmbeddedEmf( makeEmbeddedEmfWmf() );
2154
2155 BOOST_REQUIRE_EQUAL( emf.size(), 100u );
2156 BOOST_CHECK_EQUAL( emf[0], 1 );
2157 BOOST_CHECK_EQUAL( emf[40], 0x20 );
2158 BOOST_CHECK_EQUAL( emf[41], 'E' );
2159 BOOST_CHECK_EQUAL( emf[42], 'M' );
2160 BOOST_CHECK_EQUAL( emf[43], 'F' );
2161}
2162
2163
2164BOOST_AUTO_TEST_CASE( EmbeddedEmfRendersPathsAndRotatedTextDeterministically )
2165{
2166 wxImage first;
2167 wxImage second;
2168 std::vector<uint8_t> emf = makeRenderableEmf();
2169
2170 BOOST_REQUIRE( OleRenderEmf( emf, 200, 200, first ) );
2171 BOOST_REQUIRE( OleRenderEmf( emf, 200, 200, second ) );
2172 BOOST_REQUIRE_EQUAL( first.GetWidth(), 100 );
2173 BOOST_REQUIRE_EQUAL( first.GetHeight(), 100 );
2174 BOOST_REQUIRE_EQUAL( second.GetWidth(), first.GetWidth() );
2175 BOOST_REQUIRE_EQUAL( second.GetHeight(), first.GetHeight() );
2176
2177 size_t nonWhite = 0;
2178 size_t blue = 0;
2179 size_t byteCount = static_cast<size_t>( first.GetWidth() ) * first.GetHeight() * 3;
2180
2181 for( size_t i = 0; i < byteCount; i += 3 )
2182 {
2183 const unsigned char* pixel = first.GetData() + i;
2184
2185 if( pixel[0] != 255 || pixel[1] != 255 || pixel[2] != 255 )
2186 ++nonWhite;
2187
2188 if( pixel[2] > pixel[0] + 64 && pixel[2] > pixel[1] + 64 )
2189 ++blue;
2190 }
2191
2192 BOOST_CHECK_GT( nonWhite, 50u );
2193 BOOST_CHECK_GT( blue, 10u );
2194 BOOST_CHECK_EQUAL_COLLECTIONS( first.GetData(), first.GetData() + byteCount, second.GetData(),
2195 second.GetData() + byteCount );
2196
2197 for( int y = 30; y < 36; ++y )
2198 {
2199 for( int x = 70; x < 84; ++x )
2200 {
2201 BOOST_CHECK_MESSAGE( first.GetBlue( x, y ) < 160 || first.GetRed( x, y ) > 160,
2202 "glyph-index trademark descends below its baseline at " << x << ',' << y );
2203 }
2204 }
2205
2206 size_t wideGlyphPixels = 0;
2207
2208 for( int y = 15; y < 26; ++y )
2209 {
2210 for( int x = 77; x < 84; ++x )
2211 {
2212 if( first.GetBlue( x, y ) > first.GetRed( x, y ) + 64 )
2213 ++wideGlyphPixels;
2214 }
2215 }
2216
2217 BOOST_CHECK_GT( wideGlyphPixels, 2u );
2218
2219 size_t substitutedSpacePixels = 0;
2220
2221 for( int y = 70; y < 100; ++y )
2222 {
2223 for( int x = 0; x < 25; ++x )
2224 {
2225 if( first.GetBlue( x, y ) > first.GetRed( x, y ) + 64 )
2226 ++substitutedSpacePixels;
2227 }
2228 }
2229
2230 BOOST_CHECK_EQUAL( substitutedSpacePixels, 0u );
2231}
2232
2233
2234BOOST_AUTO_TEST_CASE( MalformedEmfRecordsAreRejected )
2235{
2236 std::vector<uint8_t> emf = makeRenderableEmf();
2237 emf.resize( 136 );
2238 writeLe32( emf, 48, emf.size() );
2239 writeLe32( emf, 52, 3 );
2240 writeLe32( emf, 108, 35 );
2241 writeLe32( emf, 112, 8 );
2242 writeLe32( emf, 116, 14 );
2243 writeLe32( emf, 120, 20 );
2244 std::fill( emf.begin() + 124, emf.end(), 0 );
2245 wxImage image;
2246
2247 BOOST_CHECK( !OleRenderEmf( emf, 200, 200, image ) );
2248
2249 writeLe32( emf, 108, 27 );
2250 BOOST_CHECK( !OleRenderEmf( emf, 200, 200, image ) );
2251
2252 writeLe32( emf, 112, 256 );
2253 BOOST_CHECK( !OleRenderEmf( emf, 200, 200, image ) );
2254}
2255
2256
2257BOOST_AUTO_TEST_CASE( CalibriTextUsesMetricCompatibleOutlines )
2258{
2259 wxImage image;
2260
2261 BOOST_REQUIRE( OleRenderEmf( makeCalibriMetricEmf(), 100, 50, image ) );
2262
2263 size_t nonWhite = 0;
2264
2265 for( int y = 0; y < image.GetHeight(); ++y )
2266 {
2267 for( int x = 0; x < image.GetWidth(); ++x )
2268 {
2269 if( image.GetRed( x, y ) != 255 || image.GetGreen( x, y ) != 255 || image.GetBlue( x, y ) != 255 )
2270 ++nonWhite;
2271 }
2272 }
2273
2274 BOOST_CHECK_GE( nonWhite, 110u );
2275}
2276
2277
2278BOOST_AUTO_TEST_CASE( EmfImageTagInTextRendersAsText )
2279{
2280 wxImage image;
2281
2282 BOOST_REQUIRE( OleRenderEmf( makeCalibriMetricEmf( u"<image " ), 100, 50, image ) );
2283 BOOST_CHECK( image.IsOk() );
2284}
2285
2286
2287BOOST_AUTO_TEST_CASE( MetafilePreviewPrefersEmbeddedEmfAndFallsBackToWmf )
2288{
2289 wxImage directEmfImage;
2290 wxImage emfImage;
2291 wxImage wmfImage;
2292 std::vector<uint8_t> emf = makeRenderableEmf();
2293
2294 BOOST_REQUIRE( OleRenderEmf( emf, 200, 200, directEmfImage ) );
2295 BOOST_REQUIRE( OleRenderMetafilePreview( makeEmbeddedEmfWmf( emf ), 200, 200, emfImage ) );
2296 BOOST_REQUIRE( OleRenderMetafilePreview( makeGlyphIndexWmf(), 200, 200, wmfImage ) );
2297 BOOST_CHECK_EQUAL( emfImage.GetWidth(), 100 );
2298 BOOST_CHECK_EQUAL( emfImage.GetHeight(), 100 );
2299 size_t byteCount = static_cast<size_t>( emfImage.GetWidth() ) * emfImage.GetHeight() * 3;
2300 BOOST_CHECK_EQUAL_COLLECTIONS( emfImage.GetData(), emfImage.GetData() + byteCount, directEmfImage.GetData(),
2301 directEmfImage.GetData() + byteCount );
2302 BOOST_CHECK( wmfImage.IsOk() );
2303}
2304
2305
2306BOOST_AUTO_TEST_CASE( ExternalEmbeddedEmfRendersWhenProvided )
2307{
2308 const char* emfPath = std::getenv( "KICAD_ORCAD_EMF" );
2309
2310 if( !emfPath || !*emfPath )
2311 {
2312 BOOST_TEST_MESSAGE( "KICAD_ORCAD_EMF not set; skipping external embedded-EMF check." );
2313 return;
2314 }
2315
2316 std::ifstream input( emfPath, std::ios::binary );
2317 BOOST_REQUIRE( input );
2318 std::vector<uint8_t> emf( std::istreambuf_iterator<char>( input ), {} );
2319 wxImage image;
2320
2321 BOOST_REQUIRE( OleRenderEmf( emf, 2048, 2048, image ) );
2322 BOOST_CHECK_GT( image.GetWidth(), 0 );
2323 BOOST_CHECK_GT( image.GetHeight(), 0 );
2324
2325 if( const char* outputPath = std::getenv( "KICAD_ORCAD_EMF_OUTPUT" ) )
2326 BOOST_REQUIRE( image.SaveFile( wxString::FromUTF8( outputPath ), wxBITMAP_TYPE_PNG ) );
2327}
2328
2329
2330BOOST_AUTO_TEST_CASE( ExternalWmfEmbeddedEmfRendersWhenProvided )
2331{
2332 const char* wmfPath = std::getenv( "KICAD_ORCAD_WMF" );
2333
2334 if( !wmfPath || !*wmfPath )
2335 {
2336 BOOST_TEST_MESSAGE( "KICAD_ORCAD_WMF not set; skipping external WMF/EMF check." );
2337 return;
2338 }
2339
2340 std::ifstream input( wmfPath, std::ios::binary );
2341 BOOST_REQUIRE( input );
2342 std::vector<uint8_t> wmf( std::istreambuf_iterator<char>( input ), {} );
2343 std::vector<uint8_t> emf = OleExtractEmbeddedEmf( wmf );
2344 wxImage image;
2345 wxImage directImage;
2346
2347 BOOST_REQUIRE( !emf.empty() );
2348 BOOST_REQUIRE( OleRenderEmf( emf, 2200, 1360, directImage, 2200.0 / 1360.0 ) );
2349 BOOST_REQUIRE( OleRenderMetafilePreview( wmf, 2200, 1360, image, 2200.0 / 1360.0 ) );
2350 BOOST_REQUIRE_EQUAL( image.GetWidth(), directImage.GetWidth() );
2351 BOOST_REQUIRE_EQUAL( image.GetHeight(), directImage.GetHeight() );
2352 size_t byteCount = static_cast<size_t>( image.GetWidth() ) * image.GetHeight() * 3;
2353 BOOST_CHECK_EQUAL_COLLECTIONS( image.GetData(), image.GetData() + byteCount, directImage.GetData(),
2354 directImage.GetData() + byteCount );
2355
2356 if( const char* outputPath = std::getenv( "KICAD_ORCAD_WMF_OUTPUT" ) )
2357 BOOST_REQUIRE( image.SaveFile( wxString::FromUTF8( outputPath ), wxBITMAP_TYPE_PNG ) );
2358}
2359
2360
2361BOOST_AUTO_TEST_CASE( OlePreviewWithMultiplePresentationStreams )
2362{
2363 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
2364
2365 if( !corpusEnv || !*corpusEnv )
2366 {
2367 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping multi-preview OLE check." );
2368 return;
2369 }
2370
2371 std::filesystem::path dsn;
2372
2373 for( const std::filesystem::directory_entry& entry : std::filesystem::recursive_directory_iterator( corpusEnv ) )
2374 {
2375 if( entry.is_regular_file() && entry.path().filename() == "reServer industrial J401 Carrier Board v11.DSN" )
2376 {
2377 dsn = entry.path();
2378 break;
2379 }
2380 }
2381
2382 if( dsn.empty() )
2383 {
2384 BOOST_TEST_MESSAGE( "reServer industrial J401 design not present; skipping multi-preview OLE check." );
2385 return;
2386 }
2387
2388 std::ifstream stream( dsn, std::ios::binary );
2389 std::vector<uint8_t> bytes( std::istreambuf_iterator<char>( stream ), {} );
2390 const std::array<std::array<uint8_t, 8>, 2> markers = {
2391 std::array<uint8_t, 8>{ 0x6A, 0x31, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00 },
2392 std::array<uint8_t, 8>{ 0x16, 0x94, 0x05, 0x00, 0x00, 0x01, 0x00, 0x00 }
2393 };
2394
2395 for( size_t markerIndex = 0; markerIndex < markers.size(); ++markerIndex )
2396 {
2397 const auto& marker = markers[markerIndex];
2398 auto markerPos = std::search( bytes.begin(), bytes.end(), marker.begin(), marker.end() );
2399 BOOST_REQUIRE( markerPos != bytes.end() );
2400
2401 size_t offset = static_cast<size_t>( std::distance( bytes.begin(), markerPos ) );
2402 size_t size = static_cast<size_t>( bytes[offset] ) | ( static_cast<size_t>( bytes[offset + 1] ) << 8 )
2403 | ( static_cast<size_t>( bytes[offset + 2] ) << 16 )
2404 | ( static_cast<size_t>( bytes[offset + 3] ) << 24 );
2405 BOOST_REQUIRE_LE( offset + size + 4, bytes.size() );
2406
2407 OLE_IMAGE_PAYLOAD preview =
2408 ExtractOleImageFromPayload( { bytes.begin() + offset, bytes.begin() + offset + size + 4 } );
2409 BOOST_CHECK( preview.type == OLE_IMAGE_TYPE::WMF );
2410 BOOST_CHECK_GT( preview.data.size(), 200000u );
2411
2412 wxImage image;
2413 BOOST_REQUIRE_MESSAGE( OleRenderWmf( preview.data, 2048, 2048, image ),
2414 "marker " << markerIndex << ", preview bytes " << preview.data.size() << ", "
2415 << OleDescribeImagePayload( preview.data ) );
2416 BOOST_CHECK_GT( image.GetWidth(), 1000 );
2417 BOOST_CHECK_GT( image.GetHeight(), 500 );
2418 }
2419}
2420
2421
2422BOOST_AUTO_TEST_CASE( LegacyPageNetGroups )
2423{
2424 std::vector<uint8_t> bytes = { ORCAD_ST_PAGE, 0, 0 };
2425 appendLzt( bytes, "PAGE" );
2426 appendLzt( bytes, "C" );
2427 bytes.resize( bytes.size() + 156 );
2428 appendLe16( bytes, 0 );
2429 appendLe16( bytes, 0 );
2430 appendLe16( bytes, 1 );
2431 appendLe32( bytes, 0x12345678 );
2432 appendLzt( bytes, "BUS[1:0]" );
2433 appendLe16( bytes, 2 );
2434 appendLe32( bytes, 0x11111111 );
2435 appendLe32( bytes, 0x22222222 );
2436
2437 for( int i = 0; i < 10; ++i )
2438 appendLe16( bytes, 0 );
2439
2440 std::vector<char> data( bytes.begin(), bytes.end() );
2441 ORCAD_RAW_PAGE page = OrcadParsePageV2( data, {},
2442 []( const wxString& )
2443 {
2444 } );
2445
2446 BOOST_REQUIRE_EQUAL( page.netGroups.size(), 1u );
2447 BOOST_CHECK_EQUAL( page.netGroups[0].id, 0x12345678u );
2448 BOOST_CHECK_EQUAL( page.netGroups[0].name, "BUS[1:0]" );
2449 BOOST_REQUIRE_EQUAL( page.netGroups[0].members.size(), 2u );
2450 BOOST_CHECK_EQUAL( page.netGroups[0].members[1], 0x22222222u );
2451}
2452
2453
2454static std::string terminalToken( const std::string& aRef, const std::string& aPin );
2455static std::pair<int, int> checkConnectivity( SCHEMATIC& aSchematic, const std::vector<std::set<std::string>>& aNets,
2456 std::vector<std::set<std::string>>* aInconsistent = nullptr );
2457
2458
2459static SCH_SHEET* convertRawDesign( ORCAD_DESIGN& aDesign, SCHEMATIC& aSchematic, REPORTER* aReporter = nullptr )
2460{
2461 SCH_SHEET* rootSheet = new SCH_SHEET( &aSchematic );
2462 SCH_SCREEN* rootScreen = new SCH_SCREEN( &aSchematic );
2463 rootSheet->SetScreen( rootScreen );
2464 aSchematic.SetTopLevelSheets( { rootSheet } );
2465 aSchematic.CurrentSheet().clear();
2466 aSchematic.CurrentSheet().push_back( rootSheet );
2467
2468 ORCAD_CONVERTER converter( aDesign, &aSchematic, aReporter );
2469 converter.Convert( rootSheet );
2470 return rootSheet;
2471}
2472
2473
2474BOOST_AUTO_TEST_CASE( SameNetInteriorWireCrossingGetsJunction )
2475{
2476 ORCAD_RAW_PAGE page;
2477 page.name = "CONNECTED CROSSING";
2478
2479 ORCAD_WIRE horizontal;
2480 horizontal.id = 1;
2481 horizontal.x1 = 0;
2482 horizontal.y1 = 10;
2483 horizontal.x2 = 20;
2484 horizontal.y2 = 10;
2485 page.wires.push_back( horizontal );
2486
2487 ORCAD_WIRE vertical = horizontal;
2488 vertical.x1 = 10;
2489 vertical.y1 = 0;
2490 vertical.x2 = 10;
2491 vertical.y2 = 20;
2492 page.wires.push_back( vertical );
2493 page.netmap.emplace( 1, "GND_SIGNAL" );
2494
2495 ORCAD_DESIGN design;
2496 design.sourceId = "same-net-interior-crossing";
2497 design.pages.push_back( std::move( page ) );
2498
2499 SETTINGS_MANAGER manager;
2500 manager.LoadProject( "" );
2501 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
2502 schematic->SetProject( &manager.Prj() );
2503 SCH_SHEET* root = convertRawDesign( design, *schematic );
2504 size_t junctionCount = 0;
2505
2506 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_JUNCTION_T ) )
2507 ++junctionCount;
2508
2509 BOOST_CHECK_EQUAL( junctionCount, 1u );
2510}
2511
2512
2513BOOST_AUTO_TEST_CASE( ExplicitPageDimensionsRemainAuthoritative )
2514{
2515 ORCAD_RAW_PAGE page;
2516 page.name = "OVERSIZED B";
2517 page.pageSize = "B";
2518 page.width = 20000;
2519 page.height = 12700;
2520
2521 ORCAD_WIRE wire;
2522 wire.x1 = 100;
2523 wire.y1 = 100;
2524 wire.x2 = 2200;
2525 wire.y2 = 100;
2526 page.wires.push_back( wire );
2527
2528 ORCAD_DESIGN design;
2529 design.sourceId = "inconsistent-named-page-size";
2530 design.pages.push_back( std::move( page ) );
2531
2532 SETTINGS_MANAGER manager;
2533 manager.LoadProject( "" );
2534 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
2535 schematic->SetProject( &manager.Prj() );
2536 SCH_SHEET* root = convertRawDesign( design, *schematic );
2537 const PAGE_INFO& paper = root->GetScreen()->GetPageSettings();
2538
2539 BOOST_CHECK( paper.GetType() == PAGE_SIZE_TYPE::User );
2540 BOOST_CHECK_CLOSE( paper.GetWidthMils(), 20000.0, 0.001 );
2541 BOOST_CHECK_CLOSE( paper.GetHeightMils(), 12700.0, 0.001 );
2542 BOOST_CHECK_LT( paper.GetWidthIU( schIUScale.IU_PER_MILS ), OrcadDbuToIu( wire.x2, wire.y2 ).x );
2543}
2544
2545
2546BOOST_AUTO_TEST_CASE( VisiblePageOuterBorderIsImported )
2547{
2548 ORCAD_RAW_PAGE page;
2549 page.name = "OUTER BORDER";
2550 page.pageSize = "A";
2551 page.width = 9700;
2552 page.height = 7200;
2553 page.horizontalCount = 5;
2554 page.verticalCount = 4;
2555 page.horizontalWidth = 100;
2556 page.verticalWidth = 100;
2557 page.verticalChar = true;
2558 page.borderPrinted = true;
2559 page.gridRefPrinted = true;
2560
2561 ORCAD_DESIGN design;
2562 design.sourceId = "outer-border";
2563 design.pages.push_back( std::move( page ) );
2564
2565 SETTINGS_MANAGER manager;
2566 manager.LoadProject( "" );
2567 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
2568 schematic->SetProject( &manager.Prj() );
2569 SCH_SHEET* root = convertRawDesign( design, *schematic );
2570
2571 const SCH_SHAPE* border = nullptr;
2572
2573 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHAPE_T ) )
2574 {
2575 const SCH_SHAPE* shape = static_cast<const SCH_SHAPE*>( item );
2576
2577 if( shape->GetPolyPoints().size() == 5 && shape->GetPolyPoints()[1] == OrcadDbuToIu( 970, 0 ) )
2578 border = shape;
2579 }
2580
2581 BOOST_REQUIRE( border );
2582 BOOST_CHECK( border->GetShape() == SHAPE_T::POLY );
2583 const std::vector<VECTOR2I> points = border->GetPolyPoints();
2584 BOOST_REQUIRE_EQUAL( points.size(), 5u );
2585 BOOST_CHECK( points[0] == OrcadDbuToIu( 0, 0 ) );
2586 BOOST_CHECK( points[1] == OrcadDbuToIu( 970, 0 ) );
2587 BOOST_CHECK( points[2] == OrcadDbuToIu( 970, 720 ) );
2588 BOOST_CHECK( points[3] == OrcadDbuToIu( 0, 720 ) );
2589 BOOST_CHECK( points[4] == points[0] );
2590 BOOST_CHECK( border->GetStroke().GetColor() == KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 ) );
2591
2592 std::set<wxString> labels;
2593
2594 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
2595 labels.insert( static_cast<const SCH_TEXT*>( item )->GetText() );
2596
2597 for( const wxString& expected : { wxS( "5" ), wxS( "4" ), wxS( "3" ), wxS( "2" ), wxS( "1" ),
2598 wxS( "D" ), wxS( "C" ), wxS( "B" ), wxS( "A" ) } )
2599 {
2600 BOOST_CHECK( labels.count( expected ) );
2601 }
2602}
2603
2604
2605BOOST_AUTO_TEST_CASE( TitleBlockDisplayPropertiesAreRendered )
2606{
2607 ORCAD_SYMBOL_DEF definition;
2608 definition.name = "TITLE";
2609 definition.bbox = ORCAD_BBOX{ 0, 0, 100, 50 };
2610 definition.props["SIZE"] = "N/A";
2611
2612 ORCAD_GRAPHIC_INST titleBlock;
2613 titleBlock.name = definition.name;
2614 titleBlock.x = 0;
2615 titleBlock.y = 0;
2616 titleBlock.bbox = ORCAD_BBOX{ 100, 200, 200, 250 };
2617 titleBlock.props["Title"] = "Amplifier";
2618 titleBlock.props["Page Number"] = "7";
2619 titleBlock.props["Page Count"] = "9";
2620 titleBlock.props["Page Modify Date"] = "Wednesday, January 25, 2023";
2621
2622 ORCAD_DISPLAY_PROP displayedTitle;
2623 displayedTitle.name = "Title";
2624 displayedTitle.x = 10;
2625 displayedTitle.y = 20;
2626 displayedTitle.rotation = 1;
2627 displayedTitle.dispMode = 0x100;
2628 displayedTitle.color = 5;
2629 titleBlock.displayProps.push_back( displayedTitle );
2630
2631 ORCAD_DISPLAY_PROP displayedPageSize;
2632 displayedPageSize.name = "SIZE";
2633 displayedPageSize.x = 30;
2634 displayedPageSize.y = 40;
2635 displayedPageSize.dispMode = 0x100;
2636 titleBlock.displayProps.push_back( displayedPageSize );
2637
2638 ORCAD_DISPLAY_PROP displayedDate;
2639 displayedDate.name = "Page Modify Date";
2640 displayedDate.x = 40;
2641 displayedDate.y = 45;
2642 displayedDate.dispMode = 0x100;
2643 titleBlock.displayProps.push_back( displayedDate );
2644
2645 ORCAD_DISPLAY_PROP displayedPageNumber;
2646 displayedPageNumber.name = "Page Number";
2647 displayedPageNumber.x = 50;
2648 displayedPageNumber.y = 50;
2649 displayedPageNumber.dispMode = 0x100;
2650 titleBlock.displayProps.push_back( displayedPageNumber );
2651
2652 ORCAD_DISPLAY_PROP displayedPageCount;
2653 displayedPageCount.name = "Page Count";
2654 displayedPageCount.x = 60;
2655 displayedPageCount.y = 55;
2656 displayedPageCount.dispMode = 0x100;
2657 titleBlock.displayProps.push_back( displayedPageCount );
2658
2659 ORCAD_RAW_PAGE page;
2660 page.name = "SHEET";
2661 page.pageSize = "B";
2662 page.sourcePageNumber = 2;
2663 page.sourcePageCount = 3;
2664 page.width = 1000;
2665 page.height = 1000;
2666 page.modifyTimestamp = 1674605190;
2667 page.titleBlocks.push_back( std::move( titleBlock ) );
2668
2669 ORCAD_DESIGN design;
2670 design.sourceId = "title-block-properties";
2671 design.symbols.emplace( definition.name, std::move( definition ) );
2672 design.pages.push_back( std::move( page ) );
2673
2674 SETTINGS_MANAGER manager;
2675 manager.LoadProject( "" );
2676 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
2677 schematic->SetProject( &manager.Prj() );
2678 SCH_SHEET* root = convertRawDesign( design, *schematic );
2679
2680 const SCH_TEXT* title = nullptr;
2681 const SCH_TEXT* pageSize = nullptr;
2682 const SCH_TEXT* date = nullptr;
2683
2684 for( const SCH_ITEM* item : root->GetScreen()->Items() )
2685 {
2686 if( item->Type() == SCH_TEXT_T && static_cast<const SCH_TEXT*>( item )->GetText() == wxS( "Amplifier" ) )
2687 title = static_cast<const SCH_TEXT*>( item );
2688 else if( item->Type() == SCH_TEXT_T && static_cast<const SCH_TEXT*>( item )->GetText() == wxS( "N/A" ) )
2689 pageSize = static_cast<const SCH_TEXT*>( item );
2690 else if( item->Type() == SCH_TEXT_T
2691 && static_cast<const SCH_TEXT*>( item )->GetText() == wxS( "Tuesday, January 24, 2023" ) )
2692 date = static_cast<const SCH_TEXT*>( item );
2693 }
2694
2695 BOOST_REQUIRE( title );
2696 BOOST_CHECK_EQUAL( title->GetPosition().y, OrcadDbuToIu( 110, 220 ).y );
2697 BOOST_CHECK_GT( title->GetPosition().x, OrcadDbuToIu( 110, 220 ).x );
2699 BOOST_CHECK( title->GetTextColor() == KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 ) );
2700 BOOST_REQUIRE( pageSize );
2701 BOOST_REQUIRE( date );
2702
2703 std::set<wxString> renderedText;
2704
2705 for( const SCH_ITEM* item : root->GetScreen()->Items() )
2706 {
2707 if( item->Type() == SCH_TEXT_T )
2708 renderedText.insert( static_cast<const SCH_TEXT*>( item )->GetText() );
2709 }
2710
2711 BOOST_CHECK( renderedText.count( wxS( "2" ) ) );
2712 BOOST_CHECK( renderedText.count( wxS( "3" ) ) );
2713 BOOST_CHECK( !renderedText.count( wxS( "7" ) ) );
2714 BOOST_CHECK( !renderedText.count( wxS( "9" ) ) );
2715 BOOST_CHECK( !renderedText.count( wxS( "<Title>" ) ) );
2716}
2717
2718
2719BOOST_AUTO_TEST_CASE( TitleBlockLibraryDefaultSurvivesEmptyInstanceProperty )
2720{
2721 ORCAD_SYMBOL_DEF definition;
2722 definition.name = "TITLE";
2723 definition.props["Title"] = "<Title>";
2724
2725 ORCAD_GRAPHIC_INST titleBlock;
2726 titleBlock.name = definition.name;
2727 titleBlock.props["Title"] = "";
2728
2729 ORCAD_DISPLAY_PROP displayedTitle;
2730 displayedTitle.name = "Title";
2731 displayedTitle.dispMode = 0x100;
2732 titleBlock.displayProps.push_back( displayedTitle );
2733
2734 ORCAD_RAW_PAGE page;
2735 page.name = "SHEET";
2736 page.width = 1000;
2737 page.height = 1000;
2738 page.titleBlocks.push_back( std::move( titleBlock ) );
2739
2740 ORCAD_DESIGN design;
2741 design.sourceId = "title-block-placeholder";
2742 design.symbols.emplace( definition.name, std::move( definition ) );
2743 design.pages.push_back( std::move( page ) );
2744
2745 SETTINGS_MANAGER manager;
2746 manager.LoadProject( "" );
2747 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
2748 schematic->SetProject( &manager.Prj() );
2749 SCH_SHEET* root = convertRawDesign( design, *schematic );
2750
2751 bool found = false;
2752
2753 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
2754 found |= static_cast<const SCH_TEXT*>( item )->GetText() == wxS( "<Title>" );
2755
2756 BOOST_CHECK( found );
2757}
2758
2759
2760BOOST_AUTO_TEST_CASE( SchematicModifyDateUsesPageTimestamp )
2761{
2762 ORCAD_SYMBOL_DEF definition;
2763 definition.name = "TITLE";
2764
2765 ORCAD_GRAPHIC_INST titleBlock;
2766 titleBlock.name = definition.name;
2767
2768 ORCAD_DISPLAY_PROP displayedDate;
2769 displayedDate.name = "Schematic Modify Date";
2770 displayedDate.dispMode = 0x100;
2771 titleBlock.displayProps.push_back( displayedDate );
2772
2773 ORCAD_RAW_PAGE page;
2774 page.name = "SHEET";
2775 page.width = 1000;
2776 page.height = 1000;
2777 page.modifyTimestamp = 1676607901;
2778 page.titleBlocks.push_back( std::move( titleBlock ) );
2779
2780 ORCAD_DESIGN design;
2781 design.sourceId = "schematic-modify-date";
2782 design.symbols.emplace( definition.name, std::move( definition ) );
2783 design.pages.push_back( std::move( page ) );
2784
2785 SETTINGS_MANAGER manager;
2786 manager.LoadProject( "" );
2787 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
2788 schematic->SetProject( &manager.Prj() );
2789 SCH_SHEET* root = convertRawDesign( design, *schematic );
2790
2791 std::set<wxString> renderedText;
2792
2793 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
2794 renderedText.insert( static_cast<const SCH_TEXT*>( item )->GetText() );
2795
2796 BOOST_CHECK( renderedText.count( wxS( "Thursday, February 16, 2023" ) ) );
2797 BOOST_CHECK( !renderedText.count( wxS( "<Schematic Modify Date>" ) ) );
2798}
2799
2800
2801BOOST_AUTO_TEST_CASE( RudyTitleBlockUsesCaptureShortDate )
2802{
2803 ORCAD_SYMBOL_DEF definition;
2804 definition.name = "TITLEBLK/Rudy";
2805
2806 ORCAD_GRAPHIC_INST titleBlock;
2807 titleBlock.name = definition.name;
2808
2809 ORCAD_DISPLAY_PROP displayedDate;
2810 displayedDate.name = "Page Modify Date";
2811 displayedDate.dispMode = 0x100;
2812 titleBlock.displayProps.push_back( displayedDate );
2813
2814 ORCAD_RAW_PAGE page;
2815 page.name = "SHEET";
2816 page.width = 1000;
2817 page.height = 1000;
2818 page.modifyTimestamp = 1676607901;
2819 page.titleBlocks.push_back( std::move( titleBlock ) );
2820
2821 ORCAD_DESIGN design;
2822 design.sourceId = "rudy-title-block-date";
2823 design.symbols.emplace( definition.name, std::move( definition ) );
2824 design.pages.push_back( std::move( page ) );
2825
2826 SETTINGS_MANAGER manager;
2827 manager.LoadProject( "" );
2828 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
2829 schematic->SetProject( &manager.Prj() );
2830 SCH_SHEET* root = convertRawDesign( design, *schematic );
2831
2832 std::set<wxString> renderedText;
2833
2834 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
2835 renderedText.insert( static_cast<const SCH_TEXT*>( item )->GetText() );
2836
2837 BOOST_CHECK( renderedText.count( wxS( "Feb 16, 2023" ) ) );
2838 BOOST_CHECK( !renderedText.count( wxS( "Thursday, February 16, 2023" ) ) );
2839}
2840
2841
2842BOOST_AUTO_TEST_CASE( EmptyTitleBlockDisplayPropertiesUseCapturePlaceholders )
2843{
2844 ORCAD_SYMBOL_DEF definition;
2845 definition.name = "TITLE";
2846
2847 ORCAD_GRAPHIC_INST titleBlock;
2848 titleBlock.name = definition.name;
2849 titleBlock.props["Title"] = "";
2850 titleBlock.props["Doc"] = "";
2851 titleBlock.props["RevCode"] = "?";
2852
2853 for( const std::string& name : { "Title", "Doc", "RevCode" } )
2854 {
2855 ORCAD_DISPLAY_PROP displayed;
2856 displayed.name = name;
2857 displayed.dispMode = 0x100;
2858 titleBlock.displayProps.push_back( displayed );
2859 }
2860
2861 ORCAD_RAW_PAGE page;
2862 page.name = "SHEET";
2863 page.width = 1000;
2864 page.height = 1000;
2865 page.titleBlocks.push_back( std::move( titleBlock ) );
2866
2867 ORCAD_DESIGN design;
2868 design.sourceId = "empty-title-block-placeholders";
2869 design.symbols.emplace( definition.name, std::move( definition ) );
2870 design.pages.push_back( std::move( page ) );
2871
2872 SETTINGS_MANAGER manager;
2873 manager.LoadProject( "" );
2874 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
2875 schematic->SetProject( &manager.Prj() );
2876 SCH_SHEET* root = convertRawDesign( design, *schematic );
2877
2878 std::set<wxString> renderedText;
2879
2880 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
2881 renderedText.insert( static_cast<const SCH_TEXT*>( item )->GetText() );
2882
2883 BOOST_CHECK( renderedText.count( wxS( "<Title>" ) ) );
2884 BOOST_CHECK( renderedText.count( wxS( "<Doc>" ) ) );
2885 BOOST_CHECK( renderedText.count( wxS( "<RevCode>" ) ) );
2886 BOOST_CHECK( root->GetScreen()->GetTitleBlock().GetRevision().IsEmpty() );
2887}
2888
2889
2890BOOST_AUTO_TEST_CASE( MultipleTitleBlocksAreAllRendered )
2891{
2892 ORCAD_SYMBOL_DEF revision;
2893 revision.name = "REVISION";
2894 revision.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
2895 .text = "REVISION HISTORY" } );
2896
2898 main.name = "MAIN";
2899 main.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
2900 .text = "CUSTOMER NOTICE" } );
2901
2902 ORCAD_GRAPHIC_INST revisionBlock;
2903 revisionBlock.name = revision.name;
2904 revisionBlock.props["RevCode"] = "2";
2905
2906 ORCAD_GRAPHIC_INST mainBlock;
2907 mainBlock.name = main.name;
2908 mainBlock.props["Title"] = "Power Supply";
2909
2910 ORCAD_RAW_PAGE page;
2911 page.name = "SHEET";
2912 page.width = 1000;
2913 page.height = 1000;
2914 page.titleBlocks.push_back( std::move( revisionBlock ) );
2915 page.titleBlocks.push_back( std::move( mainBlock ) );
2916
2917 ORCAD_DESIGN design;
2918 design.sourceId = "multiple-title-blocks";
2919 design.symbols.emplace( revision.name, std::move( revision ) );
2920 design.symbols.emplace( main.name, std::move( main ) );
2921 design.pages.push_back( std::move( page ) );
2922
2923 SETTINGS_MANAGER manager;
2924 manager.LoadProject( "" );
2925 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
2926 schematic->SetProject( &manager.Prj() );
2927 SCH_SHEET* root = convertRawDesign( design, *schematic );
2928
2929 std::set<wxString> renderedText;
2930
2931 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
2932 renderedText.insert( static_cast<const SCH_TEXT*>( item )->GetText() );
2933
2934 BOOST_CHECK( renderedText.count( wxS( "REVISION HISTORY" ) ) );
2935 BOOST_CHECK( renderedText.count( wxS( "CUSTOMER NOTICE" ) ) );
2936}
2937
2938
2939BOOST_AUTO_TEST_CASE( TitleBlockVariantMatchesEncodedDimensions )
2940{
2941 ORCAD_SYMBOL_DEF primary;
2942 primary.name = "TITLE";
2943 primary.bbox = ORCAD_BBOX{ 0, 0, 100, 50 };
2944 primary.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
2945 .text = "PRIMARY" } );
2946
2947 ORCAD_SYMBOL_DEF matching;
2948 matching.name = primary.name;
2949 matching.bbox = ORCAD_BBOX{ 0, 0, 200, 50 };
2950 matching.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
2951 .text = "MATCHED" } );
2952 primary.variants.push_back( std::move( matching ) );
2953
2954 ORCAD_GRAPHIC_INST titleBlock;
2955 titleBlock.name = primary.name;
2956 titleBlock.bbox = ORCAD_BBOX{ 300, 400, 200, 50 };
2957
2958 ORCAD_RAW_PAGE page;
2959 page.name = "SHEET";
2960 page.titleBlocks.push_back( std::move( titleBlock ) );
2961
2962 ORCAD_DESIGN design;
2963 design.sourceId = "title-block-variant-bounds";
2964 design.symbols.emplace( primary.name, std::move( primary ) );
2965 design.pages.push_back( std::move( page ) );
2966
2967 SETTINGS_MANAGER manager;
2968 manager.LoadProject( "" );
2969 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
2970 schematic->SetProject( &manager.Prj() );
2971 SCH_SHEET* root = convertRawDesign( design, *schematic );
2972
2973 std::set<wxString> renderedText;
2974
2975 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
2976 renderedText.insert( static_cast<const SCH_TEXT*>( item )->GetText() );
2977
2978 BOOST_CHECK( renderedText.count( wxS( "MATCHED" ) ) );
2979 BOOST_CHECK( !renderedText.count( wxS( "PRIMARY" ) ) );
2980}
2981
2982
2983BOOST_AUTO_TEST_CASE( TitleBlockVectorTextBoundsDoNotCauseWrapping )
2984{
2985 ORCAD_SYMBOL_DEF definition;
2986 definition.name = "TITLE";
2987 definition.bbox = ORCAD_BBOX{ 0, 0, 790, 180 };
2988 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::LINE,
2989 .x1 = 0,
2990 .y1 = 0,
2991 .x2 = 100,
2992 .y2 = 0,
2993 .lineWidth = 0 } );
2994 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
2995 .x1 = 53,
2996 .y1 = 4,
2997 .x2 = 215,
2998 .y2 = 24,
2999 .text = "CUSTOMER NOTICE",
3000 .fontIdx = 1,
3001 .textBoundsStart = ORCAD_POINT{ 20, 4 } } );
3002
3003 ORCAD_GRAPHIC_INST titleBlock;
3004 titleBlock.name = definition.name;
3005 titleBlock.bbox = ORCAD_BBOX{ 440, 720, 1230, 900 };
3006
3007 ORCAD_RAW_PAGE page;
3008 page.name = "SHEET";
3009 page.titleBlocks.push_back( std::move( titleBlock ) );
3010
3011 ORCAD_DESIGN design;
3012 design.sourceId = "title-block-vector-text";
3013 design.library.fonts = { ORCAD_FONT{ .height = -20, .face = "Arial Narrow", .bold = true } };
3014 design.symbols.emplace( definition.name, std::move( definition ) );
3015 design.pages.push_back( std::move( page ) );
3016
3017 SETTINGS_MANAGER manager;
3018 manager.LoadProject( "" );
3019 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3020 schematic->SetProject( &manager.Prj() );
3021 SCH_SHEET* root = convertRawDesign( design, *schematic );
3022
3023 BOOST_CHECK( root->GetScreen()->Items().OfType( SCH_TEXTBOX_T ).empty() );
3024
3025 const SCH_TEXT* vectorText = nullptr;
3026
3027 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3028 vectorText = static_cast<const SCH_TEXT*>( item );
3029
3030 BOOST_REQUIRE( vectorText );
3031 BOOST_CHECK_EQUAL( vectorText->GetText(), wxString( "CUSTOMER NOTICE" ) );
3032 BOOST_CHECK_EQUAL( vectorText->GetTextHeight(), KiROUND( schIUScale.mmToIU( 3.31 ) * 6.0 / 5.0 ) );
3033 BOOST_CHECK_EQUAL( vectorText->GetTextWidth(), KiROUND( schIUScale.mmToIU( 3.31 ) * 35.0 / 32.0 ) );
3034 BOOST_CHECK_EQUAL( vectorText->GetFont()->GetName(), wxString( "Arial Narrow" ) );
3035 BOOST_CHECK_EQUAL( vectorText->GetPosition().x, OrcadDbuToIu( 493, 724 ).x );
3036 BOOST_CHECK_EQUAL( vectorText->GetPosition().y,
3037 OrcadDbuToIu( 493, 724 ).y
3038 + OrcadTextBaselineOffset( vectorText->GetTextHeight() ) );
3039
3040 const SCH_SHAPE* vectorLine = nullptr;
3041
3042 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHAPE_T ) )
3043 vectorLine = static_cast<const SCH_SHAPE*>( item );
3044
3045 BOOST_REQUIRE( vectorLine );
3046 BOOST_CHECK_EQUAL( vectorLine->GetStroke().GetWidth(), OrcadLineWidthIu( 0 ) );
3047}
3048
3049
3050BOOST_AUTO_TEST_CASE( NumericPageNamesOverrideRotatedStorageOrder )
3051{
3052 ORCAD_DESIGN design;
3053 design.sourceId = "rotated-page-order";
3054
3055 for( const std::string& name : { "PAGE_02_CONTENT", "PAGE_03_CONTENT", "PAGE_01_INDEX" } )
3056 {
3057 ORCAD_RAW_PAGE page;
3058 page.name = name;
3059 page.pageSize = "A";
3060 page.width = 9700;
3061 page.height = 7200;
3062 page.sourcePageNumber = design.pages.size() + 1;
3063 page.sourcePageCount = 3;
3064 design.pages.push_back( std::move( page ) );
3065 }
3066
3067 SETTINGS_MANAGER manager;
3068 manager.LoadProject( "" );
3069 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3070 schematic->SetProject( &manager.Prj() );
3071 convertRawDesign( design, *schematic );
3072
3073 std::vector<SCH_SHEET*> sheets = schematic->GetTopLevelSheets();
3074 BOOST_REQUIRE_EQUAL( sheets.size(), 3u );
3075 BOOST_CHECK_EQUAL( sheets[0]->GetField( FIELD_T::SHEET_NAME )->GetText(), wxS( "PAGE_01_INDEX" ) );
3076 BOOST_CHECK_EQUAL( sheets[1]->GetField( FIELD_T::SHEET_NAME )->GetText(), wxS( "PAGE_02_CONTENT" ) );
3077 BOOST_CHECK_EQUAL( sheets[2]->GetField( FIELD_T::SHEET_NAME )->GetText(), wxS( "PAGE_03_CONTENT" ) );
3078
3079 std::map<std::string, size_t> pageNumbers;
3080
3081 for( const ORCAD_RAW_PAGE& page : design.pages )
3082 pageNumbers[page.name] = page.sourcePageNumber;
3083
3084 BOOST_CHECK_EQUAL( pageNumbers["PAGE_01_INDEX"], 1u );
3085 BOOST_CHECK_EQUAL( pageNumbers["PAGE_02_CONTENT"], 2u );
3086 BOOST_CHECK_EQUAL( pageNumbers["PAGE_03_CONTENT"], 3u );
3087}
3088
3089
3090BOOST_AUTO_TEST_CASE( TitleBlockPageNumbersOverrideStorageOrder )
3091{
3092 ORCAD_DESIGN design;
3093 design.sourceId = "title-block-page-order";
3094
3095 for( const auto& [name, pageNumber] : { std::pair{ "SECOND", "2" }, std::pair{ "FIRST", "1" } } )
3096 {
3097 ORCAD_RAW_PAGE page;
3098 page.name = name;
3099 page.pageSize = "A";
3100 page.width = 9700;
3101 page.height = 7200;
3102 page.sourcePageNumber = design.pages.size() + 1;
3103 page.sourcePageCount = 2;
3104 ORCAD_GRAPHIC_INST titleBlock;
3105 titleBlock.props["Page Number"] = pageNumber;
3106 page.titleBlocks.push_back( std::move( titleBlock ) );
3107 design.pages.push_back( std::move( page ) );
3108 }
3109
3110 SETTINGS_MANAGER manager;
3111 manager.LoadProject( "" );
3112 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3113 schematic->SetProject( &manager.Prj() );
3114 convertRawDesign( design, *schematic );
3115
3116 std::vector<SCH_SHEET*> sheets = schematic->GetTopLevelSheets();
3117 BOOST_REQUIRE_EQUAL( sheets.size(), 2u );
3118 BOOST_CHECK_EQUAL( sheets[0]->GetField( FIELD_T::SHEET_NAME )->GetText(), wxS( "FIRST" ) );
3119 BOOST_CHECK_EQUAL( sheets[1]->GetField( FIELD_T::SHEET_NAME )->GetText(), wxS( "SECOND" ) );
3120}
3121
3122
3123static SCH_SYMBOL* findConvertedSymbol( SCH_SCREEN& aScreen, const SCH_SHEET_PATH& aPath, const wxString& aReference )
3124{
3125 for( SCH_ITEM* item : aScreen.Items().OfType( SCH_SYMBOL_T ) )
3126 {
3127 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
3128
3129 if( symbol->GetRef( &aPath, false ) == aReference )
3130 return symbol;
3131 }
3132
3133 return nullptr;
3134}
3135
3136
3137BOOST_AUTO_TEST_CASE( PageGraphicUuidsFollowSourceOrder )
3138{
3139 ORCAD_GRAPHIC_INST comment;
3141 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3142 comment.nested->primitives.push_back(
3143 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT, .x1 = 10, .y1 = 30, .text = "SOURCE FIRST" } );
3144 comment.nested->primitives.push_back(
3145 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT, .x1 = 10, .y1 = 10, .text = "SOURCE SECOND" } );
3146
3147 ORCAD_RAW_PAGE page;
3148 page.name = "PAGE GRAPHIC ORDER";
3149 page.graphics.push_back( std::move( comment ) );
3150
3151 ORCAD_DESIGN design;
3152 design.sourceId = "page-graphic-order";
3153 design.pages.push_back( std::move( page ) );
3154
3155 SETTINGS_MANAGER manager;
3156 manager.LoadProject( "" );
3157 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3158 schematic->SetProject( &manager.Prj() );
3159 SCH_SHEET* root = convertRawDesign( design, *schematic );
3160
3161 std::map<wxString, KIID> textUuids;
3162
3163 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3164 {
3165 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
3166 textUuids.emplace( text->GetText(), text->m_Uuid );
3167 }
3168
3169 BOOST_REQUIRE_EQUAL( textUuids.size(), 2u );
3170 std::string role = "orcad-import:page-graphic-order:page:0:item:"
3171 + std::to_string( static_cast<int>( SCH_TEXT_T ) );
3172 BOOST_CHECK( textUuids.at( wxS( "SOURCE FIRST" ) ) == KIID::FromName( role + ":0" ) );
3173 BOOST_CHECK( textUuids.at( wxS( "SOURCE SECOND" ) ) == KIID::FromName( role + ":1" ) );
3174}
3175
3176
3177BOOST_AUTO_TEST_CASE( PageCommentSourceBoundsDefineWrapWidth )
3178{
3179 ORCAD_GRAPHIC_INST comment;
3181 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3182 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
3183 .x1 = 10,
3184 .y1 = 20,
3185 .x2 = 166,
3186 .y2 = 35,
3187 .text = "PCA ADDITIONAL PARTS",
3188 .fontIdx = 1,
3189 .textBoundsStart = ORCAD_POINT{ 10, 20 } } );
3190
3191 ORCAD_RAW_PAGE page;
3192 page.name = "BOXED COMMENT";
3193 page.graphics.push_back( std::move( comment ) );
3194
3195 ORCAD_DESIGN design;
3196 design.sourceId = "boxed-comment";
3197 design.library.fonts = { ORCAD_FONT{ .height = -17, .face = "Arial Narrow", .bold = true } };
3198 design.pages.push_back( std::move( page ) );
3199
3200 SETTINGS_MANAGER manager;
3201 manager.LoadProject( "" );
3202 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3203 schematic->SetProject( &manager.Prj() );
3204 SCH_SHEET* root = convertRawDesign( design, *schematic );
3205
3206 BOOST_CHECK( root->GetScreen()->Items().OfType( SCH_TEXTBOX_T ).empty() );
3207
3208 const SCH_TEXT* sheetText = nullptr;
3209
3210 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3211 sheetText = static_cast<const SCH_TEXT*>( item );
3212
3213 BOOST_REQUIRE( sheetText );
3214 BOOST_CHECK_EQUAL( sheetText->GetText(), wxString( "PCA ADDITIONAL\nPARTS" ) );
3215 BOOST_CHECK_LE( std::abs( sheetText->GetTextBox( nullptr ).GetWidth() - OrcadDbuToIu( 156, 0 ).x ),
3216 OrcadDbuToIu( 1, 0 ).x );
3218 BOOST_REQUIRE( sheetText->GetFont() );
3219 BOOST_CHECK_EQUAL( sheetText->GetFont()->GetName(), wxString( "Arial Narrow" ) );
3220}
3221
3222
3223BOOST_AUTO_TEST_CASE( BoldArialNarrowCommentIgnoresSmallMetricOverflow )
3224{
3225 ORCAD_GRAPHIC_INST comment;
3227 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3228 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
3229 .x1 = 400,
3230 .y1 = 30,
3231 .x2 = 658,
3232 .y2 = 63,
3233 .text = "DC590 SPI INTERFACE",
3234 .fontIdx = 1,
3235 .textBoundsStart = ORCAD_POINT{ 400, 30 } } );
3236
3237 ORCAD_RAW_PAGE page;
3238 page.name = "BOLD NARROW STORED WIDTH";
3239 page.graphics.push_back( std::move( comment ) );
3240
3241 ORCAD_DESIGN design;
3242 design.sourceId = "bold-narrow-small-overflow";
3243 design.library.fonts = {
3244 ORCAD_FONT{ .height = -28, .face = "Arial Narrow", .bold = true }
3245 };
3246 design.pages.push_back( std::move( page ) );
3247
3248 SETTINGS_MANAGER manager;
3249 manager.LoadProject( "" );
3250 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3251 schematic->SetProject( &manager.Prj() );
3252 SCH_SHEET* root = convertRawDesign( design, *schematic );
3253
3254 const SCH_TEXT* sheetText = nullptr;
3255
3256 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3257 sheetText = static_cast<const SCH_TEXT*>( item );
3258
3259 BOOST_REQUIRE( sheetText );
3260 BOOST_CHECK_EQUAL( sheetText->GetText(), wxString( "DC590 SPI INTERFACE" ) );
3261}
3262
3263
3264BOOST_AUTO_TEST_CASE( BoldArialNarrowNoteIgnoresSmallMetricOverflow )
3265{
3266 ORCAD_GRAPHIC_INST comment;
3268 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3269 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
3270 .x1 = 140,
3271 .y1 = 960,
3272 .x2 = 390,
3273 .y2 = 980,
3274 .text = "1. ALL RESISTORS ARE IN OHMS, 0402",
3275 .fontIdx = 1,
3276 .textBoundsStart = ORCAD_POINT{ 140, 960 } } );
3277
3278 ORCAD_RAW_PAGE page;
3279 page.name = "BOLD NARROW NOTE";
3280 page.graphics.push_back( std::move( comment ) );
3281
3282 ORCAD_DESIGN design;
3283 design.sourceId = "bold-narrow-note-small-overflow";
3284 design.library.fonts = {
3285 ORCAD_FONT{ .height = -16, .face = "Arial Narrow", .bold = true }
3286 };
3287 design.pages.push_back( std::move( page ) );
3288
3289 SETTINGS_MANAGER manager;
3290 manager.LoadProject( "" );
3291 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3292 schematic->SetProject( &manager.Prj() );
3293 SCH_SHEET* root = convertRawDesign( design, *schematic );
3294
3295 const SCH_TEXT* sheetText = nullptr;
3296
3297 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3298 sheetText = static_cast<const SCH_TEXT*>( item );
3299
3300 BOOST_REQUIRE( sheetText );
3301 BOOST_CHECK_EQUAL( sheetText->GetText(), wxString( "1. ALL RESISTORS ARE IN OHMS, 0402" ) );
3302}
3303
3304
3305BOOST_AUTO_TEST_CASE( PageCommentUsesSourceAnchorWhenBoundsArePresent )
3306{
3307 ORCAD_GRAPHIC_INST comment;
3309 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3310 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
3311 .x1 = 50,
3312 .y1 = 130,
3313 .x2 = 118,
3314 .y2 = 145,
3315 .text = "REF DES",
3316 .fontIdx = 1,
3317 .textBoundsStart = ORCAD_POINT{ 50, 130 } } );
3318
3319 ORCAD_RAW_PAGE page;
3320 page.name = "SOURCE-ANCHORED COMMENT";
3321 page.width = 1000;
3322 page.height = 1000;
3323 page.graphics.push_back( std::move( comment ) );
3324
3325 ORCAD_DESIGN design;
3326 design.sourceId = "source-anchored-comment";
3327 design.library.fonts = { ORCAD_FONT{ .height = -13, .face = "Arial", .italic = true } };
3328 design.pages.push_back( std::move( page ) );
3329
3330 SETTINGS_MANAGER manager;
3331 manager.LoadProject( "" );
3332 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3333 schematic->SetProject( &manager.Prj() );
3334 SCH_SHEET* root = convertRawDesign( design, *schematic );
3335
3336 const SCH_TEXT* sheetText = nullptr;
3337
3338 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3339 sheetText = static_cast<const SCH_TEXT*>( item );
3340
3341 BOOST_REQUIRE( sheetText );
3342 BOOST_CHECK_EQUAL( sheetText->GetPosition().x, OrcadDbuToIu( 50, 130 ).x );
3344 BOOST_CHECK_LE( std::abs( sheetText->GetTextBox( nullptr ).GetWidth() - OrcadDbuToIu( 68, 0 ).x ),
3345 OrcadDbuToIu( 1, 0 ).x );
3346}
3347
3348
3349BOOST_AUTO_TEST_CASE( ElephantPageCommentUsesSourceBoundsForBaseline )
3350{
3351 ORCAD_GRAPHIC_INST comment;
3353 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3354 comment.nested->primitives.push_back(
3356 .x1 = 840,
3357 .y1 = 610,
3358 .x2 = 1024,
3359 .y2 = 623,
3360 .text = "*All Test Points are No Load",
3361 .fontIdx = 1,
3362 .textBoundsStart = ORCAD_POINT{ 840, 610 } } );
3363
3364 ORCAD_RAW_PAGE page;
3365 page.name = "ELEPHANT COMMENT";
3366 page.graphics.push_back( std::move( comment ) );
3367
3368 ORCAD_DESIGN design;
3369 design.sourceId = "elephant-comment";
3370 design.library.fonts = { ORCAD_FONT{ .height = -13, .face = "Elephant" } };
3371 design.pages.push_back( std::move( page ) );
3372
3373 SETTINGS_MANAGER manager;
3374 manager.LoadProject( "" );
3375 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3376 schematic->SetProject( &manager.Prj() );
3377 SCH_SHEET* root = convertRawDesign( design, *schematic );
3378
3379 const SCH_TEXT* sheetText = nullptr;
3380
3381 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3382 sheetText = static_cast<const SCH_TEXT*>( item );
3383
3384 BOOST_REQUIRE( sheetText );
3385 BOOST_CHECK_EQUAL( sheetText->GetText(), wxS( "*All Test Points are No Load" ) );
3386 BOOST_REQUIRE( sheetText->GetFont() );
3387 BOX2I ink = sheetText->GetEffectiveTextShape( false, BOX2I(), ANGLE_0 )->BBox();
3388 ink.Offset( sheetText->GetSchematicTextOffset( nullptr )
3389 + sheetText->GetOffsetToMatchSCH_FIELD( nullptr ) );
3390 BOOST_CHECK_SMALL( ink.GetY() - OrcadDbuToIu( 0, 613 ).y, OrcadDbuToIu( 0, 1 ).y );
3391 BOOST_CHECK_SMALL( ink.GetBottom() - OrcadDbuToIu( 0, 623 ).y, OrcadDbuToIu( 0, 1 ).y );
3392}
3393
3394
3395BOOST_AUTO_TEST_CASE( ArialNarrowCommentBoundsDoNotImplyWrapping )
3396{
3397 ORCAD_GRAPHIC_INST comment;
3399 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3400 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
3401 .x1 = 80,
3402 .y1 = 940,
3403 .x2 = 243,
3404 .y2 = 956,
3405 .text = "OPTIONAL COMPONENTS",
3406 .fontIdx = 1 } );
3407
3408 ORCAD_RAW_PAGE page;
3409 page.name = "NARROW WRAPPED COMMENT";
3410 page.graphics.push_back( std::move( comment ) );
3411
3412 ORCAD_DESIGN design;
3413 design.sourceId = "narrow-wrapped-comment";
3414 design.library.fonts = { ORCAD_FONT{ .height = -16, .face = "Arial Narrow" } };
3415 design.pages.push_back( std::move( page ) );
3416
3417 SETTINGS_MANAGER manager;
3418 manager.LoadProject( "" );
3419 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3420 schematic->SetProject( &manager.Prj() );
3421 SCH_SHEET* root = convertRawDesign( design, *schematic );
3422
3423 const SCH_TEXT* sheetText = nullptr;
3424
3425 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3426 sheetText = static_cast<const SCH_TEXT*>( item );
3427
3428 BOOST_REQUIRE( sheetText );
3429 BOOST_CHECK_EQUAL( sheetText->GetText(), wxString( "OPTIONAL COMPONENTS" ) );
3430}
3431
3432
3433BOOST_AUTO_TEST_CASE( BoldArialNarrowCommentBoundsDoNotImplyWrapping )
3434{
3435 ORCAD_GRAPHIC_INST comment;
3437 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3438 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
3439 .x1 = 926,
3440 .y1 = 917,
3441 .x2 = 1210,
3442 .y2 = 933,
3443 .text = "FOR USE WITH DC2321A DUST DEMOBOARD",
3444 .fontIdx = 1 } );
3445
3446 ORCAD_RAW_PAGE page;
3447 page.name = "BOLD NARROW SINGLE LINE COMMENT";
3448 page.graphics.push_back( std::move( comment ) );
3449
3450 ORCAD_DESIGN design;
3451 design.sourceId = "bold-narrow-single-line-comment";
3452 design.library.fonts = { ORCAD_FONT{ .height = -16, .face = "Arial Narrow", .bold = true } };
3453 design.pages.push_back( std::move( page ) );
3454
3455 SETTINGS_MANAGER manager;
3456 manager.LoadProject( "" );
3457 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3458 schematic->SetProject( &manager.Prj() );
3459 SCH_SHEET* root = convertRawDesign( design, *schematic );
3460
3461 const SCH_TEXT* sheetText = nullptr;
3462
3463 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3464 sheetText = static_cast<const SCH_TEXT*>( item );
3465
3466 BOOST_REQUIRE( sheetText );
3467 BOOST_CHECK_EQUAL( sheetText->GetText(), wxString( "FOR USE WITH DC2321A DUST DEMOBOARD" ) );
3468}
3469
3470
3471BOOST_AUTO_TEST_CASE( PageCommentRenderedOverflowDoesNotImplyWrapping )
3472{
3473 ORCAD_GRAPHIC_INST comment;
3475 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3476 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
3477 .x1 = 10,
3478 .y1 = 20,
3479 .x2 = 110,
3480 .y2 = 60,
3481 .text = "NOTE: UNLESS OTHERWISE SPECIFIED",
3482 .fontIdx = 1 } );
3483
3484 ORCAD_RAW_PAGE page;
3485 page.name = "WRAPPED COMMENT";
3486 page.graphics.push_back( std::move( comment ) );
3487
3488 ORCAD_DESIGN design;
3489 design.sourceId = "wrapped-comment";
3490 design.library.fonts = { ORCAD_FONT{ .height = -20, .face = "Arial", .bold = true } };
3491 design.pages.push_back( std::move( page ) );
3492
3493 SETTINGS_MANAGER manager;
3494 manager.LoadProject( "" );
3495 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3496 schematic->SetProject( &manager.Prj() );
3497 SCH_SHEET* root = convertRawDesign( design, *schematic );
3498
3499 const SCH_TEXT* sheetText = nullptr;
3500
3501 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3502 sheetText = static_cast<const SCH_TEXT*>( item );
3503
3504 BOOST_REQUIRE( sheetText );
3505 BOOST_CHECK_EQUAL( sheetText->GetText(), wxString( "NOTE: UNLESS OTHERWISE SPECIFIED" ) );
3506}
3507
3508
3509BOOST_AUTO_TEST_CASE( PageCommentSmallMetricOverflowRemainsSingleLine )
3510{
3511 ORCAD_GRAPHIC_INST comment;
3513 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3514 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
3515 .x1 = 10,
3516 .y1 = 20,
3517 .x2 = 51,
3518 .y2 = 34,
3519 .text = "SPI BUS",
3520 .fontIdx = 1 } );
3521
3522 ORCAD_RAW_PAGE page;
3523 page.name = "SMALL METRIC OVERFLOW";
3524 page.graphics.push_back( std::move( comment ) );
3525
3526 ORCAD_DESIGN design;
3527 design.sourceId = "small-metric-overflow";
3528 design.library.fonts = { ORCAD_FONT{ .height = -11, .width = 5, .face = "Arial" } };
3529 design.pages.push_back( std::move( page ) );
3530
3531 SETTINGS_MANAGER manager;
3532 manager.LoadProject( "" );
3533 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3534 schematic->SetProject( &manager.Prj() );
3535 SCH_SHEET* root = convertRawDesign( design, *schematic );
3536
3537 const SCH_TEXT* sheetText = nullptr;
3538
3539 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3540 sheetText = static_cast<const SCH_TEXT*>( item );
3541
3542 BOOST_REQUIRE( sheetText );
3543 BOOST_CHECK_EQUAL( sheetText->GetText(), wxString( "SPI BUS" ) );
3544}
3545
3546
3547BOOST_AUTO_TEST_CASE( LegacySymbolFontsAreConvertedToUnicode )
3548{
3549 ORCAD_GRAPHIC_INST comment;
3551 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3552 comment.nested->primitives = {
3553 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT, .x1 = 10, .y1 = 20, .text = "m", .fontIdx = 1 },
3554 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT, .x1 = 30, .y1 = 20, .text = "b", .fontIdx = 2 },
3555 };
3556
3557 ORCAD_RAW_PAGE page;
3558 page.name = "LEGACY SYMBOL FONTS";
3559 page.graphics.push_back( std::move( comment ) );
3560
3561 ORCAD_DESIGN design;
3562 design.sourceId = "legacy-symbol-fonts";
3563 design.library.fonts = { ORCAD_FONT{ .height = -13, .face = "GreekC" },
3564 ORCAD_FONT{ .height = -13, .face = "CommercialPi BT" } };
3565 design.pages.push_back( std::move( page ) );
3566
3567 SETTINGS_MANAGER manager;
3568 manager.LoadProject( "" );
3569 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3570 schematic->SetProject( &manager.Prj() );
3571 SCH_SHEET* root = convertRawDesign( design, *schematic );
3572
3573 std::set<wxString> texts;
3574
3575 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3576 texts.insert( static_cast<const SCH_TEXT*>( item )->GetText() );
3577
3578 BOOST_CHECK( texts.count( wxString::FromUTF8( "µ" ) ) );
3579 BOOST_CHECK( texts.count( wxString::FromUTF8( "®" ) ) );
3580}
3581
3582
3583BOOST_AUTO_TEST_CASE( PageCommentTextThatFitsRemainsSingleLine )
3584{
3585 ORCAD_GRAPHIC_INST comment;
3587 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3588 comment.nested->primitives.push_back(
3590 .x1 = 387,
3591 .y1 = 360,
3592 .x2 = 647,
3593 .y2 = 373,
3594 .text = "SENZA resistenza di terminazione",
3595 .fontIdx = 1 } );
3596
3597 ORCAD_RAW_PAGE page;
3598 page.name = "SINGLE-LINE COMMENT";
3599 page.pageSize = "A";
3600 page.width = 9700;
3601 page.height = 7200;
3602 page.graphics.push_back( std::move( comment ) );
3603
3604 ORCAD_DESIGN design;
3605 design.sourceId = "single-line-comment";
3606 design.library.fonts = { ORCAD_FONT{ .height = -13, .face = "Courier New" } };
3607 design.pages.push_back( std::move( page ) );
3608
3609 SETTINGS_MANAGER manager;
3610 manager.LoadProject( "" );
3611 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3612 schematic->SetProject( &manager.Prj() );
3613 SCH_SHEET* root = convertRawDesign( design, *schematic );
3614
3615 BOOST_CHECK( root->GetScreen()->Items().OfType( SCH_TEXTBOX_T ).empty() );
3616
3617 const SCH_TEXT* sheetText = nullptr;
3618
3619 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3620 sheetText = static_cast<const SCH_TEXT*>( item );
3621
3622 BOOST_REQUIRE( sheetText );
3623 BOOST_CHECK_EQUAL( sheetText->GetText(), wxString( "SENZA resistenza di terminazione" ) );
3625 BOOST_CHECK_EQUAL( sheetText->GetPosition().x, OrcadDbuToIu( ( 387 + 647 ) / 2, 0 ).x );
3626}
3627
3628
3629BOOST_AUTO_TEST_CASE( LargePageGraphicTextIsNotClamped )
3630{
3631 ORCAD_GRAPHIC_INST comment;
3633 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3634 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
3635 .x1 = 100,
3636 .y1 = 100,
3637 .text = "LARGE TITLE",
3638 .fontIdx = 1 } );
3639
3640 ORCAD_RAW_PAGE page;
3641 page.name = "LARGE PAGE TEXT";
3642 page.pageSize = "A";
3643 page.width = 9700;
3644 page.height = 7200;
3645 page.graphics.push_back( std::move( comment ) );
3646
3647 ORCAD_DESIGN design;
3648 design.sourceId = "large-page-text";
3649 design.library.fonts = { ORCAD_FONT{ .height = -48, .face = "Verdana", .bold = true } };
3650 design.pages.push_back( std::move( page ) );
3651
3652 SETTINGS_MANAGER manager;
3653 manager.LoadProject( "" );
3654 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3655 schematic->SetProject( &manager.Prj() );
3656 SCH_SHEET* root = convertRawDesign( design, *schematic );
3657
3658 const SCH_TEXT* title = nullptr;
3659
3660 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3661 {
3662 BOOST_REQUIRE( !title );
3663 title = static_cast<const SCH_TEXT*>( item );
3664 }
3665
3666 BOOST_REQUIRE( title );
3667 BOOST_CHECK_EQUAL( title->GetTextHeight(), schIUScale.mmToIU( 8.70 ) );
3669 OrcadDbuToIu( 100, 100 ).y + KiROUND( title->GetTextHeight() * 0.675 ) );
3670}
3671
3672
3673BOOST_AUTO_TEST_CASE( FixedPitchPageGraphicUsesCharacterCellWidthForWrapping )
3674{
3675 ORCAD_GRAPHIC_INST comment;
3677 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3678 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
3679 .x1 = 177,
3680 .y1 = 48,
3681 .x2 = 604,
3682 .y2 = 80,
3683 .text = "POWER SUPPLY REGULATOR",
3684 .fontIdx = 1 } );
3685
3686 ORCAD_RAW_PAGE page;
3687 page.name = "FIXED PITCH PAGE TEXT";
3688 page.graphics.push_back( std::move( comment ) );
3689
3690 ORCAD_DESIGN design;
3691 design.sourceId = "fixed-pitch-page-text";
3692 design.library.fonts = {
3693 ORCAD_FONT{ .height = -32, .pitchAndFamily = 0x31, .face = "Courier New" }
3694 };
3695 design.pages.push_back( std::move( page ) );
3696
3697 SETTINGS_MANAGER manager;
3698 manager.LoadProject( "" );
3699 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3700 schematic->SetProject( &manager.Prj() );
3701 SCH_SHEET* root = convertRawDesign( design, *schematic );
3702
3703 BOOST_CHECK( root->GetScreen()->Items().OfType( SCH_TEXTBOX_T ).empty() );
3704
3705 const SCH_TEXT* title = nullptr;
3706
3707 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
3708 title = static_cast<const SCH_TEXT*>( item );
3709
3710 BOOST_REQUIRE( title );
3711 BOOST_CHECK_EQUAL( title->GetText(), wxString( "POWER SUPPLY REGULATOR" ) );
3712}
3713
3714
3715BOOST_AUTO_TEST_CASE( DefaultPageGraphicColorIsBlack )
3716{
3717 ORCAD_GRAPHIC_INST graphic;
3719 graphic.color = 48;
3720 graphic.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3721 graphic.nested->primitives.push_back(
3722 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::ELLIPSE, .x1 = 10, .y1 = 20, .x2 = 50, .y2 = 40 } );
3723
3724 ORCAD_RAW_PAGE page;
3725 page.name = "DEFAULT GRAPHIC COLOR";
3726 page.graphics.push_back( std::move( graphic ) );
3727
3728 ORCAD_DESIGN design;
3729 design.sourceId = "default-page-graphic-color";
3730 design.pages.push_back( std::move( page ) );
3731
3732 SETTINGS_MANAGER manager;
3733 manager.LoadProject( "" );
3734 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3735 schematic->SetProject( &manager.Prj() );
3736 SCH_SHEET* root = convertRawDesign( design, *schematic );
3737
3738 std::vector<SCH_SHAPE*> shapes;
3739
3740 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHAPE_T ) )
3741 shapes.push_back( static_cast<SCH_SHAPE*>( item ) );
3742
3743 BOOST_REQUIRE_EQUAL( shapes.size(), 1u );
3744 BOOST_CHECK( shapes.front()->GetStroke().GetColor() == KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 ) );
3745}
3746
3747
3748BOOST_AUTO_TEST_CASE( EmbeddedEmfOleFrameDefaultsToRed )
3749{
3750 ORCAD_GRAPHIC_INST graphic;
3752 graphic.color = 48;
3753 graphic.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3754 graphic.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::IMAGE,
3755 .x1 = 10,
3756 .y1 = 20,
3757 .x2 = 50,
3758 .y2 = 40,
3759 .data = makeOleWmfPreview(
3760 makeEmbeddedEmfWmf( makeRenderableEmf() ) ) } );
3761
3762 ORCAD_RAW_PAGE page;
3763 page.name = "DEFAULT OLE FRAME COLOR";
3764 page.graphics.push_back( std::move( graphic ) );
3765
3766 ORCAD_DESIGN design;
3767 design.sourceId = "default-ole-frame-color";
3768 design.pages.push_back( std::move( page ) );
3769
3770 SETTINGS_MANAGER manager;
3771 manager.LoadProject( "" );
3772 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3773 schematic->SetProject( &manager.Prj() );
3774 SCH_SHEET* root = convertRawDesign( design, *schematic );
3775
3776 auto bitmaps = root->GetScreen()->Items().OfType( SCH_BITMAP_T );
3777 BOOST_REQUIRE_EQUAL( std::distance( bitmaps.begin(), bitmaps.end() ), 1 );
3778
3779 std::vector<SCH_SHAPE*> shapes;
3780
3781 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHAPE_T ) )
3782 shapes.push_back( static_cast<SCH_SHAPE*>( item ) );
3783
3784 BOOST_REQUIRE_EQUAL( shapes.size(), 1u );
3785 BOOST_CHECK( shapes.front()->GetStroke().GetColor() == OrcadColor( 8 ) );
3786}
3787
3788
3789BOOST_AUTO_TEST_CASE( PlainWmfOleFrameDefaultsToBlack )
3790{
3791 ORCAD_GRAPHIC_INST graphic;
3793 graphic.color = 48;
3794 graphic.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3795 graphic.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::IMAGE,
3796 .x1 = 10,
3797 .y1 = 20,
3798 .x2 = 50,
3799 .y2 = 40,
3800 .data = makeOleWmfPreview( makeGlyphIndexWmf() ) } );
3801
3802 ORCAD_RAW_PAGE page;
3803 page.name = "DEFAULT OLE FRAME COLOR";
3804 page.graphics.push_back( std::move( graphic ) );
3805
3806 ORCAD_DESIGN design;
3807 design.sourceId = "default-ole-frame-color";
3808 design.pages.push_back( std::move( page ) );
3809
3810 SETTINGS_MANAGER manager;
3811 manager.LoadProject( "" );
3812 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3813 schematic->SetProject( &manager.Prj() );
3814 SCH_SHEET* root = convertRawDesign( design, *schematic );
3815
3816 auto bitmaps = root->GetScreen()->Items().OfType( SCH_BITMAP_T );
3817 BOOST_REQUIRE_EQUAL( std::distance( bitmaps.begin(), bitmaps.end() ), 1 );
3818
3819 std::vector<SCH_SHAPE*> shapes;
3820
3821 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHAPE_T ) )
3822 shapes.push_back( static_cast<SCH_SHAPE*>( item ) );
3823
3824 BOOST_REQUIRE_EQUAL( shapes.size(), 1u );
3825 BOOST_CHECK( shapes.front()->GetStroke().GetColor() == KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 ) );
3826}
3827
3828
3829BOOST_AUTO_TEST_CASE( WhiteFilledPageGraphicPrintsBlack )
3830{
3831 ORCAD_GRAPHIC_INST graphic;
3833 graphic.color = 47;
3834 graphic.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3835 graphic.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::ELLIPSE,
3836 .x1 = 10,
3837 .y1 = 20,
3838 .x2 = 50,
3839 .y2 = 40,
3840 .fillStyle = 0 } );
3841
3842 ORCAD_RAW_PAGE page;
3843 page.name = "WHITE FILLED GRAPHIC";
3844 page.graphics.push_back( std::move( graphic ) );
3845
3846 ORCAD_DESIGN design;
3847 design.sourceId = "white-filled-page-graphic";
3848 design.pages.push_back( std::move( page ) );
3849
3850 SETTINGS_MANAGER manager;
3851 manager.LoadProject( "" );
3852 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3853 schematic->SetProject( &manager.Prj() );
3854 SCH_SHEET* root = convertRawDesign( design, *schematic );
3855
3856 std::vector<SCH_SHAPE*> shapes;
3857
3858 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHAPE_T ) )
3859 shapes.push_back( static_cast<SCH_SHAPE*>( item ) );
3860
3861 BOOST_REQUIRE_EQUAL( shapes.size(), 1u );
3862 BOOST_CHECK( shapes.front()->GetFillMode() == FILL_T::FILLED_SHAPE );
3863 BOOST_CHECK( shapes.front()->GetStroke().GetColor() == KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 ) );
3864}
3865
3866
3867BOOST_AUTO_TEST_CASE( DegeneratePageArcDoesNotBecomeFullEllipse )
3868{
3869 ORCAD_GRAPHIC_INST graphic;
3871 graphic.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3872 graphic.nested->primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::ARC,
3873 .x1 = -20,
3874 .y1 = 0,
3875 .x2 = 20,
3876 .y2 = 40,
3877 .start = ORCAD_POINT{ 0, 0 },
3878 .end = ORCAD_POINT{ 0, 0 } } );
3879
3880 ORCAD_RAW_PAGE page;
3881 page.name = "DEGENERATE PAGE ARC";
3882 page.graphics.push_back( std::move( graphic ) );
3883
3884 ORCAD_DESIGN design;
3885 design.sourceId = "degenerate-page-arc";
3886 design.pages.push_back( std::move( page ) );
3887
3888 SETTINGS_MANAGER manager;
3889 manager.LoadProject( "" );
3890 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3891 schematic->SetProject( &manager.Prj() );
3892 SCH_SHEET* root = convertRawDesign( design, *schematic );
3893
3894 BOOST_CHECK( root->GetScreen()->Items().OfType( SCH_SHAPE_T ).empty() );
3895}
3896
3897
3898BOOST_AUTO_TEST_CASE( DesignTemplateFontIdsResolveToLogfonts )
3899{
3900 ORCAD_SYMBOL_DEF definition;
3901 definition.typeId = ORCAD_ST_LIBRARY_PART;
3902 definition.name = "MAPPED_FONT.Normal";
3903 definition.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
3904
3905 ORCAD_PLACED_INSTANCE placed;
3906 placed.pkgName = definition.name;
3907 placed.reference = "C1";
3908 placed.value = "10nF";
3909 placed.x = 100;
3910 placed.y = 100;
3911 placed.displayProps = {
3912 ORCAD_DISPLAY_PROP{ .name = "Part Reference", .dispMode = 0x101 },
3913 ORCAD_DISPLAY_PROP{ .name = "Value", .y = 20, .dispMode = 0x101 },
3914 };
3915
3916 ORCAD_RAW_PAGE page;
3917 page.name = "MAPPED FONT";
3918 page.instances.push_back( std::move( placed ) );
3919 ORCAD_WIRE wire{ .id = 1, .x1 = 50, .y1 = 50, .x2 = 150, .y2 = 50 };
3920 wire.aliases.push_back( ORCAD_ALIAS{ .name = "FONT_NET", .x = 50, .y = 50, .fontIdx = 0x20650200 } );
3921 page.netmap[wire.id] = "FONT_NET";
3922 page.wires.push_back( std::move( wire ) );
3923 ORCAD_GRAPHIC_INST comment;
3925 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
3926 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{
3927 .kind = ORCAD_PRIM_KIND::TEXT, .x1 = 10, .y1 = 10, .text = "RAW_FONT", .fontIdx = 2 } );
3928 page.graphics.push_back( std::move( comment ) );
3929
3930 ORCAD_DESIGN design;
3931 design.sourceId = "design-template-font-map";
3932 design.library.fonts = { ORCAD_FONT{ .height = -9, .face = "Arial" },
3933 ORCAD_FONT{ .height = -9, .face = "Courier New" },
3934 ORCAD_FONT{ .height = -12, .face = "Arial Narrow", .bold = true } };
3935 design.library.templateFonts.resize( 24, 1 );
3936 design.library.templateFonts[2] = 3;
3937 design.library.templateFonts[5] = 3;
3938 design.library.templateFonts[9] = 3;
3939 design.symbols.emplace( definition.name, std::move( definition ) );
3940 design.pages.push_back( std::move( page ) );
3941
3942 SETTINGS_MANAGER manager;
3943 manager.LoadProject( "" );
3944 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
3945 schematic->SetProject( &manager.Prj() );
3946 SCH_SHEET* root = convertRawDesign( design, *schematic );
3948 path.push_back( root );
3949 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "C1" ) );
3950 BOOST_REQUIRE( converted );
3951 SCH_FIELD* reference = converted->GetField( FIELD_T::REFERENCE );
3952 BOOST_REQUIRE( reference );
3953 BOOST_REQUIRE( reference->GetFont() );
3954 BOOST_CHECK_EQUAL( reference->GetFont()->GetName(), wxS( "Arial Narrow" ) );
3955 BOOST_CHECK( reference->IsBold() );
3956 BOOST_CHECK_EQUAL( reference->GetTextHeight(), schIUScale.mmToIU( 1.98 ) );
3957 VECTOR2I pageOffset = converted->GetPosition() - OrcadDbuToIu( placed.x, placed.y );
3958 BOOST_CHECK_EQUAL( reference->GetPosition().y,
3959 OrcadDbuToIu( placed.x, placed.y ).y + pageOffset.y
3960 + KiROUND( reference->GetTextHeight() * 0.62 ) );
3961
3962 SCH_LABEL* netLabel = nullptr;
3963
3964 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_LABEL_T ) )
3965 {
3966 SCH_LABEL* label = static_cast<SCH_LABEL*>( item );
3967
3968 if( label->GetText() == wxS( "FONT_NET" ) && label->GetFont() )
3969 netLabel = label;
3970 }
3971
3972 BOOST_REQUIRE( netLabel );
3973 BOOST_REQUIRE( netLabel->GetFont() );
3974 BOOST_CHECK_EQUAL( netLabel->GetFont()->GetName(), wxS( "Arial Narrow" ) );
3975 BOOST_CHECK( netLabel->IsBold() );
3976 BOOST_CHECK_EQUAL( netLabel->GetTextHeight(), schIUScale.mmToIU( 1.98 ) );
3977
3978 const SCH_TEXT* rawText = nullptr;
3979
3980 for( const SCH_ITEM* item : root->GetScreen()->Items() )
3981 {
3982 if( item->Type() == SCH_TEXT_T && static_cast<const SCH_TEXT*>( item )->GetText() == wxS( "RAW_FONT" ) )
3983 rawText = static_cast<const SCH_TEXT*>( item );
3984 }
3985
3986 BOOST_REQUIRE( rawText );
3987 BOOST_REQUIRE( rawText->GetFont() );
3988 BOOST_CHECK_EQUAL( rawText->GetFont()->GetName(), wxS( "Courier New" ) );
3989 BOOST_CHECK( !rawText->IsBold() );
3990}
3991
3992
3993BOOST_AUTO_TEST_CASE( ExplicitDisplayFontBypassesTemplateMapping )
3994{
3995 ORCAD_SYMBOL_DEF definition;
3996 definition.typeId = ORCAD_ST_LIBRARY_PART;
3997 definition.name = "EXPLICIT_FONT.Normal";
3998 definition.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
3999
4000 ORCAD_PLACED_INSTANCE placed;
4001 placed.pkgName = definition.name;
4002 placed.reference = "U1";
4003 placed.value = "LARGE";
4004 placed.x = 100;
4005 placed.y = 100;
4006 placed.displayProps = {
4007 ORCAD_DISPLAY_PROP{ .name = "Part Reference", .fontIdx = 2, .dispMode = 0x100 },
4008 ORCAD_DISPLAY_PROP{ .name = "Value", .y = 20, .fontIdx = 2, .dispMode = 0x100 },
4009 };
4010
4011 ORCAD_RAW_PAGE page;
4012 page.name = "EXPLICIT FONT";
4013 page.instances.push_back( std::move( placed ) );
4014
4015 ORCAD_DESIGN design;
4016 design.sourceId = "explicit-display-font";
4017 design.library.fonts = { ORCAD_FONT{ .height = -9, .face = "Arial Narrow" },
4018 ORCAD_FONT{ .height = -20, .face = "Arial Narrow", .bold = true } };
4019 design.library.templateFonts.resize( 12, 1 );
4020 design.symbols.emplace( definition.name, std::move( definition ) );
4021 design.pages.push_back( std::move( page ) );
4022
4023 SETTINGS_MANAGER manager;
4024 manager.LoadProject( "" );
4025 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4026 schematic->SetProject( &manager.Prj() );
4027 SCH_SHEET* root = convertRawDesign( design, *schematic );
4029 path.push_back( root );
4030 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "U1" ) );
4031 BOOST_REQUIRE( converted );
4032
4033 for( SCH_FIELD* field : { converted->GetField( FIELD_T::REFERENCE ), converted->GetField( FIELD_T::VALUE ) } )
4034 {
4035 BOOST_REQUIRE( field );
4036 BOOST_CHECK_EQUAL( field->GetTextHeight(), schIUScale.mmToIU( 3.31 ) );
4037 BOOST_CHECK( field->IsBold() );
4038 }
4039}
4040
4041
4042BOOST_AUTO_TEST_CASE( BoxedSymbolTextRemainsCenteredInItsSourceBounds )
4043{
4044 ORCAD_SYMBOL_DEF definition;
4045 definition.typeId = ORCAD_ST_LIBRARY_PART;
4046 definition.name = "BOXED_TEXT.Normal";
4047 definition.bbox = ORCAD_BBOX{ 0, 0, 100, 20 };
4048 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::RECTANGLE,
4049 .x1 = 0,
4050 .y1 = 0,
4051 .x2 = 100,
4052 .y2 = 20 } );
4053 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
4054 .x1 = 0,
4055 .y1 = 0,
4056 .x2 = 100,
4057 .y2 = 20,
4058 .text = "CENTERED",
4059 .fontIdx = 1 } );
4060
4061 ORCAD_PLACED_INSTANCE placed;
4062 placed.pkgName = definition.name;
4063 placed.reference = "LB1";
4064 placed.x = 100;
4065 placed.y = 100;
4066
4067 ORCAD_RAW_PAGE page;
4068 page.name = "BOXED TEXT";
4069 page.instances.push_back( std::move( placed ) );
4070
4071 ORCAD_DESIGN design;
4072 design.sourceId = "boxed-symbol-text";
4073 design.library.fonts = {
4074 ORCAD_FONT{ .height = -20, .width = 11, .pitchAndFamily = 0x31, .face = "Courier New" }
4075 };
4076 design.symbols.emplace( definition.name, std::move( definition ) );
4077 design.pages.push_back( std::move( page ) );
4078
4079 SETTINGS_MANAGER manager;
4080 manager.LoadProject( "" );
4081 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4082 schematic->SetProject( &manager.Prj() );
4083 SCH_SHEET* root = convertRawDesign( design, *schematic );
4085 path.push_back( root );
4086 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "LB1" ) );
4087 BOOST_REQUIRE( converted );
4088
4089 const SCH_SHAPE* rectangle = nullptr;
4090 const SCH_TEXT* text = nullptr;
4091
4092 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4093 {
4094 if( item.Type() == SCH_SHAPE_T )
4095 rectangle = static_cast<const SCH_SHAPE*>( &item );
4096 else if( item.Type() == SCH_TEXT_T && static_cast<const SCH_TEXT*>( &item )->GetText() == wxS( "CENTERED" ) )
4097 text = static_cast<const SCH_TEXT*>( &item );
4098 }
4099
4100 BOOST_REQUIRE( rectangle );
4102 VECTOR2I target( 50 * ORCAD_IU_PER_DBU, 10 * ORCAD_IU_PER_DBU + 3 * ORCAD_IU_PER_DBU / 2 );
4103 BOX2I glyphBox = text->GetEffectiveTextShape( false )->BBox();
4104 BOOST_CHECK_EQUAL( glyphBox.Centre().x, target.x );
4105 BOOST_CHECK_EQUAL( glyphBox.Centre().y, target.y );
4106 BOOST_CHECK_LE( std::abs( glyphBox.GetWidth() - 89 * ORCAD_IU_PER_DBU ), ORCAD_IU_PER_DBU / 4 );
4107 BOOST_CHECK_LE( std::abs( glyphBox.GetHeight() - 14 * ORCAD_IU_PER_DBU ), ORCAD_IU_PER_DBU / 4 );
4108}
4109
4110
4111BOOST_AUTO_TEST_CASE( BoxedMultilineSymbolTextRemainsLeftAligned )
4112{
4113 ORCAD_SYMBOL_DEF definition;
4114 definition.typeId = ORCAD_ST_LIBRARY_PART;
4115 definition.name = "MULTILINE_TEXT.Normal";
4116 definition.bbox = ORCAD_BBOX{ 0, 0, 110, 104 };
4117 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
4118 .x1 = 0,
4119 .y1 = 60,
4120 .x2 = 110,
4121 .y2 = 104,
4122 .text = "DEVICE\nVERSION : 1\nPAGE : 1 of 1\nDATE : TODAY\n",
4123 .fontIdx = 1 } );
4124
4125 ORCAD_PLACED_INSTANCE placed;
4126 placed.pkgName = definition.name;
4127 placed.reference = "U1";
4128 placed.x = 100;
4129 placed.y = 100;
4130
4131 ORCAD_RAW_PAGE page;
4132 page.name = "MULTILINE TEXT";
4133 page.instances.push_back( std::move( placed ) );
4134
4135 ORCAD_DESIGN design;
4136 design.sourceId = "multiline-symbol-text";
4137 design.library.fonts = { ORCAD_FONT{ .height = -11, .face = "Arial" } };
4138 design.symbols.emplace( definition.name, std::move( definition ) );
4139 design.pages.push_back( std::move( page ) );
4140
4141 SETTINGS_MANAGER manager;
4142 manager.LoadProject( "" );
4143 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4144 schematic->SetProject( &manager.Prj() );
4145 SCH_SHEET* root = convertRawDesign( design, *schematic );
4147 path.push_back( root );
4148 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "U1" ) );
4149 BOOST_REQUIRE( converted );
4150
4151 const SCH_TEXT* text = nullptr;
4152
4153 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4154 {
4155 if( item.Type() == SCH_TEXT_T && static_cast<const SCH_TEXT&>( item ).GetText().StartsWith( wxS( "DEVICE" ) ) )
4156 text = static_cast<const SCH_TEXT*>( &item );
4157 }
4158
4160 BOOST_CHECK( text->GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT );
4161 BOOST_CHECK( text->GetVertJustify() == GR_TEXT_V_ALIGN_CENTER );
4162 BOOST_CHECK_EQUAL( text->GetPosition().x, 0 );
4163 BOOST_CHECK_EQUAL( text->GetEffectiveTextShape( false )->BBox().Centre().y, 82 * ORCAD_IU_PER_DBU );
4164}
4165
4166
4167BOOST_AUTO_TEST_CASE( LaterBoxStrokeOccludesSymbolTextUnderscores )
4168{
4169 ORCAD_SYMBOL_DEF definition;
4170 definition.typeId = ORCAD_ST_LIBRARY_PART;
4171 definition.name = "OCCLUDED_UNDERSCORES.Normal";
4172 definition.bbox = ORCAD_BBOX{ 0, 0, 100, 20 };
4173 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
4174 .x1 = 0,
4175 .y1 = 0,
4176 .x2 = 80,
4177 .y2 = 19,
4178 .text = "ISO_EVB_LABEL",
4179 .fontIdx = 1 } );
4180 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::RECTANGLE,
4181 .x1 = 0,
4182 .y1 = 0,
4183 .x2 = 100,
4184 .y2 = 20,
4185 .lineWidth = 2 } );
4186
4187 ORCAD_PLACED_INSTANCE placed;
4188 placed.pkgName = definition.name;
4189 placed.reference = "LB1";
4190
4191 ORCAD_RAW_PAGE page;
4192 page.name = "OCCLUDED UNDERSCORES";
4193 page.instances.push_back( std::move( placed ) );
4194
4195 ORCAD_DESIGN design;
4196 design.sourceId = "occluded-symbol-text-underscores";
4197 design.library.fonts = {
4198 ORCAD_FONT{ .height = -20, .width = 11, .pitchAndFamily = 0x31, .face = "Courier New" }
4199 };
4200 design.symbols.emplace( definition.name, std::move( definition ) );
4201 design.pages.push_back( std::move( page ) );
4202
4203 SETTINGS_MANAGER manager;
4204 manager.LoadProject( "" );
4205 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4206 schematic->SetProject( &manager.Prj() );
4207 SCH_SHEET* root = convertRawDesign( design, *schematic );
4209 path.push_back( root );
4210 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "LB1" ) );
4211 BOOST_REQUIRE( converted );
4212
4213 const SCH_TEXT* primitiveText = nullptr;
4214
4215 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4216 {
4217 if( item.Type() == SCH_TEXT_T )
4218 primitiveText = static_cast<const SCH_TEXT*>( &item );
4219 }
4220
4221 BOOST_REQUIRE( primitiveText );
4222 BOOST_CHECK_EQUAL( primitiveText->GetText(), wxS( "ISO EVB LABEL" ) );
4223}
4224
4225
4226BOOST_AUTO_TEST_CASE( RotatedSymbolPreservesPrimitiveTextOrientation )
4227{
4228 ORCAD_SYMBOL_DEF definition;
4229 definition.typeId = ORCAD_ST_LIBRARY_PART;
4230 definition.name = "VERTICAL_TEXT.Normal";
4231 definition.bbox = ORCAD_BBOX{ 0, 0, 30, 20 };
4232 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
4233 .x2 = 9,
4234 .y2 = 3,
4235 .text = "0603",
4236 .fontIdx = 1 } );
4237
4238 ORCAD_PLACED_INSTANCE placed;
4239 placed.pkgName = definition.name;
4240 placed.reference = "D1";
4241 placed.x = 100;
4242 placed.y = 100;
4243 placed.rotation = 1;
4244
4245 ORCAD_RAW_PAGE page;
4246 page.name = "ROTATED SYMBOL TEXT";
4247 page.instances.push_back( std::move( placed ) );
4248
4249 ORCAD_DESIGN design;
4250 design.sourceId = "rotated-symbol-text";
4251 design.library.fonts = { ORCAD_FONT{ .height = -9, .face = "Courier New" } };
4252 design.symbols.emplace( definition.name, std::move( definition ) );
4253 design.pages.push_back( std::move( page ) );
4254
4255 SETTINGS_MANAGER manager;
4256 manager.LoadProject( "" );
4257 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4258 schematic->SetProject( &manager.Prj() );
4259 SCH_SHEET* root = convertRawDesign( design, *schematic );
4261 path.push_back( root );
4262 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "D1" ) );
4263 BOOST_REQUIRE( converted );
4264
4265 const SCH_TEXT* primitiveText = nullptr;
4266
4267 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4268 {
4269 if( item.Type() == SCH_TEXT_T && static_cast<const SCH_TEXT&>( item ).GetText() == wxS( "0603" ) )
4270 primitiveText = static_cast<const SCH_TEXT*>( &item );
4271 }
4272
4273 BOOST_REQUIRE( primitiveText );
4274 BOOST_CHECK( primitiveText->GetDrawRotation() == ANGLE_HORIZONTAL );
4275}
4276
4277
4278BOOST_AUTO_TEST_CASE( BoxedSymbolTextUsesFontEscapement )
4279{
4280 ORCAD_SYMBOL_DEF definition;
4281 definition.typeId = ORCAD_ST_LIBRARY_PART;
4282 definition.name = "VERTICAL_BOXED_TEXT.Normal";
4283 definition.bbox = ORCAD_BBOX{ 0, 0, 20, 100 };
4284 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
4285 .x1 = 0,
4286 .y1 = 0,
4287 .x2 = 20,
4288 .y2 = 100,
4289 .text = "ISOLATION BARRIER",
4290 .fontIdx = 1 } );
4291
4292 ORCAD_PLACED_INSTANCE placed;
4293 placed.pkgName = definition.name;
4294 placed.reference = "U1";
4295 placed.x = 100;
4296 placed.y = 100;
4297
4298 ORCAD_RAW_PAGE page;
4299 page.name = "VERTICAL BOXED TEXT";
4300 page.instances.push_back( std::move( placed ) );
4301
4302 ORCAD_DESIGN design;
4303 design.sourceId = "vertical-boxed-symbol-text";
4304 design.library.fonts = { ORCAD_FONT{ .height = -17, .width = 7, .escapement = 900, .face = "Arial" } };
4305 design.symbols.emplace( definition.name, std::move( definition ) );
4306 design.pages.push_back( std::move( page ) );
4307
4308 SETTINGS_MANAGER manager;
4309 manager.LoadProject( "" );
4310 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4311 schematic->SetProject( &manager.Prj() );
4312 SCH_SHEET* root = convertRawDesign( design, *schematic );
4314 path.push_back( root );
4315 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "U1" ) );
4316 BOOST_REQUIRE( converted );
4317
4318 const SCH_TEXT* primitiveText = nullptr;
4319
4320 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4321 {
4322 if( item.Type() == SCH_TEXT_T
4323 && static_cast<const SCH_TEXT&>( item ).GetText() == wxS( "ISOLATION BARRIER" ) )
4324 {
4325 primitiveText = static_cast<const SCH_TEXT*>( &item );
4326 }
4327 }
4328
4329 BOOST_REQUIRE( primitiveText );
4330 BOOST_CHECK( primitiveText->GetDrawRotation().IsVertical() );
4331 BOOST_CHECK_LE( std::abs( primitiveText->GetEffectiveTextShape( false )->BBox().Centre().x
4332 - 10 * ORCAD_IU_PER_DBU ),
4333 ORCAD_IU_PER_DBU / 4 );
4334 BOOST_CHECK_LE( std::abs( primitiveText->GetEffectiveTextShape( false )->BBox().Centre().y
4335 - 50 * ORCAD_IU_PER_DBU ),
4336 ORCAD_IU_PER_DBU / 4 );
4337}
4338
4339
4340BOOST_AUTO_TEST_CASE( SymbolTextDoesNotStealConnectedContactCircle )
4341{
4342 ORCAD_SYMBOL_DEF definition;
4343 definition.typeId = ORCAD_ST_LIBRARY_PART;
4344 definition.name = "SWITCH_CONTACT_TEXT.Normal";
4345 definition.bbox = ORCAD_BBOX{ 0, 0, 70, 160 };
4346 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::LINE,
4347 .x1 = 0,
4348 .y1 = 100,
4349 .x2 = 50,
4350 .y2 = 100 } );
4351 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::ELLIPSE,
4352 .x1 = 49,
4353 .y1 = 99,
4354 .x2 = 51,
4355 .y2 = 101 } );
4356 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::TEXT,
4357 .x1 = 50,
4358 .y1 = 90,
4359 .x2 = 57,
4360 .y2 = 99,
4361 .text = "0",
4362 .fontIdx = 1 } );
4363
4364 ORCAD_PLACED_INSTANCE placed;
4365 placed.pkgName = definition.name;
4366 placed.reference = "SW1";
4367 placed.x = 100;
4368 placed.y = 100;
4369
4370 ORCAD_RAW_PAGE page;
4371 page.name = "SWITCH CONTACT TEXT";
4372 page.instances.push_back( std::move( placed ) );
4373
4374 ORCAD_DESIGN design;
4375 design.sourceId = "switch-contact-text";
4376 design.library.fonts = { ORCAD_FONT{ .height = -9, .face = "Arial" } };
4377 design.symbols.emplace( definition.name, std::move( definition ) );
4378 design.pages.push_back( std::move( page ) );
4379
4380 SETTINGS_MANAGER manager;
4381 manager.LoadProject( "" );
4382 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4383 schematic->SetProject( &manager.Prj() );
4384 SCH_SHEET* root = convertRawDesign( design, *schematic );
4386 path.push_back( root );
4387 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "SW1" ) );
4388 BOOST_REQUIRE( converted );
4389
4390 const SCH_SHAPE* contact = nullptr;
4391
4392 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4393 {
4394 if( item.Type() == SCH_SHAPE_T && static_cast<const SCH_SHAPE&>( item ).GetShape() == SHAPE_T::CIRCLE )
4395 contact = static_cast<const SCH_SHAPE*>( &item );
4396 }
4397
4398 BOOST_REQUIRE( contact );
4399 BOOST_CHECK_EQUAL( contact->GetPosition().x, 50 * ORCAD_IU_PER_DBU );
4400 BOOST_CHECK_EQUAL( contact->GetPosition().y, 100 * ORCAD_IU_PER_DBU );
4401}
4402
4403
4404BOOST_AUTO_TEST_CASE( DegenerateSymbolArcDoesNotBecomeFullEllipse )
4405{
4406 ORCAD_SYMBOL_DEF definition;
4407 definition.typeId = ORCAD_ST_LIBRARY_PART;
4408 definition.name = "DEGENERATE_ARC.Normal";
4409 definition.bbox = ORCAD_BBOX{ -20, 0, 20, 40 };
4410 definition.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::ARC,
4411 .x1 = -20,
4412 .y1 = 0,
4413 .x2 = 20,
4414 .y2 = 40,
4415 .start = ORCAD_POINT{ 0, 0 },
4416 .end = ORCAD_POINT{ 0, 0 } } );
4417
4418 ORCAD_PLACED_INSTANCE placed;
4419 placed.pkgName = definition.name;
4420 placed.reference = "A1";
4421 placed.x = 100;
4422 placed.y = 100;
4423
4424 ORCAD_RAW_PAGE page;
4425 page.name = "DEGENERATE ARC";
4426 page.instances.push_back( std::move( placed ) );
4427
4428 ORCAD_DESIGN design;
4429 design.sourceId = "degenerate-symbol-arc";
4430 design.symbols.emplace( definition.name, std::move( definition ) );
4431 design.pages.push_back( std::move( page ) );
4432
4433 SETTINGS_MANAGER manager;
4434 manager.LoadProject( "" );
4435 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4436 schematic->SetProject( &manager.Prj() );
4437 SCH_SHEET* root = convertRawDesign( design, *schematic );
4439 path.push_back( root );
4440 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "A1" ) );
4441 BOOST_REQUIRE( converted );
4442
4443 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4444 BOOST_CHECK( item.Type() != SCH_SHAPE_T || static_cast<const SCH_SHAPE&>( item ).GetShape() != SHAPE_T::POLY );
4445}
4446
4447
4448BOOST_AUTO_TEST_CASE( ZeroLengthPinRetainsNativeNameAndNumberData )
4449{
4450 ORCAD_SYMBOL_DEF definition;
4451 definition.typeId = ORCAD_ST_LIBRARY_PART;
4452 definition.name = "HIDDEN_PIN.Normal";
4453 definition.bbox = ORCAD_BBOX{ 0, 0, 100, 20 };
4454 definition.generalFlags = 3;
4455 definition.primitives.push_back(
4456 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::RECTANGLE, .x1 = 0, .y1 = 0, .x2 = 40, .y2 = 20 } );
4457 definition.pins.push_back( ORCAD_SYMBOL_PIN{ .name = "LABEL",
4458 .position = 0,
4459 .startX = 0,
4460 .startY = 10,
4461 .hotptX = 0,
4462 .hotptY = 10,
4463 .portType = ORCAD_PORT_TYPE::POWER_IN,
4464 .shapeBits = 0 } );
4465 definition.pins.push_back( ORCAD_SYMBOL_PIN{ .name = "RIGHT",
4466 .position = 1,
4467 .startX = 40,
4468 .startY = 10,
4469 .hotptX = 40,
4470 .hotptY = 10,
4471 .portType = ORCAD_PORT_TYPE::POWER_IN,
4472 .shapeBits = 0 } );
4473
4474 ORCAD_PLACED_INSTANCE placed;
4475 placed.pkgName = definition.name;
4476 placed.reference = "LB1";
4477 placed.x = 100;
4478 placed.y = 100;
4479 placed.pins.push_back( ORCAD_PIN_INST{ .pinIndex = 1, .x = 100, .y = 110 } );
4480 placed.pins.push_back( ORCAD_PIN_INST{ .pinIndex = 2, .x = 140, .y = 110 } );
4481
4482 ORCAD_RAW_PAGE page;
4483 page.name = "HIDDEN PIN";
4484 page.instances.push_back( std::move( placed ) );
4485
4486 ORCAD_DESIGN design;
4487 design.sourceId = "zero-length-pin-text";
4488 design.symbols.emplace( definition.name, std::move( definition ) );
4489 design.pages.push_back( std::move( page ) );
4490
4491 SETTINGS_MANAGER manager;
4492 manager.LoadProject( "" );
4493 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4494 schematic->SetProject( &manager.Prj() );
4495 SCH_SHEET* root = convertRawDesign( design, *schematic );
4497 path.push_back( root );
4498 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "LB1" ) );
4499 BOOST_REQUIRE( converted );
4500 BOOST_REQUIRE_EQUAL( converted->GetPins().size(), 2u );
4501
4502 std::map<wxString, wxString> pinNames;
4503
4504 for( const SCH_PIN* pin : converted->GetPins() )
4505 {
4506 BOOST_CHECK( pin->IsVisible() );
4507 BOOST_CHECK_EQUAL( pin->GetLength(), 0 );
4508 BOOST_CHECK_GT( pin->GetNameTextSize(), 0 );
4509 BOOST_CHECK_GT( pin->GetNumberTextSize(), 0 );
4510 pinNames.emplace( pin->GetNumber(), pin->GetName() );
4511 }
4512
4513 BOOST_CHECK_EQUAL( pinNames[wxS( "1" )], wxS( "LABEL" ) );
4514 BOOST_CHECK_EQUAL( pinNames[wxS( "2" )], wxS( "RIGHT" ) );
4515
4516 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4517 {
4518 if( item.Type() == SCH_TEXT_T )
4519 {
4520 const wxString& text = static_cast<const SCH_TEXT&>( item ).GetText();
4521 BOOST_CHECK( text != wxS( "LABEL" ) && text != wxS( "RIGHT" )
4522 && text != wxS( "1" ) && text != wxS( "2" ) );
4523 }
4524 }
4525}
4526
4527
4528BOOST_AUTO_TEST_CASE( PowerStylePinRemainsVisible )
4529{
4530 ORCAD_SYMBOL_DEF definition;
4531 definition.typeId = ORCAD_ST_LIBRARY_PART;
4532 definition.name = "POWER_STYLE_PIN.Normal";
4533 definition.bbox = ORCAD_BBOX{ 0, 0, 100, 20 };
4534 definition.generalFlags = 3;
4535 definition.pins.push_back( ORCAD_SYMBOL_PIN{ .name = "1",
4536 .position = 0,
4537 .startX = 10,
4538 .startY = 10,
4539 .hotptX = 10,
4540 .hotptY = 20,
4541 .portType = ORCAD_PORT_TYPE::PASSIVE,
4542 .shapeBits = 0x81 } );
4543
4544 ORCAD_PLACED_INSTANCE placed;
4545 placed.pkgName = definition.name;
4546 placed.reference = "J1";
4547 placed.x = 100;
4548 placed.y = 100;
4549 placed.pins.push_back( ORCAD_PIN_INST{ .pinIndex = 1, .x = 110, .y = 120 } );
4550
4551 ORCAD_RAW_PAGE page;
4552 page.name = "POWER STYLE PIN";
4553 page.instances.push_back( std::move( placed ) );
4554
4555 ORCAD_DESIGN design;
4556 design.sourceId = "power-style-pin";
4557 design.symbols.emplace( definition.name, std::move( definition ) );
4558 design.pages.push_back( std::move( page ) );
4559
4560 SETTINGS_MANAGER manager;
4561 manager.LoadProject( "" );
4562 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4563 schematic->SetProject( &manager.Prj() );
4564 SCH_SHEET* root = convertRawDesign( design, *schematic );
4566 path.push_back( root );
4567 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "J1" ) );
4568 BOOST_REQUIRE( converted );
4569 BOOST_REQUIRE_EQUAL( converted->GetPins().size(), 1u );
4570 const SCH_PIN* pin = converted->GetPins().front();
4571 BOOST_CHECK( pin->IsVisible() );
4572 BOOST_CHECK_EQUAL( pin->GetName(), wxS( "1" ) );
4573 BOOST_CHECK_EQUAL( pin->GetNumber(), wxS( "1" ) );
4574 BOOST_CHECK_GT( pin->GetNameTextSize(), 0 );
4575 BOOST_CHECK_GT( pin->GetNumberTextSize(), 0 );
4576
4577 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4578 {
4579 if( item.Type() == SCH_TEXT_T && static_cast<const SCH_TEXT&>( item ).GetText() == wxS( "1" ) )
4580 BOOST_ERROR( "Pin data duplicated as SCH_TEXT" );
4581 }
4582}
4583
4584
4585BOOST_AUTO_TEST_CASE( ZeroLengthPowerStylePinHidesImplicitText )
4586{
4587 ORCAD_SYMBOL_DEF definition;
4588 definition.typeId = ORCAD_ST_LIBRARY_PART;
4589 definition.name = "ZERO_LENGTH_POWER_STYLE_PIN.Normal";
4590 definition.bbox = ORCAD_BBOX{ 0, 0, 100, 20 };
4591 definition.generalFlags = 3;
4592 definition.pins.push_back( ORCAD_SYMBOL_PIN{ .name = "GND",
4593 .position = 0,
4594 .startX = 10,
4595 .startY = 10,
4596 .hotptX = 10,
4597 .hotptY = 10,
4598 .portType = ORCAD_PORT_TYPE::POWER_IN,
4599 .shapeBits = 0x81 } );
4600
4601 ORCAD_PLACED_INSTANCE placed;
4602 placed.pkgName = definition.name;
4603 placed.reference = "J1";
4604 placed.x = 100;
4605 placed.y = 100;
4606 placed.pins.push_back( ORCAD_PIN_INST{ .pinIndex = 1, .x = 110, .y = 110 } );
4607
4608 ORCAD_RAW_PAGE page;
4609 page.name = "ZERO LENGTH POWER STYLE PIN";
4610 page.instances.push_back( std::move( placed ) );
4611
4612 ORCAD_DESIGN design;
4613 design.sourceId = "zero-length-power-style-pin";
4614 design.symbols.emplace( definition.name, std::move( definition ) );
4615 design.pages.push_back( std::move( page ) );
4616
4617 SETTINGS_MANAGER manager;
4618 manager.LoadProject( "" );
4619 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4620 schematic->SetProject( &manager.Prj() );
4621 SCH_SHEET* root = convertRawDesign( design, *schematic );
4623 path.push_back( root );
4624 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "J1" ) );
4625 BOOST_REQUIRE( converted );
4626 BOOST_REQUIRE_EQUAL( converted->GetPins().size(), 1u );
4627 BOOST_CHECK( !converted->GetPins().front()->IsVisible() );
4628
4629 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4630 BOOST_CHECK( item.Type() != SCH_TEXT_T );
4631}
4632
4633
4634BOOST_AUTO_TEST_CASE( InputPinUsesCaptureBodyWedge )
4635{
4636 ORCAD_SYMBOL_DEF definition;
4637 definition.typeId = ORCAD_ST_LIBRARY_PART;
4638 definition.name = "INPUT_WEDGE.Normal";
4639 definition.bbox = ORCAD_BBOX{ 0, 0, 40, 20 };
4640 definition.pins = {
4641 ORCAD_SYMBOL_PIN{ .name = "IN", .position = 0, .startX = 0, .startY = 10,
4642 .hotptX = -30, .hotptY = 10, .portType = ORCAD_PORT_TYPE::INPUT_TYPE,
4643 .shapeBits = 0x21 },
4644 ORCAD_SYMBOL_PIN{ .name = "OUT", .position = 1, .startX = 40, .startY = 10,
4645 .hotptX = 70, .hotptY = 10, .portType = ORCAD_PORT_TYPE::OUTPUT,
4646 .shapeBits = 0x21 },
4647 };
4648
4649 ORCAD_PLACED_INSTANCE placed;
4650 placed.pkgName = definition.name;
4651 placed.reference = "U1";
4652 placed.x = 100;
4653 placed.y = 100;
4654 placed.pins = { ORCAD_PIN_INST{ .pinIndex = 1, .x = 70, .y = 110 },
4655 ORCAD_PIN_INST{ .pinIndex = 2, .x = 170, .y = 110 } };
4656
4657 ORCAD_RAW_PAGE page;
4658 page.name = "INPUT WEDGE";
4659 page.instances.push_back( std::move( placed ) );
4660
4661 ORCAD_DESIGN design;
4662 design.sourceId = "input-wedge";
4663 design.symbols.emplace( definition.name, std::move( definition ) );
4664 design.pages.push_back( std::move( page ) );
4665
4666 SETTINGS_MANAGER manager;
4667 manager.LoadProject( "" );
4668 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4669 schematic->SetProject( &manager.Prj() );
4670 SCH_SHEET* root = convertRawDesign( design, *schematic );
4672 path.push_back( root );
4673 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "U1" ) );
4674 BOOST_REQUIRE( converted );
4675
4676 std::vector<const SCH_SHAPE*> filledPolygons;
4677
4678 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4679 {
4680 if( item.Type() != SCH_SHAPE_T )
4681 continue;
4682
4683 const SCH_SHAPE& shape = static_cast<const SCH_SHAPE&>( item );
4684
4685 if( shape.GetShape() == SHAPE_T::POLY && shape.GetFillMode() == FILL_T::FILLED_SHAPE )
4686 filledPolygons.push_back( &shape );
4687 }
4688
4689 BOOST_REQUIRE_EQUAL( filledPolygons.size(), 1u );
4690 const std::vector<VECTOR2I>& points = filledPolygons.front()->GetPolyPoints();
4691 BOOST_REQUIRE_EQUAL( points.size(), 4u );
4692 BOOST_CHECK( points.front() == OrcadDbuToIu( 0, 10 ) );
4693 BOOST_CHECK( points.back() == points.front() );
4694}
4695
4696
4697BOOST_AUTO_TEST_CASE( HorizontalPinNumberRemainsNativePinData )
4698{
4699 ORCAD_SYMBOL_DEF definition;
4700 definition.typeId = ORCAD_ST_LIBRARY_PART;
4701 definition.name = "HORIZONTAL_PIN_NUMBER.Normal";
4702 definition.bbox = ORCAD_BBOX{ 0, 0, 20, 80 };
4703 definition.generalFlags = 3;
4704 definition.pins.push_back( ORCAD_SYMBOL_PIN{ .name = "3",
4705 .position = 0,
4706 .startX = 20,
4707 .startY = 40,
4708 .hotptX = 50,
4709 .hotptY = 40,
4710 .portType = ORCAD_PORT_TYPE::PASSIVE,
4711 .shapeBits = 1 } );
4712
4713 ORCAD_PLACED_INSTANCE placed;
4714 placed.pkgName = definition.name;
4715 placed.reference = "J1";
4716 placed.x = 100;
4717 placed.y = 100;
4718 placed.pins.push_back( ORCAD_PIN_INST{ .pinIndex = 1, .x = 150, .y = 140 } );
4719
4720 ORCAD_RAW_PAGE page;
4721 page.name = "HORIZONTAL PIN NUMBER";
4722 page.instances.push_back( std::move( placed ) );
4723
4724 ORCAD_DESIGN design;
4725 design.sourceId = "horizontal-pin-number";
4726 design.symbols.emplace( definition.name, std::move( definition ) );
4727 design.pages.push_back( std::move( page ) );
4728
4729 SETTINGS_MANAGER manager;
4730 manager.LoadProject( "" );
4731 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4732 schematic->SetProject( &manager.Prj() );
4733 SCH_SHEET* root = convertRawDesign( design, *schematic );
4735 path.push_back( root );
4736 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "J1" ) );
4737 BOOST_REQUIRE( converted );
4738
4739 BOOST_REQUIRE_EQUAL( converted->GetPins().size(), 1u );
4740 const SCH_PIN* pin = converted->GetPins().front();
4741 BOOST_CHECK_EQUAL( pin->GetName(), wxS( "3" ) );
4742 BOOST_CHECK_EQUAL( pin->GetNumber(), wxS( "1" ) );
4743 BOOST_CHECK_GT( pin->GetNameTextSize(), 0 );
4744 BOOST_CHECK_GT( pin->GetNumberTextSize(), 0 );
4745
4746 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
4747 {
4748 if( item.Type() == SCH_TEXT_T && static_cast<const SCH_TEXT&>( item ).GetText() == pin->GetNumber() )
4749 BOOST_ERROR( "Pin number duplicated as SCH_TEXT" );
4750 }
4751}
4752
4753
4754BOOST_AUTO_TEST_CASE( DisplayedImplementationPropertyIsPreserved )
4755{
4756 ORCAD_SYMBOL_DEF definition;
4757 definition.typeId = ORCAD_ST_LIBRARY_PART;
4758 definition.name = "PSPICE_TRANSISTOR.Normal";
4759 definition.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
4760
4761 ORCAD_PLACED_INSTANCE placed;
4762 placed.pkgName = definition.name;
4763 placed.reference = "Q1";
4764 placed.value = "MMBT3904";
4765 placed.x = 100;
4766 placed.y = 100;
4767 placed.displayProps = {
4768 ORCAD_DISPLAY_PROP{ .name = "Part Reference", .dispMode = 0x101 },
4769 ORCAD_DISPLAY_PROP{ .name = "Implementation", .x = -10, .y = -30, .dispMode = 0x101 },
4770 };
4771
4772 ORCAD_RAW_PAGE page;
4773 page.name = "DISPLAYED IMPLEMENTATION";
4774 page.instances.push_back( std::move( placed ) );
4775
4776 ORCAD_DESIGN design;
4777 design.sourceId = "displayed-implementation";
4778 design.library.fonts = { ORCAD_FONT{ .height = -9, .face = "Arial" } };
4779 design.symbols.emplace( definition.name, std::move( definition ) );
4780 design.pages.push_back( std::move( page ) );
4781
4782 SETTINGS_MANAGER manager;
4783 manager.LoadProject( "" );
4784 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4785 schematic->SetProject( &manager.Prj() );
4786 SCH_SHEET* root = convertRawDesign( design, *schematic );
4788 path.push_back( root );
4789 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "Q1" ) );
4790 BOOST_REQUIRE( converted );
4791 SCH_FIELD* implementation = converted->GetField( wxS( "Implementation" ) );
4792 BOOST_REQUIRE( implementation );
4793 BOOST_CHECK_EQUAL( implementation->GetText(), wxS( "MMBT3904" ) );
4794 BOOST_CHECK( implementation->IsVisible() );
4795 VECTOR2I pageOffset = converted->GetPosition() - OrcadDbuToIu( 100, 100 );
4796 BOOST_CHECK( implementation->GetPosition()
4797 == OrcadDbuToIu( 90, 70 ) + pageOffset
4798 + VECTOR2I( 0, OrcadTextBaselineOffset( implementation->GetTextSize().y ) ) );
4799}
4800
4801
4802BOOST_AUTO_TEST_CASE( DisplayedPropertyNameUsesCaptureEqualsSeparator )
4803{
4804 ORCAD_SYMBOL_DEF definition;
4805 definition.typeId = ORCAD_ST_LIBRARY_PART;
4806 definition.name = "VPULSE.Normal";
4807 definition.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
4808
4809 ORCAD_PLACED_INSTANCE placed;
4810 placed.pkgName = definition.name;
4811 placed.reference = "V1";
4812 placed.x = 100;
4813 placed.y = 100;
4814 placed.props["V1"] = "0";
4815 placed.displayProps = {
4816 ORCAD_DISPLAY_PROP{ .name = "Part Reference", .dispMode = 0x101 },
4817 ORCAD_DISPLAY_PROP{ .name = "V1", .x = -50, .y = -8, .dispMode = 0x201 },
4818 };
4819
4820 ORCAD_RAW_PAGE page;
4821 page.name = "DISPLAYED PROPERTY NAME";
4822 page.instances.push_back( std::move( placed ) );
4823
4824 ORCAD_DESIGN design;
4825 design.sourceId = "displayed-property-name";
4826 design.symbols.emplace( definition.name, std::move( definition ) );
4827 design.pages.push_back( std::move( page ) );
4828
4829 SETTINGS_MANAGER manager;
4830 manager.LoadProject( "" );
4831 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4832 schematic->SetProject( &manager.Prj() );
4833 SCH_SHEET* root = convertRawDesign( design, *schematic );
4835 path.push_back( root );
4836 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "V1" ) );
4837 BOOST_REQUIRE( converted );
4838 SCH_FIELD* parameter = converted->GetField( wxS( "V1" ) );
4839 BOOST_REQUIRE( parameter );
4840 BOOST_CHECK_EQUAL( parameter->GetText(), wxS( "0" ) );
4841 BOOST_CHECK( !parameter->IsVisible() );
4842
4843 const SCH_TEXT* display = nullptr;
4844
4845 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
4846 {
4847 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
4848
4849 if( text->GetText() == wxS( "V1 = 0" ) )
4850 display = text;
4851 }
4852
4853 BOOST_REQUIRE( display );
4854}
4855
4856
4857BOOST_AUTO_TEST_CASE( DisplayedValueNameUsesCaptureEqualsSeparator )
4858{
4859 ORCAD_SYMBOL_DEF definition;
4860 definition.typeId = ORCAD_ST_LIBRARY_PART;
4861 definition.name = "RES_27.Normal";
4862 definition.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
4863
4864 ORCAD_PLACED_INSTANCE placed;
4865 placed.pkgName = definition.name;
4866 placed.reference = "R1";
4867 placed.value = "27";
4868 placed.x = 100;
4869 placed.y = 100;
4870 placed.displayProps = {
4871 ORCAD_DISPLAY_PROP{ .name = "Part Reference", .dispMode = 0x101 },
4872 ORCAD_DISPLAY_PROP{ .name = "Value", .x = -50, .y = -8, .dispMode = 0x201 },
4873 };
4874
4875 ORCAD_RAW_PAGE page;
4876 page.name = "DISPLAYED VALUE NAME";
4877 page.instances.push_back( std::move( placed ) );
4878
4879 ORCAD_DESIGN design;
4880 design.sourceId = "displayed-value-name";
4881 design.symbols.emplace( definition.name, std::move( definition ) );
4882 design.pages.push_back( std::move( page ) );
4883
4884 SETTINGS_MANAGER manager;
4885 manager.LoadProject( "" );
4886 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4887 schematic->SetProject( &manager.Prj() );
4888 SCH_SHEET* root = convertRawDesign( design, *schematic );
4890 path.push_back( root );
4891 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "R1" ) );
4892 BOOST_REQUIRE( converted );
4893 SCH_FIELD* value = converted->GetField( FIELD_T::VALUE );
4894 BOOST_REQUIRE( value );
4895 BOOST_CHECK_EQUAL( value->GetText(), wxS( "27" ) );
4896 BOOST_CHECK( !value->IsVisible() );
4897
4898 const SCH_TEXT* display = nullptr;
4899
4900 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
4901 {
4902 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
4903
4904 if( text->GetText() == wxS( "Value = 27" ) )
4905 display = text;
4906 }
4907
4908 BOOST_REQUIRE( display );
4909}
4910
4911
4912BOOST_AUTO_TEST_CASE( HiddenImplementationPathPropertyIsPreserved )
4913{
4914 ORCAD_SYMBOL_DEF definition;
4915 definition.typeId = ORCAD_ST_LIBRARY_PART;
4916 definition.name = "IMPLEMENTATION_PATH.Normal";
4917 definition.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
4918 definition.props["Implementation Path"] = "LIBRARY/PART";
4919
4920 ORCAD_PACKAGE package;
4921 package.name = "IMPLEMENTATION_PATH";
4922 package.props["Implementation Path"] = "";
4923 package.devices.push_back( ORCAD_DEVICE{} );
4924
4925 ORCAD_PLACED_INSTANCE placed;
4926 placed.pkgName = definition.name;
4927 placed.reference = "U1";
4928 placed.x = 100;
4929 placed.y = 100;
4930
4931 ORCAD_RAW_PAGE page;
4932 page.name = "HIDDEN IMPLEMENTATION PATH";
4933 page.instances.push_back( std::move( placed ) );
4934
4935 ORCAD_DESIGN design;
4936 design.sourceId = "hidden-implementation-path";
4937 design.symbols.emplace( definition.name, std::move( definition ) );
4938 design.packages.emplace( package.name, std::move( package ) );
4939 design.pages.push_back( std::move( page ) );
4940
4941 SETTINGS_MANAGER manager;
4942 manager.LoadProject( "" );
4943 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
4944 schematic->SetProject( &manager.Prj() );
4945 SCH_SHEET* root = convertRawDesign( design, *schematic );
4947 path.push_back( root );
4948 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "U1" ) );
4949 BOOST_REQUIRE( converted );
4950 SCH_FIELD* implementationPath = converted->GetField( wxS( "Implementation Path" ) );
4951 BOOST_REQUIRE( implementationPath );
4952 BOOST_CHECK_EQUAL( implementationPath->GetText(), wxS( "LIBRARY/PART" ) );
4953 BOOST_CHECK( !implementationPath->IsVisible() );
4954}
4955
4956
4957BOOST_AUTO_TEST_CASE( OccurrencePropertiesOverrideReusablePartFields )
4958{
4959 ORCAD_SYMBOL_DEF definition;
4960 definition.typeId = ORCAD_ST_LIBRARY_PART;
4961 definition.name = "SWITCH.Normal";
4962 definition.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
4963 definition.props["Manufacturer"] = "Panasonic Electronic Components";
4964
4965 ORCAD_PACKAGE package;
4966 package.name = "SWITCH";
4967 package.pcbFootprint = "EVQ-PE105K";
4968 package.devices.push_back( ORCAD_DEVICE{} );
4969
4970 ORCAD_PLACED_INSTANCE replaced;
4971 replaced.dbId = 42;
4972 replaced.pkgName = definition.name;
4973 replaced.sourcePackage = package.name;
4974 replaced.reference = "SW1";
4975 replaced.value = "EVQ-PE105K";
4976 replaced.x = 100;
4977 replaced.y = 100;
4978 replaced.displayProps = {
4979 ORCAD_DISPLAY_PROP{ .name = "Part Reference", .dispMode = 0x101 },
4980 ORCAD_DISPLAY_PROP{ .name = "Value", .y = 10, .dispMode = 0x101 },
4981 ORCAD_DISPLAY_PROP{ .name = "PCB Footprint", .y = 20, .dispMode = 0x101 },
4982 ORCAD_DISPLAY_PROP{ .name = "Manufacturer", .y = 30, .dispMode = 0x101 },
4983 };
4984
4985 ORCAD_PLACED_INSTANCE cleared = replaced;
4986 cleared.dbId = 43;
4987 cleared.reference = "SW2";
4988 cleared.x = 200;
4989
4990 ORCAD_RAW_PAGE page;
4991 page.name = "OCCURRENCE PROPERTIES";
4992 page.instances = { replaced, cleared };
4993
4994 ORCAD_DESIGN design;
4995 design.sourceId = "occurrence-properties";
4996 design.symbols.emplace( definition.name, std::move( definition ) );
4997 design.packages.emplace( package.name, std::move( package ) );
4998 design.pages.push_back( std::move( page ) );
4999 design.occurrenceRoot.partProps[42] = {
5000 { "Value", "434 123 050 816" },
5001 { "Manufacturer", "Wurth Electronics Inc" },
5002 { "PCB Footprint", "434 123 050 816" },
5003 };
5004 design.occurrenceRoot.partProps[43] = {
5005 { "Value", "" },
5006 { "Manufacturer", "" },
5007 { "PCB Footprint", "" },
5008 };
5009
5010 SETTINGS_MANAGER manager;
5011 manager.LoadProject( "" );
5012 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5013 schematic->SetProject( &manager.Prj() );
5014 SCH_SHEET* root = convertRawDesign( design, *schematic );
5016 path.push_back( root );
5017
5018 SCH_SYMBOL* replacedSymbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "SW1" ) );
5019 BOOST_REQUIRE( replacedSymbol );
5020 BOOST_CHECK_EQUAL( replacedSymbol->GetField( FIELD_T::VALUE )->GetText(), wxS( "434 123 050 816" ) );
5021 BOOST_REQUIRE( replacedSymbol->GetField( wxS( "Manufacturer" ) ) );
5022 BOOST_CHECK_EQUAL( replacedSymbol->GetField( wxS( "Manufacturer" ) )->GetText(),
5023 wxS( "Wurth Electronics Inc" ) );
5024 BOOST_REQUIRE( replacedSymbol->GetField( wxS( "OrCAD Footprint" ) ) );
5025 BOOST_CHECK_EQUAL( replacedSymbol->GetField( wxS( "OrCAD Footprint" ) )->GetText(),
5026 wxS( "434 123 050 816" ) );
5027
5028 SCH_SYMBOL* clearedSymbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "SW2" ) );
5029 BOOST_REQUIRE( clearedSymbol );
5030 BOOST_CHECK( clearedSymbol->GetField( FIELD_T::VALUE )->GetText().IsEmpty() );
5031 BOOST_CHECK( !clearedSymbol->GetField( wxS( "Manufacturer" ) ) );
5032 BOOST_CHECK( !clearedSymbol->GetField( wxS( "OrCAD Footprint" ) ) );
5033}
5034
5035
5036BOOST_AUTO_TEST_CASE( UnrelatedOccurrenceNetNamesAreIgnored )
5037{
5038 ORCAD_RAW_PAGE page;
5039 page.name = "OCCURRENCE NETS";
5040 page.netmap[11643] = "LED1_R";
5041
5042 ORCAD_WIRE named;
5043 named.dbId = 10396;
5044 named.id = 11643;
5045 named.x2 = 100;
5046 page.wires.push_back( named );
5047
5048 ORCAD_WIRE generated;
5049 generated.dbId = 6941990;
5050 generated.id = 20000;
5051 generated.y1 = 100;
5052 generated.x2 = 100;
5053 generated.y2 = 100;
5054 page.wires.push_back( generated );
5055
5056 ORCAD_DESIGN design;
5057 design.sourceId = "occurrence-net-names";
5058 design.pages.push_back( std::move( page ) );
5059 design.occurrenceRoot.netNames[19574] = "LED1_R_G13";
5060 design.occurrenceRoot.netNames[19575] = "N6941990_DSP_UL,_LL,_UR,_LR_ATDSP0";
5061
5062 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5063 SETTINGS_MANAGER manager;
5064 manager.LoadProject( "" );
5065 schematic->SetProject( &manager.Prj() );
5066 SCH_SHEET* root = convertRawDesign( design, *schematic );
5067 std::set<wxString> labels;
5068
5069 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_LABEL_T ) )
5070 labels.insert( static_cast<SCH_LABEL*>( item )->GetText() );
5071
5072 BOOST_CHECK( !labels.contains( wxS( "LED1_R_G13" ) ) );
5073 BOOST_CHECK( !labels.contains( wxS( "N6941990_DSP_UL,_LL,_UR,_LR_ATDSP0" ) ) );
5074}
5075
5076
5077BOOST_AUTO_TEST_CASE( OccurrenceNetPrefixDoesNotRenameInterfaceNet )
5078{
5079 ORCAD_RAW_PAGE page;
5080 page.name = "INTERFACE NETS";
5081 page.netmap[1] = "WL_REG_ON";
5082
5083 ORCAD_WIRE wire;
5084 wire.id = 1;
5085 wire.x2 = 100;
5086 page.wires.push_back( wire );
5087
5088 ORCAD_GRAPHIC_INST offpage;
5089 offpage.logicalName = "WL_REG_ON";
5090 page.offpage.push_back( std::move( offpage ) );
5091
5092 ORCAD_RAW_PAGE peerPage;
5093 peerPage.name = "PEER";
5094 ORCAD_GRAPHIC_INST peerOffpage;
5095 peerOffpage.logicalName = "WL_REG_ON_M2";
5096 peerPage.offpage.push_back( std::move( peerOffpage ) );
5097
5098 ORCAD_DESIGN design;
5099 design.sourceId = "occurrence-interface-prefix";
5100 design.pages.push_back( std::move( page ) );
5101 design.pages.push_back( std::move( peerPage ) );
5102 design.occurrenceRoot.netNames[10] = "BT_REG_ON";
5103 design.occurrenceRoot.netNames[11] = "WL_REG_ON_M2";
5104
5105 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5106 SETTINGS_MANAGER manager;
5107 manager.LoadProject( "" );
5108 schematic->SetProject( &manager.Prj() );
5109 SCH_SHEET* root = convertRawDesign( design, *schematic );
5110 std::set<wxString> labels;
5111
5112 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
5113 labels.insert( static_cast<SCH_GLOBALLABEL*>( item )->GetText() );
5114
5115 BOOST_CHECK( labels.contains( wxS( "WL_REG_ON" ) ) );
5116 BOOST_CHECK( !labels.contains( wxS( "WL_REG_ON_M2" ) ) );
5117}
5118
5119
5120BOOST_AUTO_TEST_CASE( OccurrenceAliasRenamesInterfaceNet )
5121{
5122 ORCAD_RAW_PAGE page;
5123 page.name = "INTERFACE ALIAS";
5124 page.netmap[1] = "UART0_CTS";
5125 page.netAliases[1] = { "TWRPI_GPIO4", "EBI_AD1/PTD5/FTM0_CH5", "UART0_CTS" };
5126
5127 ORCAD_WIRE wire;
5128 wire.dbId = 20;
5129 wire.id = 1;
5130 wire.x2 = 100;
5131 ORCAD_ALIAS connectorAlias;
5132 connectorAlias.name = "TWRPI_GPIO4";
5133 connectorAlias.x = 10;
5134 wire.aliases.push_back( std::move( connectorAlias ) );
5135 ORCAD_ALIAS interfaceAlias;
5136 interfaceAlias.name = "EBI_AD1/PTD5/FTM0_CH5";
5137 interfaceAlias.x = 15;
5138 wire.aliases.push_back( std::move( interfaceAlias ) );
5139 ORCAD_ALIAS secondaryAlias;
5140 secondaryAlias.name = "UART0_CTS";
5141 secondaryAlias.x = 50;
5142 wire.aliases.push_back( std::move( secondaryAlias ) );
5143 page.wires.push_back( wire );
5144
5145 ORCAD_PLACED_INSTANCE connector;
5146 connector.reference = "J8";
5148 pin.wordA = wire.dbId;
5149 connector.pins.push_back( pin );
5150 page.instances.push_back( std::move( connector ) );
5151
5152 ORCAD_GRAPHIC_INST offpage;
5153 offpage.logicalName = "EBI_AD1/PTD5/FTM0_CH5";
5154 offpage.x = 100;
5155 ORCAD_DISPLAY_PROP displayedName;
5156 displayedName.name = "Name";
5157 displayedName.x = -85;
5158 offpage.displayProps.push_back( std::move( displayedName ) );
5159 page.offpage.push_back( std::move( offpage ) );
5160
5161 ORCAD_DESIGN design;
5162 design.sourceId = "occurrence-interface-alias";
5163 design.pages.push_back( std::move( page ) );
5164 design.occurrenceRoot.netNames[10] = "EBI_AD1/PTD5/FTM0_CH5";
5165
5166 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5167 SETTINGS_MANAGER manager;
5168 manager.LoadProject( "" );
5169 schematic->SetProject( &manager.Prj() );
5170 SCH_SHEET* root = convertRawDesign( design, *schematic );
5171 std::set<wxString> labels;
5172
5173 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
5174 labels.insert( static_cast<SCH_GLOBALLABEL*>( item )->GetText() );
5175
5176 BOOST_CHECK( labels.contains( wxS( "TWRPI_GPIO4" ) ) );
5177}
5178
5179
5180BOOST_AUTO_TEST_CASE( OffpageUsesVisibleNativeLabelWithVerticalWireOrientation )
5181{
5182 ORCAD_RAW_PAGE page;
5183 page.name = "VERTICAL OFFPAGE";
5184 page.netmap[1] = "VBUS_P_CTRL0_CON";
5185
5186 ORCAD_WIRE wire;
5187 wire.id = 1;
5188 wire.x1 = 100;
5189 wire.y1 = 50;
5190 wire.x2 = 100;
5191 wire.y2 = 100;
5192 page.wires.push_back( wire );
5193
5194 ORCAD_GRAPHIC_INST offpage;
5195 offpage.name = "OFFPAGELEFT-L";
5196 offpage.logicalName = "VBUS_P_CTRL0_CON";
5197 offpage.x = 100;
5198 offpage.y = 100;
5199 offpage.bbox = ORCAD_BBOX{ 90, 90, 110, 110 };
5200 offpage.displayProps.push_back( ORCAD_DISPLAY_PROP{
5201 .name = "Name", .x = 4, .y = 10, .rotation = 1, .fontIdx = 1, .dispMode = 0x101 } );
5202 page.offpage.push_back( std::move( offpage ) );
5203
5204 ORCAD_DESIGN design;
5205 design.sourceId = "vertical-offpage-display-name";
5206 design.library.fonts.push_back( ORCAD_FONT{ .height = -9, .face = "Arial" } );
5207 design.symbols.emplace( "OFFPAGELEFT-L", ORCAD_SYMBOL_DEF{ .name = "OFFPAGELEFT-L" } );
5208 design.pages.push_back( std::move( page ) );
5209
5210 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5211 SETTINGS_MANAGER manager;
5212 manager.LoadProject( "" );
5213 schematic->SetProject( &manager.Prj() );
5214 SCH_SHEET* root = convertRawDesign( design, *schematic );
5215
5216 const SCH_GLOBALLABEL* connector = nullptr;
5217
5218 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
5219 {
5220 const SCH_GLOBALLABEL* label = static_cast<const SCH_GLOBALLABEL*>( item );
5221
5222 if( label->GetText() == wxS( "VBUS_P_CTRL0_CON" )
5223 && ( label->GetTextColor() == KIGFX::COLOR4D::UNSPECIFIED || label->GetTextColor().a > 0 ) )
5224 connector = label;
5225 }
5226
5227 BOOST_REQUIRE( connector );
5228 BOOST_CHECK( connector->GetSpinStyle() == SPIN_STYLE::BOTTOM );
5229 BOOST_CHECK_EQUAL( connector->GetTextHeight(), 17000 );
5230
5231 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
5232 BOOST_CHECK( static_cast<const SCH_TEXT*>( item )->GetText() != wxS( "VBUS_P_CTRL0_CON" ) );
5233}
5234
5235
5236BOOST_AUTO_TEST_CASE( OffpageIntersheetReferencePreservesStoredDisplay )
5237{
5238 ORCAD_RAW_PAGE page;
5239 page.name = "INTERSHEET REFERENCE";
5240 page.netmap[1] = "PWR_LED_CTRL";
5241
5242 ORCAD_WIRE wire;
5243 wire.id = 1;
5244 wire.x1 = 50;
5245 wire.y1 = 100;
5246 wire.x2 = 100;
5247 wire.y2 = 100;
5248 page.wires.push_back( wire );
5249
5250 ORCAD_GRAPHIC_INST offpage;
5251 offpage.name = "OFFPAGELEFT-L";
5252 offpage.logicalName = "PWR_LED_CTRL";
5253 offpage.x = 100;
5254 offpage.y = 100;
5255 offpage.bbox = ORCAD_BBOX{ 90, 90, 110, 110 };
5256 offpage.props["IREF"] = "[5,20]";
5257 offpage.displayProps.push_back( ORCAD_DISPLAY_PROP{
5258 .name = "Name", .x = 4, .y = 5, .fontIdx = 1, .dispMode = 0x101 } );
5259 offpage.displayProps.push_back( ORCAD_DISPLAY_PROP{
5260 .name = "IREF", .x = 83, .y = 5, .fontIdx = 1, .dispMode = 0x101 } );
5261 page.offpage.push_back( std::move( offpage ) );
5262
5263 ORCAD_DESIGN design;
5264 design.sourceId = "offpage-intersheet-reference";
5265 design.library.fonts.push_back( ORCAD_FONT{ .height = -9, .face = "Arial" } );
5266 design.symbols.emplace( "OFFPAGELEFT-L", ORCAD_SYMBOL_DEF{ .name = "OFFPAGELEFT-L" } );
5267 design.pages.push_back( std::move( page ) );
5268
5269 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5270 SETTINGS_MANAGER manager;
5271 manager.LoadProject( "" );
5272 schematic->SetProject( &manager.Prj() );
5273 SCH_SHEET* root = convertRawDesign( design, *schematic );
5274
5275 const SCH_TEXT* intersheetRef = nullptr;
5276
5277 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
5278 {
5279 const SCH_TEXT* text = static_cast<const SCH_TEXT*>( item );
5280
5281 if( text->GetText() == wxS( "[5,20]" ) )
5282 intersheetRef = text;
5283 }
5284
5285 BOOST_REQUIRE( intersheetRef );
5286 BOOST_CHECK( intersheetRef->IsVisible() );
5287 BOOST_CHECK( intersheetRef->GetTextAngle() == ANGLE_0 );
5289 BOOST_CHECK( intersheetRef->GetTextColor() == OrcadColor( 8 ) );
5290 int baseX = std::min( design.pages[0].offpage[0].bbox.x1, design.pages[0].offpage[0].bbox.x2 );
5291 int baseY = std::min( design.pages[0].offpage[0].bbox.y1, design.pages[0].offpage[0].bbox.y2 );
5292 int baseline = OrcadTextBaselineOffset( intersheetRef->GetTextSize().y )
5293 + KiROUND( intersheetRef->GetTextSize().y * 7.0 / 21.0 );
5294 BOOST_CHECK_EQUAL( intersheetRef->GetPosition().x, OrcadDbuToIu( baseX + 83, 0 ).x );
5295 BOOST_CHECK_EQUAL( intersheetRef->GetPosition().y, OrcadDbuToIu( 0, baseY + 5 ).y + baseline );
5296}
5297
5298
5299BOOST_AUTO_TEST_CASE( HiddenOffpageIntersheetReferenceIsNotRendered )
5300{
5301 ORCAD_RAW_PAGE page;
5302 page.name = "HIDDEN INTERSHEET REFERENCE";
5303 page.netmap[1] = "SWDIO";
5304 page.wires.push_back( ORCAD_WIRE{ .id = 1, .x2 = 100, .y2 = 100 } );
5305
5306 ORCAD_GRAPHIC_INST offpage;
5307 offpage.name = "OFFPAGELEFT-L";
5308 offpage.logicalName = "SWDIO";
5309 offpage.x = 100;
5310 offpage.y = 100;
5311 offpage.bbox = ORCAD_BBOX{ 90, 90, 110, 110 };
5312 offpage.props["IREF"] = "0";
5313 offpage.displayProps.push_back( ORCAD_DISPLAY_PROP{
5314 .name = "Name", .x = 4, .y = 5, .fontIdx = 1, .dispMode = 0x101 } );
5315 offpage.displayProps.push_back( ORCAD_DISPLAY_PROP{
5316 .name = "IREF", .x = 20, .y = 30, .fontIdx = 1, .dispMode = 0x001 } );
5317 page.offpage.push_back( std::move( offpage ) );
5318
5319 ORCAD_DESIGN design;
5320 design.sourceId = "hidden-offpage-intersheet-reference";
5321 design.library.fonts.push_back( ORCAD_FONT{ .height = -9, .face = "Arial" } );
5322 design.symbols.emplace( "OFFPAGELEFT-L", ORCAD_SYMBOL_DEF{ .name = "OFFPAGELEFT-L" } );
5323 design.pages.push_back( std::move( page ) );
5324
5325 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5326 SETTINGS_MANAGER manager;
5327 manager.LoadProject( "" );
5328 schematic->SetProject( &manager.Prj() );
5329 SCH_SHEET* root = convertRawDesign( design, *schematic );
5330
5331 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
5332 BOOST_CHECK_NE( static_cast<const SCH_TEXT*>( item )->GetText(), wxS( "0" ) );
5333}
5334
5335
5336BOOST_AUTO_TEST_CASE( PortUsesVisibleNativeLabelWithoutDuplicateGraphics )
5337{
5338 ORCAD_SYMBOL_DEF definition;
5339 definition.typeId = ORCAD_ST_PORT_SYMBOL;
5340 definition.name = "Rudy-PortRight";
5341 definition.bbox = ORCAD_BBOX{ 0, 0, 70, 20 };
5342 definition.primitives = {
5343 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::LINE, .x1 = 0, .y1 = 10, .x2 = 10, .y2 = 0 },
5344 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::LINE, .x1 = 10, .y1 = 0, .x2 = 70, .y2 = 0 },
5345 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::LINE, .x1 = 70, .y1 = 0, .x2 = 70, .y2 = 20 },
5346 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::LINE, .x1 = 70, .y1 = 20, .x2 = 10, .y2 = 20 },
5347 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::LINE, .x1 = 10, .y1 = 20, .x2 = 0, .y2 = 10 },
5348 };
5349 definition.pins.push_back( ORCAD_SYMBOL_PIN{ .hotptX = 70, .hotptY = 10 } );
5350
5351 ORCAD_GRAPHIC_INST port;
5352 port.name = definition.name;
5353 port.logicalName = "*SHORT";
5354 port.bbox = ORCAD_BBOX{ 100, 100, 170, 120 };
5355 port.displayProps.push_back(
5356 ORCAD_DISPLAY_PROP{ .name = "Name", .x = 16, .y = 3, .fontIdx = 1, .dispMode = 0x101 } );
5357
5358 ORCAD_RAW_PAGE page;
5359 page.name = "SOURCE PORT GRAPHICS";
5360 page.width = 11000;
5361 page.height = 8500;
5362 page.ports.push_back( std::move( port ) );
5363 page.wires.push_back( ORCAD_WIRE{ .id = 1, .x1 = 170, .y1 = 110, .x2 = 220, .y2 = 110 } );
5364 page.netmap[1] = "*SHORT";
5365
5366 ORCAD_DESIGN design;
5367 design.sourceId = "source-port-graphics";
5368 design.library.fonts.push_back( ORCAD_FONT{ .height = -9, .face = "Arial" } );
5369 design.symbols.emplace( definition.name, std::move( definition ) );
5370 design.pages.push_back( std::move( page ) );
5371
5372 SETTINGS_MANAGER manager;
5373 manager.LoadProject( "" );
5374 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5375 schematic->SetProject( &manager.Prj() );
5376 SCH_SHEET* root = convertRawDesign( design, *schematic );
5377 const SCH_GLOBALLABEL* portLabel = nullptr;
5378
5379 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
5380 {
5381 const SCH_GLOBALLABEL* label = static_cast<const SCH_GLOBALLABEL*>( item );
5382
5383 if( label->GetText() == wxS( "*SHORT" ) )
5384 portLabel = label;
5385 }
5386
5387 BOOST_REQUIRE( portLabel );
5388 BOOST_CHECK_EQUAL( portLabel->GetTextHeight(), schIUScale.mmToIU( 1.70 ) );
5389 BOOST_CHECK( portLabel->GetTextColor() == KIGFX::COLOR4D::UNSPECIFIED
5390 || portLabel->GetTextColor().a > 0 );
5391 BOOST_CHECK( portLabel->GetPosition() == OrcadDbuToIu( 170, 110 ) );
5392 BOOST_CHECK( portLabel->GetSpinStyle() == SPIN_STYLE::LEFT );
5393 BOOST_CHECK( root->GetScreen()->Items().OfType( SCH_SHAPE_T ).empty() );
5394
5395 for( const SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_TEXT_T ) )
5396 BOOST_CHECK_NE( static_cast<const SCH_TEXT*>( item )->GetText(), wxString( "*SHORT" ) );
5397
5398}
5399
5400
5401BOOST_AUTO_TEST_CASE( OccurrenceNameCanonicalizesCrossPageInterfaceAliasGroup )
5402{
5403 ORCAD_RAW_PAGE sensePage;
5404 sensePage.name = "SENSE";
5405 sensePage.netmap[1] = "SENSE-";
5406 sensePage.netAliases[1] = { "SENSE-", "VOUT" };
5407 ORCAD_WIRE senseWire;
5408 senseWire.id = 1;
5409 senseWire.x2 = 100;
5410 sensePage.wires.push_back( senseWire );
5411 ORCAD_GRAPHIC_INST senseOffpage;
5412 senseOffpage.logicalName = "SENSE-";
5413 sensePage.offpage.push_back( std::move( senseOffpage ) );
5414
5415 ORCAD_RAW_PAGE isnPage;
5416 isnPage.name = "ISN";
5417 isnPage.netmap[2] = "ISN1_DCR";
5418 isnPage.netAliases[2] = { "ISN1_DCR", "VOUT" };
5419 ORCAD_WIRE isnWire;
5420 isnWire.id = 2;
5421 isnWire.x2 = 100;
5422 isnPage.wires.push_back( isnWire );
5423 ORCAD_GRAPHIC_INST isnOffpage;
5424 isnOffpage.logicalName = "ISN1_DCR";
5425 isnPage.offpage.push_back( std::move( isnOffpage ) );
5426
5427 ORCAD_DESIGN design;
5428 design.sourceId = "occurrence-cross-page-interface-alias";
5429 design.pages.push_back( std::move( sensePage ) );
5430 design.pages.push_back( std::move( isnPage ) );
5431 design.occurrenceRoot.netNames[10] = "SENSE-";
5432
5433 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5434 SETTINGS_MANAGER manager;
5435 manager.LoadProject( "" );
5436 schematic->SetProject( &manager.Prj() );
5437 convertRawDesign( design, *schematic );
5438 std::set<wxString> labels;
5439
5440 for( const SCH_SHEET_PATH& path : schematic->Hierarchy() )
5441 {
5442 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
5443 labels.insert( static_cast<SCH_GLOBALLABEL*>( item )->GetText() );
5444 }
5445
5446 BOOST_CHECK( labels.contains( wxS( "SENSE-" ) ) );
5447 BOOST_CHECK( !labels.contains( wxS( "ISN1_DCR" ) ) );
5448}
5449
5450
5451BOOST_AUTO_TEST_CASE( DistinctOccurrenceInterfaceNamesRemainSeparate )
5452{
5453 ORCAD_RAW_PAGE page;
5454 page.name = "DISTINCT INTERFACES";
5455 page.netmap[1] = "PCIE_USIM_DATA";
5456 page.netAliases[1] = { "PCIE_USIM_DATA", "USIM_DATA" };
5457 ORCAD_WIRE wire;
5458 wire.id = 1;
5459 wire.x2 = 100;
5460 page.wires.push_back( wire );
5461
5462 ORCAD_GRAPHIC_INST pcieOffpage;
5463 pcieOffpage.logicalName = "PCIE_USIM_DATA";
5464 page.offpage.push_back( std::move( pcieOffpage ) );
5465 ORCAD_GRAPHIC_INST usimOffpage;
5466 usimOffpage.logicalName = "USIM_DATA";
5467 page.offpage.push_back( std::move( usimOffpage ) );
5468
5469 ORCAD_DESIGN design;
5470 design.sourceId = "distinct-occurrence-interface-names";
5471 design.pages.push_back( std::move( page ) );
5472 design.occurrenceRoot.netNames[10] = "PCIE_USIM_DATA";
5473 design.occurrenceRoot.netNames[11] = "USIM_DATA";
5474
5475 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5476 SETTINGS_MANAGER manager;
5477 manager.LoadProject( "" );
5478 schematic->SetProject( &manager.Prj() );
5479 SCH_SHEET* root = convertRawDesign( design, *schematic );
5480 std::set<wxString> labels;
5481
5482 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
5483 labels.insert( static_cast<SCH_GLOBALLABEL*>( item )->GetText() );
5484
5485 BOOST_CHECK( labels.contains( wxS( "PCIE_USIM_DATA" ) ) );
5486 BOOST_CHECK( labels.contains( wxS( "USIM_DATA" ) ) );
5487}
5488
5489
5490BOOST_AUTO_TEST_CASE( OccurrenceWireNameOverridesOffpageDisplayName )
5491{
5492 ORCAD_RAW_PAGE page;
5493 page.name = "OCCURRENCE OFFPAGE";
5494 page.netmap[1] = "VOUT";
5495 page.netAliases[1] = { "VOUT" };
5496 ORCAD_WIRE wire;
5497 wire.dbId = 22608001;
5498 wire.id = 1;
5499 wire.x2 = 100;
5500 page.wires.push_back( wire );
5501 ORCAD_GRAPHIC_INST offpage;
5502 offpage.logicalName = "VOUT";
5503 offpage.x = 100;
5504 page.offpage.push_back( std::move( offpage ) );
5505
5506 ORCAD_DESIGN design;
5507 design.sourceId = "occurrence-wire-offpage-name";
5508 design.pages.push_back( std::move( page ) );
5509 design.occurrenceRoot.netNames[10] = "VOUT_22608001";
5510
5511 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5512 SETTINGS_MANAGER manager;
5513 manager.LoadProject( "" );
5514 schematic->SetProject( &manager.Prj() );
5515 SCH_SHEET* root = convertRawDesign( design, *schematic );
5516 std::set<wxString> labels;
5517
5518 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
5519 labels.insert( static_cast<SCH_GLOBALLABEL*>( item )->GetText() );
5520
5521 BOOST_CHECK( labels.contains( wxS( "VOUT_22608001" ) ) );
5522 BOOST_CHECK( !labels.contains( wxS( "VOUT" ) ) );
5523}
5524
5525
5526BOOST_AUTO_TEST_CASE( OccurrencePowerNameDoesNotGlobalizeUnconnectedWire )
5527{
5528 ORCAD_SYMBOL_DEF power;
5530 power.name = "VCC_BAR";
5531 power.pins.push_back( ORCAD_SYMBOL_PIN() );
5532
5533 ORCAD_GRAPHIC_INST global;
5534 global.typeId = ORCAD_ST_GLOBAL;
5535 global.name = power.name;
5536 global.logicalName = "VDD";
5537 global.x = 1000;
5538
5539 ORCAD_RAW_PAGE wirePage;
5540 wirePage.name = "OCCURRENCE POWER WIRE";
5541 wirePage.netmap[1] = "VDD";
5542 ORCAD_WIRE wire;
5543 wire.id = 1;
5544 wire.x2 = 100;
5545 wirePage.wires.push_back( wire );
5546
5547 ORCAD_RAW_PAGE globalPage;
5548 globalPage.name = "OCCURRENCE POWER SYMBOL";
5549 globalPage.globals.push_back( std::move( global ) );
5550
5551 ORCAD_DESIGN design;
5552 design.sourceId = "occurrence-power-global-wire";
5553 design.symbols.emplace( power.name, std::move( power ) );
5554 design.pages.push_back( std::move( wirePage ) );
5555 design.pages.push_back( std::move( globalPage ) );
5556 design.occurrenceRoot.netNames[10] = "VDD";
5557
5558 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5559 SETTINGS_MANAGER manager;
5560 manager.LoadProject( "" );
5561 schematic->SetProject( &manager.Prj() );
5562 convertRawDesign( design, *schematic );
5563 wxString wireNet;
5564 wxString powerNet;
5565
5566 for( const SCH_SHEET_PATH& path : schematic->Hierarchy() )
5567 {
5568 for( SCH_ITEM* item : path.LastScreen()->Items() )
5569 {
5570 BOOST_CHECK( item->Type() != SCH_GLOBAL_LABEL_T );
5571
5572 if( item->Type() == SCH_LINE_T && item->GetLayer() == LAYER_WIRE )
5573 wireNet = item->Connection( &path )->Name();
5574
5575 if( item->Type() == SCH_SYMBOL_T )
5576 {
5577 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
5578 BOOST_REQUIRE_EQUAL( symbol->GetPins( &path ).size(), 1 );
5579 powerNet = symbol->GetPins( &path ).front()->Connection( &path )->Name();
5580 }
5581 }
5582 }
5583
5584 BOOST_CHECK_EQUAL( wireNet.AfterLast( '/' ), wxString( "VDD" ) );
5585 BOOST_CHECK_EQUAL( powerNet, wxString( "VDD" ) );
5586 BOOST_CHECK_NE( wireNet, powerNet );
5587}
5588
5589
5590BOOST_AUTO_TEST_CASE( PowerSymbolDisplayNameDoesNotOverrideLogicalNet )
5591{
5592 ORCAD_SYMBOL_DEF power;
5594 power.name = "VCC_CIRCLE";
5595 power.bbox = ORCAD_BBOX{ 0, 0, 30, 10 };
5596 ORCAD_SYMBOL_PIN powerPin;
5597 powerPin.hotptX = 20;
5598 power.pins.push_back( powerPin );
5599
5600 ORCAD_GRAPHIC_INST global;
5601 global.typeId = ORCAD_ST_GLOBAL;
5602 global.name = power.name;
5603 global.logicalName = "0";
5604 global.props["Name"] = "DISPLAY_ONLY";
5605 global.x = 950;
5606 global.y = 320;
5607 global.bbox = ORCAD_BBOX{ 950, 320, 980, 330 };
5608 global.displayProps.push_back( ORCAD_DISPLAY_PROP{ 0, "NODENAME", -45, 4, 0, 0, 0, 0x101 } );
5609
5610 ORCAD_GRAPHIC_INST aliasedGlobal;
5611 aliasedGlobal.typeId = ORCAD_ST_GLOBAL;
5612 aliasedGlobal.name = power.name;
5613 aliasedGlobal.logicalName = "VDD3";
5614 aliasedGlobal.x = 1090;
5615 aliasedGlobal.y = 320;
5616 aliasedGlobal.bbox = ORCAD_BBOX{ 1090, 320, 1120, 330 };
5617
5618 ORCAD_RAW_PAGE page;
5619 page.name = "POWER";
5620 ORCAD_WIRE wire;
5621 wire.id = 1;
5622 wire.x1 = 960;
5623 wire.y1 = 270;
5624 wire.x2 = 960;
5625 wire.y2 = 250;
5626 page.wires.push_back( wire );
5627 page.netmap.emplace( wire.id, "0" );
5628 ORCAD_WIRE aliasedWire;
5629 aliasedWire.id = 2;
5630 aliasedWire.x1 = 1110;
5631 aliasedWire.y1 = 320;
5632 aliasedWire.x2 = 1110;
5633 aliasedWire.y2 = 300;
5634 page.wires.push_back( aliasedWire );
5635 page.netmap.emplace( aliasedWire.id, "0" );
5636 page.netAliases[aliasedWire.id] = { "0", "VDD3" };
5637 page.globals.push_back( std::move( global ) );
5638 page.globals.push_back( std::move( aliasedGlobal ) );
5639
5640 ORCAD_DESIGN design;
5641 design.sourceId = "power-display-name";
5642 design.symbols.emplace( power.name, std::move( power ) );
5643 design.pages.push_back( std::move( page ) );
5644
5645 SETTINGS_MANAGER manager;
5646 manager.LoadProject( "" );
5647 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5648 schematic->SetProject( &manager.Prj() );
5649 SCH_SHEET* root = convertRawDesign( design, *schematic );
5651 path.push_back( root );
5652
5653 SCH_SYMBOL* symbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "#PWR0001" ) );
5654 BOOST_REQUIRE( symbol );
5655 BOOST_CHECK_EQUAL( symbol->GetValue( &path, RAW_VALUE ), wxS( "0" ) );
5656 BOOST_REQUIRE_EQUAL( symbol->GetPins().size(), 1u );
5657 BOOST_CHECK_EQUAL( symbol->GetPins().front()->GetName(), wxS( "0" ) );
5658 BOOST_CHECK( symbol->GetPins().front()->GetPosition() == OrcadDbuToIu( 960, 270 ) );
5659 SCH_FIELD* value = symbol->GetField( FIELD_T::VALUE );
5660 BOOST_REQUIRE( value );
5661 BOOST_CHECK( !value->IsVisible() );
5662 SCH_FIELD* displayName = symbol->GetField( wxS( "NODENAME" ) );
5663 BOOST_REQUIRE( displayName );
5664 BOOST_CHECK_EQUAL( displayName->GetText(), wxS( "DISPLAY_ONLY" ) );
5665 BOOST_CHECK( displayName->IsVisible() );
5666 BOOST_CHECK( displayName->GetPosition()
5667 == OrcadDbuToIu( 950 - 45, 320 + 4 )
5668 + VECTOR2I( 0, OrcadTextBaselineOffset( displayName->GetTextHeight() ) ) );
5669
5670 SCH_SYMBOL* aliased = nullptr;
5671
5672 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
5673 {
5674 SCH_SYMBOL* candidate = static_cast<SCH_SYMBOL*>( item );
5675
5676 if( !candidate->GetPins().empty() && candidate->GetPins().front()->GetPosition() == OrcadDbuToIu( 1110, 320 ) )
5677 {
5678 aliased = candidate;
5679 break;
5680 }
5681 }
5682
5683 BOOST_REQUIRE( aliased );
5684 BOOST_REQUIRE_EQUAL( aliased->GetPins().size(), 1u );
5685 BOOST_CHECK_EQUAL( aliased->GetValue( &path, RAW_VALUE ), wxS( "VDD3" ) );
5686 BOOST_CHECK_EQUAL( aliased->GetPins().front()->GetName(), wxS( "VDD3" ) );
5687
5688 schematic->ConnectionGraph()->Recalculate( schematic->BuildSheetListSortedByPageNumbers(), true );
5689
5690 BOOST_REQUIRE( symbol->GetPins().front()->Connection( &path ) );
5691 BOOST_REQUIRE( aliased->GetPins().front()->Connection( &path ) );
5692 BOOST_CHECK_NE( symbol->GetPins().front()->Connection( &path )->Name(),
5693 aliased->GetPins().front()->Connection( &path )->Name() );
5694}
5695
5696
5697BOOST_AUTO_TEST_CASE( ExplicitPowerStylePinUsesSourceNetInsteadOfImplicitGlobalName )
5698{
5699 for( int pinLength : { 10, 0 } )
5700 {
5701 ORCAD_SYMBOL_DEF part;
5703 part.name = "LOCAL_POWER.Normal";
5704 part.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
5705 part.pins.push_back( ORCAD_SYMBOL_PIN{ .name = "V-",
5706 .position = 0,
5707 .startX = pinLength,
5708 .hotptX = 0,
5709 .portType = ORCAD_PORT_TYPE::POWER_IN,
5710 .shapeBits = 0x80 } );
5711
5712 ORCAD_PLACED_INSTANCE placed;
5713 placed.pkgName = part.name;
5714 placed.reference = "U1";
5715 placed.x = 100;
5716 placed.y = 100;
5717 placed.pins.push_back( ORCAD_PIN_INST{ .pinIndex = 0,
5718 .x = 100,
5719 .y = 100,
5720 .wordA = std::numeric_limits<uint32_t>::max(),
5721 .wordB = 1 } );
5722
5723 ORCAD_RAW_PAGE page;
5724 page.name = "EXPLICIT HIDDEN POWER PIN";
5725 page.netmap[1] = "GND";
5726 page.instances.push_back( std::move( placed ) );
5727
5728 ORCAD_DESIGN design;
5729 design.sourceId = "explicit-power-style-pin";
5730 design.symbols.emplace( part.name, std::move( part ) );
5731 design.pages.push_back( std::move( page ) );
5732
5733 SETTINGS_MANAGER manager;
5734 manager.LoadProject( "" );
5735 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5736 schematic->SetProject( &manager.Prj() );
5737 SCH_SHEET* root = convertRawDesign( design, *schematic );
5739 path.push_back( root );
5740
5741 SCH_SYMBOL* symbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "U1" ) );
5742 BOOST_REQUIRE( symbol );
5743 BOOST_REQUIRE_EQUAL( symbol->GetPins().size(), 1u );
5744 BOOST_CHECK_EQUAL( symbol->GetPins().front()->IsVisible(), pinLength != 0 );
5745 BOOST_CHECK( !symbol->GetPins().front()->IsGlobalPower() );
5746 BOOST_CHECK_EQUAL( symbol->GetPins().front()->GetName(), wxString( "V-" ) );
5747 BOOST_CHECK( symbol->GetPins().front()->GetType()
5749
5750 schematic->ConnectionGraph()->Recalculate( schematic->BuildSheetListSortedByPageNumbers(), true );
5751 BOOST_REQUIRE( symbol->GetPins().front()->Connection( &path ) );
5752 BOOST_CHECK_EQUAL( symbol->GetPins().front()->Connection( &path )->Name(), wxS( "/GND" ) );
5753 }
5754}
5755
5756
5757BOOST_AUTO_TEST_CASE( NativeHiddenPowerKeepsMultiunitPackageIdentity )
5758{
5759 for( int scenario : { 0, 1, 2, 3 } )
5760 {
5761 bool powerUnitFirst = ( scenario & 1 ) == 0;
5762 bool separatePages = ( scenario & 2 ) != 0;
5763 ORCAD_SYMBOL_DEF power;
5765 power.name = "DUALA.Normal";
5766 power.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
5767 power.pins.push_back( ORCAD_SYMBOL_PIN{ .name = "$$$1",
5768 .position = 0,
5769 .portType = ORCAD_PORT_TYPE::POWER_IN,
5770 .shapeBits = 0x80 } );
5771
5772 ORCAD_SYMBOL_DEF signal = power;
5773 signal.name = "DUALB.Normal";
5774 signal.pins.front().name = "SIGNAL";
5775 signal.pins.front().startX = 10;
5776 signal.pins.front().portType = ORCAD_PORT_TYPE::PASSIVE;
5777 signal.pins.front().shapeBits = 0;
5778
5779 ORCAD_PACKAGE package;
5780 package.name = "DUAL";
5781 package.devices.push_back( ORCAD_DEVICE{ .unitRef = "A", .pinNumbers = { "1" } } );
5782 package.devices.push_back( ORCAD_DEVICE{ .unitRef = "B", .pinNumbers = { "2" } } );
5783
5784 ORCAD_RAW_PAGE page;
5785 page.name = "HIDDEN POWER MULTIUNIT";
5786 page.netmap[1] = "$$$1";
5787 page.netmap[2] = "OVERRIDE";
5788
5789 for( int part : { 1, 2, 3 } )
5790 {
5791 for( int unit : { 0, 1 } )
5792 {
5793 if( part == 3 && unit == 1 )
5794 continue;
5795
5796 ORCAD_PLACED_INSTANCE instance;
5797 instance.pkgName = unit ? signal.name : power.name;
5798 instance.sourcePackage = package.name;
5799 instance.reference = "U" + std::to_string( part );
5800 instance.unitIndex = unit;
5801 instance.x = part * 100;
5802 instance.y = ( unit + 1 ) * 100;
5803 instance.pins.push_back( ORCAD_PIN_INST{ .pinIndex = 1,
5804 .x = instance.x,
5805 .y = instance.y,
5806 .wordB = unit ? 0u : static_cast<uint32_t>( part == 2 ? 2 : 1 ) } );
5807 page.instances.push_back( std::move( instance ) );
5808 }
5809 }
5810
5811 if( !powerUnitFirst )
5812 std::reverse( page.instances.begin(), page.instances.end() );
5813
5814 ORCAD_DESIGN design;
5815 design.sourceId = "native-hidden-power-multiunit";
5816 design.symbols.emplace( power.name, std::move( power ) );
5817 design.symbols.emplace( signal.name, std::move( signal ) );
5818 design.packages.emplace( package.name, std::move( package ) );
5819 design.pages.push_back( std::move( page ) );
5820
5821 if( separatePages )
5822 {
5823 ORCAD_RAW_PAGE second;
5824 second.name = "SIGNAL UNITS";
5825 auto& first = design.pages.front().instances;
5826
5827 for( auto& instance : first )
5828 {
5829 if( instance.unitIndex == 1 )
5830 second.instances.push_back( std::move( instance ) );
5831 }
5832
5833 std::erase_if( first, []( const auto& aInstance ) { return aInstance.unitIndex == 1; } );
5834 design.pages.push_back( std::move( second ) );
5835 }
5836
5837 SETTINGS_MANAGER manager;
5838 manager.LoadProject( "" );
5839 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5840 schematic->SetProject( &manager.Prj() );
5841 SCH_SHEET* root = convertRawDesign( design, *schematic );
5842 auto checkUnits = [&]()
5843 {
5844 SCH_SHEET_LIST paths = schematic->BuildSheetListSortedByPageNumbers();
5845 schematic->ConnectionGraph()->Recalculate( paths, true );
5846 std::map<wxString, std::vector<SCH_SYMBOL*>> parts;
5847 std::map<SCH_SYMBOL*, SCH_SHEET_PATH> symbolPaths;
5848
5849 for( const SCH_SHEET_PATH& path : paths )
5850 {
5851 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
5852 {
5853 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
5854 parts[symbol->GetRef( &path, false )].push_back( symbol );
5855 symbolPaths.emplace( symbol, path );
5856 }
5857 }
5858
5859 BOOST_REQUIRE_EQUAL( parts.size(), 3u );
5860
5861 for( const auto& [reference, units] : parts )
5862 {
5863 BOOST_REQUIRE_EQUAL( units.size(), reference == wxS( "U3" ) ? 1u : 2u );
5864
5865 if( units.size() == 2 )
5866 {
5867 BOOST_CHECK( units[0]->GetLibId() == units[1]->GetLibId() );
5868 BOOST_CHECK( units[0]->GetUnit() != units[1]->GetUnit() );
5869 }
5870
5871 for( SCH_SYMBOL* symbol : units )
5872 {
5873 const SCH_SHEET_PATH& path = symbolPaths.at( symbol );
5874 BOOST_REQUIRE( symbol->GetLibSymbolRef() );
5875 BOOST_CHECK_EQUAL( symbol->GetLibSymbolRef()->GetUnitCount(), 2 );
5876
5877 for( SCH_PIN* pin : symbol->GetPins() )
5878 {
5879 if( pin->GetNumber() == wxS( "1" ) )
5880 {
5881 BOOST_CHECK( !pin->IsVisible() );
5882 BOOST_CHECK_EQUAL( pin->IsGlobalPower(), reference != wxS( "U2" ) );
5883 BOOST_REQUIRE( pin->Connection( &path ) );
5884 BOOST_CHECK_EQUAL( pin->Connection( &path )->Name( true ),
5885 reference != wxS( "U2" ) ? wxString( "$$$1" )
5886 : wxString( "OVERRIDE" ) );
5887 }
5888 }
5889 }
5890 }
5891 };
5892
5893 checkUnits();
5895 std::vector<wxString> files;
5896 std::vector<std::pair<KIID, wxString>> sheetIdentities;
5897
5898 for( SCH_SHEET* sheet : schematic->GetTopLevelSheets() )
5899 {
5900 wxString file = wxFileName::CreateTempFileName( wxS( "orcad_native_power_" ) );
5901 BOOST_REQUIRE_NO_THROW( io.SaveSchematicFile( file, sheet, schematic.get() ) );
5902 files.push_back( file );
5903 sheetIdentities.emplace_back( sheet->m_Uuid, sheet->GetName() );
5904 }
5905
5906 schematic->Reset();
5907 std::vector<SCH_SHEET*> reloaded;
5908
5909 for( const wxString& file : files )
5910 {
5911 BOOST_REQUIRE_NO_THROW( root = io.LoadSchematicFile( file, schematic.get() ) );
5912 BOOST_REQUIRE( root );
5913 const auto& [uuid, name] = sheetIdentities[reloaded.size()];
5914 const_cast<KIID&>( root->m_Uuid ) = uuid;
5915 root->SetName( name );
5916 reloaded.push_back( root );
5917 }
5918
5919 schematic->SetTopLevelSheets( reloaded );
5920 schematic->RefreshHierarchy();
5921
5922 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
5923 path.LastScreen()->UpdateLocalLibSymbolLinks();
5924
5925 checkUnits();
5926
5927 for( const wxString& file : files )
5928 wxRemoveFile( file );
5929 }
5930}
5931
5932
5933BOOST_AUTO_TEST_CASE( PowerSymbolsSharingNetKeepSourceGraphicIdentity )
5934{
5935 ORCAD_SYMBOL_DEF ground;
5937 ground.name = "GND_POWER";
5938 ground.bbox = ORCAD_BBOX{ 0, 0, 30, 10 };
5939 ORCAD_SYMBOL_PIN groundPin;
5940 groundPin.hotptX = 20;
5941 ground.pins.push_back( groundPin );
5942
5943 ORCAD_SYMBOL_DEF supply = ground;
5944 supply.name = "VCC_CIRCLE";
5945
5946 ORCAD_GRAPHIC_INST groundGlobal;
5947 groundGlobal.typeId = ORCAD_ST_GLOBAL;
5948 groundGlobal.name = ground.name;
5949 groundGlobal.logicalName = "0";
5950 groundGlobal.bbox = ORCAD_BBOX{ 940, 270, 970, 280 };
5951
5952 ORCAD_GRAPHIC_INST supplyGlobal;
5953 supplyGlobal.typeId = ORCAD_ST_GLOBAL;
5954 supplyGlobal.name = supply.name;
5955 supplyGlobal.logicalName = "VDD3";
5956 supplyGlobal.bbox = ORCAD_BBOX{ 1090, 320, 1120, 330 };
5957
5958 ORCAD_RAW_PAGE page;
5959 page.name = "POWER GRAPHICS";
5960 ORCAD_WIRE groundWire;
5961 groundWire.id = 1;
5962 groundWire.x1 = 960;
5963 groundWire.y1 = 270;
5964 groundWire.x2 = 960;
5965 groundWire.y2 = 250;
5966 page.wires.push_back( groundWire );
5967 ORCAD_WIRE supplyWire;
5968 supplyWire.id = 2;
5969 supplyWire.x1 = 1110;
5970 supplyWire.y1 = 320;
5971 supplyWire.x2 = 1110;
5972 supplyWire.y2 = 300;
5973 page.wires.push_back( supplyWire );
5974 page.netmap.emplace( 1, "0" );
5975 page.netmap.emplace( 2, "0" );
5976 page.netAliases[2] = { "0", "VDD3" };
5977 page.globals.push_back( std::move( groundGlobal ) );
5978 page.globals.push_back( std::move( supplyGlobal ) );
5979
5980 ORCAD_DESIGN design;
5981 design.sourceId = "power-graphic-identity";
5982 design.symbols.emplace( ground.name, std::move( ground ) );
5983 design.symbols.emplace( supply.name, std::move( supply ) );
5984 design.pages.push_back( std::move( page ) );
5985 design.occurrenceRoot.netNames[2] = "0";
5986
5987 SETTINGS_MANAGER manager;
5988 manager.LoadProject( "" );
5989 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
5990 schematic->SetProject( &manager.Prj() );
5991 SCH_SHEET* root = convertRawDesign( design, *schematic );
5993 path.push_back( root );
5994
5995 SCH_SYMBOL* groundSymbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "#PWR0001" ) );
5996 SCH_SYMBOL* supplySymbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "#PWR0002" ) );
5997 BOOST_REQUIRE( groundSymbol );
5998 BOOST_REQUIRE( supplySymbol );
5999 BOOST_CHECK( groundSymbol->GetLibId() != supplySymbol->GetLibId() );
6000 BOOST_REQUIRE_EQUAL( groundSymbol->GetPins().size(), 1u );
6001 BOOST_REQUIRE_EQUAL( supplySymbol->GetPins().size(), 1u );
6002 BOOST_CHECK_EQUAL( groundSymbol->GetPins().front()->GetName(), wxS( "0" ) );
6003 BOOST_CHECK_EQUAL( supplySymbol->GetPins().front()->GetName(), wxS( "0" ) );
6004 BOOST_CHECK_EQUAL( supplySymbol->GetValue( &path, RAW_VALUE ), wxS( "0" ) );
6005}
6006
6007
6008static std::filesystem::path findCorpusDesign( const std::filesystem::path& aRoot, const std::string& aFileName );
6009
6010
6011BOOST_AUTO_TEST_CASE( OccurrencePowerAliasChainUsesAuthoritativeName )
6012{
6013 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
6014
6015 if( !corpusEnv || !*corpusEnv )
6016 return;
6017
6018 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2331A-4.DSN" );
6019
6020 if( dsn.empty() )
6021 return;
6022
6023 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6024 SETTINGS_MANAGER manager;
6025 manager.LoadProject( "" );
6026 schematic->SetProject( &manager.Prj() );
6027 schematic->CurrentSheet().clear();
6028 schematic->CurrentSheet().push_back( &schematic->Root() );
6029
6030 SCH_IO_ORCAD plugin;
6031 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
6032
6033 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
6034 schematic->ConnectionGraph()->Recalculate( sheets, true );
6035 wxString c1Pin1Net;
6036
6037 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
6038 {
6039 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
6040 {
6041 for( SCH_ITEM* item : subgraph->GetItems() )
6042 {
6043 if( item->Type() != SCH_PIN_T )
6044 continue;
6045
6046 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
6047 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
6048
6049 if( symbol && symbol->GetRef( &subgraph->GetSheet(), false ) == wxS( "C1" )
6050 && pin->GetNumber() == wxS( "1" ) )
6051 {
6052 c1Pin1Net = key.Name;
6053 }
6054 }
6055 }
6056 }
6057
6058 BOOST_CHECK_EQUAL( c1Pin1Net.AfterLast( '/' ), wxS( "GND" ) );
6059}
6060
6061
6062BOOST_AUTO_TEST_CASE( OccurrencePowerAliasJoinsDistinctChildNetNames )
6063{
6064 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
6065
6066 if( !corpusEnv || !*corpusEnv )
6067 return;
6068
6069 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "M5275EVB.DSN" );
6070
6071 if( dsn.empty() )
6072 return;
6073
6074 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6075 SETTINGS_MANAGER manager;
6076 manager.LoadProject( "" );
6077 schematic->SetProject( &manager.Prj() );
6078 schematic->CurrentSheet().clear();
6079 schematic->CurrentSheet().push_back( &schematic->Root() );
6080
6081 SCH_IO_ORCAD plugin;
6082 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
6083
6084 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
6085 schematic->ConnectionGraph()->Recalculate( sheets, true );
6086 std::map<std::pair<wxString, wxString>, CONNECTION_SUBGRAPH*> pinNets;
6087
6088 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
6089 {
6090 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
6091 {
6092 if( !subgraph->GetSheet().LastScreen()->GetFileName().Contains( wxS( "_PSU" ) ) )
6093 continue;
6094
6095 for( SCH_ITEM* item : subgraph->GetItems() )
6096 {
6097 if( item->Type() != SCH_PIN_T )
6098 continue;
6099
6100 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
6101 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
6102
6103 if( symbol )
6104 pinNets[{ symbol->GetRef( &subgraph->GetSheet(), false ), pin->GetNumber() }] = subgraph;
6105 }
6106 }
6107 }
6108
6109 BOOST_REQUIRE( pinNets.count( { wxS( "C126" ), wxS( "1" ) } ) );
6110 BOOST_REQUIRE( pinNets.count( { wxS( "U15" ), wxS( "3" ) } ) );
6111 BOOST_REQUIRE( pinNets.count( { wxS( "U15" ), wxS( "8" ) } ) );
6112 BOOST_CHECK_EQUAL( pinNets.at( { wxS( "C126" ), wxS( "1" ) } ),
6113 pinNets.at( { wxS( "U15" ), wxS( "3" ) } ) );
6114 BOOST_CHECK_EQUAL( pinNets.at( { wxS( "U15" ), wxS( "8" ) } ),
6115 pinNets.at( { wxS( "U15" ), wxS( "3" ) } ) );
6116}
6117
6118
6119BOOST_AUTO_TEST_CASE( LegacyGraphicBoundingBoxUsesDefinitionExtent )
6120{
6121 ORCAD_SYMBOL_DEF power;
6123 power.name = "VCC_BAR";
6124 power.bbox = ORCAD_BBOX{ 0, 0, 80, 70 };
6125 ORCAD_SYMBOL_PIN powerPin;
6126 powerPin.hotptX = 30;
6127 powerPin.hotptY = 20;
6128 power.pins.push_back( powerPin );
6129
6130 ORCAD_GRAPHIC_INST global;
6131 global.typeId = ORCAD_ST_GLOBAL;
6132 global.name = power.name;
6133 global.logicalName = "12V";
6134 global.x = 60;
6135 global.y = 60;
6136 global.bbox = ORCAD_BBOX{ 1040, 580, 80, 70 };
6137
6138 ORCAD_RAW_PAGE page;
6139 page.name = "LEGACY POWER";
6140 ORCAD_WIRE wire;
6141 wire.id = 1;
6142 wire.x1 = 990;
6143 wire.y1 = 530;
6144 wire.x2 = 990;
6145 wire.y2 = 550;
6146 page.wires.push_back( wire );
6147 page.netmap.emplace( wire.id, "12V" );
6148 page.globals.push_back( std::move( global ) );
6149
6150 ORCAD_DESIGN design;
6151 design.sourceId = "legacy-power-bbox";
6152 design.symbols.emplace( power.name, std::move( power ) );
6153 design.pages.push_back( std::move( page ) );
6154
6155 SETTINGS_MANAGER manager;
6156 manager.LoadProject( "" );
6157 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6158 schematic->SetProject( &manager.Prj() );
6159 SCH_SHEET* root = convertRawDesign( design, *schematic );
6161 path.push_back( root );
6162
6163 SCH_SYMBOL* symbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "#PWR0001" ) );
6164 BOOST_REQUIRE( symbol );
6165 BOOST_REQUIRE_EQUAL( symbol->GetPins().size(), 1u );
6166 VECTOR2I expected = OrcadDbuToIu( 990, 530 );
6167 BOOST_CHECK_EQUAL( symbol->GetPins().front()->GetPosition().x, expected.x );
6168 BOOST_CHECK_EQUAL( symbol->GetPins().front()->GetPosition().y, expected.y );
6169}
6170
6171
6172BOOST_AUTO_TEST_CASE( NamedPageNetOverridesPowerLogicalName )
6173{
6174 ORCAD_SYMBOL_DEF power;
6176 power.name = "GND_POWER";
6177 power.bbox = ORCAD_BBOX{ 0, 0, 30, 10 };
6178 ORCAD_SYMBOL_PIN powerPin;
6179 powerPin.hotptX = 20;
6180 power.pins.push_back( powerPin );
6181
6182 ORCAD_GRAPHIC_INST global;
6183 global.typeId = ORCAD_ST_GLOBAL;
6184 global.name = power.name;
6185 global.logicalName = "GND";
6186 global.x = 940;
6187 global.y = 270;
6188 global.bbox = ORCAD_BBOX{ 940, 270, 970, 280 };
6189 ORCAD_RAW_PAGE page;
6190 page.name = "POWER ALIAS";
6191 ORCAD_WIRE wire;
6192 wire.id = 1;
6193 wire.x1 = 960;
6194 wire.y1 = 270;
6195 wire.x2 = 960;
6196 wire.y2 = 250;
6197 page.wires.push_back( wire );
6198 page.netmap.emplace( wire.id, "GND" );
6199 page.netAliases[wire.id] = { "GND", "VPORTN" };
6200 page.globals.push_back( std::move( global ) );
6201
6202 ORCAD_DESIGN design;
6203 design.sourceId = "power-page-net";
6204 design.symbols.emplace( power.name, std::move( power ) );
6205 design.pages.push_back( std::move( page ) );
6206 design.occurrenceRoot.netNames[2] = "VPORTN";
6207
6208 SETTINGS_MANAGER manager;
6209 manager.LoadProject( "" );
6210 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6211 schematic->SetProject( &manager.Prj() );
6212 SCH_SHEET* root = convertRawDesign( design, *schematic );
6214 path.push_back( root );
6215
6216 SCH_SYMBOL* symbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "#PWR0001" ) );
6217 BOOST_REQUIRE( symbol );
6218 BOOST_REQUIRE_EQUAL( symbol->GetPins().size(), 1u );
6219 BOOST_CHECK_EQUAL( symbol->GetPins().front()->GetName(), wxS( "VPORTN" ) );
6220}
6221
6222
6223BOOST_AUTO_TEST_CASE( GlobalConnectorDoesNotPromoteWireAlias )
6224{
6225 ORCAD_SYMBOL_DEF power;
6227 power.name = "VDD_POWER";
6228 power.bbox = ORCAD_BBOX{ 0, 0, 30, 10 };
6229 ORCAD_SYMBOL_PIN powerPin;
6230 powerPin.hotptX = 20;
6231 power.pins.push_back( powerPin );
6232
6233 ORCAD_GRAPHIC_INST global;
6234 global.typeId = ORCAD_ST_GLOBAL;
6235 global.dbId = 1;
6236 global.name = power.name;
6237 global.logicalName = "VDD";
6238 global.x = 940;
6239 global.y = 270;
6240 global.bbox = ORCAD_BBOX{ 940, 270, 970, 280 };
6241 ORCAD_GRAPHIC_INST duplicateGlobal;
6242 duplicateGlobal.typeId = global.typeId;
6243 duplicateGlobal.dbId = 2;
6244 duplicateGlobal.name = global.name;
6245 duplicateGlobal.logicalName = global.logicalName;
6246 duplicateGlobal.x = global.x;
6247 duplicateGlobal.y = global.y;
6248 duplicateGlobal.bbox = global.bbox;
6249
6250 ORCAD_RAW_PAGE page;
6251 page.name = "GLOBAL ALIAS";
6252 ORCAD_WIRE wire;
6253 wire.id = 1;
6254 wire.x1 = 960;
6255 wire.y1 = 270;
6256 wire.x2 = 960;
6257 wire.y2 = 250;
6258 ORCAD_ALIAS alias;
6259 alias.name = "AUX";
6260 wire.aliases.push_back( alias );
6261 page.wires.push_back( wire );
6262 page.netmap.emplace( wire.id, "AUX" );
6263 page.netAliases[wire.id] = { "AUX" };
6264 page.globals.push_back( std::move( global ) );
6265 page.globals.push_back( std::move( duplicateGlobal ) );
6266
6267 ORCAD_DESIGN design;
6268 design.sourceId = "canonical-global-alias";
6269 design.symbols.emplace( power.name, std::move( power ) );
6270 design.pages.push_back( std::move( page ) );
6271
6272 SETTINGS_MANAGER manager;
6273 manager.LoadProject( "" );
6274 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6275 schematic->SetProject( &manager.Prj() );
6276 SCH_SHEET* root = convertRawDesign( design, *schematic );
6277
6278 std::set<wxString> globalLabels;
6279 std::set<wxString> localLabels;
6280
6281 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
6282 globalLabels.insert( static_cast<SCH_GLOBALLABEL*>( item )->GetText() );
6283
6284 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_LABEL_T ) )
6285 localLabels.insert( static_cast<SCH_LABEL*>( item )->GetText() );
6286
6287 BOOST_CHECK( !globalLabels.contains( wxS( "AUX" ) ) );
6288 BOOST_CHECK( localLabels.contains( wxS( "AUX" ) ) );
6289}
6290
6291
6292BOOST_AUTO_TEST_CASE( InterfaceAliasDoesNotRenameConnectorOnDistinctNet )
6293{
6294 ORCAD_RAW_PAGE page;
6295 page.name = "DISTINCT INTERFACE NET";
6296 page.netmap[1] = "USIM_DATA";
6297 page.netAliases[1] = { "USIM_DATA", "PCIE_USIM_DATA" };
6298 page.wires.push_back( ORCAD_WIRE{ .id = 1, .x2 = 100 } );
6299 page.netmap[2] = "PCIE_USIM_DATA";
6300 page.netAliases[2] = { "PCIE_USIM_DATA" };
6301 page.wires.push_back( ORCAD_WIRE{ .id = 2, .x1 = 1000, .x2 = 1100 } );
6302
6303 ORCAD_GRAPHIC_INST aliased;
6305 aliased.logicalName = "PCIE_USIM_DATA";
6306 aliased.x = 0;
6307 aliased.y = 0;
6308 ORCAD_GRAPHIC_INST distinct;
6309 distinct.typeId = aliased.typeId;
6310 distinct.logicalName = aliased.logicalName;
6311 distinct.x = 1000;
6312 page.offpage.push_back( std::move( aliased ) );
6313 page.offpage.push_back( std::move( distinct ) );
6314
6315 ORCAD_DESIGN design;
6316 design.sourceId = "distinct-interface-net";
6317 design.pages.push_back( std::move( page ) );
6318 design.occurrenceRoot.netNames[1] = "USIM_DATA";
6319
6320 SETTINGS_MANAGER manager;
6321 manager.LoadProject( "" );
6322 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6323 schematic->SetProject( &manager.Prj() );
6324 SCH_SHEET* root = convertRawDesign( design, *schematic );
6325 std::set<wxString> labels;
6326
6327 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
6328 labels.insert( static_cast<SCH_GLOBALLABEL*>( item )->GetText() );
6329
6330 BOOST_CHECK( labels.contains( wxS( "PCIE_USIM_DATA" ) ) );
6331}
6332
6333
6334BOOST_AUTO_TEST_CASE( LeadingSlashNetNameRemainsDistinct )
6335{
6336 ORCAD_RAW_PAGE page;
6337 page.name = "ACTIVE LOW NET";
6338 page.netmap[1] = "STATUS";
6339 page.netAliases[1] = { "STATUS" };
6340 ORCAD_WIRE high;
6341 high.id = 1;
6342 high.x2 = 100;
6343 high.aliases.push_back( ORCAD_ALIAS{ .name = "STATUS" } );
6344 page.wires.push_back( std::move( high ) );
6345 page.netmap[2] = "/STATUS";
6346 page.netAliases[2] = { "/STATUS" };
6347 ORCAD_WIRE low;
6348 low.id = 2;
6349 low.x1 = 200;
6350 low.x2 = 300;
6351 low.aliases.push_back( ORCAD_ALIAS{ .name = "/STATUS", .x = 200 } );
6352 page.wires.push_back( std::move( low ) );
6353
6354 ORCAD_DESIGN design;
6355 design.sourceId = "active-low-net";
6356 design.pages.push_back( std::move( page ) );
6357
6358 SETTINGS_MANAGER manager;
6359 manager.LoadProject( "" );
6360 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6361 schematic->SetProject( &manager.Prj() );
6362 SCH_SHEET* root = convertRawDesign( design, *schematic );
6363 std::set<wxString> labels;
6364
6365 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_LABEL_T ) )
6366 labels.insert( static_cast<SCH_LABEL*>( item )->GetText() );
6367
6368 BOOST_CHECK( labels.contains( wxS( "STATUS" ) ) );
6369 BOOST_CHECK( labels.contains( wxS( "{slash}STATUS" ) ) );
6370}
6371
6372
6373BOOST_AUTO_TEST_CASE( HierarchicalInternalNetUsesBlockName )
6374{
6375 ORCAD_RAW_PAGE rootPage;
6376 rootPage.name = "ROOT";
6377
6379 drawn.dbId = 100;
6380 drawn.name = "G13";
6381 drawn.reference = "G1";
6382 rootPage.blocks.push_back( drawn );
6383
6384 ORCAD_RAW_PAGE childPage;
6385 childPage.name = "CHILD";
6386 childPage.netmap[1] = "LED1_R";
6387
6388 ORCAD_WIRE wire;
6389 wire.id = 1;
6390 wire.x2 = 100;
6391 ORCAD_ALIAS alias;
6392 alias.name = "LED1_R";
6393 wire.aliases.push_back( alias );
6394 childPage.wires.push_back( wire );
6395
6396 childPage.netmap[2] = "LOCAL_PORT_NAME";
6397 ORCAD_WIRE portWire;
6398 portWire.id = 2;
6399 portWire.y1 = 100;
6400 portWire.x2 = 100;
6401 portWire.y2 = 100;
6402 childPage.wires.push_back( portWire );
6403
6404 ORCAD_GRAPHIC_INST port;
6405 port.name = "PORTBOTH-R";
6406 port.logicalName = "PARENT_NET_NAME";
6407 port.color = 8;
6408 port.bbox = ORCAD_BBOX{ 0, 90, 70, 110 };
6409 port.x = 0;
6410 port.y = 100;
6411 port.displayProps.push_back(
6412 ORCAD_DISPLAY_PROP{ .name = "Name", .x = 10, .y = 3, .fontIdx = 1, .dispMode = 0x101 } );
6413 childPage.ports.push_back( std::move( port ) );
6414
6415 childPage.netmap[4] = "N6941990";
6416 ORCAD_WIRE generatedWire;
6417 generatedWire.dbId = 6941990;
6418 generatedWire.id = 4;
6419 generatedWire.y1 = 300;
6420 generatedWire.x2 = 100;
6421 generatedWire.y2 = 300;
6422 childPage.wires.push_back( generatedWire );
6423
6424
6425 ORCAD_OCC_BLOCK occurrence;
6426 occurrence.targetDbId = 100;
6427 occurrence.childFolder = "CHILD";
6428 occurrence.scope.netNames[10] = "LED1_R";
6429 occurrence.scope.netNames[11] = "LOCAL_PORT_NAME";
6430 occurrence.scope.netNames[12] = "N6941990";
6431
6432 ORCAD_DESIGN design;
6433 design.sourceId = "hierarchical-flat-net-name";
6434 design.library.fonts.push_back( ORCAD_FONT{ .height = -9, .face = "Arial" } );
6435 ORCAD_SYMBOL_DEF portDefinition;
6436 portDefinition.typeId = ORCAD_ST_PORT_SYMBOL;
6437 portDefinition.name = "PORTBOTH-R";
6438 portDefinition.bbox = ORCAD_BBOX{ 0, 0, 70, 20 };
6439 portDefinition.primitives.push_back(
6440 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::LINE, .x1 = 0, .y1 = 10, .x2 = 70, .y2 = 10 } );
6441 portDefinition.pins.push_back( ORCAD_SYMBOL_PIN{ .hotptX = 0, .hotptY = 10 } );
6442 design.symbols.emplace( portDefinition.name, std::move( portDefinition ) );
6443 design.pages.push_back( std::move( rootPage ) );
6444 design.childFolderPages["child"].push_back( std::move( childPage ) );
6445 design.occurrenceRoot.blocks.push_back( std::move( occurrence ) );
6446
6447 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6448 SETTINGS_MANAGER manager;
6449 manager.LoadProject( "" );
6450 schematic->SetProject( &manager.Prj() );
6451 SCH_SHEET* root = convertRawDesign( design, *schematic );
6452 SCH_SHEET* child = nullptr;
6453
6454 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHEET_T ) )
6455 child = static_cast<SCH_SHEET*>( item );
6456
6457 BOOST_REQUIRE( child );
6458 BOOST_CHECK( !child->GetField( FIELD_T::SHEET_NAME )->IsVisible() );
6459 BOOST_CHECK( !child->GetField( FIELD_T::SHEET_FILENAME )->IsVisible() );
6460 std::set<wxString> labels;
6461
6462 for( SCH_ITEM* item : child->GetScreen()->Items().OfType( SCH_LABEL_T ) )
6463 labels.insert( static_cast<SCH_LABEL*>( item )->GetText() );
6464
6465 for( SCH_ITEM* item : child->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
6466 labels.insert( static_cast<SCH_GLOBALLABEL*>( item )->GetText() );
6467
6468 BOOST_CHECK( labels.contains( wxS( "LED1_R" ) ) );
6469 BOOST_CHECK( !labels.contains( wxS( "LED1_R_G13" ) ) );
6470 BOOST_CHECK( !labels.contains( wxS( "LOCAL_PORT_NAME_G13" ) ) );
6471 BOOST_CHECK( !labels.contains( wxS( "N6941990_G13" ) ) );
6472
6473 int exactPortLabels = 0;
6474
6475 for( SCH_ITEM* item : child->GetScreen()->Items().OfType( SCH_HIER_LABEL_T ) )
6476 {
6477 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( item );
6478
6479 if( label->GetText() == wxS( "LOCAL_PORT_NAME" ) )
6480 {
6481 ++exactPortLabels;
6482 BOOST_CHECK( label->GetTextColor() == KIGFX::COLOR4D::UNSPECIFIED || label->GetTextColor().a > 0 );
6483 }
6484 }
6485
6486 BOOST_CHECK_EQUAL( exactPortLabels, 1 );
6487}
6488
6489
6490BOOST_AUTO_TEST_CASE( HierarchicalBlockDisplayFieldsPreserveSourceGeometry )
6491{
6492 ORCAD_RAW_PAGE rootPage;
6493 rootPage.name = "ROOT";
6494
6496 drawn.dbId = 100;
6497 drawn.reference = "BoM";
6498 drawn.x1 = 100;
6499 drawn.y1 = 200;
6500 drawn.w = 150;
6501 drawn.h = 60;
6502 drawn.displayProps = {
6503 ORCAD_DISPLAY_PROP{ .name = "Reference", .y = -10, .fontIdx = 2, .dispMode = 0x101 },
6504 ORCAD_DISPLAY_PROP{ .name = "Value", .x = 85, .y = -10, .fontIdx = 9, .dispMode = 0x101 },
6505 };
6506 drawn.pins.push_back( ORCAD_BLOCK_PIN{ .name = "Gate", .portType = ORCAD_PORT_TYPE::INPUT_TYPE,
6507 .x = 100, .y = 230 } );
6508 rootPage.blocks.push_back( std::move( drawn ) );
6509
6510 ORCAD_RAW_PAGE childPage;
6511 childPage.name = "Channel_BoM";
6512
6513 ORCAD_OCC_BLOCK occurrence;
6514 occurrence.targetDbId = 100;
6515 occurrence.childFolder = "Channel_BoM";
6516
6517 ORCAD_DESIGN design;
6518 design.sourceId = "hierarchical-display-fields";
6519 design.pages.push_back( std::move( rootPage ) );
6520 design.childFolderPages["channel_bom"].push_back( std::move( childPage ) );
6521 design.occurrenceRoot.blocks.push_back( std::move( occurrence ) );
6522
6523 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6524 SETTINGS_MANAGER manager;
6525 manager.LoadProject( "" );
6526 schematic->SetProject( &manager.Prj() );
6527 SCH_SHEET* root = convertRawDesign( design, *schematic );
6528 SCH_SHEET* child = nullptr;
6529
6530 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHEET_T ) )
6531 child = static_cast<SCH_SHEET*>( item );
6532
6533 BOOST_REQUIRE( child );
6534 SCH_FIELD* reference = child->GetField( FIELD_T::SHEET_NAME );
6535 SCH_FIELD* value = child->GetField( wxS( "Implementation" ) );
6536 BOOST_CHECK( reference->IsVisible() );
6537 BOOST_CHECK_EQUAL( reference->GetText(), wxS( "BoM" ) );
6538 BOOST_CHECK_EQUAL( reference->GetPosition().x, schIUScale.MilsToIU( 1000 ) );
6539 BOOST_REQUIRE( value );
6540 BOOST_CHECK( value->IsVisible() );
6541 BOOST_CHECK_EQUAL( value->GetText(), wxS( "Channel_BoM" ) );
6542 BOOST_CHECK_EQUAL( value->GetPosition().x, schIUScale.MilsToIU( 1850 ) );
6543 BOOST_CHECK( !child->GetField( FIELD_T::SHEET_FILENAME )->IsVisible() );
6544
6545 SCH_SHAPE* pinFill = nullptr;
6546
6547 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHAPE_T ) )
6548 pinFill = static_cast<SCH_SHAPE*>( item );
6549
6550 BOOST_REQUIRE( pinFill );
6551 BOOST_CHECK( pinFill->GetShape() == SHAPE_T::POLY );
6552 BOOST_CHECK( pinFill->GetFillMode() == FILL_T::FILLED_WITH_COLOR );
6553}
6554
6555
6556BOOST_AUTO_TEST_CASE( MultiPageHierarchyFollowsCaptureFolderOrder )
6557{
6558 ORCAD_RAW_PAGE titlePage;
6559 titlePage.name = "0-TITLE";
6560
6561 ORCAD_RAW_PAGE mainPage;
6562 mainPage.name = "1-MAIN";
6563
6564 ORCAD_DRAWN_INSTANCE gateBlock;
6565 gateBlock.dbId = 600;
6566 gateBlock.reference = "GATE";
6567 gateBlock.w = 100;
6568 gateBlock.h = 100;
6569 gateBlock.pins.push_back( ORCAD_BLOCK_PIN{ .name = "IN", .portType = ORCAD_PORT_TYPE::INPUT_TYPE,
6570 .x = 0, .y = 50 } );
6571 mainPage.blocks.push_back( gateBlock );
6572
6573 ORCAD_DRAWN_INSTANCE microBlock;
6574 microBlock.dbId = 200;
6575 microBlock.reference = "MICRO";
6576 microBlock.x1 = 200;
6577 microBlock.w = 100;
6578 microBlock.h = 100;
6579 microBlock.pins.push_back( ORCAD_BLOCK_PIN{ .name = "OUT", .portType = ORCAD_PORT_TYPE::OUTPUT,
6580 .x = 300, .y = 50 } );
6581 mainPage.blocks.push_back( microBlock );
6582
6583 ORCAD_OCC_BLOCK gateOccurrence;
6584 gateOccurrence.targetDbId = 600;
6585 gateOccurrence.childFolder = "Z_GATE";
6586
6587 ORCAD_OCC_BLOCK microOccurrence;
6588 microOccurrence.targetDbId = 200;
6589 microOccurrence.childFolder = "A_MICRO";
6590
6591 ORCAD_RAW_PAGE gatePage;
6592 gatePage.name = "6-GATE";
6593 ORCAD_RAW_PAGE microPage;
6594 microPage.name = "2-MICRO";
6595
6596 ORCAD_DESIGN design;
6597 design.sourceId = "multi-page-hierarchy-order";
6598 design.pages.push_back( std::move( titlePage ) );
6599 design.pages.push_back( std::move( mainPage ) );
6600 design.childFolderPages["z_gate"].push_back( std::move( gatePage ) );
6601 design.childFolderPages["a_micro"].push_back( std::move( microPage ) );
6602 design.occurrenceRoot.blocks.push_back( std::move( gateOccurrence ) );
6603 design.occurrenceRoot.blocks.push_back( std::move( microOccurrence ) );
6604
6605 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6606 SETTINGS_MANAGER manager;
6607 manager.LoadProject( "" );
6608 schematic->SetProject( &manager.Prj() );
6609 convertRawDesign( design, *schematic );
6610
6611 std::vector<SCH_SHEET*> topLevelSheets = schematic->GetTopLevelSheets();
6612 BOOST_REQUIRE_EQUAL( topLevelSheets.size(), 2u );
6613 BOOST_CHECK_EQUAL( topLevelSheets[0]->GetName(), wxS( "0-TITLE" ) );
6614 BOOST_CHECK_EQUAL( topLevelSheets[1]->GetName(), wxS( "1-MAIN" ) );
6615
6616 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
6617 BOOST_REQUIRE_EQUAL( sheets.size(), 4u );
6618
6619 for( size_t i = 0; i < sheets.size(); ++i )
6620 BOOST_CHECK_EQUAL( sheets[i].GetPageNumber(), wxString::Format( wxS( "%zu" ), i + 1 ) );
6621
6622 BOOST_CHECK_EQUAL( design.pages[0].sourcePageNumber, 1u );
6623 BOOST_CHECK_EQUAL( design.pages[1].sourcePageNumber, 2u );
6624 BOOST_CHECK_EQUAL( design.childFolderPages["a_micro"][0].sourcePageNumber, 3u );
6625 BOOST_CHECK_EQUAL( design.childFolderPages["z_gate"][0].sourcePageNumber, 4u );
6626
6627 for( const ORCAD_RAW_PAGE* page : { &design.pages[0], &design.pages[1], &design.childFolderPages["a_micro"][0],
6628 &design.childFolderPages["z_gate"][0] } )
6629 {
6630 BOOST_CHECK_EQUAL( page->sourcePageCount, 4u );
6631 }
6632
6633 std::vector<wxString> childNames;
6634
6635 for( const SCH_SHEET_PATH& path : sheets )
6636 {
6637 if( path.Last()->GetName() == wxS( "MICRO" ) || path.Last()->GetName() == wxS( "GATE" ) )
6638 childNames.push_back( path.Last()->GetName() );
6639 }
6640
6641 BOOST_REQUIRE_EQUAL( childNames.size(), 2u );
6642 BOOST_CHECK_EQUAL( childNames[0], wxS( "MICRO" ) );
6643 BOOST_CHECK_EQUAL( childNames[1], wxS( "GATE" ) );
6644
6645 for( const SCH_SHEET_PATH& path : sheets )
6646 {
6647 if( path.Last()->GetName() == wxS( "GATE" ) )
6648 {
6649 BOOST_REQUIRE_EQUAL( path.Last()->GetPins().size(), 1u );
6650 BOOST_CHECK( path.Last()->GetPins().front()->GetShape() == LABEL_FLAG_SHAPE::L_INPUT );
6651 }
6652 else if( path.Last()->GetName() == wxS( "MICRO" ) )
6653 {
6654 BOOST_REQUIRE_EQUAL( path.Last()->GetPins().size(), 1u );
6655 BOOST_CHECK( path.Last()->GetPins().front()->GetShape() == LABEL_FLAG_SHAPE::L_OUTPUT );
6656 }
6657 }
6658}
6659
6660
6661BOOST_AUTO_TEST_CASE( GeneratedParentNetUsesHierarchicalBlockPinName )
6662{
6663 ORCAD_RAW_PAGE rootPage;
6664 rootPage.name = "ROOT";
6665 rootPage.netmap[1] = "N12345";
6666
6667 ORCAD_WIRE parentWire;
6668 parentWire.id = 1;
6669 parentWire.x2 = 100;
6670 rootPage.wires.push_back( parentWire );
6671
6673 drawn.dbId = 100;
6674 drawn.reference = "G1";
6675 drawn.w = 100;
6676 drawn.h = 100;
6677 drawn.pins.push_back( ORCAD_BLOCK_PIN{ .name = "SIGNAL", .x = 0, .y = 0 } );
6678 rootPage.blocks.push_back( std::move( drawn ) );
6679
6680 ORCAD_RAW_PAGE childPage;
6681 childPage.name = "CHILD";
6682
6683 ORCAD_OCC_BLOCK occurrence;
6684 occurrence.targetDbId = 100;
6685 occurrence.childFolder = "CHILD";
6686
6687 ORCAD_DESIGN design;
6688 design.sourceId = "hierarchical-parent-net-name";
6689 design.pages.push_back( std::move( rootPage ) );
6690 design.childFolderPages["child"].push_back( std::move( childPage ) );
6691 design.occurrenceRoot.blocks.push_back( std::move( occurrence ) );
6692
6693 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6694 SETTINGS_MANAGER manager;
6695 manager.LoadProject( "" );
6696 schematic->SetProject( &manager.Prj() );
6697 SCH_SHEET* root = convertRawDesign( design, *schematic );
6698 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
6699 schematic->ConnectionGraph()->Recalculate( sheets, true );
6700 SCH_LINE* wire = nullptr;
6701 SCH_SHEET* child = nullptr;
6702
6703 for( SCH_ITEM* item : root->GetScreen()->Items() )
6704 {
6705 if( item->Type() == SCH_LINE_T && item->GetLayer() == LAYER_WIRE )
6706 wire = static_cast<SCH_LINE*>( item );
6707 else if( item->Type() == SCH_SHEET_T )
6708 child = static_cast<SCH_SHEET*>( item );
6709 else if( item->Type() == SCH_LABEL_T || item->Type() == SCH_GLOBAL_LABEL_T )
6710 BOOST_CHECK_NE( static_cast<SCH_LABEL_BASE*>( item )->GetText(), wxString( "N12345" ) );
6711 }
6712
6713 BOOST_REQUIRE( wire );
6714 BOOST_REQUIRE( child );
6715 SCH_SHEET_PATH rootPath;
6716 rootPath.push_back( root );
6717 SCH_CONNECTION* connection = wire->Connection( &rootPath );
6718 BOOST_REQUIRE( connection );
6719 BOOST_REQUIRE_GT( connection->NetCode(), 0 );
6720 std::set<wxString> pinNames;
6721
6722 for( SCH_SHEET_PIN* pin : child->GetPins() )
6723 {
6724 pinNames.insert( pin->GetText() );
6725 BOOST_REQUIRE( pin->Connection( &rootPath ) );
6726 BOOST_CHECK_EQUAL( pin->Connection( &rootPath )->NetCode(), connection->NetCode() );
6727 }
6728
6729 BOOST_CHECK( pinNames.contains( wxS( "SIGNAL" ) ) );
6730 const IMPORT_NET_MAP* map = schematic->GetImportNetMap();
6731 BOOST_REQUIRE( map );
6732 auto mapped = std::find_if( map->entries.begin(), map->entries.end(),
6733 []( const IMPORT_NET_MAP_ENTRY& entry )
6734 {
6735 return entry.sourceNetId == 1 && entry.originalName == wxS( "N12345" );
6736 } );
6737 BOOST_REQUIRE( mapped != map->entries.end() );
6739 BOOST_CHECK_EQUAL( mapped->nameAtImport, connection->Name() );
6740 BOOST_CHECK_NE( mapped->nameAtImport, wxString( "N12345" ) );
6741}
6742
6743
6744BOOST_AUTO_TEST_CASE( GeneratedParentNetUsesFirstConnectedHierarchicalBlockPinName )
6745{
6746 ORCAD_RAW_PAGE rootPage;
6747 rootPage.name = "ROOT";
6748 rootPage.netmap[1] = "N12345";
6749
6750 ORCAD_WIRE parentWire;
6751 parentWire.id = 1;
6752 parentWire.x2 = 100;
6753 rootPage.wires.push_back( parentWire );
6754
6756 drawn.dbId = 100;
6757 drawn.reference = "G1";
6758 drawn.w = 100;
6759 drawn.h = 100;
6760 drawn.pins.push_back( ORCAD_BLOCK_PIN{ .name = "G1", .x = 0, .y = 0 } );
6761 drawn.pins.push_back( ORCAD_BLOCK_PIN{ .name = "G5", .x = 100, .y = 0 } );
6762 rootPage.blocks.push_back( std::move( drawn ) );
6763
6764 ORCAD_RAW_PAGE childPage;
6765 childPage.name = "CHILD";
6766
6767 ORCAD_OCC_BLOCK occurrence;
6768 occurrence.targetDbId = 100;
6769 occurrence.childFolder = "CHILD";
6770
6771 ORCAD_DESIGN design;
6772 design.sourceId = "hierarchical-parent-shared-net-name";
6773 design.pages.push_back( std::move( rootPage ) );
6774 design.childFolderPages["child"].push_back( std::move( childPage ) );
6775 design.occurrenceRoot.blocks.push_back( std::move( occurrence ) );
6776
6777 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6778 SETTINGS_MANAGER manager;
6779 manager.LoadProject( "" );
6780 schematic->SetProject( &manager.Prj() );
6781 SCH_SHEET* root = convertRawDesign( design, *schematic );
6782 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
6783 schematic->ConnectionGraph()->Recalculate( sheets, true );
6784 SCH_LINE* wire = nullptr;
6785 SCH_SHEET* child = nullptr;
6786
6787 for( SCH_ITEM* item : root->GetScreen()->Items() )
6788 {
6789 if( item->Type() == SCH_LINE_T && item->GetLayer() == LAYER_WIRE )
6790 wire = static_cast<SCH_LINE*>( item );
6791 else if( item->Type() == SCH_SHEET_T )
6792 child = static_cast<SCH_SHEET*>( item );
6793 else if( item->Type() == SCH_LABEL_T || item->Type() == SCH_GLOBAL_LABEL_T )
6794 BOOST_CHECK_NE( static_cast<SCH_LABEL_BASE*>( item )->GetText(), wxString( "N12345" ) );
6795 }
6796
6797 BOOST_REQUIRE( wire );
6798 BOOST_REQUIRE( child );
6799 SCH_SHEET_PATH rootPath;
6800 rootPath.push_back( root );
6801 SCH_CONNECTION* connection = wire->Connection( &rootPath );
6802 BOOST_REQUIRE( connection );
6803 BOOST_REQUIRE_GT( connection->NetCode(), 0 );
6804 std::set<wxString> pinNames;
6805
6806 for( SCH_SHEET_PIN* pin : child->GetPins() )
6807 {
6808 pinNames.insert( pin->GetText() );
6809 BOOST_REQUIRE( pin->Connection( &rootPath ) );
6810 BOOST_CHECK_EQUAL( pin->Connection( &rootPath )->NetCode(), connection->NetCode() );
6811 }
6812
6813 BOOST_CHECK( pinNames.contains( wxS( "G1" ) ) );
6814 BOOST_CHECK( pinNames.contains( wxS( "G5" ) ) );
6815 const IMPORT_NET_MAP* map = schematic->GetImportNetMap();
6816 BOOST_REQUIRE( map );
6817 auto mapped = std::find_if( map->entries.begin(), map->entries.end(),
6818 []( const IMPORT_NET_MAP_ENTRY& entry )
6819 {
6820 return entry.sourceNetId == 1 && entry.originalName == wxS( "N12345" );
6821 } );
6822 BOOST_REQUIRE( mapped != map->entries.end() );
6824 BOOST_CHECK_EQUAL( mapped->nameAtImport, connection->Name() );
6825 BOOST_CHECK_NE( mapped->nameAtImport, wxString( "N12345" ) );
6826}
6827
6828
6829BOOST_AUTO_TEST_CASE( S593487_StaleCachedPinGeometry )
6830{
6831 ORCAD_SYMBOL_DEF cached;
6832 cached.name = "THERMISTOR.Normal";
6833 cached.bbox = ORCAD_BBOX{ 0, 0, 20, 30 };
6834 cached.primitives.push_back( ORCAD_PRIMITIVE{} );
6835 cached.pins.resize( 4 );
6836 for( size_t i = 0; i < cached.pins.size(); ++i )
6837 {
6838 cached.pins[i].hotptX = static_cast<int>( i ) * 10;
6839 cached.pins[i].hotptY = 10;
6840 cached.pins[i].startX = cached.pins[i].hotptX;
6841 cached.pins[i].startY = 10;
6842 }
6843
6844 ORCAD_PLACED_INSTANCE placed;
6845 placed.x = 60;
6846 placed.y = 1060;
6847 placed.rotation = 1;
6848 placed.pins.resize( 2 );
6849 placed.pins[0].pinIndex = 2;
6850 placed.pins[0].x = 70;
6851 placed.pins[0].y = 1060;
6852 placed.pins[1].pinIndex = -4;
6853 placed.pins[1].x = 70;
6854 placed.pins[1].y = 1080;
6855
6856 placed.pkgName = cached.name;
6857 placed.sourcePackage = "THERMISTOR";
6858 placed.reference = "U1";
6859 placed.dbId = 1;
6860
6861 ORCAD_SYMBOL_DEF load;
6863 load.name = "LOAD.Normal";
6864 load.bbox = ORCAD_BBOX{ 0, 0, 10, 10 };
6865 load.pins.resize( 1 );
6866 load.pins[0].hotptX = 0;
6867 load.pins[0].hotptY = 0;
6868 load.pins[0].startX = 0;
6869 load.pins[0].startY = 0;
6870
6871 ORCAD_PLACED_INSTANCE loadPlaced;
6872 loadPlaced.pkgName = load.name;
6873 loadPlaced.sourcePackage = "LOAD";
6874 loadPlaced.reference = "U2";
6875 loadPlaced.dbId = 2;
6876 loadPlaced.x = 100;
6877 loadPlaced.y = 1060;
6878 loadPlaced.pins.push_back( ORCAD_PIN_INST{ 1, 100, 1060 } );
6879
6880 ORCAD_RAW_PAGE page;
6881 page.name = "PIN GEOMETRY";
6882 page.instances.push_back( placed );
6883 page.instances.push_back( loadPlaced );
6884 page.wires.push_back( ORCAD_WIRE{ 10, 10, 70, 1060, 100, 1060 } );
6885
6886 ORCAD_DESIGN design;
6887 design.sourceId = "s593487-stale-pin-geometry";
6888 design.symbols.emplace( cached.name, cached );
6889 design.symbols.emplace( load.name, load );
6890 design.pages.push_back( std::move( page ) );
6891
6892 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6893 SETTINGS_MANAGER manager;
6894 manager.LoadProject( "" );
6895 schematic->SetProject( &manager.Prj() );
6896
6897 SCH_SHEET* rootSheet = new SCH_SHEET( schematic.get() );
6898 SCH_SCREEN* rootScreen = new SCH_SCREEN( schematic.get() );
6899 rootSheet->SetScreen( rootScreen );
6900 schematic->SetTopLevelSheets( { rootSheet } );
6901 schematic->CurrentSheet().clear();
6902 schematic->CurrentSheet().push_back( rootSheet );
6903
6904 ORCAD_CONVERTER converter( design, schematic.get(), nullptr );
6905 converter.Convert( rootSheet );
6906
6907 auto [consistent, checkable] =
6908 checkConnectivity( *schematic, { { terminalToken( "U1", "2" ), terminalToken( "U2", "1" ) } } );
6909 BOOST_CHECK_EQUAL( checkable, 1 );
6910 BOOST_CHECK_EQUAL( consistent, 1 );
6911
6912 SCH_SHEET_PATH rootPath;
6913 rootPath.push_back( rootSheet );
6914 SCH_SYMBOL* converted = nullptr;
6915 SCH_NO_CONNECT* noConnect = nullptr;
6916
6917 for( SCH_ITEM* item : rootScreen->Items() )
6918 {
6919 if( item->Type() == SCH_SYMBOL_T )
6920 {
6921 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
6922
6923 if( symbol->GetRef( &rootPath, false ) == wxS( "U1" ) )
6924 converted = symbol;
6925 }
6926 else if( item->Type() == SCH_NO_CONNECT_T )
6927 {
6928 noConnect = static_cast<SCH_NO_CONNECT*>( item );
6929 }
6930 }
6931
6932 BOOST_REQUIRE( converted );
6933 BOOST_REQUIRE( noConnect );
6934 std::set<std::pair<int, int>> convertedHotpoints;
6935 SCH_PIN* noConnectPin = nullptr;
6936
6937 for( SCH_PIN* pin : converted->GetPins() )
6938 {
6939 convertedHotpoints.emplace( pin->GetPosition().x, pin->GetPosition().y );
6940
6941 if( pin->GetNumber() == wxS( "4" ) )
6942 noConnectPin = pin;
6943 }
6944
6945 BOOST_CHECK_EQUAL( convertedHotpoints.size(), converted->GetPins().size() );
6946 BOOST_REQUIRE( noConnectPin );
6947 BOOST_CHECK( noConnectPin->GetPosition() == noConnect->GetPosition() );
6948}
6949
6950
6951BOOST_AUTO_TEST_CASE( PlacedPinLocationIsTheConnectionPoint )
6952{
6953 ORCAD_SYMBOL_DEF cached;
6955 cached.name = "TESTPOINT.Normal";
6956 cached.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
6957 cached.pins.resize( 1 );
6958 cached.pins[0].position = 0;
6959 cached.pins[0].startX = 0;
6960 cached.pins[0].startY = 10;
6961 cached.pins[0].hotptX = -10;
6962 cached.pins[0].hotptY = 10;
6963
6964 ORCAD_PLACED_INSTANCE placed;
6965 placed.pkgName = cached.name;
6966 placed.sourcePackage = "TESTPOINT";
6967 placed.reference = "TP1";
6968 placed.x = 100;
6969 placed.y = 100;
6970 placed.pins = { ORCAD_PIN_INST{ 1, 90, 110 } };
6971
6972 ORCAD_RAW_PAGE page;
6973 page.name = "PIN BODY END";
6974 page.instances.push_back( placed );
6975
6976 ORCAD_DESIGN design;
6977 design.sourceId = "placed-pin-body-end";
6978 design.symbols.emplace( cached.name, cached );
6979 design.pages.push_back( std::move( page ) );
6980
6981 SETTINGS_MANAGER manager;
6982 manager.LoadProject( "" );
6983 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
6984 schematic->SetProject( &manager.Prj() );
6985 SCH_SHEET* root = convertRawDesign( design, *schematic );
6987 path.push_back( root );
6988 SCH_SYMBOL* symbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "TP1" ) );
6989
6990 BOOST_REQUIRE( symbol );
6991 BOOST_REQUIRE_EQUAL( symbol->GetPins().size(), 1u );
6992 VECTOR2I relative = symbol->GetPins().front()->GetPosition() - symbol->GetPosition();
6993 BOOST_CHECK_EQUAL( relative.x, -10 * ORCAD_IU_PER_DBU );
6994 BOOST_CHECK_EQUAL( relative.y, 10 * ORCAD_IU_PER_DBU );
6995}
6996
6997
6998BOOST_AUTO_TEST_CASE( SynthesizedSinglePinBodyUsesRotatedPlacementExtent )
6999{
7000 ORCAD_PLACED_INSTANCE placed;
7001 placed.pkgName = "MISSING_TESTPOINT.Normal";
7002 placed.sourcePackage = "MISSING_TESTPOINT";
7003 placed.reference = "TP1";
7004 placed.x = 100;
7005 placed.y = 100;
7006 placed.pins = { ORCAD_PIN_INST{ 1, 90, 110 } };
7007
7008 ORCAD_RAW_PAGE page;
7009 page.name = "MISSING TESTPOINT";
7010 page.instances.push_back( placed );
7011 placed.reference = "TP2";
7012 placed.x = 200;
7013 placed.rotation = 2;
7014 placed.pins = { ORCAD_PIN_INST{ 1, 230, 110 } };
7015 page.instances.push_back( placed );
7016
7017 ORCAD_DESIGN design;
7018 design.sourceId = "synthesized-single-pin";
7019 design.pages.push_back( std::move( page ) );
7020
7021 SETTINGS_MANAGER manager;
7022 manager.LoadProject( "" );
7023 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7024 schematic->SetProject( &manager.Prj() );
7025 convertRawDesign( design, *schematic );
7026
7027 const ORCAD_SYMBOL_DEF& generated = design.symbols.at( placed.pkgName );
7028 BOOST_REQUIRE( generated.bbox );
7029 BOOST_CHECK_EQUAL( generated.bbox->x1, 0 );
7030 BOOST_CHECK_EQUAL( generated.bbox->y1, 0 );
7031 BOOST_CHECK_EQUAL( generated.bbox->x2, 20 );
7032 BOOST_CHECK_EQUAL( generated.bbox->y2, 20 );
7033 BOOST_REQUIRE_EQUAL( generated.pins.size(), 1u );
7034 BOOST_CHECK_EQUAL( generated.pins.front().hotptX, -10 );
7035 BOOST_CHECK_EQUAL( generated.pins.front().hotptY, 10 );
7036 BOOST_CHECK_EQUAL( generated.pins.front().startX, 0 );
7037 BOOST_CHECK_EQUAL( generated.pins.front().startY, 10 );
7038}
7039
7040
7041BOOST_AUTO_TEST_CASE( S593487_ClosestCachedBodyIsFitted )
7042{
7043 ORCAD_SYMBOL_DEF primary;
7044 primary.typeId = ORCAD_ST_LIBRARY_PART;
7045 primary.name = "BODY.Normal";
7046 primary.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
7047 primary.primitives.push_back( ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::RECTANGLE, .x2 = 20, .y2 = 20 } );
7048 primary.pins.resize( 2 );
7049 primary.pins[0].hotptX = 0;
7050 primary.pins[1].hotptX = 20;
7051 primary.pins[0].startX = primary.pins[0].hotptX;
7052 primary.pins[1].startX = primary.pins[1].hotptX;
7053
7054 ORCAD_SYMBOL_DEF alternate = primary;
7055 alternate.bbox = ORCAD_BBOX{ 0, 0, 40, 20 };
7056 alternate.primitives.front().x2 = 40;
7057 alternate.pins[0].hotptX = 99;
7058 alternate.pins[1].hotptX = 119;
7059 alternate.pins[0].startX = alternate.pins[0].hotptX;
7060 alternate.pins[1].startX = alternate.pins[1].hotptX;
7061 primary.variants.push_back( alternate );
7062
7063 ORCAD_PLACED_INSTANCE placed;
7064 placed.pkgName = primary.name;
7065 placed.sourcePackage = "BODY";
7066 placed.reference = "U1";
7067 placed.x = 100;
7068 placed.y = 100;
7069 placed.pins = { ORCAD_PIN_INST{ 1, 200, 100 }, ORCAD_PIN_INST{ 2, 220, 100 } };
7070
7071 ORCAD_RAW_PAGE page;
7072 page.name = "CLOSEST BODY";
7073 page.instances.push_back( placed );
7074
7075 ORCAD_DESIGN design;
7076 design.sourceId = "s593487-closest-body";
7077 design.symbols.emplace( primary.name, primary );
7078 design.pages.push_back( std::move( page ) );
7079
7080 SETTINGS_MANAGER manager;
7081 manager.LoadProject( "" );
7082 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7083 schematic->SetProject( &manager.Prj() );
7084 convertRawDesign( design, *schematic );
7085
7086 const ORCAD_SYMBOL_DEF& converted = design.symbols.at( primary.name );
7087 BOOST_REQUIRE_EQUAL( converted.variants.size(), 2u );
7088 BOOST_REQUIRE_EQUAL( converted.variants.back().primitives.size(), 1u );
7089 BOOST_REQUIRE( converted.variants.back().bbox );
7090 BOOST_CHECK_EQUAL( converted.variants.back().bbox->x2, 40 );
7091 BOOST_CHECK_EQUAL( converted.variants.back().primitives.front().x2, 40 );
7092}
7093
7094
7095BOOST_AUTO_TEST_CASE( S593487_PrimaryGraphicsUsePlacedVariantGeometry )
7096{
7097 ORCAD_SYMBOL_DEF primary;
7098 primary.typeId = ORCAD_ST_LIBRARY_PART;
7099 primary.name = "MIXED.Normal";
7100 primary.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
7101 primary.primitives.push_back( ORCAD_PRIMITIVE{
7102 .kind = ORCAD_PRIM_KIND::TEXT, .x1 = 10, .y1 = -20, .x2 = 30, .y2 = -5, .text = "primary cue" } );
7103 primary.pins.resize( 2 );
7104 primary.pins[0].hotptX = 10;
7105 primary.pins[0].hotptY = -10;
7106 primary.pins[0].startX = primary.pins[0].hotptX;
7107 primary.pins[0].startY = primary.pins[0].hotptY;
7108 primary.pins[1].hotptX = 10;
7109 primary.pins[1].hotptY = 40;
7110 primary.pins[1].startX = primary.pins[1].hotptX;
7111 primary.pins[1].startY = primary.pins[1].hotptY;
7112
7113 ORCAD_SYMBOL_DEF alternate = primary;
7114 alternate.bbox = ORCAD_BBOX{ 0, 0, 30, 20 };
7115 alternate.primitives.front().text = "alternate cue";
7116 ORCAD_SYMBOL_DEF stale = primary;
7117 stale.pins[0].hotptX = 0;
7118 stale.pins[1].hotptX = 0;
7119 stale.pins[0].startX = stale.pins[0].hotptX;
7120 stale.pins[1].startX = stale.pins[1].hotptX;
7121 stale.synthesized = true;
7122 primary.variants = { stale, alternate };
7123
7124 ORCAD_PLACED_INSTANCE placed;
7125 placed.pkgName = primary.name;
7126 placed.sourcePackage = "MIXED";
7127 placed.reference = "RT1";
7128 placed.x = 100;
7129 placed.y = 100;
7130 placed.rotation = 1;
7131 placed.pins = { ORCAD_PIN_INST{ 1, 90, 120 }, ORCAD_PIN_INST{ 2, 140, 120 } };
7132
7133 ORCAD_RAW_PAGE page;
7134 page.name = "MIXED GRAPHICS";
7135 page.instances.push_back( placed );
7136
7137 ORCAD_DESIGN design;
7138 design.sourceId = "s593487-mixed-graphics";
7139 design.symbols.emplace( primary.name, primary );
7140 design.pages.push_back( std::move( page ) );
7141
7142 SETTINGS_MANAGER manager;
7143 manager.LoadProject( "" );
7144 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7145 schematic->SetProject( &manager.Prj() );
7146 convertRawDesign( design, *schematic );
7147
7148 const ORCAD_SYMBOL_DEF& converted = design.symbols.at( primary.name );
7149 BOOST_REQUIRE_EQUAL( converted.variants.size(), 3u );
7150 BOOST_CHECK( converted.variants.front().synthesized );
7151 BOOST_REQUIRE_EQUAL( converted.variants.front().primitives.size(), 1u );
7152 BOOST_CHECK_EQUAL( converted.variants.front().primitives.front().text, "primary cue" );
7153 BOOST_REQUIRE( converted.variants.front().bbox );
7154 BOOST_CHECK_EQUAL( converted.variants.front().bbox->x2, 30 );
7155 BOOST_CHECK_EQUAL( converted.variants.front().pins[0].hotptX, 10 );
7156 BOOST_CHECK_EQUAL( converted.variants.front().pins[1].hotptX, 10 );
7157}
7158
7159
7160BOOST_AUTO_TEST_CASE( SourceMatchedVariantPreservesItsGraphics )
7161{
7162 ORCAD_SYMBOL_DEF primary;
7163 primary.typeId = ORCAD_ST_LIBRARY_PART;
7164 primary.name = "RESISTOR.Normal";
7165 primary.sourceLib = "PSPICE_ELEM.OLB";
7166 primary.bbox = ORCAD_BBOX{ 0, 0, 30, 20 };
7167 primary.primitives.push_back(
7168 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::LINE, .x1 = 4, .y1 = 10, .x2 = 28, .y2 = 10 } );
7169 primary.pins.resize( 2 );
7170 primary.pins[0].hotptX = -10;
7171 primary.pins[0].hotptY = 10;
7172 primary.pins[1].hotptX = 40;
7173 primary.pins[1].hotptY = 10;
7174
7175 ORCAD_SYMBOL_DEF alternate = primary;
7176 alternate.sourceLib = "DISCRETE.OLB";
7177 alternate.bbox = ORCAD_BBOX{ 0, 0, 20, 30 };
7178 alternate.primitives.front() =
7179 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::LINE, .x1 = 10, .y1 = 3, .x2 = 10, .y2 = 27 };
7180 alternate.pins[0].hotptX = 10;
7181 alternate.pins[0].hotptY = -10;
7182 alternate.pins[1].hotptX = 10;
7183 alternate.pins[1].hotptY = 40;
7184 primary.variants.push_back( alternate );
7185
7186 ORCAD_PLACED_INSTANCE placed;
7187 placed.pkgName = primary.name;
7188 placed.sourcePackage = "RESISTOR";
7189 placed.sourceLibrary = alternate.sourceLib;
7190 placed.reference = "R1";
7191 placed.x = 100;
7192 placed.y = 100;
7193 placed.pins = { ORCAD_PIN_INST{ 1, 110, 90 }, ORCAD_PIN_INST{ 2, 110, 140 } };
7194
7195 ORCAD_RAW_PAGE page;
7196 page.name = "SOURCE MATCHED GRAPHICS";
7197 page.instances.push_back( placed );
7198
7199 ORCAD_DESIGN design;
7200 design.sourceId = "source-matched-graphics";
7201 design.symbols.emplace( primary.name, primary );
7202 design.pages.push_back( std::move( page ) );
7203
7204 SETTINGS_MANAGER manager;
7205 manager.LoadProject( "" );
7206 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7207 schematic->SetProject( &manager.Prj() );
7208 convertRawDesign( design, *schematic );
7209
7210 const ORCAD_SYMBOL_DEF& converted = design.symbols.at( primary.name );
7211 BOOST_REQUIRE_EQUAL( converted.variants.size(), 1u );
7212 BOOST_REQUIRE_EQUAL( converted.variants.front().primitives.size(), 1u );
7213 BOOST_CHECK_EQUAL( converted.variants.front().primitives.front().x1, 10 );
7214 BOOST_CHECK_EQUAL( converted.variants.front().primitives.front().x2, 10 );
7215}
7216
7217
7218BOOST_AUTO_TEST_CASE( S593487_StackedPlacedPinsRemainStacked )
7219{
7220 ORCAD_SYMBOL_DEF cached;
7222 cached.name = "STACK.Normal";
7223 cached.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
7224 cached.pins.resize( 3 );
7225
7226 for( size_t i = 0; i < cached.pins.size(); ++i )
7227 {
7228 cached.pins[i].hotptX = static_cast<int>( i ) * 10;
7229 cached.pins[i].startX = cached.pins[i].hotptX;
7230 }
7231
7232 ORCAD_PLACED_INSTANCE placed;
7233 placed.pkgName = cached.name;
7234 placed.sourcePackage = "STACK";
7235 placed.reference = "U1";
7236 placed.x = 100;
7237 placed.y = 100;
7238 placed.pins = { ORCAD_PIN_INST{ 1, 120, 100 }, ORCAD_PIN_INST{ 2, 120, 100 } };
7239
7240 ORCAD_RAW_PAGE page;
7241 page.name = "STACKED";
7242 page.instances.push_back( placed );
7243
7244 ORCAD_DESIGN design;
7245 design.sourceId = "s593487-stacked";
7246 design.symbols.emplace( cached.name, cached );
7247 design.pages.push_back( std::move( page ) );
7248
7249 SETTINGS_MANAGER manager;
7250 manager.LoadProject( "" );
7251 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7252 schematic->SetProject( &manager.Prj() );
7253 SCH_SHEET* root = convertRawDesign( design, *schematic );
7255 path.push_back( root );
7256 SCH_SYMBOL* symbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "U1" ) );
7257 BOOST_REQUIRE( symbol );
7258
7259 std::map<wxString, VECTOR2I> positions;
7260 std::map<wxString, bool> visibility;
7261
7262 for( SCH_PIN* pin : symbol->GetPins() )
7263 {
7264 positions[pin->GetNumber()] = pin->GetPosition();
7265 visibility[pin->GetNumber()] = pin->IsVisible();
7266 }
7267
7268 BOOST_REQUIRE_EQUAL( positions.size(), 3u );
7269 BOOST_CHECK( positions[wxS( "1" )] == positions[wxS( "2" )] );
7270 BOOST_CHECK( positions[wxS( "1" )] != positions[wxS( "3" )] );
7271 BOOST_CHECK( visibility[wxS( "1" )] );
7272 BOOST_CHECK( visibility[wxS( "2" )] );
7273 BOOST_CHECK( visibility[wxS( "3" )] );
7274}
7275
7276
7277BOOST_AUTO_TEST_CASE( DistinctNetsOnStackedPinsRemainDistinct )
7278{
7279 ORCAD_SYMBOL_DEF stacked;
7280 stacked.typeId = ORCAD_ST_LIBRARY_PART;
7281 stacked.name = "STACKED.Normal";
7282 stacked.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
7283 stacked.pins = { ORCAD_SYMBOL_PIN{ .name = "1", .position = 0 }, ORCAD_SYMBOL_PIN{ .name = "2", .position = 1 } };
7284
7285 ORCAD_SYMBOL_DEF load;
7287 load.name = "LOAD.Normal";
7288 load.bbox = ORCAD_BBOX{ 0, 0, 10, 10 };
7289 load.pins.push_back( ORCAD_SYMBOL_PIN{ .name = "1", .position = 0 } );
7290
7291 auto makePlaced = []( const std::string& aPackage, const std::string& aReference, int aX,
7292 std::initializer_list<uint32_t> aNets )
7293 {
7294 ORCAD_PLACED_INSTANCE placed;
7295 placed.pkgName = aPackage;
7296 placed.sourcePackage = aPackage.substr( 0, aPackage.find( '.' ) );
7297 placed.reference = aReference;
7298 placed.x = aX;
7299
7300 int pinIndex = 1;
7301
7302 for( uint32_t net : aNets )
7303 placed.pins.push_back(
7304 ORCAD_PIN_INST{ .pinIndex = static_cast<int16_t>( pinIndex++ ), .x = aX, .wordB = net } );
7305
7306 return placed;
7307 };
7308
7309 ORCAD_RAW_PAGE page;
7310 page.name = "DISTINCT STACKED NETS";
7311 page.netmap = { { 1, "LEFT" }, { 2, "COMMON" }, { 3, "RIGHT" } };
7312 page.instances.push_back( makePlaced( stacked.name, "Q1", 100, { 1, 2 } ) );
7313 page.instances.push_back( makePlaced( stacked.name, "Q2", 200, { 3, 2 } ) );
7314 page.instances.push_back( makePlaced( load.name, "R1", 300, { 1 } ) );
7315 page.instances.push_back( makePlaced( load.name, "R2", 400, { 3 } ) );
7316
7317 ORCAD_DESIGN design;
7318 design.sourceId = "distinct-stacked-nets";
7319 design.symbols.emplace( stacked.name, std::move( stacked ) );
7320 design.symbols.emplace( load.name, std::move( load ) );
7321 design.pages.push_back( std::move( page ) );
7322
7323 SETTINGS_MANAGER manager;
7324 manager.LoadProject( "" );
7325 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7326 schematic->SetProject( &manager.Prj() );
7327 convertRawDesign( design, *schematic );
7328
7329 auto [consistent, checkable] =
7330 checkConnectivity( *schematic, { { terminalToken( "Q1", "1" ), terminalToken( "R1", "1" ) },
7331 { terminalToken( "Q2", "1" ), terminalToken( "R2", "1" ) },
7332 { terminalToken( "Q1", "2" ), terminalToken( "Q2", "2" ) } } );
7333 BOOST_CHECK_EQUAL( checkable, 3 );
7334 BOOST_CHECK_EQUAL( consistent, 3 );
7335}
7336
7337
7338BOOST_AUTO_TEST_CASE( S593487_ReorderedSignedPinsValidateExactly )
7339{
7340 ORCAD_SYMBOL_DEF cached;
7342 cached.name = "ORDER.Normal";
7343 cached.bbox = ORCAD_BBOX{ 0, 0, 30, 20 };
7344 cached.pins.resize( 4 );
7345
7346 for( size_t i = 0; i < cached.pins.size(); ++i )
7347 {
7348 cached.pins[i].hotptX = static_cast<int>( i ) * 10;
7349 cached.pins[i].startX = cached.pins[i].hotptX;
7350 }
7351
7352 ORCAD_PLACED_INSTANCE placed;
7353 placed.pkgName = cached.name;
7354 placed.sourcePackage = "ORDER";
7355 placed.reference = "U1";
7356 placed.x = 100;
7357 placed.y = 100;
7358 placed.pins = { ORCAD_PIN_INST{ -4, 130, 100 }, ORCAD_PIN_INST{ 2, 110, 100 }, ORCAD_PIN_INST{ 1, 100, 100 },
7359 ORCAD_PIN_INST{ 3, 120, 100 } };
7360
7361 ORCAD_RAW_PAGE page;
7362 page.name = "REORDERED";
7363 page.instances.push_back( placed );
7364
7365 ORCAD_DESIGN design;
7366 design.sourceId = "s593487-reordered";
7367 design.symbols.emplace( cached.name, cached );
7368 design.pages.push_back( std::move( page ) );
7369
7370 SETTINGS_MANAGER manager;
7371 manager.LoadProject( "" );
7372 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7373 schematic->SetProject( &manager.Prj() );
7375 SCH_SHEET* root = convertRawDesign( design, *schematic, &reporter );
7376 BOOST_CHECK( !reporter.GetMessages().Contains( wxS( "pin positions mismatch" ) ) );
7377
7379 path.push_back( root );
7380 SCH_SYMBOL* symbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "U1" ) );
7381 BOOST_REQUIRE( symbol );
7382 std::map<int, VECTOR2I> positions;
7383
7384 for( SCH_PIN* pin : symbol->GetPins() )
7385 positions[std::stoi( pin->GetNumber().ToStdString() )] = pin->GetPosition();
7386
7387 BOOST_REQUIRE_EQUAL( positions.size(), 4u );
7388
7389 for( int pin = 2; pin <= 4; ++pin )
7390 BOOST_CHECK_EQUAL( positions[pin].x - positions[1].x, ( pin - 1 ) * 10 * ORCAD_IU_PER_DBU );
7391
7392 SCH_NO_CONNECT* noConnect = nullptr;
7393
7394 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_NO_CONNECT_T ) )
7395 noConnect = static_cast<SCH_NO_CONNECT*>( item );
7396
7397 BOOST_REQUIRE( noConnect );
7398 BOOST_CHECK( noConnect->GetPosition() == positions[4] );
7399}
7400
7401
7402BOOST_AUTO_TEST_CASE( S593487_FittedVariantImportIsDeterministic )
7403{
7404 auto makeDesign = []
7405 {
7406 ORCAD_SYMBOL_DEF cached;
7408 cached.name = "DETERMINISTIC.Normal";
7409 cached.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
7410 cached.pins.resize( 3 );
7411
7412 for( size_t i = 0; i < cached.pins.size(); ++i )
7413 cached.pins[i].hotptX = static_cast<int>( i ) * 10;
7414
7415 ORCAD_PLACED_INSTANCE placed;
7416 placed.pkgName = cached.name;
7417 placed.sourcePackage = "DETERMINISTIC";
7418 placed.reference = "U1";
7419 placed.x = 100;
7420 placed.y = 100;
7421 placed.pins = { ORCAD_PIN_INST{ 1, 120, 100 }, ORCAD_PIN_INST{ -2, 120, 100 } };
7422
7423 ORCAD_RAW_PAGE page;
7424 page.name = "DETERMINISTIC";
7425 page.instances.push_back( placed );
7426 placed.reference = "U2";
7427 placed.x = 200;
7428 placed.y = 200;
7429 placed.pins = { ORCAD_PIN_INST{ 1, 220, 200 }, ORCAD_PIN_INST{ -2, 220, 200 } };
7430 page.instances.push_back( placed );
7431
7432 ORCAD_DESIGN design;
7433 design.sourceId = "s593487-deterministic";
7434 design.symbols.emplace( cached.name, cached );
7435 design.pages.push_back( std::move( page ) );
7436 return design;
7437 };
7438
7439 SETTINGS_MANAGER manager;
7440 manager.LoadProject( "" );
7441
7442 auto importSignature = [&]( ORCAD_DESIGN& aDesign )
7443 {
7444 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7445 schematic->SetProject( &manager.Prj() );
7446 SCH_SHEET* root = convertRawDesign( aDesign, *schematic );
7448 path.push_back( root );
7449 SCH_SYMBOL* symbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "U1" ) );
7450 BOOST_REQUIRE( symbol );
7451 std::string signature;
7452
7453 for( SCH_PIN* pin : symbol->GetPins() )
7454 {
7455 signature += pin->GetNumber().ToStdString() + "@" + std::to_string( pin->GetPosition().x ) + ","
7456 + std::to_string( pin->GetPosition().y ) + ";";
7457 }
7458
7459 signature += "variants=" + std::to_string( aDesign.symbols.at( "DETERMINISTIC.Normal" ).variants.size() );
7460 return signature;
7461 };
7462
7463 ORCAD_DESIGN first = makeDesign();
7464 ORCAD_DESIGN second = makeDesign();
7465 BOOST_CHECK_EQUAL( importSignature( first ), importSignature( second ) );
7466 BOOST_CHECK_EQUAL( first.symbols.at( "DETERMINISTIC.Normal" ).variants.size(), 1u );
7467 BOOST_CHECK_EQUAL( second.symbols.at( "DETERMINISTIC.Normal" ).variants.size(), 1u );
7468}
7469
7470
7471BOOST_AUTO_TEST_CASE( PartReferenceDisplayPreservesUnitDesignator )
7472{
7473 ORCAD_SYMBOL_DEF definition;
7474 definition.typeId = ORCAD_ST_LIBRARY_PART;
7475 definition.name = "MULTIPARTA.Normal";
7476 definition.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
7477
7478 ORCAD_PACKAGE package;
7479 package.name = "MULTIPART";
7480 package.devices.push_back( ORCAD_DEVICE{ .unitRef = "A" } );
7481
7482 ORCAD_PLACED_INSTANCE placed;
7483 placed.pkgName = definition.name;
7484 placed.sourcePackage = package.name;
7485 placed.reference = "U14";
7486 placed.value = "74HC125";
7487 placed.x = 100;
7488 placed.y = 100;
7489 placed.props["Reference"] = "U14";
7490 placed.displayProps = {
7491 ORCAD_DISPLAY_PROP{ .name = "Part Reference", .x = 30, .y = 10, .fontIdx = 0, .dispMode = 0x101 },
7492 ORCAD_DISPLAY_PROP{ .name = "Value", .x = 30, .y = 20, .fontIdx = 0, .dispMode = 0x101 },
7493 };
7494
7495 ORCAD_RAW_PAGE page;
7496 page.name = "MULTIPART REFERENCE";
7497 page.instances.push_back( placed );
7498
7499 ORCAD_DESIGN design;
7500 design.sourceId = "multipart-reference";
7501 design.symbols.emplace( definition.name, std::move( definition ) );
7502 design.packages.emplace( package.name, std::move( package ) );
7503 design.pages.push_back( std::move( page ) );
7504
7505 SETTINGS_MANAGER manager;
7506 manager.LoadProject( "" );
7507 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7508 schematic->SetProject( &manager.Prj() );
7509 SCH_SHEET* root = convertRawDesign( design, *schematic );
7511 path.push_back( root );
7512
7513 SCH_SYMBOL* symbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "U14" ) );
7514 BOOST_REQUIRE( symbol );
7515 BOOST_CHECK_EQUAL( symbol->GetRef( &path, false ), wxS( "U14" ) );
7516 BOOST_CHECK( !symbol->GetField( FIELD_T::REFERENCE )->IsVisible() );
7517
7518 SCH_FIELD* displayedReference = symbol->GetField( wxS( "Part Reference" ) );
7519 BOOST_REQUIRE( displayedReference );
7520 BOOST_CHECK_EQUAL( displayedReference->GetText(), wxS( "U14A" ) );
7521 BOOST_CHECK( displayedReference->IsVisible() );
7522 VECTOR2I pageOffset = symbol->GetPosition() - OrcadDbuToIu( placed.x, placed.y );
7523 BOOST_CHECK_EQUAL( displayedReference->GetPosition().x, OrcadDbuToIu( 130, 110 ).x + pageOffset.x );
7524 BOOST_CHECK_EQUAL( displayedReference->GetPosition().y,
7525 OrcadDbuToIu( 130, 110 ).y + pageOffset.y
7526 + OrcadTextBaselineOffset( displayedReference->GetTextHeight() ) );
7527}
7528
7529
7530BOOST_AUTO_TEST_CASE( OccurrenceReferenceOverridesDisplayedTemplateReference )
7531{
7532 ORCAD_SYMBOL_DEF definition;
7533 definition.typeId = ORCAD_ST_LIBRARY_PART;
7534 definition.name = "TESTPOINT.Normal";
7535 definition.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
7536 definition.props["Part Reference"] = "TP119";
7537
7538 ORCAD_PACKAGE package;
7539 package.name = "TESTPOINT";
7540 package.devices.push_back( ORCAD_DEVICE{} );
7541
7542 ORCAD_PLACED_INSTANCE placed;
7543 placed.dbId = 42;
7544 placed.pkgName = definition.name;
7545 placed.sourcePackage = package.name;
7546 placed.reference = "TP119";
7547 placed.x = 100;
7548 placed.y = 100;
7549 placed.displayProps = {
7550 ORCAD_DISPLAY_PROP{ .name = "Part Reference", .x = 30, .y = 10, .fontIdx = 0, .dispMode = 0x101 },
7551 };
7552
7553 ORCAD_RAW_PAGE page;
7554 page.name = "OCCURRENCE REFERENCE";
7555 page.instances.push_back( placed );
7556
7557 ORCAD_DESIGN design;
7558 design.sourceId = "occurrence-reference-display";
7559 design.symbols.emplace( definition.name, std::move( definition ) );
7560 design.packages.emplace( package.name, std::move( package ) );
7561 design.pages.push_back( std::move( page ) );
7562 design.occurrenceRoot.partRefs[42] = "TP17";
7563
7564 SETTINGS_MANAGER manager;
7565 manager.LoadProject( "" );
7566 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7567 schematic->SetProject( &manager.Prj() );
7568 SCH_SHEET* root = convertRawDesign( design, *schematic );
7570 path.push_back( root );
7571
7572 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "TP17" ) );
7573 BOOST_REQUIRE( converted );
7574 BOOST_CHECK_EQUAL( converted->GetRef( &path, false ), wxS( "TP17" ) );
7575 BOOST_REQUIRE( converted->GetField( FIELD_T::REFERENCE ) );
7576 BOOST_CHECK( converted->GetField( FIELD_T::REFERENCE )->IsVisible() );
7577
7578 SCH_FIELD* templateReference = converted->GetField( wxS( "Part Reference" ) );
7579 BOOST_CHECK( !templateReference || !templateReference->IsVisible() );
7580}
7581
7582
7583BOOST_AUTO_TEST_CASE( SingleUnitReferenceDoesNotUseCacheNameAsUnitDesignator )
7584{
7585 ORCAD_SYMBOL_DEF connectorDefinition;
7586 connectorDefinition.typeId = ORCAD_ST_LIBRARY_PART;
7587 connectorDefinition.name = "BANANA, KEYSTONE-575-4.Normal";
7588 connectorDefinition.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
7589
7590 ORCAD_PACKAGE connectorPackage;
7591 connectorPackage.name = "BANANA_KEYSTONE-575-4";
7592 connectorPackage.devices.push_back( ORCAD_DEVICE{} );
7593
7594 ORCAD_PLACED_INSTANCE connector;
7595 connector.pkgName = connectorDefinition.name;
7596 connector.sourcePackage = connectorPackage.name;
7597 connector.reference = "J1";
7598 connector.x = 100;
7599 connector.y = 100;
7600 connector.displayProps = {
7601 ORCAD_DISPLAY_PROP{ .name = "Part Reference", .x = 30, .fontIdx = 0, .dispMode = 0x100 },
7602 };
7603
7604 ORCAD_SYMBOL_DEF padDefinition;
7605 padDefinition.typeId = ORCAD_ST_LIBRARY_PART;
7606 padDefinition.name = "PAD_2.Normal";
7607 padDefinition.bbox = ORCAD_BBOX{ 0, 0, 20, 20 };
7608
7609 ORCAD_PACKAGE padPackage;
7610 padPackage.name = "PAD_2";
7611 padPackage.devices.push_back( ORCAD_DEVICE{} );
7612
7614 pad.pkgName = padDefinition.name;
7615 pad.sourcePackage = padPackage.name;
7616 pad.reference = "SW";
7617 pad.x = 200;
7618 pad.y = 200;
7619 pad.displayProps = {
7620 ORCAD_DISPLAY_PROP{ .name = "Part Reference", .y = 26, .fontIdx = 0, .dispMode = 0x000 },
7621 ORCAD_DISPLAY_PROP{ .name = "Reference", .x = 5, .y = 15, .fontIdx = 2, .dispMode = 0x100 },
7622 };
7623
7624 ORCAD_RAW_PAGE page;
7625 page.name = "SINGLE UNIT REFERENCE";
7626 page.instances = { connector, pad };
7627
7628 ORCAD_DESIGN design;
7629 design.sourceId = "single-unit-reference";
7630 design.symbols.emplace( connectorDefinition.name, std::move( connectorDefinition ) );
7631 design.symbols.emplace( padDefinition.name, std::move( padDefinition ) );
7632 design.packages.emplace( connectorPackage.name, std::move( connectorPackage ) );
7633 design.packages.emplace( padPackage.name, std::move( padPackage ) );
7634 design.pages.push_back( std::move( page ) );
7635
7636 SETTINGS_MANAGER manager;
7637 manager.LoadProject( "" );
7638 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7639 schematic->SetProject( &manager.Prj() );
7640 SCH_SHEET* root = convertRawDesign( design, *schematic );
7642 path.push_back( root );
7643
7644 SCH_SYMBOL* convertedConnector = findConvertedSymbol( *root->GetScreen(), path, wxS( "J1" ) );
7645 BOOST_REQUIRE( convertedConnector );
7646 SCH_FIELD* connectorReference = convertedConnector->GetField( FIELD_T::REFERENCE );
7647 BOOST_REQUIRE( connectorReference );
7648 BOOST_CHECK( connectorReference->IsVisible() );
7649 BOOST_CHECK_EQUAL( convertedConnector->GetRef( &path, false ), wxS( "J1" ) );
7650 BOOST_CHECK( !convertedConnector->GetField( wxS( "Part Reference" ) ) );
7651
7652 SCH_SYMBOL* convertedPad = findConvertedSymbol( *root->GetScreen(), path, wxS( "SW" ) );
7653 BOOST_REQUIRE( convertedPad );
7654 SCH_FIELD* padReference = convertedPad->GetField( FIELD_T::REFERENCE );
7655 BOOST_REQUIRE( padReference );
7656 BOOST_CHECK( padReference->IsVisible() );
7657 BOOST_CHECK_EQUAL( convertedPad->GetRef( &path, false ), wxS( "SW" ) );
7658}
7659
7660
7661BOOST_AUTO_TEST_CASE( S593487_RT44DisplayedFootprintAndPassivePins )
7662{
7663 ORCAD_SYMBOL_DEF thermistor;
7664 thermistor.typeId = ORCAD_ST_LIBRARY_PART;
7665 thermistor.name = "THERMISTOR_2.Normal";
7666 thermistor.bbox = ORCAD_BBOX{ 0, 0, 20, 30 };
7667 thermistor.pins.resize( 2 );
7668 thermistor.pins[0].position = 0;
7669 thermistor.pins[0].startX = 10;
7670 thermistor.pins[0].hotptX = 10;
7671 thermistor.pins[0].hotptY = -10;
7672 thermistor.pins[0].portType = ORCAD_PORT_TYPE::PASSIVE;
7673 thermistor.pins[1].position = 1;
7674 thermistor.pins[1].startX = 10;
7675 thermistor.pins[1].startY = 30;
7676 thermistor.pins[1].hotptX = 10;
7677 thermistor.pins[1].hotptY = 40;
7678 thermistor.pins[1].portType = ORCAD_PORT_TYPE::PASSIVE;
7679 thermistor.primitives.push_back( ORCAD_PRIMITIVE{
7680 .kind = ORCAD_PRIM_KIND::TEXT, .x1 = 12, .y1 = -8, .x2 = 30, .y2 = 7, .text = "t", .fontIdx = 1 } );
7681 thermistor.primitives.push_back(
7682 ORCAD_PRIMITIVE{ .kind = ORCAD_PRIM_KIND::ELLIPSE, .x1 = 16, .y1 = 1, .x2 = 18, .y2 = 3 } );
7683 thermistor.props["2ND PART FIELD"] = "%";
7684
7685 ORCAD_PACKAGE package;
7686 package.name = "THERMISTOR_2";
7687 package.refDes = "RT";
7688 package.pcbFootprint = "2920";
7689 package.devices.push_back( ORCAD_DEVICE{ .pinNumbers = { "1", "2" }, .pinIgnore = { false, false } } );
7690
7691 ORCAD_PLACED_INSTANCE placed;
7692 placed.pkgName = thermistor.name;
7693 placed.sourcePackage = package.name;
7694 placed.reference = "RT44";
7695 placed.value = "3A";
7696 placed.x = 70;
7697 placed.y = 460;
7698 placed.rotation = 1;
7699 placed.props["Assembly"] = "Fitted";
7700 placed.props["Datasheet"] = "rt44.pdf";
7701 placed.props["2nd Part Field"] = "(OPT)";
7702 placed.displayProps = {
7703 ORCAD_DISPLAY_PROP{ .name = "Part Reference", .x = -20, .y = 10, .fontIdx = 0, .dispMode = 0x101 },
7704 ORCAD_DISPLAY_PROP{ .name = "Reference", .x = 30, .y = 14, .fontIdx = 2, .dispMode = 0x001 },
7705 ORCAD_DISPLAY_PROP{ .name = "Value", .x = 30, .y = 20, .fontIdx = 0, .dispMode = 0x101 },
7706 ORCAD_DISPLAY_PROP{ .name = "PCB Footprint", .x = -20, .y = 20, .fontIdx = 2, .dispMode = 0x101 },
7707 ORCAD_DISPLAY_PROP{ .name = "2nd Part Field", .x = -20, .y = 30, .fontIdx = 0, .dispMode = 0x101 },
7708 };
7709 placed.pins = { ORCAD_PIN_INST{ 1, 60, 470 }, ORCAD_PIN_INST{ 2, 110, 470 } };
7710
7711 ORCAD_RAW_PAGE page;
7712 page.name = "RT44 FIDELITY";
7713 page.instances.push_back( placed );
7714
7715 ORCAD_PLACED_INSTANCE resistor = placed;
7716 resistor.reference = "R31";
7717 resistor.value = "100k";
7718 resistor.x = 900;
7719 resistor.y = 690;
7720 resistor.rotation = 0;
7721 resistor.displayProps = {
7722 ORCAD_DISPLAY_PROP{ .name = "Reference", .x = 20, .fontIdx = 0, .dispMode = 0x101 },
7723 ORCAD_DISPLAY_PROP{ .name = "Value", .x = 20, .y = 10, .fontIdx = 3, .dispMode = 0x101 },
7724 ORCAD_DISPLAY_PROP{ .name = "PCB Footprint", .x = 20, .y = 20, .fontIdx = 4, .dispMode = 0x101 },
7725 };
7726 resistor.pins = { ORCAD_PIN_INST{ 1, 900, 680 }, ORCAD_PIN_INST{ 2, 900, 730 } };
7727 page.instances.push_back( resistor );
7728
7729 ORCAD_SYMBOL_DEF q9Definition = thermistor;
7730 q9Definition.name = "IRF7416_0.Normal";
7731 q9Definition.generalFlags = 0;
7732 ORCAD_PLACED_INSTANCE q9 = placed;
7733 q9.pkgName = q9Definition.name;
7734 q9.sourcePackage.clear();
7735 q9.reference = "Q9";
7736 q9.x = 1200;
7737 q9.y = 500;
7738 q9.rotation = 0;
7739 q9.pins = { ORCAD_PIN_INST{ -1, 1210, 490, 1, 0 }, ORCAD_PIN_INST{ -2, 1210, 540 } };
7740 page.instances.push_back( q9 );
7741
7742 ORCAD_SYMBOL_DEF connectorDefinition = thermistor;
7743 connectorDefinition.name = "CON24_46.Normal";
7744 connectorDefinition.generalFlags = 5;
7745 ORCAD_PACKAGE connectorPackage = package;
7746 connectorPackage.name = "CON24_46";
7747 connectorPackage.refDes = "J";
7748 connectorPackage.devices.front().pinNumbers = { "1", "2" };
7749 ORCAD_PLACED_INSTANCE connector = placed;
7750 connector.pkgName = connectorDefinition.name;
7751 connector.sourcePackage = connectorPackage.name;
7752 connector.reference = "JCA3";
7753 connector.x = 1400;
7754 connector.y = 500;
7755 connector.rotation = 0;
7756 connector.pins = { ORCAD_PIN_INST{ -1, 1410, 490, 1, 0 }, ORCAD_PIN_INST{ -2, 1410, 540, 1, 0 } };
7757 page.instances.push_back( connector );
7758
7759 ORCAD_PLACED_INSTANCE mirrored = placed;
7760 mirrored.reference = "RTM";
7761 mirrored.x = 1600;
7762 mirrored.y = 500;
7763 mirrored.mirror = true;
7764 mirrored.pins = { ORCAD_PIN_INST{ 1, 1590, 510 }, ORCAD_PIN_INST{ 2, 1640, 510 } };
7765 page.instances.push_back( mirrored );
7766
7767 ORCAD_SYMBOL_DEF blankMappedDefinition = thermistor;
7768 blankMappedDefinition.name = "BLANK_MAPPED.Normal";
7769 blankMappedDefinition.generalFlags = 0;
7770 blankMappedDefinition.pins[0].name = "1";
7771 blankMappedDefinition.pins[1].name = "2";
7772 ORCAD_PACKAGE blankMappedPackage = package;
7773 blankMappedPackage.name = "BLANK_MAPPED";
7774 blankMappedPackage.devices.front().pinNumbers = { "", "" };
7775 ORCAD_PLACED_INSTANCE blankMapped = placed;
7776 blankMapped.pkgName = blankMappedDefinition.name;
7777 blankMapped.sourcePackage = blankMappedPackage.name;
7778 blankMapped.reference = "RBLANK";
7779 blankMapped.x = 1800;
7780 blankMapped.y = 500;
7781 blankMapped.pins = { ORCAD_PIN_INST{ 1, 1810, 490 }, ORCAD_PIN_INST{ 2, 1810, 540 } };
7782 page.instances.push_back( blankMapped );
7783
7784 ORCAD_SYMBOL_DEF dualPinTextDefinition = blankMappedDefinition;
7785 dualPinTextDefinition.name = "DUAL_PIN_TEXT.Normal";
7786 dualPinTextDefinition.generalFlags = 1;
7787 ORCAD_PACKAGE dualPinTextPackage = blankMappedPackage;
7788 dualPinTextPackage.name = "DUAL_PIN_TEXT";
7789 dualPinTextPackage.devices.front().pinNumbers = { "1", "2" };
7790 ORCAD_PLACED_INSTANCE dualPinText = blankMapped;
7791 dualPinText.pkgName = dualPinTextDefinition.name;
7792 dualPinText.sourcePackage = dualPinTextPackage.name;
7793 dualPinText.reference = "JDUAL";
7794 dualPinText.x = 2000;
7795 dualPinText.pins = { ORCAD_PIN_INST{ 1, 2010, 490 }, ORCAD_PIN_INST{ 2, 2010, 540 } };
7796 page.instances.push_back( dualPinText );
7797
7798 ORCAD_GRAPHIC_INST comment;
7800 comment.color = 48;
7801 comment.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
7802 comment.nested->primitives.push_back( ORCAD_PRIMITIVE{
7803 .kind = ORCAD_PRIM_KIND::TEXT, .x1 = 840, .y1 = 410, .x2 = 859, .y2 = 426,
7804 .text = ".5W\nNEXT", .fontIdx = 1 } );
7805 page.graphics.push_back( std::move( comment ) );
7806
7807 ORCAD_GRAPHIC_INST ercMarker;
7808 ercMarker.typeId = ORCAD_ST_ERC_OBJECT;
7809 ercMarker.name = "ERC";
7810 ercMarker.x = 2200;
7811 ercMarker.y = 500;
7812 page.ercObjects.push_back( std::move( ercMarker ) );
7813
7814 ORCAD_DESIGN design;
7815 design.sourceId = "s593487-rt44-fidelity";
7816 design.library.fonts.push_back( ORCAD_FONT{ .height = -9, .face = "Arial" } );
7817 design.library.fonts.push_back(
7818 ORCAD_FONT{ .height = -9, .width = 4, .pitchAndFamily = 0x31, .face = "Courier New" } );
7819 design.library.fonts.push_back( ORCAD_FONT{ .height = -9, .face = "Arial" } );
7820 design.library.fonts.push_back( ORCAD_FONT{ .height = -9, .face = "Arial", .italic = true } );
7821 design.symbols.emplace( thermistor.name, thermistor );
7822 design.symbols.emplace( q9Definition.name, q9Definition );
7823 design.symbols.emplace( connectorDefinition.name, connectorDefinition );
7824 design.symbols.emplace( blankMappedDefinition.name, blankMappedDefinition );
7825 design.symbols.emplace( dualPinTextDefinition.name, dualPinTextDefinition );
7826 design.packages.emplace( package.name, package );
7827 design.packages.emplace( connectorPackage.name, connectorPackage );
7828 design.packages.emplace( blankMappedPackage.name, blankMappedPackage );
7829 design.packages.emplace( dualPinTextPackage.name, dualPinTextPackage );
7830
7831 std::vector<uint8_t> packageStream;
7832 appendLe16( packageStream, 1 );
7833 packageStream.push_back( ORCAD_ST_PART_CELL );
7834 appendLe16( packageStream, 0 );
7835 appendLzt( packageStream, "THERMISTOR_2" );
7836 appendLzt( packageStream, "" );
7837 appendLe16( packageStream, 1 );
7838 appendLzt( packageStream, "THERMISTOR_2.Normal" );
7839 appendLe16( packageStream, 1 );
7840 packageStream.push_back( ORCAD_ST_LIBRARY_PART );
7841 appendLe16( packageStream, 0 );
7842 appendLzt( packageStream, "THERMISTOR_2.Normal" );
7843 appendLzt( packageStream, "" );
7844 appendLe32( packageStream, 0 );
7845 appendLe16( packageStream, 0 );
7846
7847 for( int coordinate : { 0, 0, 20, 30 } )
7848 appendLe16( packageStream, static_cast<uint16_t>( coordinate ) );
7849
7850 appendLe16( packageStream, 0 );
7851 appendLe16( packageStream, 0 );
7852 appendLzt( packageStream, "" );
7853 appendLzt( packageStream, "" );
7854 appendLzt( packageStream, "RT" );
7855 appendLzt( packageStream, "" );
7856 appendLe16( packageStream, 6 );
7857 packageStream.push_back( ORCAD_ST_PACKAGE );
7858 appendLe16( packageStream, 0 );
7859 appendLzt( packageStream, "THERMISTOR_2" );
7860 appendLzt( packageStream, "" );
7861 appendLzt( packageStream, "RT" );
7862 appendLzt( packageStream, "" );
7863 appendLzt( packageStream, "2920" );
7864 appendLe16( packageStream, 0 );
7865
7866 std::map<std::string, ORCAD_SYMBOL_DEF> packageSymbols;
7867 std::map<std::string, ORCAD_PACKAGE> packageDefinitions;
7868 OrcadParseOlbPackageStreamV2( std::vector<char>( packageStream.begin(), packageStream.end() ), {}, packageSymbols,
7869 packageDefinitions );
7870 OrcadMergeSymbolGeneralProperties( design.symbols, packageSymbols );
7871 design.pages.push_back( std::move( page ) );
7872
7873 SETTINGS_MANAGER manager;
7874 manager.LoadProject( "" );
7875 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
7876 schematic->SetProject( &manager.Prj() );
7877 SCH_SHEET* root = convertRawDesign( design, *schematic );
7879 path.push_back( root );
7880
7881 const SCH_TEXT* commentText = nullptr;
7882
7883 for( const SCH_ITEM* item : root->GetScreen()->Items() )
7884 {
7885 if( item->Type() == SCH_TEXT_T
7886 && static_cast<const SCH_TEXT*>( item )->GetText() == wxS( ".5W\nNEXT" ) )
7887 commentText = static_cast<const SCH_TEXT*>( item );
7888 }
7889
7890 BOOST_REQUIRE( commentText );
7891 BOOST_CHECK_CLOSE( commentText->GetLineSpacing(), 5.0 / 6.0, 0.1 );
7892 BOOST_CHECK_EQUAL( commentText->GetInterline( nullptr ), schIUScale.mmToIU( 9.0 * 25.4 / 96.0 ) );
7893 BOOST_CHECK_EQUAL( commentText->GetPosition().x, OrcadDbuToIu( 900, 470 ).x );
7894 BOOST_CHECK_EQUAL( commentText->GetPosition().y,
7895 OrcadDbuToIu( 900, 470 ).y
7896 + OrcadTextBaselineOffset( schIUScale.mmToIU( 1.70 ) ) );
7897
7898 SCH_SYMBOL* symbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "RT44" ) );
7899 BOOST_REQUIRE( symbol );
7900 BOOST_CHECK( !symbol->GetShowPinNumbers() );
7901 BOOST_CHECK_EQUAL( symbol->GetField( FIELD_T::DATASHEET )->GetText(), wxS( "rt44.pdf" ) );
7902
7903 const SCH_TEXT* cueText = nullptr;
7904 const SCH_SHAPE* cueDegree = nullptr;
7905
7906 for( const SCH_ITEM& item : symbol->GetLibSymbolRef()->GetDrawItems() )
7907 {
7908 if( item.Type() == SCH_TEXT_T )
7909 cueText = static_cast<const SCH_TEXT*>( &item );
7910 else if( item.Type() == SCH_SHAPE_T && static_cast<const SCH_SHAPE&>( item ).GetShape() == SHAPE_T::CIRCLE )
7911 cueDegree = static_cast<const SCH_SHAPE*>( &item );
7912 }
7913
7914 BOOST_REQUIRE( cueText );
7915 BOOST_REQUIRE( cueDegree );
7916 int cueBaseline = KiROUND( cueText->GetTextSize().y * 8.0 / 21.0 );
7917 BOOST_CHECK_EQUAL( cueText->GetText(), wxS( "t" ) );
7918 BOOST_CHECK( cueText->GetDrawRotation() == ANGLE_VERTICAL );
7919 VECTOR2I cueTextPage = symbol->GetTransform().TransformCoordinate( cueText->GetPosition() ) + symbol->GetPosition();
7920 VECTOR2I pageOffset = symbol->GetPosition() - OrcadDbuToIu( placed.x, placed.y + 20 );
7921 VECTOR2I expectedCuePage = OrcadDbuToIu( placed.x + 21, placed.y ) + pageOffset + VECTOR2I( 0, cueBaseline );
7922 BOOST_CHECK_EQUAL( cueTextPage.x, expectedCuePage.x );
7923 BOOST_CHECK_EQUAL( cueTextPage.y, expectedCuePage.y );
7924 BOX2I cueTextBox = symbol->GetTransform().TransformCoordinate( cueText->GetBoundingBox() );
7925 int degreeRadius = cueDegree->GetRadius();
7926 VECTOR2I cueDegreePage = symbol->GetTransform().TransformCoordinate( cueDegree->GetPosition() );
7927 BOOST_CHECK_EQUAL( cueDegreePage.x, cueTextBox.GetRight() + degreeRadius + ORCAD_IU_PER_DBU );
7928 BOOST_CHECK_EQUAL( cueDegreePage.y, cueTextBox.Centre().y );
7929
7930 SCH_FIELD* reference = symbol->GetField( FIELD_T::REFERENCE );
7931 SCH_FIELD* value = symbol->GetField( FIELD_T::VALUE );
7932 SCH_FIELD* footprint = symbol->GetField( wxS( "OrCAD Footprint" ) );
7933 BOOST_REQUIRE( footprint );
7934 int fieldBaseline = KiROUND( schIUScale.mmToIU( 1.70 ) * 8.0 / 21.0 );
7935 int valueBaseline = fieldBaseline;
7936 BOOST_CHECK_EQUAL( reference->GetPosition().x, OrcadDbuToIu( 50, 470 ).x + pageOffset.x );
7937 BOOST_CHECK_EQUAL( reference->GetPosition().y, OrcadDbuToIu( 50, 470 ).y + pageOffset.y + fieldBaseline );
7938 BOOST_CHECK_EQUAL( value->GetPosition().x, OrcadDbuToIu( 100, 480 ).x + pageOffset.x );
7939 BOOST_CHECK_EQUAL( value->GetPosition().y, OrcadDbuToIu( 100, 480 ).y + pageOffset.y + valueBaseline );
7940 BOOST_CHECK_EQUAL( footprint->GetPosition().x, OrcadDbuToIu( 50, 480 ).x + pageOffset.x );
7941 BOOST_CHECK_EQUAL( footprint->GetPosition().y, OrcadDbuToIu( 50, 480 ).y + pageOffset.y + fieldBaseline );
7942 BOOST_CHECK( reference->GetDrawRotation() == ANGLE_HORIZONTAL );
7943 BOOST_CHECK( value->GetDrawRotation() == ANGLE_HORIZONTAL );
7944 BOOST_CHECK( footprint->GetDrawRotation() == ANGLE_HORIZONTAL );
7945 BOOST_CHECK_EQUAL( reference->GetBoundingBox().GetOrigin().x, OrcadDbuToIu( 50, 470 ).x + pageOffset.x );
7946 BOOST_CHECK_LE(
7947 std::abs( reference->GetBoundingBox().GetOrigin().y - ( OrcadDbuToIu( 50, 470 ).y + pageOffset.y ) ),
7948 schIUScale.mmToIU( 0.7 ) );
7949 BOOST_CHECK_EQUAL( value->GetBoundingBox().GetOrigin().x, OrcadDbuToIu( 100, 480 ).x + pageOffset.x );
7950 BOOST_CHECK_LE( std::abs( value->GetBoundingBox().GetOrigin().y - ( OrcadDbuToIu( 100, 480 ).y + pageOffset.y ) ),
7951 schIUScale.mmToIU( 0.7 ) );
7952 BOOST_CHECK_EQUAL( footprint->GetBoundingBox().GetOrigin().x, OrcadDbuToIu( 50, 480 ).x + pageOffset.x );
7953 BOOST_CHECK_LE(
7954 std::abs( footprint->GetBoundingBox().GetOrigin().y - ( OrcadDbuToIu( 50, 480 ).y + pageOffset.y ) ),
7955 schIUScale.mmToIU( 0.7 ) );
7962 BOOST_CHECK( reference->IsVisible() );
7963 BOOST_CHECK( value->IsVisible() );
7964 BOOST_CHECK( footprint->IsVisible() );
7965 BOOST_CHECK_EQUAL( footprint->GetText(), wxS( "2920" ) );
7966 int sourceFontSize = schIUScale.mmToIU( 1.70 );
7967 int sourceFontWidth = schIUScale.mmToIU( 0.95 );
7968 BOOST_CHECK_EQUAL( reference->GetTextSize().x, sourceFontSize );
7969 BOOST_CHECK_EQUAL( reference->GetTextSize().y, sourceFontSize );
7970 BOOST_CHECK_EQUAL( value->GetTextSize().x, sourceFontSize );
7971 BOOST_CHECK_EQUAL( value->GetTextSize().y, sourceFontSize );
7972 BOOST_CHECK_EQUAL( footprint->GetTextSize().x, sourceFontWidth );
7973 BOOST_CHECK_EQUAL( footprint->GetTextSize().y, sourceFontSize );
7974 BOOST_REQUIRE( reference->GetFont() );
7975 BOOST_REQUIRE( value->GetFont() );
7976 BOOST_REQUIRE( footprint->GetFont() );
7977 BOOST_CHECK_EQUAL( reference->GetFont()->GetName(), wxS( "Arial" ) );
7978 BOOST_CHECK_EQUAL( value->GetFont()->GetName(), wxS( "Arial" ) );
7979 BOOST_CHECK_EQUAL( footprint->GetFont()->GetName(), wxS( "Courier New" ) );
7980 BOOST_CHECK( !reference->IsItalic() );
7981 BOOST_CHECK( !value->IsItalic() );
7982 BOOST_CHECK( !footprint->IsItalic() );
7983
7984 size_t secondPartFields = std::count_if(
7985 symbol->GetFields().begin(), symbol->GetFields().end(),
7986 []( const SCH_FIELD& aField ) { return aField.GetName().CmpNoCase( wxS( "2nd Part Field" ) ) == 0; } );
7987 BOOST_CHECK_EQUAL( secondPartFields, 1u );
7988 SCH_FIELD* secondPartField = symbol->GetField( wxS( "2ND PART FIELD" ) );
7989 BOOST_REQUIRE( secondPartField );
7990 BOOST_CHECK_EQUAL( secondPartField->GetText(), wxS( "(OPT)" ) );
7991 BOOST_CHECK( secondPartField->IsVisible() );
7992
7993 SCH_SYMBOL* r31 = findConvertedSymbol( *root->GetScreen(), path, wxS( "R31" ) );
7994 BOOST_REQUIRE( r31 );
7995 BOOST_CHECK_NE( symbol->GetLibSymbolRef().get(), r31->GetLibSymbolRef().get() );
7996 const SCH_TEXT* r31Cue = nullptr;
7997
7998 for( const SCH_ITEM& item : r31->GetLibSymbolRef()->GetDrawItems() )
7999 {
8000 if( item.Type() == SCH_TEXT_T )
8001 r31Cue = static_cast<const SCH_TEXT*>( &item );
8002 }
8003
8004 BOOST_REQUIRE( r31Cue );
8005 int r31CueBaseline = KiROUND( r31Cue->GetTextSize().y * 8.0 / 21.0 );
8006 VECTOR2I r31CuePage = r31->GetTransform().TransformCoordinate( r31Cue->GetPosition() ) + r31->GetPosition();
8007 VECTOR2I expectedR31CuePage =
8008 OrcadDbuToIu( resistor.x + 21, resistor.y ) + pageOffset + VECTOR2I( 0, r31CueBaseline );
8009 BOOST_CHECK_EQUAL( r31CuePage.x, expectedR31CuePage.x );
8010 BOOST_CHECK_EQUAL( r31CuePage.y, expectedR31CuePage.y );
8011 SCH_FIELD* r31Value = r31->GetField( FIELD_T::VALUE );
8012 SCH_FIELD* r31Reference = r31->GetField( FIELD_T::REFERENCE );
8013 SCH_FIELD* r31Footprint = r31->GetField( wxS( "OrCAD Footprint" ) );
8014 BOOST_REQUIRE( r31Footprint );
8015 BOOST_CHECK( r31Reference->IsVisible() );
8016 BOOST_CHECK_EQUAL( r31Reference->GetPosition().x, OrcadDbuToIu( 920, 690 ).x + pageOffset.x );
8017 BOOST_CHECK_EQUAL( r31Reference->GetPosition().y,
8018 OrcadDbuToIu( 920, 690 ).y + pageOffset.y + fieldBaseline );
8019 BOOST_CHECK_EQUAL( r31Value->GetFont()->GetName(), wxS( "Arial" ) );
8020 BOOST_CHECK_EQUAL( r31Footprint->GetFont()->GetName(), wxS( "Arial" ) );
8021 BOOST_CHECK( !r31Value->IsItalic() );
8022 BOOST_CHECK( r31Footprint->IsItalic() );
8023 BOOST_CHECK_EQUAL( r31Value->GetTextSize().x, sourceFontSize );
8024 BOOST_CHECK_EQUAL( r31Footprint->GetTextSize().x, sourceFontSize );
8025 BOOST_CHECK_EQUAL( r31Value->GetBoundingBox().GetOrigin().x, r31Value->GetPosition().x );
8026 BOOST_CHECK_EQUAL( r31Footprint->GetBoundingBox().GetOrigin().x, r31Footprint->GetPosition().x );
8027
8028 SCH_SYMBOL* q9Symbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "Q9" ) );
8029 BOOST_REQUIRE( q9Symbol );
8030 BOOST_CHECK( !q9Symbol->GetShowPinNames() );
8031 BOOST_CHECK( q9Symbol->GetShowPinNumbers() );
8032 std::set<VECTOR2I> noConnects;
8033
8034 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_NO_CONNECT_T ) )
8035 noConnects.insert( item->GetPosition() );
8036
8037 BOOST_CHECK_EQUAL( noConnects.size(), 1u );
8038
8039 SCH_SYMBOL* jca3 = findConvertedSymbol( *root->GetScreen(), path, wxS( "JCA3" ) );
8040 BOOST_REQUIRE( jca3 );
8041 BOOST_CHECK( jca3->GetShowPinNames() );
8042 BOOST_CHECK( !jca3->GetShowPinNumbers() );
8043 BOOST_REQUIRE_EQUAL( jca3->GetPins().size(), 2u );
8044 BOOST_CHECK_EQUAL( jca3->GetPins()[0]->GetName(), jca3->GetPins()[0]->GetNumber() );
8045 BOOST_CHECK_EQUAL( jca3->GetPins()[1]->GetName(), jca3->GetPins()[1]->GetNumber() );
8046
8047 SCH_SYMBOL* blankMappedSymbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "RBLANK" ) );
8048 BOOST_REQUIRE( blankMappedSymbol );
8049 BOOST_REQUIRE_EQUAL( blankMappedSymbol->GetPins().size(), 2u );
8050 BOOST_CHECK_EQUAL( blankMappedSymbol->GetPins()[0]->GetNumber(), wxS( "1" ) );
8051 BOOST_CHECK_EQUAL( blankMappedSymbol->GetPins()[1]->GetNumber(), wxS( "2" ) );
8052 size_t visiblePinNumbers = 0;
8053 std::set<wxString> blankMappedNumbers = { wxS( "1" ), wxS( "2" ) };
8054
8055 for( const SCH_ITEM& item : blankMappedSymbol->GetLibSymbolRef()->GetDrawItems() )
8056 {
8057 if( item.Type() == SCH_TEXT_T
8058 && blankMappedNumbers.contains( static_cast<const SCH_TEXT&>( item ).GetText() ) )
8059 {
8060 ++visiblePinNumbers;
8061 }
8062 }
8063
8064 BOOST_CHECK_EQUAL( visiblePinNumbers, 0u );
8065
8066 SCH_SYMBOL* dualPinTextSymbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "JDUAL" ) );
8067 BOOST_REQUIRE( dualPinTextSymbol );
8068 BOOST_CHECK( dualPinTextSymbol->GetShowPinNames() );
8069 BOOST_CHECK( dualPinTextSymbol->GetShowPinNumbers() );
8070
8071 for( const SCH_PIN* pin : dualPinTextSymbol->GetPins() )
8072 {
8073 BOOST_CHECK_EQUAL( pin->GetName(), pin->GetNumber() );
8074 BOOST_CHECK_GT( pin->GetNameTextSize(), 0 );
8075 BOOST_CHECK_GT( pin->GetNumberTextSize(), 0 );
8076 }
8077
8078 for( const SCH_ITEM& item : dualPinTextSymbol->GetLibSymbolRef()->GetDrawItems() )
8079 {
8080 if( item.Type() == SCH_TEXT_T
8081 && ( static_cast<const SCH_TEXT&>( item ).GetText() == wxS( "1" )
8082 || static_cast<const SCH_TEXT&>( item ).GetText() == wxS( "2" ) ) )
8083 {
8084 BOOST_ERROR( "Pin data duplicated as SCH_TEXT" );
8085 }
8086 }
8087
8088 SCH_SYMBOL* mirroredSymbol = findConvertedSymbol( *root->GetScreen(), path, wxS( "RTM" ) );
8089 BOOST_REQUIRE( mirroredSymbol );
8090 const SCH_TEXT* mirroredCue = nullptr;
8091
8092 for( const SCH_ITEM& item : mirroredSymbol->GetLibSymbolRef()->GetDrawItems() )
8093 {
8094 if( item.Type() == SCH_TEXT_T )
8095 mirroredCue = static_cast<const SCH_TEXT*>( &item );
8096 }
8097
8098 BOOST_REQUIRE( mirroredCue );
8099 int mirroredBaseline = KiROUND( mirroredCue->GetTextSize().y * 8.0 / 21.0 );
8100 VECTOR2I mirroredCuePage = mirroredSymbol->GetTransform().TransformCoordinate( mirroredCue->GetPosition() )
8101 + mirroredSymbol->GetPosition();
8102 VECTOR2I expectedMirroredCuePage =
8103 OrcadDbuToIu( mirrored.x + 21, mirrored.y ) + pageOffset + VECTOR2I( 0, mirroredBaseline );
8104 BOOST_CHECK_EQUAL( mirroredCuePage.x, expectedMirroredCuePage.x );
8105 BOOST_CHECK_EQUAL( mirroredCuePage.y, expectedMirroredCuePage.y );
8106}
8107
8108
8109// OrCAD and SPECCTRA share the .dsn extension. Require an OrCAD compound document.
8110
8111BOOST_AUTO_TEST_CASE( RejectsSpecctraTextDsn )
8112{
8113 TEMP_TEST_FILE specctra( wxS( "qa_orcad_specctra_impostor.dsn" ),
8114 wxS( "(pcb \"impostor.dsn\"\n (parser\n (string_quote \")\n )\n)\n" ) );
8115
8116 BOOST_REQUIRE( wxFileName::FileExists( specctra.m_path ) );
8117 BOOST_CHECK( !m_plugin.CanReadSchematicFile( specctra.m_path ) );
8118}
8119
8120
8121BOOST_AUTO_TEST_CASE( RejectsNonexistentFile )
8122{
8123 wxFileName missing( wxFileName::GetTempDir(), wxS( "qa_orcad_does_not_exist.dsn" ) );
8124
8125 BOOST_REQUIRE( !missing.FileExists() );
8126 BOOST_CHECK( !m_plugin.CanReadSchematicFile( missing.GetFullPath() ) );
8127}
8128
8129
8130BOOST_AUTO_TEST_CASE( RejectsWrongExtension )
8131{
8132 TEMP_TEST_FILE textFile( wxS( "qa_orcad_impostor.txt" ), wxS( "Just some text, not a schematic.\n" ) );
8133
8134 BOOST_REQUIRE( wxFileName::FileExists( textFile.m_path ) );
8135 BOOST_CHECK( !m_plugin.CanReadSchematicFile( textFile.m_path ) );
8136}
8137
8138
8139// No positive-load test until a redistributable .dsn fixture exists under qa/data/eeschema/io/orcad/.
8140
8141
8142// Set KICAD_ORCAD_CORPUS to test private DSN files against adjacent NET and BOM exports.
8143
8144static std::string trimCell( std::string aText )
8145{
8146 auto notSpace = []( unsigned char c )
8147 {
8148 return !std::isspace( c );
8149 };
8150 aText.erase( aText.begin(), std::find_if( aText.begin(), aText.end(), notSpace ) );
8151 aText.erase( std::find_if( aText.rbegin(), aText.rend(), notSpace ).base(), aText.end() );
8152
8153 if( aText.size() >= 2 && aText.front() == '"' && aText.back() == '"' )
8154 aText = aText.substr( 1, aText.size() - 2 );
8155
8156 return aText;
8157}
8158
8159
8160static std::vector<std::string> splitRefs( const std::string& aCell )
8161{
8162 std::vector<std::string> refs;
8163 std::string token;
8164
8165 for( char c : aCell )
8166 {
8167 if( c == ',' )
8168 {
8169 std::string r = trimCell( token );
8170
8171 if( !r.empty() )
8172 refs.push_back( r );
8173
8174 token.clear();
8175 }
8176 else
8177 {
8178 token += c;
8179 }
8180 }
8181
8182 std::string r = trimCell( token );
8183
8184 if( !r.empty() )
8185 refs.push_back( r );
8186
8187 return refs;
8188}
8189
8190
8191static std::set<std::string> parseBomRefs( const std::string& aPath )
8192{
8193 std::set<std::string> refs;
8194 std::ifstream in( aPath );
8195 std::string line;
8196 int refCol = -1;
8197
8198 while( std::getline( in, line ) )
8199 {
8200 if( !line.empty() && line.back() == '\r' )
8201 line.pop_back();
8202
8203 std::vector<std::string> cols;
8204 std::string cell;
8205
8206 for( char c : line )
8207 {
8208 if( c == '\t' )
8209 {
8210 cols.push_back( cell );
8211 cell.clear();
8212 }
8213 else
8214 {
8215 cell += c;
8216 }
8217 }
8218
8219 cols.push_back( cell );
8220
8221 // Header row names reference column; capture index once
8222 if( refCol < 0 )
8223 {
8224 for( size_t i = 0; i < cols.size(); ++i )
8225 {
8226 if( trimCell( cols[i] ) == "Reference" )
8227 {
8228 refCol = static_cast<int>( i );
8229 break;
8230 }
8231 }
8232
8233 continue;
8234 }
8235
8236 if( refCol < static_cast<int>( cols.size() ) )
8237 {
8238 for( const std::string& r : splitRefs( trimCell( cols[refCol] ) ) )
8239 refs.insert( r );
8240 }
8241 }
8242
8243 return refs;
8244}
8245
8246
8247static std::set<std::string> parseNetComs( const std::string& aPath )
8248{
8249 std::set<std::string> refs;
8250 std::ifstream in( aPath );
8251 std::string line;
8252
8253 while( std::getline( in, line ) )
8254 {
8255 if( line.rfind( ".ADD_COM", 0 ) != 0 )
8256 continue;
8257
8258 // .ADD_COM <ref> "<footprint>"
8259 std::string rest = trimCell( line.substr( 8 ) );
8260 std::string ref;
8261
8262 for( char c : rest )
8263 {
8264 if( std::isspace( static_cast<unsigned char>( c ) ) )
8265 break;
8266
8267 ref += c;
8268 }
8269
8270 if( !ref.empty() )
8271 refs.insert( ref );
8272 }
8273
8274 return refs;
8275}
8276
8277
8280static std::set<std::string> collectImportedRefs( SCHEMATIC& aSchematic )
8281{
8282 std::set<std::string> refs;
8284
8285 for( const SCH_SHEET_PATH& path : sheets )
8286 {
8287 SCH_SCREEN* screen = path.LastScreen();
8288
8289 if( !screen )
8290 continue;
8291
8292 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
8293 {
8294 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
8295 wxString ref = symbol->GetRef( &path, false );
8296
8297 // Leading '#' = power/hidden pseudo-part, not BOM; trailing '?' = unannotated.
8298 if( ref.IsEmpty() || ref.StartsWith( wxS( "#" ) ) || ref.EndsWith( wxS( "?" ) ) )
8299 continue;
8300
8301 refs.insert( std::string( ref.ToUTF8() ) );
8302 }
8303 }
8304
8305 return refs;
8306}
8307
8308
8309static std::string terminalToken( const std::string& aRef, const std::string& aPin )
8310{
8311 return trimCell( aRef ) + "." + trimCell( aPin );
8312}
8313
8314
8317static std::vector<std::set<std::string>> parseNetTerminals( const std::string& aPath )
8318{
8319 std::vector<std::set<std::string>> nets;
8320 std::set<std::string> current;
8321 std::ifstream in( aPath );
8322 std::string line;
8323
8324 auto flush = [&]()
8325 {
8326 if( current.size() >= 2 )
8327 nets.push_back( current );
8328
8329 current.clear();
8330 };
8331
8332 while( std::getline( in, line ) )
8333 {
8334 if( !line.empty() && line.back() == '\r' )
8335 line.pop_back();
8336
8337 bool addTer = line.rfind( ".ADD_TER", 0 ) == 0;
8338 bool ter = line.rfind( ".TER", 0 ) == 0;
8339 bool cont = !line.empty() && std::isspace( static_cast<unsigned char>( line[0] ) );
8340
8341 if( line.rfind( ".END", 0 ) == 0 )
8342 break;
8343
8344 if( addTer )
8345 flush();
8346
8347 if( addTer || ter || cont )
8348 {
8349 std::istringstream ss( addTer ? line.substr( 8 ) : ter ? line.substr( 4 ) : line );
8350 std::string ref, pin;
8351
8352 if( ss >> ref >> pin )
8353 current.insert( terminalToken( ref, pin ) );
8354 }
8355 }
8356
8357 flush();
8358 return nets;
8359}
8360
8361
8364static std::pair<int, int> checkConnectivity( SCHEMATIC& aSchematic, const std::vector<std::set<std::string>>& aNets,
8365 std::vector<std::set<std::string>>* aInconsistent )
8366{
8368 aSchematic.ConnectionGraph()->Recalculate( sheets, true );
8369
8370 std::map<std::string, int> pinNet;
8371 int netId = 0;
8372
8373 for( const auto& [key, subgraphs] : aSchematic.ConnectionGraph()->GetNetMap() )
8374 {
8375 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
8376 {
8377 for( SCH_ITEM* item : subgraph->GetItems() )
8378 {
8379 if( item->Type() != SCH_PIN_T )
8380 continue;
8381
8382 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
8383 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
8384
8385 if( !symbol )
8386 continue;
8387
8388 wxString ref = symbol->GetRef( &subgraph->GetSheet(), false );
8389
8390 if( ref.IsEmpty() || ref.StartsWith( wxS( "#" ) ) || ref.EndsWith( wxS( "?" ) ) )
8391 continue;
8392
8393 pinNet[terminalToken( std::string( ref.ToUTF8() ), std::string( pin->GetNumber().ToUTF8() ) )] = netId;
8394 }
8395 }
8396
8397 ++netId;
8398 }
8399
8400 struct CHECKED_NET
8401 {
8402 const std::set<std::string>* terminals;
8403 std::set<int> ids;
8404 };
8405
8406 std::vector<CHECKED_NET> checkedNets;
8407 std::map<int, int> sourceNetsPerImportedNet;
8408
8409 for( const std::set<std::string>& net : aNets )
8410 {
8411 std::set<int> ids;
8412 int resolved = 0;
8413
8414 for( const std::string& term : net )
8415 {
8416 auto it = pinNet.find( term );
8417
8418 if( it != pinNet.end() )
8419 {
8420 ids.insert( it->second );
8421 ++resolved;
8422 }
8423 }
8424
8425 if( resolved >= 2 )
8426 {
8427 if( ids.size() == 1 )
8428 sourceNetsPerImportedNet[*ids.begin()]++;
8429
8430 checkedNets.push_back( { &net, std::move( ids ) } );
8431 }
8432 }
8433
8434 int consistent = 0;
8435
8436 for( const CHECKED_NET& net : checkedNets )
8437 {
8438 bool exact = net.ids.size() == 1 && sourceNetsPerImportedNet[*net.ids.begin()] == 1;
8439
8440 if( exact )
8441 ++consistent;
8442 else if( aInconsistent )
8443 aInconsistent->push_back( *net.terminals );
8444 }
8445
8446 return { consistent, static_cast<int>( checkedNets.size() ) };
8447}
8448
8449
8450static wxString terminalNetName( SCHEMATIC& aSchematic, const wxString& aReference, const wxString& aPinNumber )
8451{
8453 aSchematic.ConnectionGraph()->Recalculate( sheets, true );
8454
8455 for( const auto& [key, subgraphs] : aSchematic.ConnectionGraph()->GetNetMap() )
8456 {
8457 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
8458 {
8459 for( SCH_ITEM* item : subgraph->GetItems() )
8460 {
8461 if( item->Type() != SCH_PIN_T )
8462 continue;
8463
8464 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
8465 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
8466
8467 if( symbol && symbol->GetRef( &subgraph->GetSheet(), false ) == aReference
8468 && pin->GetNumber() == aPinNumber )
8469 {
8470 return key.Name;
8471 }
8472 }
8473 }
8474 }
8475
8476 return {};
8477}
8478
8479
8481static std::set<std::string> expectedRefsFor( const std::filesystem::path& aDsn, std::string& aSource )
8482{
8483 for( const char* ext : { ".NET", ".net", ".BOM", ".bom" } )
8484 {
8485 std::filesystem::path candidate = aDsn;
8486 candidate.replace_extension( ext );
8487
8488 if( std::filesystem::exists( candidate ) )
8489 {
8490 aSource = candidate.filename().string();
8491
8492 bool isNet = std::string( ext ) == ".NET" || std::string( ext ) == ".net";
8493 return isNet ? parseNetComs( candidate.string() ) : parseBomRefs( candidate.string() );
8494 }
8495 }
8496
8497 aSource.clear();
8498 return {};
8499}
8500
8501
8502BOOST_AUTO_TEST_CASE( CorpusValidation )
8503{
8504 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
8505
8506 if( !corpusEnv || !*corpusEnv )
8507 {
8508 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping OrCAD corpus validation." );
8509 return;
8510 }
8511
8512 namespace fs = std::filesystem;
8513 fs::path root( corpusEnv );
8514
8515 BOOST_REQUIRE_MESSAGE( fs::exists( root ), "KICAD_ORCAD_CORPUS path does not exist." );
8516
8517 std::vector<fs::path> designs;
8518
8519 for( auto it = fs::recursive_directory_iterator( root, fs::directory_options::skip_permission_denied );
8520 it != fs::recursive_directory_iterator(); ++it )
8521 {
8522 if( !it->is_regular_file() )
8523 continue;
8524
8525 std::string ext = it->path().extension().string();
8526 std::transform( ext.begin(), ext.end(), ext.begin(),
8527 []( unsigned char c )
8528 {
8529 return std::tolower( c );
8530 } );
8531
8532 if( ext == ".dsn" )
8533 designs.push_back( it->path() );
8534 }
8535
8536 std::sort( designs.begin(), designs.end() );
8537
8538 BOOST_TEST_MESSAGE( "OrCAD corpus: " << designs.size() << " .DSN files under " << root );
8539
8540 int imported = 0, crashed = 0, unsupported = 0, rejected = 0, checked = 0;
8541 unsigned int totalExpected = 0, totalMatched = 0, totalMissing = 0, totalExtra = 0;
8542 int netConsistent = 0, netCheckable = 0, netTotal = 0;
8543 uint64_t importedPages = 0, importedComponents = 0, importedPowerSymbols = 0;
8544 uint64_t importedPins = 0, importedWires = 0, importedBuses = 0;
8545 uint64_t importedLabels = 0, importedShapes = 0, importedTexts = 0, importedBitmaps = 0;
8546
8547 const char* debugEnv = std::getenv( "KICAD_ORCAD_DEBUG" );
8548 std::string debugFilter = debugEnv ? debugEnv : "";
8549 const char* filterEnv = std::getenv( "KICAD_ORCAD_FILTER" );
8550 std::string designFilter = filterEnv ? filterEnv : "";
8551
8552 for( const fs::path& dsn : designs )
8553 {
8554 std::string rel = fs::relative( dsn, root ).string();
8555
8556 if( !designFilter.empty() && rel.find( designFilter ) == std::string::npos )
8557 continue;
8558
8559 BOOST_TEST_INFO_SCOPE( "OrCAD source: " << rel );
8560
8561 SCH_IO_ORCAD plugin;
8562 uint64_t perDesignPages = 0, perDesignComponents = 0, perDesignPowerSymbols = 0;
8563 uint64_t perDesignPins = 0, perDesignWires = 0, perDesignBuses = 0;
8564 uint64_t perDesignLabels = 0, perDesignShapes = 0, perDesignTexts = 0;
8565 uint64_t perDesignBitmaps = 0;
8566 uint64_t perDesignRedWires = 0;
8567 uint64_t perDesignVddmPowerSymbols = 0;
8568
8569 bool debug = !debugFilter.empty() && rel.find( debugFilter ) != std::string::npos;
8570
8571 if( !plugin.CanReadSchematicFile( dsn.string() ) )
8572 {
8573 ++rejected;
8574 continue;
8575 }
8576
8577 SETTINGS_MANAGER manager;
8578 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
8579 manager.LoadProject( "" );
8580 schematic->SetProject( &manager.Prj() );
8581 schematic->CurrentSheet().clear();
8582 schematic->CurrentSheet().push_back( &schematic->Root() );
8583
8585 plugin.SetReporter( &reporter );
8586
8587 try
8588 {
8589 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
8590 schematic->CurrentSheet().UpdateAllScreenReferences();
8591 }
8592 catch( const std::exception& e )
8593 {
8594 // Pre-2003 designs out of scope, rejected cleanly
8595 if( std::string( e.what() ).find( "pre-2003" ) != std::string::npos )
8596 ++unsupported;
8597 else
8598 ++crashed;
8599
8600 BOOST_TEST_MESSAGE( " THROW " << rel << " : " << e.what() );
8601 continue;
8602 }
8603
8604 ++imported;
8605
8606 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
8607 {
8608 SCH_SCREEN* screen = path.LastScreen();
8609
8610 if( !screen )
8611 continue;
8612
8613 ++importedPages;
8614 ++perDesignPages;
8615
8616 for( SCH_ITEM* item : screen->Items() )
8617 {
8618 switch( item->Type() )
8619 {
8620 case SCH_SYMBOL_T:
8621 {
8622 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
8623 wxString ref = symbol->GetRef( &path, false );
8624
8625 if( ref.StartsWith( wxS( "#" ) ) )
8626 {
8627 ++importedPowerSymbols;
8628 ++perDesignPowerSymbols;
8629
8630 if( symbol->GetValue( &path, RAW_VALUE ) == wxS( "VDDM" ) )
8631 ++perDesignVddmPowerSymbols;
8632 }
8633 else
8634 {
8635 ++importedComponents;
8636 ++perDesignComponents;
8637 }
8638
8639 size_t pinCount = symbol->GetPins( &path ).size();
8640 importedPins += pinCount;
8641 perDesignPins += pinCount;
8642 break;
8643 }
8644
8645 case SCH_LINE_T:
8646 {
8647 SCH_LINE* line = static_cast<SCH_LINE*>( item );
8648
8649 if( line->GetLayer() == LAYER_BUS )
8650 {
8651 ++importedBuses;
8652 ++perDesignBuses;
8653 }
8654 else if( line->GetLayer() == LAYER_WIRE )
8655 {
8656 ++importedWires;
8657 ++perDesignWires;
8658
8659 if( line->GetLineColor() == OrcadColor( 8 ) )
8660 ++perDesignRedWires;
8661 }
8662
8663 break;
8664 }
8665
8666 case SCH_LABEL_T:
8667 case SCH_GLOBAL_LABEL_T:
8668 case SCH_HIER_LABEL_T:
8669 ++importedLabels;
8670 ++perDesignLabels;
8671 break;
8672
8673 case SCH_SHAPE_T:
8674 ++importedShapes;
8675 ++perDesignShapes;
8676 break;
8677
8678 case SCH_TEXT_T:
8679 ++importedTexts;
8680 ++perDesignTexts;
8681 break;
8682
8683 case SCH_BITMAP_T:
8684 ++importedBitmaps;
8685 ++perDesignBitmaps;
8686 break;
8687
8688 default: break;
8689 }
8690 }
8691 }
8692
8693 BOOST_TEST_MESSAGE( " AUDIT " << rel << "|pages=" << perDesignPages << "|components=" << perDesignComponents
8694 << "|power=" << perDesignPowerSymbols << "|pins=" << perDesignPins
8695 << "|wires=" << perDesignWires << "|buses=" << perDesignBuses
8696 << "|labels=" << perDesignLabels << "|shapes=" << perDesignShapes
8697 << "|texts=" << perDesignTexts << "|bitmaps=" << perDesignBitmaps );
8698
8699 if( rel == "allegro/beagleboard-xm/SCH/BeagleBoard-xM_ORCAD.DSN" )
8700 {
8701 BOOST_CHECK_EQUAL( perDesignBitmaps, 10u );
8702 BOOST_CHECK_EQUAL( perDesignShapes, 108u );
8703 BOOST_CHECK_EQUAL( perDesignTexts, 175u );
8704 }
8705
8706 if( rel
8707 == "allegro/OpenCellular-LED/Rev-C/schematic/"
8708 "OpenCellular_Connect-1_LED_Life-3_Schematic.DSN" )
8709 {
8710 BOOST_CHECK_EQUAL( perDesignVddmPowerSymbols, 31u );
8711 }
8712
8713 if( rel
8714 == "orcad/OpenCellular-GBC-Elgon_ARM/Rev-A/schematics/"
8715 "CN81XX_GBCV2_sch_0530.DSN" )
8716 {
8717 BOOST_CHECK_EQUAL( perDesignRedWires, 35u );
8718 }
8719
8720 if( debug )
8721 BOOST_TEST_MESSAGE( " DEBUG " << rel << " warnings:\n"
8722 << std::string( reporter.GetMessages().ToUTF8() ) );
8723
8724 std::set<std::string> got = collectImportedRefs( *schematic );
8725 std::string source;
8726 std::set<std::string> expected = expectedRefsFor( dsn, source );
8727
8728 if( debug )
8729 BOOST_TEST_MESSAGE( " DEBUG " << rel << " imported " << got.size() << " refs" );
8730
8731 if( expected.empty() )
8732 continue;
8733
8734 std::set<std::string> missing, extra;
8735 std::set_difference( expected.begin(), expected.end(), got.begin(), got.end(),
8736 std::inserter( missing, missing.begin() ) );
8737 std::set_difference( got.begin(), got.end(), expected.begin(), expected.end(),
8738 std::inserter( extra, extra.begin() ) );
8739
8740 unsigned int matched = static_cast<unsigned int>( expected.size() - missing.size() );
8741
8742 ++checked;
8743 totalExpected += expected.size();
8744 totalMatched += matched;
8745 totalMissing += missing.size();
8746 totalExtra += extra.size();
8747
8748 BOOST_TEST_MESSAGE( " CHECK " << rel << " : " << matched << "/" << expected.size() << " refs ("
8749 << int( 100.0 * matched / expected.size() ) << "%), extra " << extra.size()
8750 << " [" << source << "]" );
8751
8752 if( debug )
8753 {
8754 for( const std::string& ref : missing )
8755 BOOST_TEST_MESSAGE( " missing ref: " << ref );
8756
8757 for( const std::string& ref : extra )
8758 BOOST_TEST_MESSAGE( " extra ref: " << ref );
8759 }
8760
8761 // .NET ground truth carries terminal connectivity; verify pins group per net after rebuild.
8762 std::filesystem::path net = dsn;
8763 net.replace_extension( source.size() >= 4 && source.substr( source.size() - 4 ) == ".net" ? ".net" : ".NET" );
8764
8765 if( std::filesystem::exists( net ) )
8766 {
8767 std::vector<std::set<std::string>> nets = parseNetTerminals( net.string() );
8768
8769 if( !nets.empty() )
8770 {
8771 std::vector<std::set<std::string>> inconsistent;
8772 auto [consistent, checkableNets] = checkConnectivity( *schematic, nets, &inconsistent );
8773 netConsistent += consistent;
8774 netCheckable += checkableNets;
8775 netTotal += static_cast<int>( nets.size() );
8776
8777 BOOST_TEST_MESSAGE( " connectivity: " << consistent << "/" << checkableNets
8778 << " nets consistent" );
8779
8780 if( debug )
8781 {
8782 for( const std::set<std::string>& terminals : inconsistent )
8783 {
8784 std::string joined;
8785
8786 for( const std::string& terminal : terminals )
8787 {
8788 if( !joined.empty() )
8789 joined += ", ";
8790
8791 joined += terminal;
8792 }
8793
8794 BOOST_TEST_MESSAGE( " inconsistent net: " << joined );
8795 }
8796 }
8797 }
8798 }
8799 }
8800
8801 BOOST_TEST_MESSAGE( "==== OrCAD corpus summary ====" );
8802 BOOST_TEST_MESSAGE( " designs: " << designs.size() << " imported: " << imported << " crashed: " << crashed
8803 << " unsupported: " << unsupported << " rejected: " << rejected );
8804 BOOST_TEST_MESSAGE( " objects: pages "
8805 << importedPages << " components " << importedComponents << " power " << importedPowerSymbols
8806 << " pins " << importedPins << " wires " << importedWires << " buses " << importedBuses
8807 << " labels " << importedLabels << " shapes " << importedShapes << " texts " << importedTexts
8808 << " bitmaps " << importedBitmaps );
8809
8810 if( checked )
8811 {
8812 BOOST_TEST_MESSAGE( " refdes coverage: " << totalMatched << "/" << totalExpected << " ("
8813 << int( 100.0 * totalMatched / totalExpected )
8814 << "%) missing: " << totalMissing << " extra: " << totalExtra );
8815 }
8816
8817 if( netCheckable )
8818 {
8819 BOOST_TEST_MESSAGE( " net connectivity: " << netConsistent << "/" << netCheckable << " ("
8820 << int( 100.0 * netConsistent / netCheckable ) << "%)" );
8821 }
8822
8823 // Only pre-2003 format may throw; anything else is a crash
8824 BOOST_CHECK_MESSAGE( crashed == 0, crashed << " design(s) crashed during import." );
8825
8826 const char* snapshotEnv = std::getenv( "KICAD_ORCAD_CORPUS_SNAPSHOT" );
8827
8828 // Guard against vacuous pass when no companion files present.
8829 if( designFilter.empty() )
8830 BOOST_REQUIRE_MESSAGE( checked > 0, "No ground-truth .BOM/.NET companions were validated." );
8831
8832 if( designFilter.empty() && snapshotEnv && *snapshotEnv )
8833 {
8834 BOOST_CHECK_EQUAL( imported, 92 );
8835 BOOST_CHECK_EQUAL( rejected, 1 );
8836 BOOST_CHECK_EQUAL( importedPages, 854u );
8837 BOOST_CHECK_EQUAL( importedBitmaps, 616u );
8838 }
8839
8840 // Occurrence-annotation decode holds this above 95%; dropped Hierarchy-stream ref overlay collapses it.
8841 if( checked )
8842 BOOST_CHECK_GE( 100.0 * totalMatched / totalExpected, 95.0 );
8843
8844 // Pin-placement/geometry regression breaking connectivity collapses this. Checkable floor
8845 // (>= 2 resolvable terminals per net) stops broad pin loss passing vacuously.
8846 if( netTotal )
8847 {
8848 BOOST_CHECK_GE( 100.0 * netCheckable / netTotal, 80.0 );
8849 BOOST_CHECK_EQUAL( netConsistent, netCheckable );
8850 }
8851}
8852
8853
8854static std::filesystem::path findCorpusDesign( const std::filesystem::path& aRoot, const std::string& aFileName )
8855{
8856 for( const std::filesystem::directory_entry& entry : std::filesystem::recursive_directory_iterator( aRoot ) )
8857 {
8858 if( entry.is_regular_file() && entry.path().filename() == aFileName )
8859 return entry.path();
8860 }
8861
8862 return {};
8863}
8864
8865
8866BOOST_AUTO_TEST_CASE( CisVariantFallbackUsesBytewiseFirstBomName )
8867{
8868 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
8869
8870 if( !corpusEnv || !*corpusEnv )
8871 return;
8872
8873 std::filesystem::path dsn =
8874 std::filesystem::path( corpusEnv ) / "PADS" / "skyworks-eval" / "SI34061FB12V4KIT"
8875 / "si34061-evb-ext_1.7.1.20220111" / "schematic" / "SI34061-EVB-EXT.DSN";
8876
8877 if( !std::filesystem::exists( dsn ) )
8878 {
8879 BOOST_TEST_MESSAGE( "SI34061-EVB-EXT 12 V design not present; skipping CIS variant check." );
8880 return;
8881 }
8882
8883 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
8884 SETTINGS_MANAGER manager;
8885 manager.LoadProject( "" );
8886 schematic->SetProject( &manager.Prj() );
8887 schematic->CurrentSheet().clear();
8888 schematic->CurrentSheet().push_back( &schematic->Root() );
8889
8890 SCH_IO_ORCAD plugin;
8891 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
8892
8893 std::map<wxString, SCH_SYMBOL*> symbols;
8894
8895 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
8896 {
8897 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
8898 {
8899 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
8900 symbols.emplace( symbol->GetRef( &path, false ), symbol );
8901 }
8902 }
8903
8904 BOOST_REQUIRE( symbols.count( wxS( "U1" ) ) );
8905 BOOST_REQUIRE( symbols.count( wxS( "D13" ) ) );
8906 BOOST_REQUIRE( symbols.count( wxS( "T2" ) ) );
8907 BOOST_REQUIRE( symbols.count( wxS( "LB1" ) ) );
8908 BOOST_CHECK_EQUAL( symbols[wxS( "U1" )]->GetField( FIELD_T::VALUE )->GetText(),
8909 wxS( "NVMFS5C680NLT1G" ) );
8910 BOOST_CHECK_EQUAL( symbols[wxS( "D13" )]->GetField( FIELD_T::VALUE )->GetText(), wxS( "PDS5100" ) );
8911 BOOST_CHECK_EQUAL( symbols[wxS( "T2" )]->GetField( FIELD_T::VALUE )->GetText(), wxS( "LDT1026-50R" ) );
8912 BOOST_CHECK_EQUAL( symbols[wxS( "LB1" )]->GetField( FIELD_T::VALUE )->GetText(),
8913 wxS( "LABEL-Si34061-EVB-EXT-BOM-R1.7-12V" ) );
8914 BOOST_REQUIRE( symbols[wxS( "U1" )]->GetField( wxS( "Voltage" ) ) );
8915 BOOST_CHECK_EQUAL( symbols[wxS( "U1" )]->GetField( wxS( "Voltage" ) )->GetText(), wxS( "60V" ) );
8916 BOOST_REQUIRE( symbols.count( wxS( "D15" ) ) );
8917 BOOST_REQUIRE( symbols.count( wxS( "R35" ) ) );
8918 BOOST_REQUIRE( symbols.count( wxS( "TP1" ) ) );
8919 BOOST_CHECK( symbols[wxS( "D15" )]->GetDNP() );
8920 BOOST_CHECK( symbols[wxS( "R35" )]->GetDNP() );
8921 BOOST_CHECK( symbols[wxS( "TP1" )]->GetDNP() );
8922 BOOST_CHECK_EQUAL( symbols[wxS( "D15" )]->GetField( FIELD_T::VALUE )->GetText(), wxS( "NI" ) );
8923 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
8924 {
8925 bool variantNameFound = false;
8926
8927 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_TEXT_T ) )
8928 {
8929 const wxString& text = static_cast<SCH_TEXT*>( item )->GetText();
8930 BOOST_CHECK_NE( text, wxS( "<Core Design>" ) );
8931 variantNameFound |= text == wxS( "12V" );
8932 }
8933
8934 BOOST_CHECK( variantNameFound );
8935 BOOST_CHECK_EQUAL( path.LastScreen()->GetTitleBlock().GetComment( 1 ), wxS( "Variant Name: 12V" ) );
8936 }
8937}
8938
8939
8940BOOST_AUTO_TEST_CASE( UnreferencedSchematicFoldersRemainVisibleButExcludedFromBoard )
8941{
8942 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
8943
8944 if( !corpusEnv || !*corpusEnv )
8945 return;
8946
8947 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "PROJET INDUS.DSN" );
8948
8949 if( dsn.empty() )
8950 return;
8951
8952 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
8953 SETTINGS_MANAGER manager;
8954 manager.LoadProject( "" );
8955 schematic->SetProject( &manager.Prj() );
8956 schematic->CurrentSheet().clear();
8957 schematic->CurrentSheet().push_back( &schematic->Root() );
8958
8959 SCH_IO_ORCAD plugin;
8960 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
8961
8962 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
8963 size_t excluded = 0;
8964 size_t excludedSymbols = 0;
8965
8966 for( const SCH_SHEET_PATH& path : sheets )
8967 {
8968 excluded += path.GetExcludedFromBoard();
8969
8970 if( !path.GetExcludedFromBoard() )
8971 continue;
8972
8973 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
8974 {
8975 ++excludedSymbols;
8976 BOOST_CHECK( static_cast<SCH_SYMBOL*>( item )->GetExcludedFromBoard() );
8977 }
8978 }
8979
8980 BOOST_CHECK_EQUAL( sheets.size(), 5u );
8981 BOOST_CHECK_EQUAL( excluded, 4u );
8982 BOOST_CHECK_GT( excludedSymbols, 0u );
8983}
8984
8985
8986BOOST_AUTO_TEST_CASE( ViewsDirectoryIgnoresStaleStoredFolders )
8987{
8988 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
8989
8990 if( !corpusEnv || !*corpusEnv )
8991 return;
8992
8993 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC1987A-2.DSN" );
8994
8995 if( dsn.empty() )
8996 return;
8997
8998 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
8999 SETTINGS_MANAGER manager;
9000 manager.LoadProject( "" );
9001 schematic->SetProject( &manager.Prj() );
9002 schematic->CurrentSheet().clear();
9003 schematic->CurrentSheet().push_back( &schematic->Root() );
9004
9005 SCH_IO_ORCAD plugin;
9006 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
9007
9008 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
9009 BOOST_REQUIRE_EQUAL( sheets.size(), 1u );
9010}
9011
9012
9013BOOST_AUTO_TEST_CASE( OccurrenceFlatNetConnectsAcrossPagesWithoutOffpageSymbols )
9014{
9015 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
9016
9017 if( !corpusEnv || !*corpusEnv )
9018 return;
9019
9020 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "M5275EVB.DSN" );
9021
9022 if( dsn.empty() )
9023 return;
9024
9025 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
9026 SETTINGS_MANAGER manager;
9027 manager.LoadProject( "" );
9028 schematic->SetProject( &manager.Prj() );
9029 schematic->CurrentSheet().clear();
9030 schematic->CurrentSheet().push_back( &schematic->Root() );
9031
9032 SCH_IO_ORCAD plugin;
9033 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
9034
9035 auto [consistent, checkable] =
9036 checkConnectivity( *schematic, { { terminalToken( "J3", "38" ), terminalToken( "RP49", "7" ),
9037 terminalToken( "U6", "D10" ) } } );
9038 BOOST_CHECK_EQUAL( checkable, 1 );
9039 BOOST_CHECK_EQUAL( consistent, 1 );
9040}
9041
9042
9043static std::map<std::string, std::vector<std::string>> collectImportedUuids( const SCHEMATIC& aSchematic )
9044{
9045 std::map<std::string, std::vector<std::string>> uuids;
9046
9047 auto itemIdentity = []( const SCH_ITEM& item, const SCH_SHEET_PATH& path )
9048 {
9049 std::ostringstream identity;
9050 const VECTOR2I position = item.GetPosition();
9051 const BOX2I bounds = item.GetBoundingBox();
9052 identity << item.Type() << ':' << item.GetLayer() << ':' << position.x << ',' << position.y
9053 << ':' << bounds.GetX() << ',' << bounds.GetY() << ',' << bounds.GetWidth() << ','
9054 << bounds.GetHeight();
9055
9056 auto text = [&]( const wxString& value )
9057 {
9058 const std::string utf8 = value.ToStdString( wxConvUTF8 );
9059 identity << ':' << utf8.size() << ':' << utf8;
9060 };
9061
9062 if( const auto* line = dynamic_cast<const SCH_LINE*>( &item ) )
9063 {
9064 identity << ':' << line->GetEndPoint().x << ',' << line->GetEndPoint().y;
9065 }
9066
9067 if( const auto* label = dynamic_cast<const EDA_TEXT*>( &item ) )
9068 {
9069 text( label->GetText() );
9070 identity << ':' << label->GetTextAngleDegrees() << ':' << label->GetTextWidth() << ','
9071 << label->GetTextHeight();
9072 }
9073
9074 if( const auto* shape = dynamic_cast<const SCH_SHAPE*>( &item ) )
9075 {
9076 identity << ':' << static_cast<int>( shape->GetShape() ) << ':' << shape->GetStart().x
9077 << ',' << shape->GetStart().y << ':' << shape->GetEnd().x << ',' << shape->GetEnd().y;
9078
9079 if( shape->GetShape() == SHAPE_T::POLY )
9080 identity << ':' << shape->GetPolyShape().Format();
9081 else if( shape->GetShape() == SHAPE_T::ARC )
9082 identity << ':' << shape->GetArcMid().x << ',' << shape->GetArcMid().y;
9083 else if( shape->GetShape() == SHAPE_T::BEZIER )
9084 identity << ':' << shape->GetBezierC1().x << ',' << shape->GetBezierC1().y << ':'
9085 << shape->GetBezierC2().x << ',' << shape->GetBezierC2().y;
9086 }
9087
9088 if( const auto* symbol = dynamic_cast<const SCH_SYMBOL*>( &item ) )
9089 {
9090 text( symbol->GetRef( &path ) );
9091 text( symbol->GetLibId().Format() );
9092 identity << ':' << symbol->GetUnitSelection( &path ) << ':' << symbol->GetOrientation();
9093 }
9094
9095 if( const auto* pin = dynamic_cast<const SCH_PIN*>( &item ) )
9096 {
9097 text( pin->GetNumber() );
9098 text( pin->GetName() );
9099 identity << ':' << pin->GetUnit() << ':' << pin->GetBodyStyle() << ':'
9100 << static_cast<int>( pin->GetOrientation() ) << ':' << pin->GetLength();
9101 }
9102
9103 if( const auto* sheet = dynamic_cast<const SCH_SHEET*>( &item ) )
9104 {
9105 text( sheet->GetName() );
9106 text( sheet->GetFileName() );
9107 }
9108
9109 return identity.str();
9110 };
9111
9112 for( const SCH_SHEET_PATH& path : aSchematic.Hierarchy() )
9113 {
9114 std::string scope;
9115
9116 for( size_t index = 0; index < path.size(); ++index )
9117 {
9118 SCH_SHEET* sheet = path.at( index );
9119 const std::string name = sheet->GetName().ToStdString( wxConvUTF8 );
9120 scope += std::to_string( name.size() ) + ':' + name + '/';
9121 }
9122
9123 SCH_SHEET* sheet = path.Last();
9124 uuids[scope + "sheet"].push_back( sheet->m_Uuid.AsStdString() );
9125 SCH_SCREEN* screen = path.LastScreen();
9126 BOOST_REQUIRE( screen );
9127 uuids[scope + "screen"].push_back( screen->GetUuid().AsStdString() );
9128
9129 for( SCH_ITEM* item : screen->Items() )
9130 {
9131 const std::string identity = scope + itemIdentity( *item, path );
9132 uuids[identity].push_back( item->m_Uuid.AsStdString() );
9133
9134 if( auto* symbol = dynamic_cast<SCH_SYMBOL*>( item ) )
9135 {
9136 for( const std::unique_ptr<SCH_PIN>& pin : symbol->GetRawPins() )
9137 uuids[identity + "/pin/" + itemIdentity( *pin, path )].push_back( pin->m_Uuid.AsStdString() );
9138 }
9139 else if( const auto* child = dynamic_cast<const SCH_SHEET*>( item ) )
9140 {
9141 for( const SCH_SHEET_PIN* pin : child->GetPins() )
9142 uuids[identity + "/pin/" + itemIdentity( *pin, path )].push_back( pin->m_Uuid.AsStdString() );
9143 }
9144 }
9145 }
9146
9147 // Identical overlapping objects are interchangeable, but UUIDs must stay attached to their geometry and owner.
9148 for( auto& [identity, values] : uuids )
9149 std::sort( values.begin(), values.end() );
9150
9151 return uuids;
9152}
9153
9154BOOST_AUTO_TEST_CASE( RepeatedImportHasDeterministicUuids )
9155{
9156 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
9157
9158 if( !corpusEnv || !*corpusEnv )
9159 {
9160 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping OrCAD determinism check." );
9161 return;
9162 }
9163
9164 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "mc33163.dsn" );
9165
9166 if( dsn.empty() )
9167 {
9168 BOOST_TEST_MESSAGE( "mc33163.dsn not present in corpus; skipping OrCAD determinism check." );
9169 return;
9170 }
9171
9172 auto importUuids = [&]( const std::filesystem::path& aDsn )
9173 {
9174 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
9175 SETTINGS_MANAGER manager;
9176 manager.LoadProject( "" );
9177 schematic->SetProject( &manager.Prj() );
9178 schematic->CurrentSheet().clear();
9179 schematic->CurrentSheet().push_back( &schematic->Root() );
9180
9181 SCH_IO_ORCAD plugin;
9182 plugin.LoadSchematicFile( aDsn.string(), schematic.get() );
9183 return collectImportedUuids( *schematic );
9184 };
9185
9186 const auto first = importUuids( dsn );
9187 const auto second = importUuids( dsn );
9188
9189 BOOST_REQUIRE( !first.empty() );
9190 BOOST_REQUIRE_EQUAL( first.size(), second.size() );
9191
9192 for( const auto& [identity, values] : first )
9193 {
9194 BOOST_TEST_CONTEXT( identity )
9195 {
9196 const auto found = second.find( identity );
9197 BOOST_REQUIRE( found != second.end() );
9198 BOOST_CHECK_EQUAL_COLLECTIONS( values.begin(), values.end(), found->second.begin(), found->second.end() );
9199 }
9200 }
9201}
9202
9203
9204BOOST_AUTO_TEST_CASE( ImportNetMapRetainsSecondarySourceAliases )
9205{
9206 ORCAD_RAW_PAGE page;
9207 page.name = "ALIASES";
9208 page.netmap[1] = "N12345";
9209 page.netAliases[1] = { "TABLE_ALIAS" };
9210 ORCAD_WIRE wire;
9211 wire.id = 1;
9212 wire.x2 = 100;
9213 wire.aliases.push_back( ORCAD_ALIAS{ .name = "PRIMARY", .x = 20 } );
9214 wire.aliases.push_back( ORCAD_ALIAS{ .name = "SECONDARY", .x = 80 } );
9215 page.wires.push_back( std::move( wire ) );
9216
9217 ORCAD_DESIGN design;
9218 design.sourceId = "secondary-source-aliases";
9219 design.pages.push_back( std::move( page ) );
9220 SCHEMATIC schematic( nullptr );
9221 SETTINGS_MANAGER manager;
9222 manager.LoadProject( "" );
9223 schematic.SetProject( &manager.Prj() );
9224 SCH_SHEET* root = convertRawDesign( design, schematic );
9226 path.push_back( root );
9227 wxString finalName;
9228
9229 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_LINE_T ) )
9230 finalName = item->Connection( &path )->Name();
9231
9232 BOOST_REQUIRE( !finalName.IsEmpty() );
9233 const IMPORT_NET_MAP* map = schematic.GetImportNetMap();
9234 BOOST_REQUIRE( map );
9235 std::set<wxString> originalNames;
9236
9237 for( const IMPORT_NET_MAP_ENTRY& entry : map->entries )
9238 {
9239 BOOST_CHECK_EQUAL( entry.sourceNetId, 1 );
9241 BOOST_CHECK_EQUAL( entry.nameAtImport, finalName );
9242 originalNames.insert( entry.originalName );
9243 }
9244
9245 BOOST_CHECK( originalNames == std::set<wxString>( { "N12345", "TABLE_ALIAS", "PRIMARY", "SECONDARY" } ) );
9246}
9247
9248
9249BOOST_AUTO_TEST_CASE( AutoGeneratedNetNamesAreMappedWithoutNamingLabels )
9250{
9251 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
9252
9253 if( !corpusEnv || !*corpusEnv )
9254 {
9255 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping generated net-name check." );
9256 return;
9257 }
9258
9259 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CutiePi_V2.3-20210409.DSN" );
9260
9261 if( dsn.empty() )
9262 {
9263 BOOST_TEST_MESSAGE( "CutiePi_V2.3-20210409.DSN not present in corpus; skipping generated "
9264 "net-name check." );
9265 return;
9266 }
9267
9268 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
9269 SETTINGS_MANAGER manager;
9270 manager.LoadProject( "" );
9271 schematic->SetProject( &manager.Prj() );
9272 schematic->CurrentSheet().clear();
9273 schematic->CurrentSheet().push_back( &schematic->Root() );
9274
9275 SCH_IO_ORCAD plugin;
9276 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
9277
9278 const IMPORT_NET_MAP* map = schematic->GetImportNetMap();
9279 BOOST_REQUIRE( map );
9280
9281 for( const auto& [sourceName, reference, number] :
9282 { std::tuple{ wxString( "N12720539" ), wxString( "D10" ), wxString( "A" ) },
9283 std::tuple{ wxString( "N132252170" ), wxString( "D23" ), wxString( "1" ) } } )
9284 {
9285 wxString netName = terminalNetName( *schematic, reference, number );
9286 BOOST_CHECK_NE( netName, sourceName );
9287 bool found = false;
9288
9289 for( const IMPORT_NET_MAP_ENTRY& entry : map->entries )
9290 {
9291 if( entry.originalName == sourceName )
9292 {
9294 BOOST_CHECK_EQUAL( entry.nameAtImport, netName );
9295 BOOST_CHECK( !entry.terminals.empty() );
9296 found = true;
9297 }
9298 }
9299
9300 BOOST_CHECK( found );
9301
9302 for( const SCH_SHEET_PATH& sheet : schematic->BuildSheetListSortedByPageNumbers() )
9303 {
9304 for( SCH_ITEM* item : sheet.LastScreen()->Items() )
9305 {
9306 if( auto* label = dynamic_cast<SCH_LABEL_BASE*>( item ) )
9307 {
9308 BOOST_CHECK_NE( label->GetText(), sourceName );
9309 BOOST_CHECK( label->GetTextColor() == KIGFX::COLOR4D::UNSPECIFIED
9310 || label->GetTextColor().a > 0 );
9311 }
9312 }
9313 }
9314 }
9315}
9316
9317
9318BOOST_AUTO_TEST_CASE( CaptureNetIdsAndBlankUnitLettersAreAuthoritative )
9319{
9320 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
9321
9322 if( !corpusEnv || !*corpusEnv )
9323 {
9324 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping Capture net/unit regression check." );
9325 return;
9326 }
9327
9328 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "BEAGLEBONEBLK_C3.DSN" );
9329
9330 if( dsn.empty() )
9331 {
9332 BOOST_TEST_MESSAGE( "BEAGLEBONEBLK_C3.DSN not present in corpus; skipping Capture net/unit "
9333 "regression check." );
9334 return;
9335 }
9336
9337 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
9338 SETTINGS_MANAGER manager;
9339 manager.LoadProject( "" );
9340 schematic->SetProject( &manager.Prj() );
9341 schematic->CurrentSheet().clear();
9342 schematic->CurrentSheet().push_back( &schematic->Root() );
9343
9344 SCH_IO_ORCAD plugin;
9345 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
9346
9347 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
9348 schematic->ConnectionGraph()->Recalculate( sheets, true );
9349
9350 std::map<std::tuple<std::string, std::string, std::string>, int> terminalNets;
9351 int netId = 0;
9352
9353 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
9354 {
9355 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
9356 {
9357 std::string page = subgraph->GetSheet().LastScreen()->GetFileName().ToStdString();
9358
9359 for( SCH_ITEM* item : subgraph->GetItems() )
9360 {
9361 if( item->Type() != SCH_PIN_T )
9362 continue;
9363
9364 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
9365 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
9366
9367 if( symbol )
9368 {
9369 terminalNets[{ page, symbol->GetRef( &subgraph->GetSheet(), false ).ToStdString(),
9370 pin->GetNumber().ToStdString() }] = netId;
9371 }
9372 }
9373 }
9374
9375 ++netId;
9376 }
9377
9378 auto findNet = [&]( const std::string& aPage, const std::string& aRef, const std::string& aPin )
9379 {
9380 for( const auto& [terminal, id] : terminalNets )
9381 {
9382 if( std::get<0>( terminal ).find( aPage ) != std::string::npos && std::get<1>( terminal ) == aRef
9383 && std::get<2>( terminal ) == aPin )
9384 {
9385 return id;
9386 }
9387 }
9388
9389 return -1;
9390 };
9391
9392 int ground = findNet( "AM335x 2_3, USB", "J1", "1" );
9393 int rx = findNet( "AM335x 2_3, USB", "J1", "4" );
9394 int tx = findNet( "AM335x 2_3, USB", "J1", "5" );
9395
9396 BOOST_REQUIRE_NE( ground, -1 );
9397 BOOST_REQUIRE_NE( rx, -1 );
9398 BOOST_REQUIRE_NE( tx, -1 );
9399 BOOST_CHECK_NE( ground, rx );
9400 BOOST_CHECK_NE( ground, tx );
9401 BOOST_CHECK_NE( rx, tx );
9402
9403 BOOST_CHECK_EQUAL( findNet( "LED, Config", "D3", "1" ), findNet( "LED, Config", "Q1", "3" ) );
9404 BOOST_CHECK_EQUAL( findNet( "LED, Config", "R77", "1" ), findNet( "LED, Config", "Q1", "5" ) );
9405
9406 std::set<int> q1Units;
9407 std::set<wxString> q1ShownReferences;
9408
9409 for( const SCH_SHEET_PATH& path : sheets )
9410 {
9411 if( path.LastScreen()->GetFileName().Find( wxS( "LED, Config" ) ) == wxNOT_FOUND )
9412 continue;
9413
9414 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
9415 {
9416 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
9417
9418 if( symbol->GetRef( &path, false ) == wxS( "Q1" ) )
9419 {
9420 q1Units.insert( symbol->GetUnit() );
9421 q1ShownReferences.insert( symbol->GetRef( &path, true ) );
9422 }
9423 }
9424 }
9425
9426 const std::set<int> expectedUnits = { 1, 2 };
9427 BOOST_CHECK_EQUAL_COLLECTIONS( q1Units.begin(), q1Units.end(), expectedUnits.begin(), expectedUnits.end() );
9428
9429 const std::set<wxString> expectedShownReferences = { wxS( "Q1" ) };
9430 BOOST_CHECK_EQUAL_COLLECTIONS( q1ShownReferences.begin(), q1ShownReferences.end(),
9431 expectedShownReferences.begin(), expectedShownReferences.end() );
9432}
9433
9434
9435BOOST_AUTO_TEST_CASE( BusMembersWithoutLocalWiresRetainNetMapReferences )
9436{
9437 const char* corpus = std::getenv( "KICAD_ORCAD_CORPUS" );
9438
9439 if( !corpus || !*corpus )
9440 return;
9441
9442 std::filesystem::path dsn = findCorpusDesign( corpus, "SCH-20380.DSN" );
9443
9444 if( dsn.empty() )
9445 return;
9446
9447 SETTINGS_MANAGER manager;
9448 manager.LoadProject( "" );
9449 SCHEMATIC schematic( &manager.Prj() );
9450 SCH_IO_ORCAD plugin;
9451 plugin.LoadSchematicFile( dsn.string(), &schematic );
9452 schematic.RefreshHierarchy();
9453 const IMPORT_NET_MAP* map = schematic.GetImportNetMap();
9454 BOOST_REQUIRE( map );
9455 bool memberFound = false;
9456 bool bundleFound = false;
9457 wxString actualName = terminalNetName( schematic, wxS( "J10" ), wxS( "25" ) );
9458 BOOST_REQUIRE( !actualName.IsEmpty() );
9459
9460 for( const IMPORT_NET_MAP_ENTRY& entry : map->entries )
9461 {
9462 if( entry.view != wxS( "Hierarchical Interconnects" ) )
9463 continue;
9464
9465 if( entry.originalName == wxS( "/IRQ[7:1]" ) )
9466 {
9467 bundleFound = true;
9469 BOOST_CHECK( !entry.nameAtImport.IsEmpty() );
9470 BOOST_CHECK( !entry.terminals.empty() );
9471 }
9472
9473 if( entry.originalName != wxS( "/IRQ7" ) )
9474 continue;
9475
9476 memberFound = true;
9478 BOOST_CHECK_EQUAL( entry.nameAtImport, actualName );
9479 BOOST_CHECK( !entry.itemUuids.empty() );
9480 std::set<std::string> mappedTerminals;
9481
9482 for( const IMPORT_NET_TERMINAL& terminal : entry.terminals )
9483 {
9485 auto* symbol = dynamic_cast<SCH_SYMBOL*>( schematic.ResolveItem( terminal.symbolUuid, &path ) );
9486 BOOST_REQUIRE( symbol );
9487 mappedTerminals.insert( terminalToken( symbol->GetRef( &path, false ).ToStdString(),
9488 terminal.pinNumber.ToStdString() ) );
9489 }
9490
9491 const std::set<std::string> expected = { "J10.25", "R77.2", "RP16.7", "U10.R8" };
9492 BOOST_CHECK( mappedTerminals == expected );
9493 }
9494
9495 BOOST_CHECK( memberFound );
9496 BOOST_CHECK( bundleFound );
9497}
9498
9499
9500BOOST_AUTO_TEST_CASE( NetMapIncludesPinsConnectedByImportJunctionCleanup )
9501{
9502 const char* corpus = std::getenv( "KICAD_ORCAD_CORPUS" );
9503
9504 if( !corpus || !*corpus )
9505 return;
9506
9507 std::filesystem::path dsn = findCorpusDesign( corpus, "DC1096B-1.DSN" );
9508
9509 if( dsn.empty() )
9510 return;
9511
9512 SETTINGS_MANAGER manager;
9513 manager.LoadProject( "" );
9514 SCHEMATIC schematic( &manager.Prj() );
9515 SCH_IO_ORCAD plugin;
9516 plugin.LoadSchematicFile( dsn.string(), &schematic );
9517 const IMPORT_NET_MAP* map = schematic.GetImportNetMap();
9518 BOOST_REQUIRE( map );
9519 bool found = false;
9520
9521 for( const IMPORT_NET_MAP_ENTRY& entry : map->entries )
9522 {
9523 if( entry.sourceNetId == 16903175 && entry.originalName == wxS( "GND" ) )
9524 {
9525 found = true;
9527 BOOST_CHECK_EQUAL( entry.nameAtImport, wxString( "GND" ) );
9528 }
9529 }
9530
9531 BOOST_CHECK( found );
9532
9533 for( const wxString& reference : { wxString( "U1" ), wxString( "U2" ), wxString( "U4" ) } )
9534 BOOST_CHECK_EQUAL( terminalNetName( schematic, reference, wxS( "11" ) ), wxString( "GND" ) );
9535}
9536
9537
9538BOOST_AUTO_TEST_CASE( HierarchicalBusMembersMapToPropagatedScalarNets )
9539{
9540 const char* corpus = std::getenv( "KICAD_ORCAD_CORPUS" );
9541
9542 if( !corpus || !*corpus )
9543 return;
9544
9545 std::filesystem::path dsn = findCorpusDesign( corpus, "meta_carrier_sch_rev1.dsn" );
9546
9547 if( dsn.empty() )
9548 return;
9549
9550 SETTINGS_MANAGER manager;
9551 manager.LoadProject( "" );
9552 SCHEMATIC schematic( &manager.Prj() );
9553 SCH_IO_ORCAD plugin;
9554 plugin.LoadSchematicFile( dsn.string(), &schematic );
9555 schematic.RefreshHierarchy();
9556 const IMPORT_NET_MAP* map = schematic.GetImportNetMap();
9557 BOOST_REQUIRE( map );
9558 bool found = false;
9559
9560 for( const IMPORT_NET_MAP_ENTRY& entry : map->entries )
9561 {
9562 if( entry.sourceNetId != 9438909 || entry.originalName != wxS( "OUT_P6" )
9563 || entry.occurrence != std::vector<wxString>( { "TOP", "9136400", "TILE_AD9523" } ) )
9564 continue;
9565
9566 found = true;
9568 BOOST_CHECK_EQUAL( entry.nameAtImport, wxString( "CLK_P5" ) );
9569 BOOST_REQUIRE( !entry.terminals.empty() );
9570
9571 for( const IMPORT_NET_TERMINAL& terminal : entry.terminals )
9572 {
9574 auto* symbol = dynamic_cast<SCH_SYMBOL*>( schematic.ResolveItem( terminal.symbolUuid, &path ) );
9575 BOOST_REQUIRE( symbol );
9576 BOOST_CHECK_EQUAL( terminalNetName( schematic, symbol->GetRef( &path, false ), terminal.pinNumber ),
9577 entry.nameAtImport );
9578 }
9579 }
9580
9581 BOOST_CHECK( found );
9582}
9583
9584
9585BOOST_AUTO_TEST_CASE( NamedWirelessPinUsesOccurrenceNetName )
9586{
9587 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
9588
9589 if( !corpusEnv || !*corpusEnv )
9590 {
9591 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping named wireless-pin check." );
9592 return;
9593 }
9594
9595 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CURRENT_SENSOR.DSN" );
9596
9597 if( dsn.empty() )
9598 {
9599 BOOST_TEST_MESSAGE( "CURRENT_SENSOR.DSN not present in corpus; skipping named wireless-pin check." );
9600 return;
9601 }
9602
9603 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
9604 SETTINGS_MANAGER manager;
9605 manager.LoadProject( "" );
9606 schematic->SetProject( &manager.Prj() );
9607 schematic->CurrentSheet().clear();
9608 schematic->CurrentSheet().push_back( &schematic->Root() );
9609
9610 SCH_IO_ORCAD plugin;
9611 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
9612
9613 wxString netName = terminalNetName( *schematic, wxS( "U1" ), wxS( "7" ) );
9614 BOOST_CHECK_EQUAL( netName.AfterLast( '/' ), wxString( "VCC" ) );
9615 const IMPORT_NET_MAP* map = schematic->GetImportNetMap();
9616 BOOST_REQUIRE( map );
9617 bool mapped = false;
9618
9619 for( const IMPORT_NET_MAP_ENTRY& entry : map->entries )
9620 {
9621 if( entry.originalName == wxS( "VCC" ) )
9622 {
9624 BOOST_CHECK_EQUAL( entry.nameAtImport, netName );
9625 BOOST_CHECK( !entry.terminals.empty() );
9626 mapped = true;
9627 }
9628 }
9629
9630 BOOST_CHECK( mapped );
9631}
9632
9633
9634BOOST_AUTO_TEST_CASE( ReservedDatasheetPropertyUsesStandardField )
9635{
9636 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
9637
9638 if( !corpusEnv || !*corpusEnv )
9639 return;
9640
9641 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "reComputer J202_V1.0.DSN" );
9642
9643 if( dsn.empty() )
9644 return;
9645
9646 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
9647 SETTINGS_MANAGER manager;
9648 manager.LoadProject( "" );
9649 schematic->SetProject( &manager.Prj() );
9650 schematic->CurrentSheet().clear();
9651 schematic->CurrentSheet().push_back( &schematic->Root() );
9652
9653 SCH_IO_ORCAD plugin;
9654 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
9655 SCH_SYMBOL* resistor = nullptr;
9656
9657 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
9658 {
9659 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
9660 {
9661 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
9662
9663 if( symbol->GetRef( &path, false ) == wxS( "R48" ) )
9664 {
9665 resistor = symbol;
9666 break;
9667 }
9668 }
9669
9670 if( resistor )
9671 break;
9672 }
9673
9674 BOOST_REQUIRE( resistor );
9675 size_t datasheetFields = std::count_if(
9676 resistor->GetFields().begin(), resistor->GetFields().end(),
9677 []( const SCH_FIELD& field ) { return field.GetName() == wxS( "Datasheet" ); } );
9678 BOOST_CHECK_EQUAL( datasheetFields, 1u );
9680 resistor->GetField( FIELD_T::DATASHEET )->GetText(),
9681 wxS( "Y:\\01_Cadence_Library\\05_Datasheet\\301010000_YAGEO_RC0402JR-070RL_Datasheet.pdf" ) );
9682}
9683
9684
9685BOOST_AUTO_TEST_CASE( ReservedFootprintPropertyUsesDistinctMetadataField )
9686{
9687 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
9688
9689 if( !corpusEnv || !*corpusEnv )
9690 return;
9691
9692 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CY4532 Power Board Schematic.DSN" );
9693
9694 if( dsn.empty() )
9695 return;
9696
9697 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
9698 SETTINGS_MANAGER manager;
9699 manager.LoadProject( "" );
9700 schematic->SetProject( &manager.Prj() );
9701 schematic->CurrentSheet().clear();
9702 schematic->CurrentSheet().push_back( &schematic->Root() );
9703
9704 SCH_IO_ORCAD plugin;
9705 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
9706 SCH_SYMBOL* capacitor = nullptr;
9707
9708 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
9709 {
9710 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
9711 {
9712 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
9713
9714 if( symbol->GetRef( &path, false ) == wxS( "C45" ) )
9715 {
9716 capacitor = symbol;
9717 break;
9718 }
9719 }
9720
9721 if( capacitor )
9722 break;
9723 }
9724
9725 BOOST_REQUIRE( capacitor );
9726 size_t footprintFields = std::count_if(
9727 capacitor->GetFields().begin(), capacitor->GetFields().end(),
9728 []( const SCH_FIELD& field ) { return field.GetName() == wxS( "Footprint" ); } );
9729 BOOST_CHECK_EQUAL( footprintFields, 1u );
9730 BOOST_CHECK( capacitor->GetField( FIELD_T::FOOTPRINT )->GetText().IsEmpty() );
9731
9732 SCH_FIELD* metadata = capacitor->GetField( wxS( "OrCAD Footprint Property" ) );
9733 BOOST_REQUIRE( metadata );
9734 BOOST_CHECK_EQUAL( metadata->GetText(), wxS( "0402" ) );
9735}
9736
9737
9738BOOST_AUTO_TEST_CASE( DisplayedPropertiesUseCaptureNameMatchingAndStandardFields )
9739{
9740 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
9741
9742 if( !corpusEnv || !*corpusEnv )
9743 return;
9744
9745 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CY4532 Power Board Schematic.DSN" );
9746
9747 if( dsn.empty() )
9748 return;
9749
9750 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
9751 SETTINGS_MANAGER manager;
9752 manager.LoadProject( "" );
9753 schematic->SetProject( &manager.Prj() );
9754 schematic->CurrentSheet().clear();
9755 schematic->CurrentSheet().push_back( &schematic->Root() );
9756
9757 SCH_IO_ORCAD plugin;
9758 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
9759 SCH_SYMBOL* capacitor = nullptr;
9760 SCH_SYMBOL* resistor = nullptr;
9761 SCH_SYMBOL* testPoint = nullptr;
9762
9763 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
9764 {
9765 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
9766 {
9767 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
9768 wxString reference = symbol->GetRef( &path, false );
9769
9770 if( reference == wxS( "C104" ) )
9771 capacitor = symbol;
9772 else if( reference == wxS( "R58" ) )
9773 resistor = symbol;
9774 else if( reference == wxS( "TP8" ) )
9775 testPoint = symbol;
9776 }
9777 }
9778
9779 BOOST_REQUIRE( capacitor );
9780 SCH_FIELD* voltage = capacitor->GetField( wxS( "Voltage" ) );
9781 BOOST_REQUIRE( voltage );
9782 BOOST_CHECK( voltage->IsVisible() );
9783 BOOST_CHECK( voltage->GetDrawRotation() == ANGLE_HORIZONTAL );
9784 BOOST_CHECK( voltage->GetPosition()
9785 == OrcadDbuToIu( 722, 929 )
9786 + VECTOR2I( 0, OrcadTextBaselineOffset( voltage->GetTextSize().y ) ) );
9787
9788 BOOST_REQUIRE( resistor );
9789 BOOST_CHECK( resistor->GetField( FIELD_T::VALUE )->IsVisible() );
9790
9791 BOOST_REQUIRE( testPoint );
9792 SCH_FIELD* description = testPoint->GetField( FIELD_T::DESCRIPTION );
9793 BOOST_REQUIRE( description );
9794 BOOST_CHECK( description->IsVisible() );
9795 BOOST_CHECK( description->GetDrawRotation() == ANGLE_HORIZONTAL );
9796 BOOST_CHECK( description->GetPosition()
9797 == OrcadDbuToIu( 810, 945 )
9798 + VECTOR2I( 0, OrcadTextBaselineOffset( description->GetTextSize().y ) ) );
9799}
9800
9801
9802BOOST_AUTO_TEST_CASE( DisplayedInheritedPartFieldsArePreserved )
9803{
9804 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
9805
9806 if( !corpusEnv || !*corpusEnv )
9807 return;
9808
9809 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC1859A-2.DSN" );
9810
9811 if( dsn.empty() )
9812 return;
9813
9814 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
9815 SETTINGS_MANAGER manager;
9816 manager.LoadProject( "" );
9817 schematic->SetProject( &manager.Prj() );
9818 schematic->CurrentSheet().clear();
9819 schematic->CurrentSheet().push_back( &schematic->Root() );
9820
9821 SCH_IO_ORCAD plugin;
9822 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
9823 SCH_SYMBOL* capacitor = nullptr;
9824
9825 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
9826 {
9827 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
9828 {
9829 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
9830
9831 if( symbol->GetRef( &path, false ) == wxS( "C2" ) )
9832 {
9833 capacitor = symbol;
9834 break;
9835 }
9836 }
9837
9838 if( capacitor )
9839 break;
9840 }
9841
9842 BOOST_REQUIRE( capacitor );
9843 SCH_FIELD* voltage = capacitor->GetField( wxS( "1st Part Field" ) );
9844 BOOST_REQUIRE( voltage );
9845 BOOST_CHECK_EQUAL( voltage->GetText(), wxS( "10V" ) );
9846 BOOST_CHECK( voltage->IsVisible() );
9847}
9848
9849
9850BOOST_AUTO_TEST_CASE( DisplayTypeTwoPropertiesRenderNamesAndValues )
9851{
9852 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
9853
9854 if( !corpusEnv || !*corpusEnv )
9855 return;
9856
9857 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SCH-48278.DSN" );
9858
9859 if( dsn.empty() )
9860 return;
9861
9862 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
9863 SETTINGS_MANAGER manager;
9864 manager.LoadProject( "" );
9865 schematic->SetProject( &manager.Prj() );
9866 schematic->CurrentSheet().clear();
9867 schematic->CurrentSheet().push_back( &schematic->Root() );
9868
9869 SCH_IO_ORCAD plugin;
9870 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
9871 SCH_FIELD* jumper = nullptr;
9872 bool renderedJumper = false;
9873
9874 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
9875 {
9876 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
9877 {
9878 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
9879
9880 if( symbol->GetRef( &path, false ) == wxS( "J2" ) )
9881 {
9882 jumper = symbol->GetField( wxS( "JUMPER(DEFAULT)" ) );
9883 break;
9884 }
9885 }
9886
9887 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_TEXT_T ) )
9888 {
9889 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
9890 renderedJumper = renderedJumper
9891 || text->GetText() == wxS( "JUMPER(DEFAULT) = OFF:POLARITY_SEL_L" );
9892 }
9893
9894 if( jumper )
9895 break;
9896 }
9897
9898 BOOST_REQUIRE( jumper );
9899 BOOST_CHECK_EQUAL( jumper->GetText(), wxS( "OFF:POLARITY_SEL_L" ) );
9900 BOOST_CHECK( renderedJumper );
9901}
9902
9903
9904BOOST_AUTO_TEST_CASE( DisplayTypeFourPropertiesShowOnlyTheirValues )
9905{
9906 ORCAD_DISPLAY_PROP property;
9907 property.dispMode = 0x400;
9908
9909 BOOST_CHECK( OrcadDisplayPropVisible( property ) );
9910 BOOST_CHECK( !OrcadDisplayPropShowsName( property ) );
9911 BOOST_CHECK( OrcadDisplayPropShowsValue( property ) );
9912}
9913
9914
9915BOOST_AUTO_TEST_CASE( DisplayTypeThreePropertiesShowOnlyTheirNames )
9916{
9917 ORCAD_DISPLAY_PROP property;
9918 property.dispMode = 0x300;
9919
9920 BOOST_CHECK( OrcadDisplayPropVisible( property ) );
9921 BOOST_CHECK( OrcadDisplayPropShowsName( property ) );
9922 BOOST_CHECK( !OrcadDisplayPropShowsValue( property ) );
9923}
9924
9925
9926BOOST_AUTO_TEST_CASE( SimulationResultPropertiesAreNotPersistentGraphics )
9927{
9928 ORCAD_DISPLAY_PROP property;
9929 property.dispMode = 0x100;
9930
9931 property.name = "BiasValue Power";
9932 BOOST_CHECK( !OrcadDisplayPropVisible( property ) );
9933
9934 property.name = "BiasValue Current";
9935 BOOST_CHECK( !OrcadDisplayPropVisible( property ) );
9936
9937 property.name = "BiasValue Voltage";
9938 BOOST_CHECK( !OrcadDisplayPropVisible( property ) );
9939}
9940
9941
9942BOOST_AUTO_TEST_CASE( GeneratedWirelessNetOverridesPeerPinName )
9943{
9944 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
9945
9946 if( !corpusEnv || !*corpusEnv )
9947 {
9948 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping wireless peer-net check." );
9949 return;
9950 }
9951
9952 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CY3280_MBR3 EVK Schematic.DSN" );
9953
9954 if( dsn.empty() )
9955 {
9956 BOOST_TEST_MESSAGE( "CY3280_MBR3 EVK Schematic.DSN not present; skipping wireless peer-net check." );
9957 return;
9958 }
9959
9960 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
9961 SETTINGS_MANAGER manager;
9962 manager.LoadProject( "" );
9963 schematic->SetProject( &manager.Prj() );
9964 schematic->CurrentSheet().clear();
9965 schematic->CurrentSheet().push_back( &schematic->Root() );
9966
9967 SCH_IO_ORCAD plugin;
9968 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
9969
9970 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
9971 schematic->ConnectionGraph()->Recalculate( sheets, true );
9972 std::map<std::pair<wxString, wxString>, CONNECTION_SUBGRAPH*> pinNets;
9973
9974 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
9975 {
9976 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
9977 {
9978 for( SCH_ITEM* item : subgraph->GetItems() )
9979 {
9980 if( item->Type() != SCH_PIN_T )
9981 continue;
9982
9983 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
9984 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
9985
9986 if( symbol )
9987 pinNets[{ symbol->GetRef( &subgraph->GetSheet(), false ), pin->GetNumber() }] = subgraph;
9988 }
9989 }
9990 }
9991
9992 BOOST_REQUIRE_EQUAL( pinNets.count( { wxS( "R51" ), wxS( "1" ) } ), 1u );
9993 BOOST_REQUIRE_EQUAL( pinNets.count( { wxS( "R51" ), wxS( "2" ) } ), 1u );
9994 BOOST_REQUIRE_EQUAL( pinNets.count( { wxS( "R52" ), wxS( "1" ) } ), 1u );
9995 BOOST_REQUIRE_EQUAL( pinNets.count( { wxS( "R52" ), wxS( "2" ) } ), 1u );
9996 CONNECTION_SUBGRAPH* r51Pin1 = pinNets.at( { wxS( "R51" ), wxS( "1" ) } );
9997 CONNECTION_SUBGRAPH* r51Pin2 = pinNets.at( { wxS( "R51" ), wxS( "2" ) } );
9998 CONNECTION_SUBGRAPH* r52Pin1 = pinNets.at( { wxS( "R52" ), wxS( "1" ) } );
9999 CONNECTION_SUBGRAPH* r52Pin2 = pinNets.at( { wxS( "R52" ), wxS( "2" ) } );
10000
10001 BOOST_CHECK_NE( r51Pin1, r51Pin2 );
10002 BOOST_CHECK_NE( r52Pin1, r52Pin2 );
10003}
10004
10005
10006BOOST_AUTO_TEST_CASE( GlobalNetNamesAreCaseInsensitive )
10007{
10008 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10009
10010 if( !corpusEnv || !*corpusEnv )
10011 {
10012 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping global-net case check." );
10013 return;
10014 }
10015
10016 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "BeagleBoard-xM_ORCAD.DSN" );
10017
10018 if( dsn.empty() )
10019 {
10020 BOOST_TEST_MESSAGE( "BeagleBoard-xM_ORCAD.DSN not present in corpus; skipping global-net case check." );
10021 return;
10022 }
10023
10024 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10025 SETTINGS_MANAGER manager;
10026 manager.LoadProject( "" );
10027 schematic->SetProject( &manager.Prj() );
10028 schematic->CurrentSheet().clear();
10029 schematic->CurrentSheet().push_back( &schematic->Root() );
10030
10031 SCH_IO_ORCAD plugin;
10032 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10033
10034 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
10035 schematic->ConnectionGraph()->Recalculate( sheets, true );
10036
10037 std::map<std::tuple<std::string, std::string, std::string>, int> terminalNets;
10038 int netId = 0;
10039
10040 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
10041 {
10042 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
10043 {
10044 std::string page = subgraph->GetSheet().LastScreen()->GetFileName().ToStdString();
10045
10046 for( SCH_ITEM* item : subgraph->GetItems() )
10047 {
10048 if( item->Type() != SCH_PIN_T )
10049 continue;
10050
10051 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
10052 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
10053
10054 if( symbol )
10055 {
10056 terminalNets[{ page, symbol->GetRef( &subgraph->GetSheet(), false ).ToStdString(),
10057 pin->GetNumber().ToStdString() }] = netId;
10058 }
10059 }
10060 }
10061
10062 ++netId;
10063 }
10064
10065 auto findNet = [&]( const std::string& aPage, const std::string& aRef, const std::string& aPin )
10066 {
10067 for( const auto& [terminal, id] : terminalNets )
10068 {
10069 if( std::get<0>( terminal ).find( aPage ) != std::string::npos && std::get<1>( terminal ) == aRef
10070 && std::get<2>( terminal ) == aPin )
10071 {
10072 return id;
10073 }
10074 }
10075
10076 return -1;
10077 };
10078
10079 int processor = findNet( "PROCESSOR_C", "C75", "1" );
10080 int power = findNet( "PMIC _POWER", "C122", "1" );
10081
10082 BOOST_REQUIRE_NE( processor, -1 );
10083 BOOST_REQUIRE_NE( power, -1 );
10084 BOOST_CHECK_EQUAL( processor, power );
10085}
10086
10087
10088BOOST_AUTO_TEST_CASE( CaptureBusRangesUseKiCadSyntax )
10089{
10090 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10091
10092 if( !corpusEnv || !*corpusEnv )
10093 {
10094 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping bus-range syntax check." );
10095 return;
10096 }
10097
10098 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "parallella_e16_z7020_schematic.dsn" );
10099
10100 if( dsn.empty() )
10101 {
10102 BOOST_TEST_MESSAGE( "parallella_e16_z7020_schematic.dsn not present in corpus; skipping bus-range check." );
10103 return;
10104 }
10105
10106 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10107 SETTINGS_MANAGER manager;
10108 manager.LoadProject( "" );
10109 schematic->SetProject( &manager.Prj() );
10110 schematic->CurrentSheet().clear();
10111 schematic->CurrentSheet().push_back( &schematic->Root() );
10112
10113 SCH_IO_ORCAD plugin;
10114 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10115
10116 bool found = false;
10117
10118 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
10119 {
10120 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
10121 {
10122 wxString text = static_cast<SCH_GLOBALLABEL*>( item )->GetText();
10123 BOOST_CHECK_NE( text, wxS( "DDR_DQ[31:0]" ) );
10124 found |= text == wxS( "DDR_DQ[31..0]" );
10125 }
10126 }
10127
10128 BOOST_CHECK( found );
10129}
10130
10131
10132BOOST_AUTO_TEST_CASE( CanonicalPropertiesIgnoreCaseInsensitiveDuplicates )
10133{
10134 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10135
10136 if( !corpusEnv || !*corpusEnv )
10137 return;
10138
10139 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "parallella_e16_z7020_schematic.dsn" );
10140
10141 if( dsn.empty() )
10142 return;
10143
10144 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10145 SETTINGS_MANAGER manager;
10146 manager.LoadProject( "" );
10147 schematic->SetProject( &manager.Prj() );
10148 schematic->CurrentSheet().clear();
10149 schematic->CurrentSheet().push_back( &schematic->Root() );
10150
10151 SCH_IO_ORCAD plugin;
10152 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10153 SCH_SYMBOL* platedHole = nullptr;
10154
10155 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
10156 {
10157 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
10158 {
10159 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
10160
10161 if( symbol->GetRef( &path, false ) == wxS( "PTH1" ) )
10162 {
10163 platedHole = symbol;
10164 break;
10165 }
10166 }
10167
10168 if( platedHole )
10169 break;
10170 }
10171
10172 BOOST_REQUIRE( platedHole );
10173 size_t valueFields = std::count_if(
10174 platedHole->GetFields().begin(), platedHole->GetFields().end(),
10175 []( const SCH_FIELD& aField ) { return aField.GetName().CmpNoCase( wxS( "Value" ) ) == 0; } );
10176 BOOST_CHECK_EQUAL( valueFields, 1u );
10177 BOOST_CHECK_EQUAL( platedHole->GetField( FIELD_T::VALUE )->GetShownText( FOR_CANVAS ), wxS( "PTH125_200PAD" ) );
10178}
10179
10180
10181BOOST_AUTO_TEST_CASE( CollidingOccurrenceAliasesRemainSeparate )
10182{
10183 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10184
10185 if( !corpusEnv || !*corpusEnv )
10186 return;
10187
10188 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "parallella_e16_z7020_schematic.dsn" );
10189
10190 if( dsn.empty() )
10191 return;
10192
10193 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10194 SETTINGS_MANAGER manager;
10195 manager.LoadProject( "" );
10196 schematic->SetProject( &manager.Prj() );
10197 schematic->CurrentSheet().clear();
10198 schematic->CurrentSheet().push_back( &schematic->Root() );
10199
10200 SCH_IO_ORCAD plugin;
10201 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10202
10203 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
10204 schematic->ConnectionGraph()->Recalculate( sheets, true );
10205
10206 std::map<std::string, int> terminalNets;
10207 int netId = 0;
10208
10209 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
10210 {
10211 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
10212 {
10213 for( SCH_ITEM* item : subgraph->GetItems() )
10214 {
10215 if( item->Type() != SCH_PIN_T )
10216 continue;
10217
10218 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
10219 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
10220
10221 if( symbol )
10222 {
10223 terminalNets[terminalToken( symbol->GetRef( &subgraph->GetSheet(), false ).ToStdString(),
10224 pin->GetNumber().ToStdString() )] = netId;
10225 }
10226 }
10227 }
10228
10229 ++netId;
10230 }
10231
10232 BOOST_REQUIRE( terminalNets.count( "R41.2" ) );
10233 BOOST_REQUIRE( terminalNets.count( "U26.53" ) );
10234 BOOST_REQUIRE( terminalNets.count( "R56.2" ) );
10235 BOOST_REQUIRE( terminalNets.count( "U11.1" ) );
10236 BOOST_REQUIRE( terminalNets.count( "U11.3" ) );
10237 BOOST_REQUIRE( terminalNets.count( "U14.1" ) );
10238 BOOST_CHECK_EQUAL( terminalNets["R41.2"], terminalNets["U26.53"] );
10239 BOOST_CHECK_EQUAL( terminalNets["R56.2"], terminalNets["U11.1"] );
10240 BOOST_CHECK_EQUAL( terminalNets["R56.2"], terminalNets["U11.3"] );
10241 BOOST_CHECK_EQUAL( terminalNets["R56.2"], terminalNets["U14.1"] );
10242 BOOST_CHECK_NE( terminalNets["R41.2"], terminalNets["R56.2"] );
10243}
10244
10245
10246BOOST_AUTO_TEST_CASE( CollidingOffpageOccurrenceAliasesRemainSeparate )
10247{
10248 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10249
10250 if( !corpusEnv || !*corpusEnv )
10251 return;
10252
10253 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "parallella_gen0.dsn" );
10254
10255 if( dsn.empty() )
10256 return;
10257
10258 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10259 SETTINGS_MANAGER manager;
10260 manager.LoadProject( "" );
10261 schematic->SetProject( &manager.Prj() );
10262 schematic->CurrentSheet().clear();
10263 schematic->CurrentSheet().push_back( &schematic->Root() );
10264
10265 SCH_IO_ORCAD plugin;
10266 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10267
10268 auto [consistent, checkable] = checkConnectivity(
10269 *schematic,
10270 { { terminalToken( "R41", "2" ), terminalToken( "U26", "53" ) },
10271 { terminalToken( "R57", "2" ), terminalToken( "R97", "1" ), terminalToken( "U14", "1" ) } } );
10272 BOOST_CHECK_EQUAL( checkable, 2 );
10273 BOOST_CHECK_EQUAL( consistent, 2 );
10274}
10275
10276
10277BOOST_AUTO_TEST_CASE( FlatFolderOccurrenceAliasesConnectDisplacedPins )
10278{
10279 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10280
10281 if( !corpusEnv || !*corpusEnv )
10282 return;
10283
10284 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SCH-20380.DSN" );
10285
10286 if( dsn.empty() )
10287 return;
10288
10289 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10290 SETTINGS_MANAGER manager;
10291 manager.LoadProject( "" );
10292 schematic->SetProject( &manager.Prj() );
10293 schematic->CurrentSheet().clear();
10294 schematic->CurrentSheet().push_back( &schematic->Root() );
10295
10296 SCH_IO_ORCAD plugin;
10297 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10298
10299 auto [consistent, checkable] = checkConnectivity(
10300 *schematic, { { terminalToken( "J10", "29" ), terminalToken( "RP15", "3" ), terminalToken( "U10", "R9" ),
10301 terminalToken( "U33", "30" ) },
10302 { terminalToken( "J10", "30" ), terminalToken( "JP62", "1" ), terminalToken( "RP16", "1" ),
10303 terminalToken( "U10", "P9" ) },
10304 { terminalToken( "J7", "50" ), terminalToken( "RP14", "5" ), terminalToken( "U10", "D12" ),
10305 terminalToken( "U33", "21" ), terminalToken( "U5", "C" ) } } );
10306 BOOST_CHECK_EQUAL( checkable, 3 );
10307 BOOST_CHECK_EQUAL( consistent, 3 );
10308}
10309
10310
10311BOOST_AUTO_TEST_CASE( PowerNetNameWinsOverSecondaryOccurrencePortAlias )
10312{
10313 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10314
10315 if( !corpusEnv || !*corpusEnv )
10316 return;
10317
10318 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SCH-38863_CX1.DSN" );
10319
10320 if( dsn.empty() )
10321 return;
10322
10323 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10324 SETTINGS_MANAGER manager;
10325 manager.LoadProject( "" );
10326 schematic->SetProject( &manager.Prj() );
10327 schematic->CurrentSheet().clear();
10328 schematic->CurrentSheet().push_back( &schematic->Root() );
10329
10330 SCH_IO_ORCAD plugin;
10331 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10332
10333 const IMPORT_NET_MAP* map = schematic->GetImportNetMap();
10334 BOOST_REQUIRE( map );
10335 std::set<wxString> netNames;
10336
10337 for( const auto& [reference, peer, sourceName] :
10338 { std::tuple{ wxString( "J16" ), wxString( "C520" ), wxString( "GNDISOHU" ) },
10339 std::tuple{ wxString( "J17" ), wxString( "C590" ), wxString( "GNDISOHV" ) },
10340 std::tuple{ wxString( "J18" ), wxString( "C661" ), wxString( "GNDISOHW" ) } } )
10341 {
10342 wxString netName = terminalNetName( *schematic, reference, wxS( "1" ) );
10343 BOOST_CHECK_EQUAL( netName.AfterLast( '/' ).Upper(), sourceName );
10344 BOOST_CHECK_EQUAL( terminalNetName( *schematic, peer, wxS( "2" ) ), netName );
10345 netNames.insert( netName );
10346 bool mapped = false;
10347
10348 for( const IMPORT_NET_MAP_ENTRY& entry : map->entries )
10349 {
10350 if( entry.originalName.CmpNoCase( sourceName ) == 0 )
10351 {
10353 BOOST_CHECK_EQUAL( entry.nameAtImport, netName );
10354 mapped = true;
10355 }
10356 }
10357
10358 BOOST_CHECK( mapped );
10359 }
10360
10361 BOOST_CHECK_EQUAL( netNames.size(), 3u );
10362}
10363
10364
10365BOOST_AUTO_TEST_CASE( DuplicatePageNetIdsPreserveAliases )
10366{
10367 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10368
10369 if( !corpusEnv || !*corpusEnv )
10370 {
10371 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping duplicate page-net check." );
10372 return;
10373 }
10374
10375 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "MTB4.DSN" );
10376
10377 if( dsn.empty() )
10378 {
10379 BOOST_TEST_MESSAGE( "MTB4.DSN not present in corpus; skipping duplicate page-net check." );
10380 return;
10381 }
10382
10383 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10384 SETTINGS_MANAGER manager;
10385 manager.LoadProject( "" );
10386 schematic->SetProject( &manager.Prj() );
10387 schematic->CurrentSheet().clear();
10388 schematic->CurrentSheet().push_back( &schematic->Root() );
10389
10390 SCH_IO_ORCAD plugin;
10391 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10392
10393 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
10394 schematic->ConnectionGraph()->Recalculate( sheets, true );
10395
10396 std::map<std::string, wxString> pinNets;
10397
10398 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
10399 {
10400 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
10401 {
10402 if( subgraph->GetSheet().LastScreen()->GetFileName().Find( wxS( "PAGE 2 - MCU" ) ) == wxNOT_FOUND )
10403 continue;
10404
10405 for( SCH_ITEM* item : subgraph->GetItems() )
10406 {
10407 if( item->Type() != SCH_PIN_T )
10408 continue;
10409
10410 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
10411 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
10412
10413 if( symbol && symbol->GetRef( &subgraph->GetSheet(), false ) == wxS( "U1" ) )
10414 pinNets[pin->GetNumber().ToStdString()] = key.Name;
10415 }
10416 }
10417 }
10418
10419 BOOST_REQUIRE_EQUAL( pinNets.count( "F3" ), 1u );
10420 BOOST_REQUIRE_EQUAL( pinNets.count( "F8" ), 1u );
10421 BOOST_REQUIRE_EQUAL( pinNets.count( "G3" ), 1u );
10422 BOOST_REQUIRE_EQUAL( pinNets.count( "G7" ), 1u );
10423 BOOST_CHECK( pinNets["F3"].EndsWith( wxS( "I2C2_SDA" ) ) );
10424 BOOST_CHECK( pinNets["F8"].EndsWith( wxS( "I2C2_SDA" ) ) );
10425 BOOST_CHECK( pinNets["G3"].EndsWith( wxS( "I2C2_SCL" ) ) );
10426 BOOST_CHECK( pinNets["G7"].EndsWith( wxS( "I2C2_SCL" ) ) );
10427}
10428
10429
10430BOOST_AUTO_TEST_CASE( DuplicatePageNetIdsDoNotShortDistinctNets )
10431{
10432 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10433
10434 if( !corpusEnv || !*corpusEnv )
10435 {
10436 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping duplicate page-net isolation check." );
10437 return;
10438 }
10439
10440 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2125A-2.DSN" );
10441
10442 if( dsn.empty() )
10443 {
10444 BOOST_TEST_MESSAGE( "DC2125A-2.DSN not present; skipping duplicate page-net isolation check." );
10445 return;
10446 }
10447
10448 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10449 SETTINGS_MANAGER manager;
10450 manager.LoadProject( "" );
10451 schematic->SetProject( &manager.Prj() );
10452 schematic->CurrentSheet().clear();
10453 schematic->CurrentSheet().push_back( &schematic->Root() );
10454
10455 SCH_IO_ORCAD plugin;
10456 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10457
10458 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
10459 schematic->ConnectionGraph()->Recalculate( sheets, true );
10460 std::map<std::pair<wxString, wxString>, CONNECTION_SUBGRAPH*> pinNets;
10461
10462 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
10463 {
10464 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
10465 {
10466 for( SCH_ITEM* item : subgraph->GetItems() )
10467 {
10468 if( item->Type() != SCH_PIN_T )
10469 continue;
10470
10471 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
10472 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
10473
10474 if( symbol )
10475 pinNets[{ symbol->GetRef( &subgraph->GetSheet(), false ), pin->GetNumber() }] = subgraph;
10476 }
10477 }
10478 }
10479
10480 CONNECTION_SUBGRAPH* earth = pinNets.at( { wxS( "J2" ), wxS( "9" ) } );
10481 CONNECTION_SUBGRAPH* vportn = pinNets.at( { wxS( "C1" ), wxS( "2" ) } );
10482 BOOST_CHECK_NE( earth, vportn );
10483}
10484
10485
10486BOOST_AUTO_TEST_CASE( AliasAtCrossingDoesNotShortCaptureNets )
10487{
10488 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10489
10490 if( !corpusEnv || !*corpusEnv )
10491 {
10492 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping alias-at-crossing isolation check." );
10493 return;
10494 }
10495
10496 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "OC_CONNECT1_FRONTEND_REV_C_V1P1.DSN" );
10497
10498 if( dsn.empty() )
10499 {
10500 BOOST_TEST_MESSAGE( "OC_CONNECT1_FRONTEND_REV_C_V1P1.DSN not present; skipping alias-at-crossing check." );
10501 return;
10502 }
10503
10504 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10505 SETTINGS_MANAGER manager;
10506 manager.LoadProject( "" );
10507 schematic->SetProject( &manager.Prj() );
10508 schematic->CurrentSheet().clear();
10509 schematic->CurrentSheet().push_back( &schematic->Root() );
10510
10511 SCH_IO_ORCAD plugin;
10512 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10513
10514 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
10515 schematic->ConnectionGraph()->Recalculate( sheets, true );
10516 std::map<std::pair<wxString, wxString>, CONNECTION_SUBGRAPH*> pinNets;
10517
10518 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
10519 {
10520 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
10521 {
10522 for( SCH_ITEM* item : subgraph->GetItems() )
10523 {
10524 if( item->Type() != SCH_PIN_T )
10525 continue;
10526
10527 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
10528 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
10529
10530 if( symbol )
10531 pinNets[{ symbol->GetRef( &subgraph->GetSheet(), false ), pin->GetNumber() }] = subgraph;
10532 }
10533 }
10534 }
10535
10536 CONNECTION_SUBGRAPH* a0 = pinNets.at( { wxS( "U7157" ), wxS( "3" ) } );
10537 CONNECTION_SUBGRAPH* a2 = pinNets.at( { wxS( "U7157" ), wxS( "5" ) } );
10538 CONNECTION_SUBGRAPH* io1 = pinNets.at( { wxS( "U7157" ), wxS( "7" ) } );
10539 BOOST_CHECK_EQUAL( a0, pinNets.at( { wxS( "R1150" ), wxS( "2" ) } ) );
10540 BOOST_CHECK_EQUAL( a0, pinNets.at( { wxS( "R1153" ), wxS( "1" ) } ) );
10541 BOOST_CHECK_EQUAL( a2, pinNets.at( { wxS( "R1152" ), wxS( "2" ) } ) );
10542 BOOST_CHECK_EQUAL( a2, pinNets.at( { wxS( "R1155" ), wxS( "1" ) } ) );
10543 BOOST_CHECK_EQUAL( io1, pinNets.at( { wxS( "R954" ), wxS( "2" ) } ) );
10544 BOOST_CHECK_NE( a0, a2 );
10545 BOOST_CHECK_NE( a0, io1 );
10546 BOOST_CHECK_NE( a2, io1 );
10547}
10548
10549
10550BOOST_AUTO_TEST_CASE( WirelessPinUsesPageNetId )
10551{
10552 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10553
10554 if( !corpusEnv || !*corpusEnv )
10555 {
10556 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping wireless page-net check." );
10557 return;
10558 }
10559
10560 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "Si828X-BW-GDB.DSN" );
10561
10562 if( dsn.empty() )
10563 {
10564 BOOST_TEST_MESSAGE( "Si828X-BW-GDB.DSN not present; skipping wireless page-net check." );
10565 return;
10566 }
10567
10568 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10569 SETTINGS_MANAGER manager;
10570 manager.LoadProject( "" );
10571 schematic->SetProject( &manager.Prj() );
10572 schematic->CurrentSheet().clear();
10573 schematic->CurrentSheet().push_back( &schematic->Root() );
10574
10575 SCH_IO_ORCAD plugin;
10576 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10577
10578 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
10579 schematic->ConnectionGraph()->Recalculate( sheets, true );
10580 std::map<std::pair<wxString, wxString>, wxString> pinNames;
10581
10582 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
10583 {
10584 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
10585 {
10586 for( SCH_ITEM* item : subgraph->GetItems() )
10587 {
10588 if( item->Type() != SCH_PIN_T )
10589 continue;
10590
10591 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
10592 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
10593
10594 if( symbol )
10595 pinNames[{ symbol->GetRef( &subgraph->GetSheet(), false ), pin->GetNumber() }] = key.Name;
10596 }
10597 }
10598 }
10599
10600 BOOST_CHECK_EQUAL( pinNames.at( { wxS( "CB28" ), wxS( "2" ) } ), wxS( "5V" ) );
10601 BOOST_CHECK_EQUAL( pinNames.at( { wxS( "RT17" ), wxS( "2" ) } ), wxS( "5V" ) );
10602}
10603
10604
10605BOOST_AUTO_TEST_CASE( AmbiguousWirelessPinUsesOccurrenceNetName )
10606{
10607 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10608
10609 if( !corpusEnv || !*corpusEnv )
10610 {
10611 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping ambiguous wireless-net check." );
10612 return;
10613 }
10614
10615 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2047A-3-A.DSN" );
10616
10617 if( dsn.empty() )
10618 {
10619 BOOST_TEST_MESSAGE( "DC2047A-3-A.DSN not present; skipping ambiguous wireless-net check." );
10620 return;
10621 }
10622
10623 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10624 SETTINGS_MANAGER manager;
10625 manager.LoadProject( "" );
10626 schematic->SetProject( &manager.Prj() );
10627 schematic->CurrentSheet().clear();
10628 schematic->CurrentSheet().push_back( &schematic->Root() );
10629
10630 SCH_IO_ORCAD plugin;
10631 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10632
10633 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
10634 schematic->ConnectionGraph()->Recalculate( sheets, true );
10635 wxString d15Pin2;
10636
10637 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
10638 {
10639 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
10640 {
10641 for( SCH_ITEM* item : subgraph->GetItems() )
10642 {
10643 if( item->Type() != SCH_PIN_T )
10644 continue;
10645
10646 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
10647 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
10648
10649 if( symbol && symbol->GetRef( &subgraph->GetSheet(), false ) == wxS( "D15" )
10650 && pin->GetNumber() == wxS( "2" ) )
10651 {
10652 d15Pin2 = key.Name;
10653 }
10654 }
10655 }
10656 }
10657
10658 BOOST_CHECK_EQUAL( d15Pin2, wxS( "AUX_RECTIFIED-" ) );
10659}
10660
10661
10662BOOST_AUTO_TEST_CASE( BlankPackagePinNumbersUseLogicalNames )
10663{
10664 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10665
10666 if( !corpusEnv || !*corpusEnv )
10667 return;
10668
10669 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2084A-3.DSN" );
10670
10671 if( dsn.empty() )
10672 return;
10673
10674 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10675 SETTINGS_MANAGER manager;
10676 manager.LoadProject( "" );
10677 schematic->SetProject( &manager.Prj() );
10678 schematic->CurrentSheet().clear();
10679 schematic->CurrentSheet().push_back( &schematic->Root() );
10680
10681 SCH_IO_ORCAD plugin;
10682 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10683
10684 std::map<wxString, std::set<wxString>> pinNumbers;
10685
10686 for( const SCH_SHEET_PATH& sheet : schematic->BuildSheetListSortedByPageNumbers() )
10687 {
10688 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
10689 {
10690 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
10691
10692 wxString reference = symbol->GetRef( &sheet, false );
10693
10694 if( reference != wxS( "C31" ) && reference != wxS( "C42" ) )
10695 continue;
10696
10697 for( const std::unique_ptr<SCH_PIN>& pin : symbol->GetRawPins() )
10698 pinNumbers[reference].insert( pin->GetNumber() );
10699 }
10700 }
10701
10702 BOOST_CHECK( pinNumbers[wxS( "C31" )] == std::set<wxString>( { wxS( "1" ), wxS( "2" ) } ) );
10703 BOOST_CHECK( pinNumbers[wxS( "C42" )] == std::set<wxString>( { wxS( "1" ), wxS( "2" ) } ) );
10704}
10705
10706
10707BOOST_AUTO_TEST_CASE( FallbackPackagePrefersNumberedPins )
10708{
10709 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10710
10711 if( !corpusEnv || !*corpusEnv )
10712 return;
10713
10714 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2091A-3.DSN" );
10715
10716 if( dsn.empty() )
10717 return;
10718
10719 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10720 SETTINGS_MANAGER manager;
10721 manager.LoadProject( "" );
10722 schematic->SetProject( &manager.Prj() );
10723 schematic->CurrentSheet().clear();
10724 schematic->CurrentSheet().push_back( &schematic->Root() );
10725
10726 SCH_IO_ORCAD plugin;
10727 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10728
10729 std::map<wxString, std::set<wxString>> pinNumbers;
10730
10731 for( const SCH_SHEET_PATH& sheet : schematic->BuildSheetListSortedByPageNumbers() )
10732 {
10733 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
10734 {
10735 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
10736 wxString reference = symbol->GetRef( &sheet, false );
10737
10738 if( reference != wxS( "C1" ) && reference != wxS( "C2" ) )
10739 continue;
10740
10741 for( const std::unique_ptr<SCH_PIN>& pin : symbol->GetRawPins() )
10742 pinNumbers[reference].insert( pin->GetNumber() );
10743 }
10744 }
10745
10746 const std::set<wxString> expected = { wxS( "1" ), wxS( "2" ) };
10747 BOOST_CHECK( pinNumbers[wxS( "C1" )] == expected );
10748 BOOST_CHECK( pinNumbers[wxS( "C2" )] == expected );
10749}
10750
10751
10752BOOST_AUTO_TEST_CASE( FallbackPackageUsesLogicalPinOrder )
10753{
10754 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10755
10756 if( !corpusEnv || !*corpusEnv )
10757 return;
10758
10759 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2228A-3.DSN" );
10760
10761 if( dsn.empty() )
10762 return;
10763
10764 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10765 SETTINGS_MANAGER manager;
10766 manager.LoadProject( "" );
10767 schematic->SetProject( &manager.Prj() );
10768 schematic->CurrentSheet().clear();
10769 schematic->CurrentSheet().push_back( &schematic->Root() );
10770
10771 SCH_IO_ORCAD plugin;
10772 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10773
10774 std::vector<std::set<std::string>> expected = { { terminalToken( "R188", "2" ), terminalToken( "C100", "2" ) } };
10775 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
10776 BOOST_CHECK_EQUAL( checkable, 1 );
10777 BOOST_CHECK_EQUAL( consistent, 1 );
10778}
10779
10780
10781BOOST_AUTO_TEST_CASE( NumericUnitNamesUseNaturalOrder )
10782{
10783 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10784
10785 if( !corpusEnv || !*corpusEnv )
10786 return;
10787
10788 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2228A-3.DSN" );
10789
10790 if( dsn.empty() )
10791 return;
10792
10793 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10794 SETTINGS_MANAGER manager;
10795 manager.LoadProject( "" );
10796 schematic->SetProject( &manager.Prj() );
10797 schematic->CurrentSheet().clear();
10798 schematic->CurrentSheet().push_back( &schematic->Root() );
10799
10800 SCH_IO_ORCAD plugin;
10801 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10802
10803 std::map<int, size_t> pinCounts;
10804
10805 for( const SCH_SHEET_PATH& sheet : schematic->BuildSheetListSortedByPageNumbers() )
10806 {
10807 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
10808 {
10809 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
10810
10811 if( symbol->GetRef( &sheet, false ) == wxS( "U9" ) )
10812 pinCounts[symbol->GetUnit()] = symbol->GetPins().size();
10813 }
10814 }
10815
10816 const std::map<int, size_t> expected = {
10817 { 1, 2 }, { 2, 21 }, { 3, 14 }, { 4, 9 }, { 5, 11 }, { 6, 8 }, { 7, 13 },
10818 { 8, 8 }, { 9, 9 }, { 10, 7 }, { 11, 5 }, { 12, 7 }, { 13, 15 }, { 14, 17 },
10819 { 15, 16 }, { 16, 12 }, { 17, 32 }, { 18, 6 }, { 19, 22 }, { 20, 22 },
10820 };
10821 BOOST_CHECK( pinCounts == expected );
10822}
10823
10824
10825BOOST_AUTO_TEST_CASE( OccurrenceReferencesOverridePlacedTemplateReferences )
10826{
10827 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10828
10829 if( !corpusEnv || !*corpusEnv )
10830 return;
10831
10832 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SI82AX-CX_NB8_EVB.DSN" );
10833
10834 if( dsn.empty() )
10835 return;
10836
10837 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10838 SETTINGS_MANAGER manager;
10839 manager.LoadProject( "" );
10840 schematic->SetProject( &manager.Prj() );
10841 schematic->CurrentSheet().clear();
10842 schematic->CurrentSheet().push_back( &schematic->Root() );
10843
10844 SCH_IO_ORCAD plugin;
10845 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10846
10847 bool foundR2 = false;
10848 bool foundR17 = false;
10849 wxString refsAtR2Position;
10850 wxString refsAtR17Position;
10851
10852 for( const SCH_SHEET_PATH& sheet : schematic->BuildSheetListSortedByPageNumbers() )
10853 {
10854 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
10855 {
10856 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
10857 wxString reference = symbol->GetRef( &sheet, false );
10858
10859 if( symbol->GetPosition() == OrcadDbuToIu( 890, 200 ) )
10860 {
10861 refsAtR2Position += reference + wxS( " " );
10862 foundR2 = foundR2 || reference == wxS( "R2" );
10863 }
10864
10865 if( symbol->GetPosition() == OrcadDbuToIu( 1060, 395 ) )
10866 {
10867 refsAtR17Position += reference + wxS( " " );
10868 foundR17 = foundR17 || reference == wxS( "R17" );
10869 }
10870 }
10871 }
10872
10873 BOOST_CHECK_MESSAGE( foundR2, "references at R2 position: " << refsAtR2Position );
10874 BOOST_CHECK_MESSAGE( foundR17, "references at R17 position: " << refsAtR17Position );
10875}
10876
10877
10878BOOST_AUTO_TEST_CASE( RootOccurrenceTargetIdsOverridePlacedTemplateReferences )
10879{
10880 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10881
10882 if( !corpusEnv || !*corpusEnv )
10883 return;
10884
10885 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "Low EMI demo board.DSN" );
10886
10887 if( dsn.empty() )
10888 return;
10889
10890 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10891 SETTINGS_MANAGER manager;
10892 manager.LoadProject( "" );
10893 schematic->SetProject( &manager.Prj() );
10894 schematic->CurrentSheet().clear();
10895 schematic->CurrentSheet().push_back( &schematic->Root() );
10896
10897 SCH_IO_ORCAD plugin;
10898 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10899
10900 std::set<std::string> refs = collectImportedRefs( *schematic );
10901
10902 BOOST_CHECK( refs.count( "C1" ) );
10903 BOOST_CHECK( refs.count( "U1" ) );
10904 BOOST_CHECK( !refs.count( "C71" ) );
10905 BOOST_CHECK( !refs.count( "U12" ) );
10906}
10907
10908
10909BOOST_AUTO_TEST_CASE( FlatModernOccurrenceReferencesOverridePlacedReferences )
10910{
10911 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10912
10913 if( !corpusEnv || !*corpusEnv )
10914 return;
10915
10916 std::filesystem::path dsn;
10917
10918 for( const std::filesystem::directory_entry& entry :
10919 std::filesystem::recursive_directory_iterator( corpusEnv ) )
10920 {
10921 if( entry.is_regular_file() && entry.path().filename() == "BDC.DSN"
10922 && entry.path().string().find( "backpack-bdc" ) != std::string::npos )
10923 {
10924 dsn = entry.path();
10925 break;
10926 }
10927 }
10928
10929 if( dsn.empty() )
10930 return;
10931
10932 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10933 SETTINGS_MANAGER manager;
10934 manager.LoadProject( "" );
10935 schematic->SetProject( &manager.Prj() );
10936 schematic->CurrentSheet().clear();
10937 schematic->CurrentSheet().push_back( &schematic->Root() );
10938
10939 SCH_IO_ORCAD plugin;
10940 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10941
10942 wxString reference;
10943
10944 for( const SCH_SHEET_PATH& sheet : schematic->BuildSheetListSortedByPageNumbers() )
10945 {
10946 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
10947 {
10948 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
10949
10950 if( symbol->GetPosition() == OrcadDbuToIu( 485, 270 ) )
10951 reference = symbol->GetRef( &sheet, false );
10952 }
10953 }
10954
10955 BOOST_CHECK_EQUAL( reference, wxString( "U10" ) );
10956}
10957
10958
10959BOOST_AUTO_TEST_CASE( SuperSpeedOccurrencePropertiesOverrideReusableComponents )
10960{
10961 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
10962
10963 if( !corpusEnv || !*corpusEnv )
10964 return;
10965
10966 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SuperSpeed Explorer Kit Schematic.DSN" );
10967
10968 if( dsn.empty() )
10969 return;
10970
10971 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
10972 SETTINGS_MANAGER manager;
10973 manager.LoadProject( "" );
10974 schematic->SetProject( &manager.Prj() );
10975 schematic->CurrentSheet().clear();
10976 schematic->CurrentSheet().push_back( &schematic->Root() );
10977
10978 SCH_IO_ORCAD plugin;
10979 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
10980
10981 std::map<wxString, SCH_SYMBOL*> switches;
10982
10983 for( const SCH_SHEET_PATH& sheet : schematic->BuildSheetListSortedByPageNumbers() )
10984 {
10985 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
10986 {
10987 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
10988 wxString reference = symbol->GetRef( &sheet, false );
10989
10990 if( reference == wxS( "SW1" ) || reference == wxS( "SW2" ) )
10991 switches[reference] = symbol;
10992 }
10993 }
10994
10995 BOOST_REQUIRE_EQUAL( switches.size(), 2u );
10996
10997 for( const wxString& reference : { wxS( "SW1" ), wxS( "SW2" ) } )
10998 {
10999 SCH_SYMBOL* symbol = switches.at( reference );
11000 BOOST_CHECK_EQUAL( symbol->GetField( FIELD_T::VALUE )->GetText(), wxS( "434 123 050 816" ) );
11001 BOOST_REQUIRE( symbol->GetField( wxS( "Manufacturer" ) ) );
11002 BOOST_CHECK_EQUAL( symbol->GetField( wxS( "Manufacturer" ) )->GetText(),
11003 wxS( "Wurth Electronics Inc" ) );
11004 BOOST_REQUIRE( symbol->GetField( wxS( "OrCAD Footprint" ) ) );
11005 BOOST_CHECK_EQUAL( symbol->GetField( wxS( "OrCAD Footprint" ) )->GetText(), wxS( "EVQ-PE105K" ) );
11006 }
11007}
11008
11009
11010BOOST_AUTO_TEST_CASE( EmptyOccurrencePropertiesClearTemplateMetadata )
11011{
11012 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11013
11014 if( !corpusEnv || !*corpusEnv )
11015 return;
11016
11017 auto checkFieldCleared = [&]( const wxString& aFileName, const wxString& aReference,
11018 const wxString& aFieldName )
11019 {
11020 std::filesystem::path dsn = findCorpusDesign( corpusEnv, aFileName.ToStdString() );
11021 BOOST_REQUIRE( !dsn.empty() );
11022
11023 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11024 SETTINGS_MANAGER manager;
11025 manager.LoadProject( "" );
11026 schematic->SetProject( &manager.Prj() );
11027 schematic->CurrentSheet().clear();
11028 schematic->CurrentSheet().push_back( &schematic->Root() );
11029
11030 SCH_IO_ORCAD plugin;
11031 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11032
11033 SCH_FIELD* field = nullptr;
11034
11035 for( const SCH_SHEET_PATH& sheet : schematic->BuildSheetListSortedByPageNumbers() )
11036 {
11037 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
11038 {
11039 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
11040
11041 if( symbol->GetRef( &sheet, false ) == aReference )
11042 field = symbol->GetField( aFieldName );
11043 }
11044 }
11045
11046 BOOST_CHECK( !field || field->GetText().IsEmpty() );
11047 };
11048
11049 checkFieldCleared( wxS( "DC2596A-3.DSN" ), wxS( "L2" ), wxS( "4th Part Field" ) );
11050 checkFieldCleared( wxS( "CY4532 Power Board Schematic.DSN" ), wxS( "J2" ), wxS( "PART_NUMBER" ) );
11051}
11052
11053
11054BOOST_AUTO_TEST_CASE( CapturePseudoGlobalWirelessPinsConnectAcrossPages )
11055{
11056 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11057
11058 if( !corpusEnv || !*corpusEnv )
11059 return;
11060
11061 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2641A3-SCH.DSN" );
11062
11063 if( dsn.empty() )
11064 return;
11065
11066 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11067 SETTINGS_MANAGER manager;
11068 manager.LoadProject( "" );
11069 schematic->SetProject( &manager.Prj() );
11070 schematic->CurrentSheet().clear();
11071 schematic->CurrentSheet().push_back( &schematic->Root() );
11072
11073 SCH_IO_ORCAD plugin;
11074 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11075
11076 std::vector<std::set<std::string>> expected = { { terminalToken( "L1", "3" ), terminalToken( "L4", "3" ) } };
11077 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11078 BOOST_CHECK_EQUAL( checkable, 1 );
11079 BOOST_CHECK_EQUAL( consistent, 1 );
11080 size_t nativePowerPins = 0;
11081
11082 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
11083 {
11084 for( SCH_ITEM* item : path.LastScreen()->Items() )
11085 {
11086 if( item->Type() == SCH_SYMBOL_T )
11087 {
11088 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
11089 wxString ref = symbol->GetRef( &path, false );
11090
11091 if( ref != wxS( "L1" ) && ref != wxS( "L4" ) )
11092 continue;
11093
11094 for( SCH_PIN* pin : symbol->GetPins() )
11095 {
11096 if( pin->GetNumber() != wxS( "3" ) )
11097 continue;
11098
11099 BOOST_CHECK( pin->GetType() == ELECTRICAL_PINTYPE::PT_POWER_IN );
11100 BOOST_CHECK( !pin->IsVisible() );
11101 BOOST_CHECK( pin->IsGlobalPower() );
11102 BOOST_CHECK_EQUAL( pin->GetName(), wxString( "$$$1" ) );
11103 ++nativePowerPins;
11104 }
11105 }
11106 else if( SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( item ) )
11107 {
11108 BOOST_CHECK_NE( label->GetText(), wxString( "$$$1" ) );
11109 }
11110 }
11111 }
11112
11113 BOOST_CHECK_EQUAL( nativePowerPins, 2 );
11114
11115}
11116
11117
11118BOOST_AUTO_TEST_CASE( PowerAliasesConnectAcrossPages )
11119{
11120 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11121
11122 if( !corpusEnv || !*corpusEnv )
11123 return;
11124
11125 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SCH-54852_A5.DSN" );
11126
11127 if( dsn.empty() )
11128 return;
11129
11130 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11131 SETTINGS_MANAGER manager;
11132 manager.LoadProject( "" );
11133 schematic->SetProject( &manager.Prj() );
11134 schematic->CurrentSheet().clear();
11135 schematic->CurrentSheet().push_back( &schematic->Root() );
11136
11137 SCH_IO_ORCAD plugin;
11138 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11139
11140 std::vector<std::set<std::string>> expected = { { terminalToken( "R711", "2" ), terminalToken( "C104", "1" ) } };
11141 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11142 BOOST_CHECK_EQUAL( checkable, 1 );
11143 BOOST_CHECK_EQUAL( consistent, 1 );
11144}
11145
11146
11147BOOST_AUTO_TEST_CASE( ReusedPowerNetIdsDoNotJoinDisconnectedNets )
11148{
11149 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11150
11151 if( !corpusEnv || !*corpusEnv )
11152 return;
11153
11154 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SCH-31399_C4.DSN" );
11155
11156 if( dsn.empty() )
11157 return;
11158
11159 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11160 SETTINGS_MANAGER manager;
11161 manager.LoadProject( "" );
11162 schematic->SetProject( &manager.Prj() );
11163 schematic->CurrentSheet().clear();
11164 schematic->CurrentSheet().push_back( &schematic->Root() );
11165
11166 SCH_IO_ORCAD plugin;
11167 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11168
11169 std::vector<std::set<std::string>> expected = { { terminalToken( "C105", "1" ), terminalToken( "C106", "1" ) },
11170 { terminalToken( "C127", "1" ), terminalToken( "C128", "1" ) } };
11171 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11172 BOOST_CHECK_EQUAL( checkable, 2 );
11173 BOOST_CHECK_EQUAL( consistent, 2 );
11174}
11175
11176
11177BOOST_AUTO_TEST_CASE( ReusedPowerNetAliasesRemainPhysicallyScoped )
11178{
11179 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11180
11181 if( !corpusEnv || !*corpusEnv )
11182 return;
11183
11184 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC607A.DSN" );
11185
11186 if( dsn.empty() )
11187 return;
11188
11189 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11190 SETTINGS_MANAGER manager;
11191 manager.LoadProject( "" );
11192 schematic->SetProject( &manager.Prj() );
11193 schematic->CurrentSheet().clear();
11194 schematic->CurrentSheet().push_back( &schematic->Root() );
11195
11196 SCH_IO_ORCAD plugin;
11197 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11198
11199 std::vector<std::set<std::string>> expected = { { terminalToken( "C1", "2" ), terminalToken( "C13", "2" ) },
11200 { terminalToken( "C14", "2" ), terminalToken( "C2", "2" ) },
11201 { terminalToken( "C3", "1" ), terminalToken( "C4", "1" ) },
11202 { terminalToken( "C13", "1" ), terminalToken( "C31", "1" ) } };
11203 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11204 BOOST_CHECK_EQUAL( checkable, 4 );
11205 BOOST_CHECK_EQUAL( consistent, 4 );
11206}
11207
11208
11209BOOST_AUTO_TEST_CASE( SinglePowerMeaningPropagatesAcrossReusedNetId )
11210{
11211 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11212
11213 if( !corpusEnv || !*corpusEnv )
11214 return;
11215
11216 std::filesystem::path dsn =
11217 findCorpusDesign( corpusEnv, "630-60651-01_04_CYW9BTM2BASE3_20829_BaseBoard_Schematics.DSN" );
11218
11219 if( dsn.empty() )
11220 return;
11221
11222 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11223 SETTINGS_MANAGER manager;
11224 manager.LoadProject( "" );
11225 schematic->SetProject( &manager.Prj() );
11226 schematic->CurrentSheet().clear();
11227 schematic->CurrentSheet().push_back( &schematic->Root() );
11228
11229 SCH_IO_ORCAD plugin;
11230 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11231
11232 std::vector<std::set<std::string>> expected = { { terminalToken( "C1", "2" ), terminalToken( "U5", "62" ),
11233 terminalToken( "U5", "63" ), terminalToken( "U5", "65" ) } };
11234 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11235 BOOST_CHECK_EQUAL( checkable, 1 );
11236 BOOST_CHECK_EQUAL( consistent, 1 );
11237}
11238
11239
11240BOOST_AUTO_TEST_CASE( RepeatedLocalNetNamesRemainSheetScoped )
11241{
11242 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11243
11244 if( !corpusEnv || !*corpusEnv )
11245 return;
11246
11247 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "parallella_gen0.dsn" );
11248
11249 if( dsn.empty() )
11250 return;
11251
11252 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11253 SETTINGS_MANAGER manager;
11254 manager.LoadProject( "" );
11255 schematic->SetProject( &manager.Prj() );
11256 schematic->CurrentSheet().clear();
11257 schematic->CurrentSheet().push_back( &schematic->Root() );
11258
11259 SCH_IO_ORCAD plugin;
11260 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11261
11262 std::vector<std::set<std::string>> expected = { { terminalToken( "R41", "2" ), terminalToken( "U26", "53" ) },
11263 { terminalToken( "R57", "2" ), terminalToken( "R97", "1" ),
11264 terminalToken( "U14", "1" ) } };
11265 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11266 BOOST_CHECK_EQUAL( checkable, 2 );
11267 BOOST_CHECK_EQUAL( consistent, 2 );
11268}
11269
11270
11271BOOST_AUTO_TEST_CASE( RepeatedHierarchicalPortNamesRemainSheetScoped )
11272{
11273 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11274
11275 if( !corpusEnv || !*corpusEnv )
11276 return;
11277
11278 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SI347X-DC-EB.DSN" );
11279
11280 if( dsn.empty() )
11281 return;
11282
11283 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11284 SETTINGS_MANAGER manager;
11285 manager.LoadProject( "" );
11286 schematic->SetProject( &manager.Prj() );
11287 schematic->CurrentSheet().clear();
11288 schematic->CurrentSheet().push_back( &schematic->Root() );
11289
11290 SCH_IO_ORCAD plugin;
11291 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11292
11293 std::vector<std::set<std::string>> expected;
11294
11295 for( int channel = 1; channel <= 8; ++channel )
11296 {
11297 std::string q = "Q" + std::to_string( channel );
11298 std::string r = "R" + std::to_string( channel );
11299 std::string gatePin = std::to_string( std::array{ 1, 7, 8, 14, 29, 35, 36, 42 }[channel - 1] );
11300 std::string sourcePin = std::to_string( std::array{ 2, 6, 9, 13, 30, 34, 37, 41 }[channel - 1] );
11301 expected.push_back( { terminalToken( q, "G" ), terminalToken( "U1", gatePin ) } );
11302 expected.push_back( { terminalToken( q, "2" ), terminalToken( q, "3" ), terminalToken( q, "S" ),
11303 terminalToken( r, "2" ), terminalToken( "U1", sourcePin ) } );
11304 }
11305
11306 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11307 BOOST_CHECK_EQUAL( checkable, 16 );
11308 BOOST_CHECK_EQUAL( consistent, 16 );
11309}
11310
11311
11312BOOST_AUTO_TEST_CASE( RepeatedHierarchicalOffpageNamesRemainSheetScoped )
11313{
11314 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11315
11316 if( !corpusEnv || !*corpusEnv )
11317 return;
11318
11319 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "Si828X-BW-GDB.DSN" );
11320
11321 if( dsn.empty() )
11322 return;
11323
11324 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11325 SETTINGS_MANAGER manager;
11326 manager.LoadProject( "" );
11327 schematic->SetProject( &manager.Prj() );
11328 schematic->CurrentSheet().clear();
11329 schematic->CurrentSheet().push_back( &schematic->Root() );
11330
11331 SCH_IO_ORCAD plugin;
11332 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11333
11334 std::vector<std::set<std::string>> expected = {
11335 { terminalToken( "D317", "K" ), terminalToken( "JT1", "2" ), terminalToken( "Q13", "E" ) },
11336 { terminalToken( "D322", "K" ), terminalToken( "JT6", "2" ), terminalToken( "Q12", "E" ) },
11337 { terminalToken( "Q6", "C" ), terminalToken( "Q7", "C" ), terminalToken( "Q15", "C" ),
11338 terminalToken( "U204", "9" ) },
11339 { terminalToken( "Q10", "C" ), terminalToken( "Q11", "C" ), terminalToken( "Q20", "C" ),
11340 terminalToken( "U2", "13" ) }
11341 };
11342 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11343 BOOST_CHECK_EQUAL( checkable, 4 );
11344 BOOST_CHECK_EQUAL( consistent, 4 );
11345}
11346
11347
11348BOOST_AUTO_TEST_CASE( NestedOccurrenceReferencesOverridePlacedTemplateReferences )
11349{
11350 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11351
11352 if( !corpusEnv || !*corpusEnv )
11353 return;
11354
11355 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SI8284v2-EVB.DSN" );
11356
11357 if( dsn.empty() )
11358 return;
11359
11360 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11361 SETTINGS_MANAGER manager;
11362 manager.LoadProject( "" );
11363 schematic->SetProject( &manager.Prj() );
11364 schematic->CurrentSheet().clear();
11365 schematic->CurrentSheet().push_back( &schematic->Root() );
11366
11367 SCH_IO_ORCAD plugin;
11368 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11369
11370 std::set<std::string> refs = collectImportedRefs( *schematic );
11371
11372 BOOST_CHECK( refs.count( "Q1-1" ) );
11373 BOOST_CHECK( refs.count( "Q1-2" ) );
11374 BOOST_CHECK( refs.count( "Q8-1" ) );
11375 BOOST_CHECK( refs.count( "Q8-2" ) );
11376}
11377
11378
11379BOOST_AUTO_TEST_CASE( SharedOccurrenceNetWithoutTerminalPeerKeepsBaseName )
11380{
11381 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11382
11383 if( !corpusEnv || !*corpusEnv )
11384 return;
11385
11386 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SI8284v2-EVB.DSN" );
11387
11388 if( dsn.empty() )
11389 return;
11390
11391 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11392 SETTINGS_MANAGER manager;
11393 manager.LoadProject( "" );
11394 schematic->SetProject( &manager.Prj() );
11395 schematic->CurrentSheet().clear();
11396 schematic->CurrentSheet().push_back( &schematic->Root() );
11397
11398 SCH_IO_ORCAD plugin;
11399 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11400
11401 BOOST_CHECK_EQUAL( terminalNetName( *schematic, wxS( "J15" ), wxS( "2" ) ).AfterLast( '/' ).Lower(),
11402 wxString( wxS( "s3" ) ) );
11403}
11404
11405
11406BOOST_AUTO_TEST_CASE( PowerNetNameOverridesLocalWireAlias )
11407{
11408 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11409
11410 if( !corpusEnv || !*corpusEnv )
11411 return;
11412
11413 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SI8284v2-EVB.DSN" );
11414
11415 if( dsn.empty() )
11416 return;
11417
11418 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11419 SETTINGS_MANAGER manager;
11420 manager.LoadProject( "" );
11421 schematic->SetProject( &manager.Prj() );
11422 schematic->CurrentSheet().clear();
11423 schematic->CurrentSheet().push_back( &schematic->Root() );
11424
11425 SCH_IO_ORCAD plugin;
11426 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11427
11428 wxString boostNet = terminalNetName( *schematic, wxS( "C36" ), wxS( "1" ) );
11429 wxString supplyNet = terminalNetName( *schematic, wxS( "C12" ), wxS( "1" ) );
11430 BOOST_CHECK_EQUAL( boostNet.AfterLast( '/' ), wxString( "VDDB" ) );
11431 BOOST_CHECK_EQUAL( terminalNetName( *schematic, wxS( "Q4" ), wxS( "C" ) ), boostNet );
11432 BOOST_CHECK_EQUAL( terminalNetName( *schematic, wxS( "U2" ), wxS( "20" ) ), supplyNet );
11433 BOOST_CHECK_NE( boostNet, supplyNet );
11434
11435 const IMPORT_NET_MAP* map = schematic->GetImportNetMap();
11436 BOOST_REQUIRE( map );
11437 auto mapped = std::find_if( map->entries.begin(), map->entries.end(),
11438 []( const IMPORT_NET_MAP_ENTRY& entry )
11439 {
11440 return entry.sourceNetId == 16939716
11441 && entry.originalName == wxS( "N16864826" );
11442 } );
11443 BOOST_REQUIRE( mapped != map->entries.end() );
11445 BOOST_CHECK_EQUAL( mapped->nameAtImport, boostNet );
11446}
11447
11448
11449BOOST_AUTO_TEST_CASE( NativePowerNamesIgnoreSourceCase )
11450{
11451 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11452
11453 if( !corpusEnv || !*corpusEnv )
11454 return;
11455
11456 for( const char* filename : { "Si828X-BW-GDB.DSN", "SI828X-AW-GDB.DSN" } )
11457 {
11458 BOOST_TEST_CONTEXT( filename )
11459 {
11460 std::filesystem::path dsn = findCorpusDesign( corpusEnv, filename );
11461 BOOST_REQUIRE_MESSAGE( !dsn.empty(), filename << " not present in corpus." );
11462 SETTINGS_MANAGER manager;
11463 manager.LoadProject( "" );
11464 SCHEMATIC schematic( &manager.Prj() );
11465 SCH_IO_ORCAD plugin;
11466 plugin.LoadSchematicFile( dsn.string(), &schematic );
11467
11468 wxString netName = terminalNetName( schematic, wxS( "C306" ), wxS( "1" ) );
11469 BOOST_CHECK_EQUAL( netName.Upper(), wxString( "LS-SOURCE" ) );
11470
11471 for( const auto& [reference, pin] :
11472 { std::pair{ wxString( "U2" ), wxString( "16" ) },
11473 std::pair{ wxString( "C307" ), wxString( "2" ) },
11474 std::pair{ wxString( "C309" ), wxString( "1" ) },
11475 std::pair{ wxString( "C310" ), wxString( "1" ) },
11476 std::pair{ wxString( "R333" ), wxString( "1" ) },
11477 std::pair{ wxString( "UB8" ), wxString( "3" ) },
11478 std::pair{ wxString( "UT6" ), wxString( "5" ) } } )
11479 {
11480 BOOST_CHECK_EQUAL( terminalNetName( schematic, reference, pin ), netName );
11481 }
11482
11483 std::set<wxString> powerNames;
11484
11485 for( const SCH_SHEET_PATH& sheet : schematic.BuildSheetListSortedByPageNumbers() )
11486 {
11487 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
11488 {
11489 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
11490
11491 if( symbol->GetField( FIELD_T::VALUE )->GetText().CmpNoCase( wxS( "LS-SOURCE" ) ) == 0
11492 && symbol->GetLibSymbolRef()->IsGlobalPower() )
11493 {
11494 powerNames.insert( symbol->GetField( FIELD_T::VALUE )->GetText() );
11495 }
11496 }
11497 }
11498
11499 BOOST_REQUIRE_EQUAL( powerNames.size(), 1u );
11500 BOOST_CHECK_EQUAL( *powerNames.begin(), netName );
11501 }
11502 }
11503}
11504
11505
11506BOOST_AUTO_TEST_CASE( HiddenWireLabelsAvoidBusCrossings )
11507{
11508 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11509
11510 if( !corpusEnv || !*corpusEnv )
11511 return;
11512
11513 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CN81XX_GBCV2_sch_0530.DSN" );
11514
11515 if( dsn.empty() )
11516 return;
11517
11518 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11519 SETTINGS_MANAGER manager;
11520 manager.LoadProject( "" );
11521 schematic->SetProject( &manager.Prj() );
11522 schematic->CurrentSheet().clear();
11523 schematic->CurrentSheet().push_back( &schematic->Root() );
11524
11525 SCH_IO_ORCAD plugin;
11526 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11527
11528 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
11529 schematic->ConnectionGraph()->Recalculate( sheets, true );
11530 wxString r13Pin1Net;
11531
11532 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
11533 {
11534 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
11535 {
11536 for( SCH_ITEM* item : subgraph->GetItems() )
11537 {
11538 if( item->Type() != SCH_PIN_T )
11539 continue;
11540
11541 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
11542 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
11543
11544 if( symbol && symbol->GetRef( &subgraph->GetSheet(), false ) == wxS( "R13" )
11545 && pin->GetNumber() == wxS( "1" ) )
11546 {
11547 r13Pin1Net = key.Name;
11548 }
11549 }
11550 }
11551 }
11552
11553 BOOST_CHECK_EQUAL( r13Pin1Net.AfterLast( '/' ), wxS( "DDR0_DM1" ) );
11554}
11555
11556
11557BOOST_AUTO_TEST_CASE( IncompleteCachedSymbolsUsePlacedPins )
11558{
11559 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11560
11561 if( !corpusEnv || !*corpusEnv )
11562 return;
11563
11564 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "1979A.DSN" );
11565
11566 if( dsn.empty() )
11567 return;
11568
11569 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11570 SETTINGS_MANAGER manager;
11571 manager.LoadProject( "" );
11572 schematic->SetProject( &manager.Prj() );
11573 schematic->CurrentSheet().clear();
11574 schematic->CurrentSheet().push_back( &schematic->Root() );
11575
11576 SCH_IO_ORCAD plugin;
11577 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11578
11579 std::vector<std::set<std::string>> expected = { { terminalToken( "J1", "1" ), terminalToken( "R5", "1" ) },
11580 { terminalToken( "J2", "1" ), terminalToken( "R6", "2" ) },
11581 { terminalToken( "J1", "2" ), terminalToken( "J1", "3" ),
11582 terminalToken( "J2", "2" ) } };
11583 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11584 BOOST_CHECK_EQUAL( checkable, 3 );
11585 BOOST_CHECK_EQUAL( consistent, 3 );
11586}
11587
11588
11589BOOST_AUTO_TEST_CASE( LegacyOffpageConnectorsKeepDistinctPinPositions )
11590{
11591 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11592
11593 if( !corpusEnv || !*corpusEnv )
11594 return;
11595
11596 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC726A-1.DSN" );
11597
11598 if( dsn.empty() )
11599 return;
11600
11601 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11602 SETTINGS_MANAGER manager;
11603 manager.LoadProject( "" );
11604 schematic->SetProject( &manager.Prj() );
11605 schematic->CurrentSheet().clear();
11606 schematic->CurrentSheet().push_back( &schematic->Root() );
11607
11608 SCH_IO_ORCAD plugin;
11609 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11610
11611 std::vector<std::set<std::string>> expected = { { terminalToken( "J1", "4" ), terminalToken( "R40", "1" ),
11612 terminalToken( "R9", "1" ), terminalToken( "U5", "14" ) },
11613 { terminalToken( "J1", "6" ), terminalToken( "R10", "1" ),
11614 terminalToken( "U5", "13" ) },
11615 { terminalToken( "J1", "7" ), terminalToken( "R39", "1" ),
11616 terminalToken( "R5", "1" ), terminalToken( "U5", "16" ) } };
11617 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11618 BOOST_CHECK_EQUAL( checkable, 3 );
11619 BOOST_CHECK_EQUAL( consistent, 3 );
11620}
11621
11622
11623BOOST_AUTO_TEST_CASE( EmptyPackagePinNumbersUseLogicalPinNames )
11624{
11625 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11626
11627 if( !corpusEnv || !*corpusEnv )
11628 return;
11629
11630 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2283A-2.DSN" );
11631
11632 if( dsn.empty() )
11633 return;
11634
11635 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11636 SETTINGS_MANAGER manager;
11637 manager.LoadProject( "" );
11638 schematic->SetProject( &manager.Prj() );
11639 schematic->CurrentSheet().clear();
11640 schematic->CurrentSheet().push_back( &schematic->Root() );
11641
11642 SCH_IO_ORCAD plugin;
11643 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11644
11645 std::vector<std::set<std::string>> expected = { { terminalToken( "C41", "1" ), terminalToken( "C42", "1" ) },
11646 { terminalToken( "C42", "2" ), terminalToken( "J12", "1" ) } };
11647 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11648 BOOST_CHECK_EQUAL( checkable, 2 );
11649 BOOST_CHECK_EQUAL( consistent, 2 );
11650}
11651
11652
11653BOOST_AUTO_TEST_CASE( EmptyPackagePinNumbersPreserveAlphabeticNames )
11654{
11655 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11656
11657 if( !corpusEnv || !*corpusEnv )
11658 return;
11659
11660 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "EMS4_0.DSN" );
11661
11662 if( dsn.empty() )
11663 return;
11664
11665 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11666 SETTINGS_MANAGER manager;
11667 manager.LoadProject( "" );
11668 schematic->SetProject( &manager.Prj() );
11669 schematic->CurrentSheet().clear();
11670 schematic->CurrentSheet().push_back( &schematic->Root() );
11671
11672 SCH_IO_ORCAD plugin;
11673 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11674
11675 std::vector<std::set<std::string>> expected = { { terminalToken( "DL1", "C" ), terminalToken( "R14", "1" ) },
11676 { terminalToken( "TP1", "A" ), terminalToken( "U1", "7" ) },
11677 { terminalToken( "U10", "PAD" ), terminalToken( "U11", "PAD" ) } };
11678 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11679 BOOST_CHECK_EQUAL( checkable, 3 );
11680 BOOST_CHECK_EQUAL( consistent, 3 );
11681}
11682
11683
11684BOOST_AUTO_TEST_CASE( EmbeddedTwoPinPackageNamesDoNotReplacePhysicalNumbers )
11685{
11686 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11687
11688 if( !corpusEnv || !*corpusEnv )
11689 return;
11690
11691 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2793A-3.DSN" );
11692
11693 if( dsn.empty() )
11694 return;
11695
11696 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11697 SETTINGS_MANAGER manager;
11698 manager.LoadProject( "" );
11699 schematic->SetProject( &manager.Prj() );
11700 schematic->CurrentSheet().clear();
11701 schematic->CurrentSheet().push_back( &schematic->Root() );
11702
11703 SCH_IO_ORCAD plugin;
11704 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11705
11706 std::vector<std::set<std::string>> expected = { { terminalToken( "D1", "1" ), terminalToken( "R6", "2" ) },
11707 { terminalToken( "D1", "2" ), terminalToken( "R4", "2" ) } };
11708 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11709 BOOST_CHECK_EQUAL( checkable, 2 );
11710 BOOST_CHECK_EQUAL( consistent, 2 );
11711}
11712
11713
11714BOOST_AUTO_TEST_CASE( DirectPowerSymbolSharesComponentPin )
11715{
11716 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11717
11718 if( !corpusEnv || !*corpusEnv )
11719 return;
11720
11721 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CY4532 Power Board Schematic.DSN" );
11722
11723 if( dsn.empty() )
11724 return;
11725
11726 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11727 SETTINGS_MANAGER manager;
11728 manager.LoadProject( "" );
11729 schematic->SetProject( &manager.Prj() );
11730 schematic->CurrentSheet().clear();
11731 schematic->CurrentSheet().push_back( &schematic->Root() );
11732
11733 SCH_IO_ORCAD plugin;
11734 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11735
11736 std::vector<std::set<std::string>> expected = { { terminalToken( "C70", "2" ), terminalToken( "C72", "2" ) } };
11737 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11738 BOOST_CHECK_EQUAL( checkable, 1 );
11739 BOOST_CHECK_EQUAL( consistent, 1 );
11740}
11741
11742
11743BOOST_AUTO_TEST_CASE( DirectPowerSymbolUsesComponentPinNet )
11744{
11745 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11746
11747 if( !corpusEnv || !*corpusEnv )
11748 return;
11749
11750 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2047A-3-A.DSN" );
11751
11752 if( dsn.empty() )
11753 return;
11754
11755 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11756 SETTINGS_MANAGER manager;
11757 manager.LoadProject( "" );
11758 schematic->SetProject( &manager.Prj() );
11759 schematic->CurrentSheet().clear();
11760 schematic->CurrentSheet().push_back( &schematic->Root() );
11761
11762 SCH_IO_ORCAD plugin;
11763 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11764
11765 std::vector<std::set<std::string>> expected = { { terminalToken( "D15", "2" ), terminalToken( "D16", "2" ) } };
11766 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11767 BOOST_CHECK_EQUAL( checkable, 1 );
11768 BOOST_CHECK_EQUAL( consistent, 1 );
11769}
11770
11771
11772BOOST_AUTO_TEST_CASE( NearbyOffpageConnectorsRemainDistinct )
11773{
11774 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11775
11776 if( !corpusEnv || !*corpusEnv )
11777 return;
11778
11779 std::filesystem::path dsn =
11780 findCorpusDesign( corpusEnv, "630-60651-01_04_CYW9BTM2BASE3_20829_BaseBoard_Schematics.DSN" );
11781
11782 if( dsn.empty() )
11783 return;
11784
11785 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11786 SETTINGS_MANAGER manager;
11787 manager.LoadProject( "" );
11788 schematic->SetProject( &manager.Prj() );
11789 schematic->CurrentSheet().clear();
11790 schematic->CurrentSheet().push_back( &schematic->Root() );
11791
11792 SCH_IO_ORCAD plugin;
11793 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11794
11795 std::vector<std::set<std::string>> expected = { { terminalToken( "J14", "1" ), terminalToken( "J16", "17" ),
11796 terminalToken( "R110", "2" ) },
11797 { terminalToken( "J16", "19" ), terminalToken( "J2", "2" ) },
11798 { terminalToken( "J16", "67" ), terminalToken( "J2", "3" ) } };
11799 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11800 BOOST_CHECK_EQUAL( checkable, 3 );
11801 BOOST_CHECK_EQUAL( consistent, 3 );
11802}
11803
11804
11805BOOST_AUTO_TEST_CASE( MatchingSymbolAndPackageVariantsPreservePhysicalPinNames )
11806{
11807 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11808
11809 if( !corpusEnv || !*corpusEnv )
11810 return;
11811
11812 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CY4532 Power Board Schematic.DSN" );
11813
11814 if( dsn.empty() )
11815 return;
11816
11817 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
11818 SETTINGS_MANAGER manager;
11819 manager.LoadProject( "" );
11820 schematic->SetProject( &manager.Prj() );
11821 schematic->CurrentSheet().clear();
11822 schematic->CurrentSheet().push_back( &schematic->Root() );
11823
11824 SCH_IO_ORCAD plugin;
11825 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
11826
11827 std::vector<std::set<std::string>> expected = { { terminalToken( "D5", "A" ), terminalToken( "U2", "24" ) },
11828 { terminalToken( "D5", "K" ), terminalToken( "U2", "20" ) } };
11829 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
11830 BOOST_CHECK_EQUAL( checkable, 2 );
11831 BOOST_CHECK_EQUAL( consistent, 2 );
11832}
11833
11834
11835BOOST_AUTO_TEST_CASE( OffpageParentBindingPreservesPinNameAndDirection )
11836{
11837 ORCAD_RAW_PAGE rootPage;
11838 rootPage.name = "ROOT";
11839 rootPage.width = 10000;
11840 rootPage.height = 8000;
11842 drawn.dbId = 100;
11843 drawn.reference = "G1";
11844 drawn.x1 = drawn.y1 = 100;
11845 drawn.w = drawn.h = 100;
11846 drawn.pins.push_back( ORCAD_BLOCK_PIN{ .name = "Gate", .portType = ORCAD_PORT_TYPE::INPUT_TYPE,
11847 .x = 100, .y = 150 } );
11848 rootPage.blocks.push_back( std::move( drawn ) );
11849
11850 ORCAD_RAW_PAGE childPage;
11851 childPage.name = "CHILD";
11852 childPage.width = 10000;
11853 childPage.height = 8000;
11854 childPage.netmap[1] = "gate";
11855 ORCAD_WIRE wire;
11856 wire.id = 1;
11857 wire.x1 = 100;
11858 wire.x2 = 150;
11859 wire.y1 = wire.y2 = 100;
11860 childPage.wires.push_back( wire );
11861 ORCAD_GRAPHIC_INST offpage;
11862 offpage.logicalName = "gate";
11863 offpage.x = offpage.y = 100;
11864 childPage.offpage.push_back( std::move( offpage ) );
11865
11866 ORCAD_OCC_BLOCK occurrence;
11867 occurrence.targetDbId = 100;
11868 occurrence.childFolder = "CHILD";
11869 ORCAD_DESIGN design;
11870 design.sourceId = "offpage-parent-input-binding";
11871 design.pages.push_back( std::move( rootPage ) );
11872 design.childFolderPages["child"].push_back( std::move( childPage ) );
11873 design.occurrenceRoot.blocks.push_back( std::move( occurrence ) );
11874 SCHEMATIC schematic( nullptr );
11875 SETTINGS_MANAGER manager;
11876 manager.LoadProject( "" );
11877 schematic.SetProject( &manager.Prj() );
11878 SCH_SHEET* root = convertRawDesign( design, schematic );
11879 SCH_SHEET* child = nullptr;
11880
11881 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_SHEET_T ) )
11882 child = static_cast<SCH_SHEET*>( item );
11883
11884 BOOST_REQUIRE( child );
11885 BOOST_REQUIRE_EQUAL( child->GetPins().size(), 1u );
11886 int checked = 0;
11887
11888 for( SCH_ITEM* item : child->GetScreen()->Items().OfType( SCH_HIER_LABEL_T ) )
11889 {
11890 SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( item );
11891
11892 if( label->GetText().CmpNoCase( "gate" ) != 0 )
11893 continue;
11894
11895 ++checked;
11896 BOOST_CHECK_EQUAL( label->GetText(), child->GetPins().front()->GetText() );
11897 BOOST_CHECK( label->GetShape() == LABEL_FLAG_SHAPE::L_INPUT );
11898 }
11899
11900 BOOST_CHECK_EQUAL( checked, 1 );
11901}
11902
11903
11904BOOST_AUTO_TEST_CASE( OffpageAtCutCrossingStaysOnItsSourceNet )
11905{
11906 for( int mode : { 0, 1, 2 } )
11907 {
11908 bool endpoint = mode == 1;
11909 bool ambiguous = mode == 2;
11910 ORCAD_RAW_PAGE page;
11911 page.name = "OFFPAGE AT CUT CROSSING";
11912 page.width = 10000;
11913 page.height = 8000;
11914 page.netmap[1] = "VERTICAL";
11915 page.netmap[2] = "HORIZONTAL";
11916
11917 ORCAD_WIRE vertical;
11918 vertical.id = 1;
11919 vertical.x1 = vertical.x2 = 100;
11920 vertical.y1 = endpoint ? 100 : 50;
11921 vertical.y2 = 150;
11922 page.wires.push_back( vertical );
11923
11924 ORCAD_WIRE horizontal;
11925 horizontal.id = 2;
11926 horizontal.x1 = 50;
11927 horizontal.x2 = 150;
11928 horizontal.y1 = horizontal.y2 = 100;
11929 page.wires.push_back( horizontal );
11930
11931 ORCAD_GRAPHIC_INST offpage;
11932 offpage.logicalName = ambiguous ? "UNKNOWN" : "VERTICAL";
11933 offpage.x = offpage.y = 100;
11934 page.offpage.push_back( std::move( offpage ) );
11935
11936 ORCAD_DESIGN design;
11937 design.sourceId = endpoint ? "offpage-at-cut-endpoint" : "offpage-at-cut-interior";
11938 design.pages.push_back( std::move( page ) );
11939 SCHEMATIC schematic( nullptr );
11940 SETTINGS_MANAGER manager;
11941 manager.LoadProject( "" );
11942 schematic.SetProject( &manager.Prj() );
11943 if( ambiguous )
11944 {
11945 BOOST_CHECK_THROW( convertRawDesign( design, schematic ), IO_ERROR );
11946 continue;
11947 }
11948
11949 SCH_SHEET* root = convertRawDesign( design, schematic );
11951 schematic.ConnectionGraph()->Recalculate( sheets, true );
11952 SCH_LINE* verticalWire = nullptr;
11953 SCH_LINE* horizontalWire = nullptr;
11954 const SCH_GLOBALLABEL* connector = nullptr;
11955
11956 for( SCH_ITEM* item : root->GetScreen()->Items() )
11957 {
11958 if( item->Type() == SCH_LINE_T )
11959 {
11960 SCH_LINE* line = static_cast<SCH_LINE*>( item );
11961
11962 if( line->GetLayer() != LAYER_WIRE )
11963 continue;
11964
11965 if( line->GetStartPoint().x == line->GetEndPoint().x )
11966 verticalWire = line;
11967 else
11968 horizontalWire = line;
11969 }
11970 else if( item->Type() == SCH_GLOBAL_LABEL_T )
11971 {
11972 const SCH_GLOBALLABEL* label = static_cast<const SCH_GLOBALLABEL*>( item );
11973
11974 if( label->GetText() == "VERTICAL"
11975 && ( label->GetTextColor() == KIGFX::COLOR4D::UNSPECIFIED || label->GetTextColor().a > 0 ) )
11976 connector = label;
11977 }
11978 }
11979
11980 BOOST_REQUIRE( connector );
11981 BOOST_REQUIRE( verticalWire );
11982 BOOST_REQUIRE( horizontalWire );
11983 BOOST_CHECK( !horizontalWire->GetSeg().Contains( connector->GetPosition() ) );
11984 BOOST_CHECK_NE( verticalWire->Connection( &sheets.front() )->Name(),
11985 horizontalWire->Connection( &sheets.front() )->Name() );
11986 }
11987}
11988
11989
11990BOOST_AUTO_TEST_CASE( OffpageParentBindingKeepsVisibleLabelsApart )
11991{
11992 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
11993
11994 if( !corpusEnv || !*corpusEnv )
11995 return;
11996
11997 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SI347XY_MB_EVB.DSN" );
11998
11999 if( dsn.empty() )
12000 return;
12001
12002 SCHEMATIC schematic( nullptr );
12003 SETTINGS_MANAGER manager;
12004 manager.LoadProject( "" );
12005 schematic.SetProject( &manager.Prj() );
12006 SCH_IO_ORCAD plugin;
12007 plugin.LoadSchematicFile( dsn.string(), &schematic );
12008 int checked = 0;
12009
12010 for( const SCH_SHEET_PATH& sheet : schematic.BuildSheetListSortedByPageNumbers() )
12011 {
12012 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
12013 {
12014 SCH_GLOBALLABEL* label = static_cast<SCH_GLOBALLABEL*>( item );
12015
12016 if( label->GetText() != "VDD" )
12017 continue;
12018
12019 for( SCH_ITEM* other : sheet.LastScreen()->Items().OfType( SCH_HIER_LABEL_T ) )
12020 {
12021 SCH_HIERLABEL* hierLabel = static_cast<SCH_HIERLABEL*>( other );
12022
12023 if( hierLabel->GetText() != label->GetText() || hierLabel->GetPosition() != label->GetPosition() )
12024 continue;
12025
12026 ++checked;
12027 BOOST_CHECK( hierLabel->GetSpinStyle()
12028 == label->GetSpinStyle().RotateCCW().RotateCCW().Spin() );
12029 BOOST_CHECK( label->GetTextColor() == KIGFX::COLOR4D::UNSPECIFIED
12030 || label->GetTextColor().a > 0 );
12031 BOOST_CHECK( hierLabel->GetTextColor() == KIGFX::COLOR4D::UNSPECIFIED
12032 || hierLabel->GetTextColor().a > 0 );
12033 }
12034 }
12035 }
12036
12037 BOOST_CHECK_EQUAL( checked, 1 );
12038}
12039
12040
12041BOOST_AUTO_TEST_CASE( OffpageAtWireBranchHasVisibleSafeAnchor )
12042{
12043 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12044
12045 if( !corpusEnv || !*corpusEnv )
12046 return;
12047
12048 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2659A-4.DSN" );
12049
12050 if( dsn.empty() )
12051 return;
12052
12053 SCHEMATIC schematic( nullptr );
12054 SETTINGS_MANAGER manager;
12055 manager.LoadProject( "" );
12056 schematic.SetProject( &manager.Prj() );
12057 SCH_IO_ORCAD plugin;
12058 plugin.LoadSchematicFile( dsn.string(), &schematic );
12059 int checked = 0;
12060
12061 for( const SCH_SHEET_PATH& sheet : schematic.BuildSheetListSortedByPageNumbers() )
12062 {
12063 for( SCH_ITEM* item : sheet.LastScreen()->Items() )
12064 {
12065 if( item->Type() != SCH_GLOBAL_LABEL_T )
12066 continue;
12067
12068 SCH_GLOBALLABEL* label = static_cast<SCH_GLOBALLABEL*>( item );
12069
12070 // PAGE2's source connector starts at a three-wire branch; nearby labels belong to other source objects.
12071 if( label->GetText() != "VOUT2"
12072 || ( label->GetPosition() - VECTOR2I( 4140200, 901700 ) ).SquaredEuclideanNorm()
12073 > int64_t( 15000 ) * 15000 )
12074 continue;
12075
12076 ++checked;
12077 BOOST_CHECK( label->GetTextColor() == KIGFX::COLOR4D::UNSPECIFIED
12078 || label->GetTextColor().a > 0 );
12079 int incident = 0;
12080
12081 for( SCH_ITEM* other : sheet.LastScreen()->Items() )
12082 {
12083 if( other->Type() != SCH_LINE_T )
12084 continue;
12085
12086 SCH_LINE* line = static_cast<SCH_LINE*>( other );
12087
12088 if( ( line->GetLayer() == LAYER_WIRE || line->GetLayer() == LAYER_BUS )
12089 && line->GetSeg().Contains( label->GetPosition() ) )
12090 ++incident;
12091 }
12092
12093 BOOST_CHECK_LE( incident, 1 );
12094 }
12095 }
12096
12097 BOOST_CHECK_EQUAL( checked, 1 );
12098}
12099
12100
12101BOOST_AUTO_TEST_CASE( OffpageDisplayNameDoesNotChangeConnectivity )
12102{
12103 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12104
12105 if( !corpusEnv || !*corpusEnv )
12106 {
12107 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping off-page display-name check." );
12108 return;
12109 }
12110
12111 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2509A-1.DSN" );
12112
12113 if( dsn.empty() )
12114 {
12115 BOOST_TEST_MESSAGE( "DC2509A-1.DSN not present in corpus; skipping off-page display-name check." );
12116 return;
12117 }
12118
12119 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12120 SETTINGS_MANAGER manager;
12121 manager.LoadProject( "" );
12122 schematic->SetProject( &manager.Prj() );
12123 schematic->CurrentSheet().clear();
12124 schematic->CurrentSheet().push_back( &schematic->Root() );
12125
12126 SCH_IO_ORCAD plugin;
12127 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12128
12129 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
12130 schematic->ConnectionGraph()->Recalculate( sheets, true );
12131
12132 std::map<std::string, CONNECTION_SUBGRAPH*> pinNets;
12133
12134 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
12135 {
12136 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
12137 {
12138 for( SCH_ITEM* item : subgraph->GetItems() )
12139 {
12140 if( item->Type() != SCH_PIN_T )
12141 continue;
12142
12143 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
12144 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
12145
12146 if( !symbol || pin->GetNumber() != wxS( "1" ) )
12147 continue;
12148
12149 wxString ref = symbol->GetRef( &subgraph->GetSheet(), false );
12150
12151 if( ref == wxS( "U3" ) || ref == wxS( "U4" ) )
12152 pinNets[ref.ToStdString()] = subgraph;
12153 }
12154 }
12155 }
12156
12157 BOOST_REQUIRE_EQUAL( pinNets.count( "U3" ), 1u );
12158 BOOST_REQUIRE_EQUAL( pinNets.count( "U4" ), 1u );
12159 BOOST_CHECK_EQUAL( pinNets["U3"], pinNets["U4"] );
12160}
12161
12162
12163BOOST_AUTO_TEST_CASE( SingleLeafOccurrencePreservesNamedNet )
12164{
12165 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12166
12167 if( !corpusEnv || !*corpusEnv )
12168 {
12169 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping single leaf occurrence check." );
12170 return;
12171 }
12172
12173 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SCH-28988.DSN" );
12174
12175 if( dsn.empty() )
12176 {
12177 BOOST_TEST_MESSAGE( "SCH-28988.DSN not present in corpus; skipping single leaf occurrence check." );
12178 return;
12179 }
12180
12181 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12182 SETTINGS_MANAGER manager;
12183 manager.LoadProject( "" );
12184 schematic->SetProject( &manager.Prj() );
12185 schematic->CurrentSheet().clear();
12186 schematic->CurrentSheet().push_back( &schematic->Root() );
12187
12188 SCH_IO_ORCAD plugin;
12189 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12190
12191 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
12192 schematic->ConnectionGraph()->Recalculate( sheets, true );
12193 wxString netName;
12194
12195 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
12196 {
12197 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
12198 {
12199 for( SCH_ITEM* item : subgraph->GetItems() )
12200 {
12201 if( item->Type() != SCH_PIN_T )
12202 continue;
12203
12204 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
12205 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
12206
12207 if( symbol && symbol->GetRef( &subgraph->GetSheet(), false ) == wxS( "C3" )
12208 && pin->GetNumber() == wxS( "1" ) )
12209 {
12210 netName = key.Name;
12211 }
12212 }
12213 }
12214 }
12215
12216 BOOST_CHECK_EQUAL( netName.AfterLast( '/' ), wxString( "ANALOG5V" ) );
12217 BOOST_CHECK_EQUAL( terminalNetName( *schematic, wxS( "SH2" ), wxS( "2" ) ), netName );
12218 BOOST_CHECK_EQUAL( terminalNetName( *schematic, wxS( "U1" ), wxS( "4" ) ), netName );
12219 BOOST_CHECK_EQUAL( terminalNetName( *schematic, wxS( "VOUT_5" ), wxS( "1" ) ), netName );
12220
12221 const IMPORT_NET_MAP* map = schematic->GetImportNetMap();
12222 BOOST_REQUIRE( map );
12223 auto mapped = std::find_if( map->entries.begin(), map->entries.end(),
12224 []( const IMPORT_NET_MAP_ENTRY& entry )
12225 {
12226 return entry.sourceNetId == 16645429
12227 && entry.originalName == wxS( "ANALOG5V" );
12228 } );
12229 BOOST_REQUIRE( mapped != map->entries.end() );
12231 BOOST_CHECK_EQUAL( mapped->nameAtImport, netName );
12232 BOOST_REQUIRE( !mapped->occurrence.empty() );
12233 BOOST_CHECK_EQUAL( mapped->occurrence.back(), wxString( "BRKTSTBCDP5004" ) );
12234}
12235
12236
12237BOOST_AUTO_TEST_CASE( PackageVariantsAndIgnoredPinsArePreserved )
12238{
12239 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12240
12241 if( !corpusEnv || !*corpusEnv )
12242 {
12243 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping package-variant check." );
12244 return;
12245 }
12246
12247 auto load = [&]( const char* aName )
12248 {
12249 std::filesystem::path dsn = findCorpusDesign( corpusEnv, aName );
12250 BOOST_REQUIRE_MESSAGE( !dsn.empty(), aName << " not present in corpus." );
12251
12252 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12253 SETTINGS_MANAGER* manager = new SETTINGS_MANAGER;
12254 manager->LoadProject( "" );
12255 schematic->SetProject( &manager->Prj() );
12256 schematic->CurrentSheet().clear();
12257 schematic->CurrentSheet().push_back( &schematic->Root() );
12258
12259 SCH_IO_ORCAD plugin;
12260 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12261 return std::pair( std::move( schematic ), std::unique_ptr<SETTINGS_MANAGER>( manager ) );
12262 };
12263
12264 auto [breakout, breakoutManager] = load( "OC_CONNECT_1_BRKOUT_BRD.DSN" );
12265 std::map<wxString, std::set<wxString>> switchPins;
12266
12267 for( const SCH_SHEET_PATH& path : breakout->BuildSheetListSortedByPageNumbers() )
12268 {
12269 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
12270 {
12271 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
12272 wxString ref = symbol->GetRef( &path, false );
12273
12274 if( ref != wxS( "S1" ) && ref != wxS( "S3" ) )
12275 continue;
12276
12277 for( SCH_PIN* pin : symbol->GetPins( &path ) )
12278 switchPins[ref].insert( pin->GetNumber() );
12279 }
12280 }
12281
12282 const std::set<wxString> expectedS1 = { wxS( "1" ), wxS( "2" ), wxS( "3" ), wxS( "4" ) };
12283 const std::set<wxString> expectedS3 = { wxS( "1" ), wxS( "2" ), wxS( "3" ) };
12284 BOOST_CHECK_EQUAL_COLLECTIONS( switchPins[wxS( "S1" )].begin(), switchPins[wxS( "S1" )].end(), expectedS1.begin(),
12285 expectedS1.end() );
12286 BOOST_CHECK_EQUAL_COLLECTIONS( switchPins[wxS( "S3" )].begin(), switchPins[wxS( "S3" )].end(), expectedS3.begin(),
12287 expectedS3.end() );
12288
12289 auto [j401, j401Manager] = load( "reServer industrial J401 Carrier Board v11.DSN" );
12290 std::set<wxString> j10Pins;
12291
12292 for( const SCH_SHEET_PATH& path : j401->BuildSheetListSortedByPageNumbers() )
12293 {
12294 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
12295 {
12296 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
12297
12298 if( symbol->GetRef( &path, false ) != wxS( "J10" ) )
12299 continue;
12300
12301 for( SCH_PIN* pin : symbol->GetPins( &path ) )
12302 j10Pins.insert( pin->GetNumber() );
12303 }
12304 }
12305
12306 BOOST_CHECK_EQUAL( j10Pins.size(), 53u );
12307 BOOST_CHECK_EQUAL( j10Pins.count( wxS( "SS1" ) ), 0u );
12308 BOOST_CHECK_EQUAL( j10Pins.count( wxS( "SS2" ) ), 0u );
12309}
12310
12311
12312BOOST_AUTO_TEST_CASE( LegacyDesignCachePackagePinMapsArePreserved )
12313{
12314 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12315
12316 if( !corpusEnv || !*corpusEnv )
12317 {
12318 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping legacy package-map check." );
12319 return;
12320 }
12321
12322 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "EXAMPLE.DSN" );
12323
12324 if( dsn.empty() )
12325 {
12326 BOOST_TEST_MESSAGE( "EXAMPLE.DSN not present in corpus; skipping legacy package-map check." );
12327 return;
12328 }
12329
12330 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12331 SETTINGS_MANAGER manager;
12332 manager.LoadProject( "" );
12333 schematic->SetProject( &manager.Prj() );
12334 schematic->CurrentSheet().clear();
12335 schematic->CurrentSheet().push_back( &schematic->Root() );
12336
12337 SCH_IO_ORCAD plugin;
12338 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12339
12340 std::set<wxString> pins;
12341
12342 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
12343 {
12344 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
12345 {
12346 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
12347
12348 if( symbol->GetRef( &path, false ) != wxS( "U4" ) )
12349 continue;
12350
12351 for( SCH_PIN* pin : symbol->GetPins( &path ) )
12352 pins.insert( pin->GetNumber() );
12353 }
12354 }
12355
12356 const std::set<wxString> expected = { wxS( "2" ), wxS( "3" ), wxS( "4" ), wxS( "5" ), wxS( "12" ) };
12357 BOOST_CHECK_EQUAL_COLLECTIONS( pins.begin(), pins.end(), expected.begin(), expected.end() );
12358}
12359
12360
12361BOOST_AUTO_TEST_CASE( S593487_PartialConnectorPinOrderIsPreserved )
12362{
12363 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12364
12365 if( !corpusEnv || !*corpusEnv )
12366 {
12367 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping S-593487 connector check." );
12368 return;
12369 }
12370
12371 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "S-593487-REV-B.DSN" );
12372 BOOST_REQUIRE_MESSAGE( !dsn.empty(), "S-593487-REV-B.DSN not present in corpus." );
12373
12374 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12375 SETTINGS_MANAGER manager;
12376 manager.LoadProject( "" );
12377 schematic->SetProject( &manager.Prj() );
12378 schematic->CurrentSheet().clear();
12379 schematic->CurrentSheet().push_back( &schematic->Root() );
12380
12381 SCH_IO_ORCAD plugin;
12382 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12383
12384 auto [consistent, checkable] =
12385 checkConnectivity( *schematic, { { terminalToken( "JCA2", "1" ), terminalToken( "RT1", "1" ) },
12386 { terminalToken( "JCA2", "2" ), terminalToken( "RT2", "1" ) },
12387 { terminalToken( "JCA2", "3" ), terminalToken( "RT3", "1" ) },
12388 { terminalToken( "JCA2", "4" ), terminalToken( "RT4", "1" ) } } );
12389 BOOST_CHECK_EQUAL( checkable, 4 );
12390 BOOST_CHECK_EQUAL( consistent, 4 );
12391}
12392
12393
12394BOOST_AUTO_TEST_CASE( M5275_ExplicitPowerPinsRemainVisible )
12395{
12396 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12397
12398 if( !corpusEnv || !*corpusEnv )
12399 {
12400 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping M5275 visible-power-pin check." );
12401 return;
12402 }
12403
12404 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "M5275EVB.DSN" );
12405 BOOST_REQUIRE_MESSAGE( !dsn.empty(), "M5275EVB.DSN not present in corpus." );
12406
12407 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12408 SETTINGS_MANAGER manager;
12409 manager.LoadProject( "" );
12410 schematic->SetProject( &manager.Prj() );
12411 schematic->CurrentSheet().clear();
12412 schematic->CurrentSheet().push_back( &schematic->Root() );
12413
12414 SCH_IO_ORCAD plugin;
12415 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12416
12417 std::map<wxString, bool> visible;
12418
12419 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
12420 {
12421 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
12422 {
12423 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
12424
12425 if( symbol->GetRef( &path, false ) != wxS( "U21" ) )
12426 continue;
12427
12428 for( SCH_PIN* pin : symbol->GetPins( &path ) )
12429 visible[pin->GetNumber()] = pin->IsVisible();
12430 }
12431 }
12432
12433 BOOST_REQUIRE_EQUAL( visible.size(), 6u );
12434 BOOST_CHECK( visible[wxS( "2" )] );
12435 BOOST_CHECK( visible[wxS( "5" )] );
12436}
12437
12438
12439BOOST_AUTO_TEST_CASE( SI34062_StackedSwitchPinsShareNet )
12440{
12441 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12442
12443 if( !corpusEnv || !*corpusEnv )
12444 {
12445 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping SI34062 stacked-pin check." );
12446 return;
12447 }
12448
12449 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SI34062-ISO-FB-EVB.DSN" );
12450 BOOST_REQUIRE_MESSAGE( !dsn.empty(), "SI34062-ISO-FB-EVB.DSN not present in corpus." );
12451
12452 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12453 SETTINGS_MANAGER manager;
12454 manager.LoadProject( "" );
12455 schematic->SetProject( &manager.Prj() );
12456 schematic->CurrentSheet().clear();
12457 schematic->CurrentSheet().push_back( &schematic->Root() );
12458
12459 SCH_IO_ORCAD plugin;
12460 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12461
12462 auto [consistent, checkable] = checkConnectivity(
12463 *schematic, { { terminalToken( "S2", "1" ), terminalToken( "S2", "2" ), terminalToken( "U5", "K" ) } } );
12464 BOOST_CHECK_EQUAL( checkable, 1 );
12465 BOOST_CHECK_EQUAL( consistent, 1 );
12466}
12467
12468
12469BOOST_AUTO_TEST_CASE( DuplicatePowerAliasTextDoesNotMergeDistinctNets )
12470{
12471 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12472
12473 if( !corpusEnv || !*corpusEnv )
12474 {
12475 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping duplicate power-alias check." );
12476 return;
12477 }
12478
12479 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SI8284v2-EVB.DSN" );
12480 BOOST_REQUIRE_MESSAGE( !dsn.empty(), "SI8284v2-EVB.DSN not present in corpus." );
12481
12482 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12483 SETTINGS_MANAGER manager;
12484 manager.LoadProject( "" );
12485 schematic->SetProject( &manager.Prj() );
12486 schematic->CurrentSheet().clear();
12487 schematic->CurrentSheet().push_back( &schematic->Root() );
12488
12489 SCH_IO_ORCAD plugin;
12490 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12491
12492 auto [consistent, checkable] =
12493 checkConnectivity( *schematic, { { terminalToken( "C12", "1" ), terminalToken( "U2", "20" ) },
12494 { terminalToken( "C36", "1" ), terminalToken( "Q4", "C" ) } } );
12495 BOOST_CHECK_EQUAL( checkable, 2 );
12496 BOOST_CHECK_EQUAL( consistent, 2 );
12497}
12498
12499
12500BOOST_AUTO_TEST_CASE( LogicalPowerPinNameDoesNotOverrideConnectedWire )
12501{
12502 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12503
12504 if( !corpusEnv || !*corpusEnv )
12505 {
12506 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping logical power-pin check." );
12507 return;
12508 }
12509
12510 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CY8CKIT-041-41XX Schematic.DSN" );
12511 BOOST_REQUIRE_MESSAGE( !dsn.empty(), "CY8CKIT-041-41XX Schematic.DSN not present in corpus." );
12512
12513 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12514 SETTINGS_MANAGER manager;
12515 manager.LoadProject( "" );
12516 schematic->SetProject( &manager.Prj() );
12517 schematic->CurrentSheet().clear();
12518 schematic->CurrentSheet().push_back( &schematic->Root() );
12519
12520 SCH_IO_ORCAD plugin;
12521 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12522
12523 bool checkedHeaderPin = false;
12524
12525 for( const SCH_SHEET_PATH& path : schematic->Hierarchy() )
12526 {
12527 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
12528 {
12529 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
12530
12531 if( symbol->GetRef( &path, false ) != wxS( "J8" )
12532 || !path.Last()->GetName().Contains( wxS( "PSoC 5LP Programmer" ) ) )
12533 {
12534 continue;
12535 }
12536
12537 for( SCH_PIN* pin : symbol->GetPins() )
12538 {
12539 if( pin->GetNumber() == wxS( "3" ) )
12540 {
12541 BOOST_CHECK( pin->GetPosition() == OrcadDbuToIu( 1192, 1006 ) );
12542 checkedHeaderPin = true;
12543 }
12544 }
12545 }
12546 }
12547
12548 BOOST_REQUIRE( checkedHeaderPin );
12549
12550 auto [consistent, checkable] =
12551 checkConnectivity( *schematic, { { terminalToken( "L4", "2" ), terminalToken( "U1", "40" ) },
12552 { terminalToken( "C59", "1" ), terminalToken( "U15", "44" ) },
12553 { terminalToken( "J8", "3" ), terminalToken( "U15", "28" ),
12554 terminalToken( "R40", "1" ) } } );
12555 BOOST_CHECK_EQUAL( checkable, 3 );
12556 BOOST_CHECK_EQUAL( consistent, 3 );
12557}
12558
12559
12560BOOST_AUTO_TEST_CASE( SourceLibrarySelectsPackageVariantBeforeSharedFootprint )
12561{
12562 ORCAD_SYMBOL_DEF symbol;
12564 symbol.name = "PART.Normal";
12565 symbol.sourceLib = "right.dsn";
12566 symbol.bbox = ORCAD_BBOX{ 0, 0, 30, 20 };
12567 symbol.pins = { ORCAD_SYMBOL_PIN{ .name = "A", .position = 0, .hotptX = 0, .hotptY = 10 },
12568 ORCAD_SYMBOL_PIN{ .name = "K", .position = 1, .hotptX = 30, .hotptY = 10 } };
12569
12570 ORCAD_PACKAGE package;
12571 package.name = "PART";
12572 package.sourceLib = "wrong.dsn";
12573 package.pcbFootprint = "SOT23";
12574 package.devices.push_back( ORCAD_DEVICE{ .pinNumbers = { "1", "2" }, .pinIgnore = { false, false } } );
12575 ORCAD_PACKAGE variant = package;
12576 variant.sourceLib = symbol.sourceLib;
12577 variant.devices.front().pinNumbers = { "1", "3" };
12578 package.variants.push_back( std::move( variant ) );
12579
12580 ORCAD_PLACED_INSTANCE placed;
12581 placed.pkgName = symbol.name;
12582 placed.sourcePackage = package.name;
12583 placed.sourceLibrary = symbol.sourceLib;
12584 placed.reference = "D1";
12585 placed.props["PCB Footprint"] = package.pcbFootprint;
12586 placed.x = 100;
12587 placed.y = 100;
12588 placed.pins = { ORCAD_PIN_INST{ 1, 100, 110 }, ORCAD_PIN_INST{ 2, 130, 110 } };
12589
12590 ORCAD_RAW_PAGE page;
12591 page.name = "PACKAGE SOURCE";
12592 page.instances.push_back( std::move( placed ) );
12593
12594 ORCAD_DESIGN design;
12595 design.sourceId = "package-source-before-footprint";
12596 design.symbols.emplace( symbol.name, std::move( symbol ) );
12597 design.packages.emplace( package.name, std::move( package ) );
12598 design.pages.push_back( std::move( page ) );
12599
12600 SETTINGS_MANAGER manager;
12601 manager.LoadProject( "" );
12602 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12603 schematic->SetProject( &manager.Prj() );
12604 SCH_SHEET* root = convertRawDesign( design, *schematic );
12606 path.push_back( root );
12607 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "D1" ) );
12608 BOOST_REQUIRE( converted );
12609 std::set<wxString> pinNumbers;
12610
12611 for( SCH_PIN* pin : converted->GetPins() )
12612 pinNumbers.insert( pin->GetNumber() );
12613
12614 const std::set<wxString> expected = { wxS( "1" ), wxS( "3" ) };
12615 BOOST_CHECK_EQUAL_COLLECTIONS( pinNumbers.begin(), pinNumbers.end(), expected.begin(), expected.end() );
12616}
12617
12618
12619BOOST_AUTO_TEST_CASE( PinNamesAndNumbersRemainNativePinData )
12620{
12621 ORCAD_SYMBOL_DEF symbol;
12623 symbol.name = "VERTICAL.Normal";
12624 symbol.bbox = ORCAD_BBOX{ 0, 10, 20, 30 };
12625 symbol.generalFlags = 1;
12626 symbol.pins = { ORCAD_SYMBOL_PIN{ .name = "2", .position = 0, .startX = 10, .startY = 10,
12627 .hotptX = 10, .hotptY = 0 },
12628 ORCAD_SYMBOL_PIN{ .name = "HIN", .position = 1, .startX = 0, .startY = 20,
12629 .hotptX = -10, .hotptY = 20 } };
12630 ORCAD_SYMBOL_DEF rotated = symbol;
12631 rotated.name = "ROTATED.Normal";
12632 rotated.generalFlags = 3;
12633
12634 ORCAD_PACKAGE package;
12635 package.name = "VERTICAL";
12636 package.devices.push_back( ORCAD_DEVICE{ .pinNumbers = { "2", "3" }, .pinIgnore = { false, false } } );
12637 ORCAD_PACKAGE rotatedPackage = package;
12638 rotatedPackage.name = "ROTATED";
12639
12640 ORCAD_PLACED_INSTANCE placed;
12641 placed.pkgName = symbol.name;
12642 placed.sourcePackage = package.name;
12643 placed.reference = "U1";
12644 placed.x = 100;
12645 placed.y = 100;
12646 placed.pins = { ORCAD_PIN_INST{ 1, 110, 100 }, ORCAD_PIN_INST{ 2, 90, 120 } };
12647 ORCAD_PLACED_INSTANCE rotatedPlaced = placed;
12648 rotatedPlaced.pkgName = rotated.name;
12649 rotatedPlaced.sourcePackage = rotatedPackage.name;
12650 rotatedPlaced.reference = "U2";
12651 rotatedPlaced.x = 200;
12652 rotatedPlaced.pins = { ORCAD_PIN_INST{ 1, 210, 100 } };
12653
12654 ORCAD_RAW_PAGE page;
12655 page.name = "VERTICAL PIN TEXT";
12656 page.instances.push_back( std::move( placed ) );
12657 page.instances.push_back( std::move( rotatedPlaced ) );
12658
12659 ORCAD_DESIGN design;
12660 design.sourceId = "vertical-pin-text";
12661 design.library.fonts = { ORCAD_FONT{ .height = -12, .width = 5, .pitchAndFamily = 0x22,
12662 .face = "Arial Narrow" },
12663 ORCAD_FONT{ .height = -8, .face = "Arial" } };
12664 design.library.templateFonts.resize( 12 );
12665 design.library.templateFonts[10] = 1;
12666 design.library.templateFonts[11] = 2;
12667 design.library.pinNameFont = 10;
12668 design.library.pinNumberFont = 11;
12669 design.symbols.emplace( symbol.name, std::move( symbol ) );
12670 design.symbols.emplace( rotated.name, std::move( rotated ) );
12671 design.packages.emplace( package.name, std::move( package ) );
12672 design.packages.emplace( rotatedPackage.name, std::move( rotatedPackage ) );
12673 design.pages.push_back( std::move( page ) );
12674
12675 SETTINGS_MANAGER manager;
12676 manager.LoadProject( "" );
12677 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12678 schematic->SetProject( &manager.Prj() );
12679 SCH_SHEET* root = convertRawDesign( design, *schematic );
12681 path.push_back( root );
12682 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "U1" ) );
12683 BOOST_REQUIRE( converted );
12684 BOOST_REQUIRE( converted->GetLibSymbolRef() );
12685
12686 const SCH_PIN* verticalPin = converted->GetLibSymbolRef()->GetPin( wxS( "2" ) );
12687 BOOST_REQUIRE( verticalPin );
12688 BOOST_CHECK_EQUAL( verticalPin->GetName(), wxS( "2" ) );
12689 BOOST_CHECK_EQUAL( verticalPin->GetNameTextSize(), schIUScale.mmToIU( 2.17 ) );
12690 BOOST_CHECK_EQUAL( verticalPin->GetNumberTextSize(), schIUScale.mmToIU( 1.51 ) );
12691
12692 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
12693 {
12694 if( item.Type() == SCH_TEXT_T )
12695 {
12696 const SCH_TEXT& text = static_cast<const SCH_TEXT&>( item );
12697 BOOST_CHECK_NE( text.GetText(), wxS( "2" ) );
12698 }
12699 }
12700
12701 SCH_SYMBOL* rotatedConverted = findConvertedSymbol( *root->GetScreen(), path, wxS( "U2" ) );
12702 BOOST_REQUIRE( rotatedConverted );
12703 BOOST_REQUIRE( rotatedConverted->GetLibSymbolRef() );
12704 const SCH_PIN* rotatedPin = rotatedConverted->GetLibSymbolRef()->GetPin( wxS( "2" ) );
12705 BOOST_REQUIRE( rotatedPin );
12706 BOOST_CHECK_EQUAL( rotatedPin->GetName(), wxS( "2" ) );
12707 BOOST_CHECK_EQUAL( rotatedPin->GetNameTextSize(), schIUScale.mmToIU( 2.17 ) );
12708 BOOST_CHECK_EQUAL( rotatedPin->GetNumberTextSize(), schIUScale.mmToIU( 1.51 ) );
12709
12710 for( const SCH_ITEM& item : rotatedConverted->GetLibSymbolRef()->GetDrawItems() )
12711 {
12712 if( item.Type() == SCH_TEXT_T )
12713 {
12714 const SCH_TEXT& text = static_cast<const SCH_TEXT&>( item );
12715 BOOST_CHECK_NE( text.GetText(), wxS( "2" ) );
12716 }
12717 }
12718}
12719
12720
12721BOOST_AUTO_TEST_CASE( PinNameOverbarsUseKiCadMarkup )
12722{
12723 BOOST_CHECK_EQUAL( OrcadPinNameMarkup( wxS( "\\\\C\\S\\ ADD1" ) ), wxS( "~{CS} ADD1" ) );
12724 BOOST_CHECK_EQUAL( OrcadPinNameMarkup( wxS( "\\\\I\\N\\T\\" ) ), wxS( "~{INT}" ) );
12725 BOOST_CHECK_EQUAL( OrcadPinNameMarkup( wxS( "READY" ) ), wxS( "READY" ) );
12726}
12727
12728
12729BOOST_AUTO_TEST_CASE( PinDisplayOverrideRetainsNativePinName )
12730{
12731 ORCAD_DISPLAY_PROP display{ .name = "Name", .x = 20, .y = 0, .rotation = 0,
12732 .fontIdx = 1, .color = 48, .dispMode = 0x01E9 };
12733 ORCAD_SYMBOL_DEF symbol;
12735 symbol.name = "PIN_OVERRIDE.Normal";
12736 symbol.bbox = ORCAD_BBOX{ 0, 0, 30, 50 };
12737 symbol.generalFlags = 7;
12738 symbol.pins = { ORCAD_SYMBOL_PIN{ .name = "F1",
12739 .position = 0,
12740 .startX = 20,
12741 .startY = 0,
12742 .hotptX = 20,
12743 .hotptY = -10,
12744 .displayProps = { display } } };
12745
12746 ORCAD_PACKAGE package;
12747 package.name = "PIN_OVERRIDE";
12748 package.devices.push_back( ORCAD_DEVICE{ .pinNumbers = { "1" }, .pinIgnore = { false } } );
12749
12750 ORCAD_PLACED_INSTANCE placed;
12751 placed.pkgName = symbol.name;
12752 placed.sourcePackage = package.name;
12753 placed.reference = "J1";
12754 placed.x = 100;
12755 placed.y = 100;
12756 placed.pins = { ORCAD_PIN_INST{ 1, 120, 90 } };
12757
12758 ORCAD_RAW_PAGE page;
12759 page.name = "PIN DISPLAY OVERRIDE";
12760 page.instances.push_back( std::move( placed ) );
12761
12762 ORCAD_DESIGN design;
12763 design.sourceId = "pin-display-override";
12764 design.library.fonts = { ORCAD_FONT{ .height = -12, .width = 5, .pitchAndFamily = 0x22,
12765 .face = "Arial Narrow" } };
12766 design.library.pinNameFont = 1;
12767 design.symbols.emplace( symbol.name, std::move( symbol ) );
12768 design.packages.emplace( package.name, std::move( package ) );
12769 design.pages.push_back( std::move( page ) );
12770
12771 SETTINGS_MANAGER manager;
12772 manager.LoadProject( "" );
12773 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12774 schematic->SetProject( &manager.Prj() );
12775 SCH_SHEET* root = convertRawDesign( design, *schematic );
12777 path.push_back( root );
12778 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "J1" ) );
12779 BOOST_REQUIRE( converted );
12780 BOOST_REQUIRE( converted->GetLibSymbolRef() );
12781 const SCH_PIN* pin = converted->GetLibSymbolRef()->GetPin( wxS( "1" ) );
12782 BOOST_REQUIRE( pin );
12783 BOOST_CHECK_EQUAL( pin->GetName(), wxS( "F1" ) );
12784 BOOST_CHECK_EQUAL( pin->GetNameTextSize(), schIUScale.mmToIU( 2.17 ) );
12785 BOOST_CHECK_EQUAL( pin->GetNumberTextSize(), 0 );
12786
12787 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
12788 {
12789 if( item.Type() == SCH_TEXT_T && static_cast<const SCH_TEXT&>( item ).GetText() == wxS( "F1" ) )
12790 BOOST_ERROR( "Pin name duplicated as SCH_TEXT" );
12791 }
12792}
12793
12794
12795BOOST_AUTO_TEST_CASE( RotatedSymbolRetainsNativePinNumber )
12796{
12797 ORCAD_SYMBOL_DEF symbol;
12799 symbol.name = "ROTATED_PIN_NUMBER.Normal";
12800 symbol.bbox = ORCAD_BBOX{ 0, 0, 30, 20 };
12801 symbol.generalFlags = 3;
12802 symbol.pins = { ORCAD_SYMBOL_PIN{ .position = 0, .startX = 10, .startY = 10,
12803 .hotptX = 0, .hotptY = 10 } };
12804
12805 ORCAD_PACKAGE package;
12806 package.name = "ROTATED_PIN_NUMBER";
12807 package.devices.push_back( ORCAD_DEVICE{ .pinNumbers = { "1" }, .pinIgnore = { false } } );
12808
12809 ORCAD_PLACED_INSTANCE placed;
12810 placed.pkgName = symbol.name;
12811 placed.sourcePackage = package.name;
12812 placed.reference = "Q1";
12813 placed.x = 100;
12814 placed.y = 100;
12815 placed.rotation = 1;
12816
12817 ORCAD_RAW_PAGE page;
12818 page.name = "ROTATED PIN NUMBER";
12819 page.instances.push_back( std::move( placed ) );
12820
12821 ORCAD_DESIGN design;
12822 design.sourceId = "rotated-pin-number";
12823 design.symbols.emplace( symbol.name, std::move( symbol ) );
12824 design.packages.emplace( package.name, std::move( package ) );
12825 design.pages.push_back( std::move( page ) );
12826
12827 SETTINGS_MANAGER manager;
12828 manager.LoadProject( "" );
12829 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12830 schematic->SetProject( &manager.Prj() );
12831 SCH_SHEET* root = convertRawDesign( design, *schematic );
12833 path.push_back( root );
12834 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "Q1" ) );
12835 BOOST_REQUIRE( converted );
12836 const SCH_PIN* pin = converted->GetLibSymbolRef()->GetPin( wxS( "1" ) );
12837 BOOST_REQUIRE( pin );
12838 BOOST_CHECK_GT( pin->GetNumberTextSize(), 0 );
12839
12840 for( const SCH_ITEM& item : converted->GetLibSymbolRef()->GetDrawItems() )
12841 {
12842 if( item.Type() == SCH_TEXT_T && static_cast<const SCH_TEXT&>( item ).GetText() == wxS( "1" ) )
12843 BOOST_ERROR( "Pin number duplicated as SCH_TEXT" );
12844 }
12845}
12846
12847
12848BOOST_AUTO_TEST_CASE( NumericCachePinsOverrideStructurallyIncompatiblePackage )
12849{
12850 ORCAD_SYMBOL_DEF symbol;
12852 symbol.name = "ORDERED.Normal";
12853 symbol.sourceLib = "desired.dsn";
12854 symbol.bbox = ORCAD_BBOX{ 0, 0, 30, 10 };
12855 symbol.pins = { ORCAD_SYMBOL_PIN{ .name = "3", .hotptX = 0 }, ORCAD_SYMBOL_PIN{ .name = "4", .hotptX = 10 },
12856 ORCAD_SYMBOL_PIN{ .name = "2", .hotptX = 20 }, ORCAD_SYMBOL_PIN{ .name = "1", .hotptX = 30 } };
12857
12858 for( ORCAD_SYMBOL_PIN& pin : symbol.pins )
12859 pin.startX = pin.hotptX;
12860
12861 ORCAD_PACKAGE package;
12862 package.name = "ORDERED";
12863 package.sourceLib = "stale.dsn";
12864 package.devices.push_back( ORCAD_DEVICE{ .pinNumbers = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" },
12865 .pinIgnore = std::vector<bool>( 10, false ) } );
12866
12867 ORCAD_PLACED_INSTANCE placed;
12868 placed.pkgName = symbol.name;
12869 placed.sourcePackage = package.name;
12870 placed.sourceLibrary = symbol.sourceLib;
12871 placed.reference = "J1";
12872 placed.x = 100;
12873 placed.y = 100;
12874
12875 for( int pin = 1; pin <= 4; ++pin )
12876 placed.pins.push_back( ORCAD_PIN_INST{ static_cast<int16_t>( pin ), 90 + 10 * pin, 100 } );
12877
12878 ORCAD_RAW_PAGE page;
12879 page.name = "ORDERED";
12880 page.instances.push_back( std::move( placed ) );
12881
12882 ORCAD_DESIGN design;
12883 design.sourceId = "numeric-cache-pins";
12884 design.symbols.emplace( symbol.name, std::move( symbol ) );
12885 design.packages.emplace( package.name, std::move( package ) );
12886 design.pages.push_back( std::move( page ) );
12887
12888 SETTINGS_MANAGER manager;
12889 manager.LoadProject( "" );
12890 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12891 schematic->SetProject( &manager.Prj() );
12892 SCH_SHEET* root = convertRawDesign( design, *schematic );
12894 path.push_back( root );
12895 SCH_SYMBOL* converted = findConvertedSymbol( *root->GetScreen(), path, wxS( "J1" ) );
12896 BOOST_REQUIRE( converted );
12897 std::map<wxString, VECTOR2I> positions;
12898
12899 for( SCH_PIN* pin : converted->GetPins() )
12900 positions[pin->GetNumber()] = pin->GetPosition();
12901
12902 BOOST_REQUIRE_EQUAL( positions.size(), 4u );
12903 BOOST_CHECK_EQUAL( positions[wxS( "4" )].x - positions[wxS( "3" )].x, 10 * ORCAD_IU_PER_DBU );
12904 BOOST_CHECK_EQUAL( positions[wxS( "2" )].x - positions[wxS( "3" )].x, 20 * ORCAD_IU_PER_DBU );
12905 BOOST_CHECK_EQUAL( positions[wxS( "1" )].x - positions[wxS( "3" )].x, 30 * ORCAD_IU_PER_DBU );
12906}
12907
12908
12909BOOST_AUTO_TEST_CASE( RenamedPowerNetsRemainElectricallyDistinct )
12910{
12911 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12912
12913 if( !corpusEnv || !*corpusEnv )
12914 return;
12915
12916 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CY4605_Schematic.dsn" );
12917
12918 if( dsn.empty() )
12919 return;
12920
12921 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12922 SETTINGS_MANAGER manager;
12923 manager.LoadProject( "" );
12924 schematic->SetProject( &manager.Prj() );
12925 schematic->CurrentSheet().clear();
12926 schematic->CurrentSheet().push_back( &schematic->Root() );
12927
12928 SCH_IO_ORCAD plugin;
12929 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12930
12931 std::vector<std::set<std::string>> expected = { { terminalToken( "C16", "1" ), terminalToken( "TP4", "1" ) },
12932 { terminalToken( "C14", "1" ), terminalToken( "TP2", "1" ) } };
12933 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
12934 BOOST_CHECK_EQUAL( checkable, 2 );
12935 BOOST_CHECK_EQUAL( consistent, 2 );
12936}
12937
12938
12939BOOST_AUTO_TEST_CASE( MinorityPowerOccurrenceAliasDoesNotMergeGlobalNets )
12940{
12941 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12942
12943 if( !corpusEnv || !*corpusEnv )
12944 return;
12945
12946 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CYW920706WCDEVAL Evaluation Kit Schematics.DSN" );
12947
12948 if( dsn.empty() )
12949 return;
12950
12951 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12952 SETTINGS_MANAGER manager;
12953 manager.LoadProject( "" );
12954 schematic->SetProject( &manager.Prj() );
12955 schematic->CurrentSheet().clear();
12956 schematic->CurrentSheet().push_back( &schematic->Root() );
12957
12958 SCH_IO_ORCAD plugin;
12959 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12960
12961 std::vector<std::set<std::string>> expected = { { terminalToken( "C1", "2" ), terminalToken( "C10", "2" ) },
12962 { terminalToken( "C26", "2" ), terminalToken( "C27", "2" ) } };
12963 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
12964 BOOST_CHECK_EQUAL( checkable, 2 );
12965 BOOST_CHECK_EQUAL( consistent, 2 );
12966}
12967
12968
12969BOOST_AUTO_TEST_CASE( OccurrenceSuffixedPowerNetsRemainDistinct )
12970{
12971 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
12972
12973 if( !corpusEnv || !*corpusEnv )
12974 return;
12975
12976 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2263A-2.DSN" );
12977
12978 if( dsn.empty() )
12979 return;
12980
12981 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
12982 SETTINGS_MANAGER manager;
12983 manager.LoadProject( "" );
12984 schematic->SetProject( &manager.Prj() );
12985 schematic->CurrentSheet().clear();
12986 schematic->CurrentSheet().push_back( &schematic->Root() );
12987
12988 SCH_IO_ORCAD plugin;
12989 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
12990
12991 std::vector<std::set<std::string>> expected = { { terminalToken( "C13", "1" ), terminalToken( "C18", "1" ) },
12992 { terminalToken( "C40", "1" ), terminalToken( "R61", "1" ) } };
12993 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
12994 BOOST_CHECK_EQUAL( checkable, 2 );
12995 BOOST_CHECK_EQUAL( consistent, 2 );
12996}
12997
12998
12999BOOST_AUTO_TEST_CASE( GeneratedOccurrenceNetNameRemainsDistinct )
13000{
13001 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13002
13003 if( !corpusEnv || !*corpusEnv )
13004 return;
13005
13006 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "OC_CONNECT-1_BB_BOARD_20072023_01.DSN" );
13007
13008 if( dsn.empty() )
13009 return;
13010
13011 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13012 SETTINGS_MANAGER manager;
13013 manager.LoadProject( "" );
13014 schematic->SetProject( &manager.Prj() );
13015 schematic->CurrentSheet().clear();
13016 schematic->CurrentSheet().push_back( &schematic->Root() );
13017
13018 SCH_IO_ORCAD plugin;
13019 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13020
13021 std::vector<std::set<std::string>> expected = { { terminalToken( "C1", "2" ), terminalToken( "C10", "2" ) },
13022 { terminalToken( "LED14", "1" ), terminalToken( "R304", "1" ) } };
13023 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
13024 BOOST_CHECK_EQUAL( checkable, 2 );
13025 BOOST_CHECK_EQUAL( consistent, 2 );
13026}
13027
13028
13029BOOST_AUTO_TEST_CASE( PackageVariantPreservesPhysicalPinMap )
13030{
13031 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13032
13033 if( !corpusEnv || !*corpusEnv )
13034 return;
13035
13036 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "710-DC2693A_REV02_PCA_SCHEMATIC.DSN" );
13037
13038 if( dsn.empty() )
13039 return;
13040
13041 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13042 SETTINGS_MANAGER manager;
13043 manager.LoadProject( "" );
13044 schematic->SetProject( &manager.Prj() );
13045 schematic->CurrentSheet().clear();
13046 schematic->CurrentSheet().push_back( &schematic->Root() );
13047
13048 SCH_IO_ORCAD plugin;
13049 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13050
13051 std::vector<std::set<std::string>> expected = { { terminalToken( "C101", "1" ), terminalToken( "U101", "6" ) },
13052 { terminalToken( "R105", "2" ), terminalToken( "U101", "3" ) },
13053 { terminalToken( "R106", "1" ), terminalToken( "U101", "4" ) } };
13054 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
13055 BOOST_CHECK_EQUAL( checkable, 3 );
13056 BOOST_CHECK_EQUAL( consistent, 3 );
13057}
13058
13059
13060BOOST_AUTO_TEST_CASE( DistinctPowerAndOffpageInterfaceNamesRemainDistinct )
13061{
13062 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13063
13064 if( !corpusEnv || !*corpusEnv )
13065 return;
13066
13067 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "OpenCellular_Connect-1_GBC_Life-3_Schematic_v1.2.DSN" );
13068
13069 if( dsn.empty() )
13070 return;
13071
13072 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13073 SETTINGS_MANAGER manager;
13074 manager.LoadProject( "" );
13075 schematic->SetProject( &manager.Prj() );
13076 schematic->CurrentSheet().clear();
13077 schematic->CurrentSheet().push_back( &schematic->Root() );
13078
13079 SCH_IO_ORCAD plugin;
13080 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13081
13082 std::vector<std::set<std::string>> expected = { { terminalToken( "C1559", "2" ), terminalToken( "D8", "2" ) },
13083 { terminalToken( "L28", "3" ), terminalToken( "R1009", "1" ) } };
13084 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
13085 BOOST_CHECK_EQUAL( checkable, 2 );
13086 BOOST_CHECK_EQUAL( consistent, 2 );
13087}
13088
13089
13090BOOST_AUTO_TEST_CASE( FlatTopLevelOffpageConnectsAcrossPages )
13091{
13092 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13093
13094 if( !corpusEnv || !*corpusEnv )
13095 return;
13096
13097 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC1931B.DSN" );
13098
13099 if( dsn.empty() )
13100 return;
13101
13102 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13103 SETTINGS_MANAGER manager;
13104 manager.LoadProject( "" );
13105 schematic->SetProject( &manager.Prj() );
13106 schematic->CurrentSheet().clear();
13107 schematic->CurrentSheet().push_back( &schematic->Root() );
13108
13109 SCH_IO_ORCAD plugin;
13110 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13111
13112 std::vector<std::set<std::string>> expected = { { terminalToken( "J4", "H26" ), terminalToken( "U2", "N1" ) } };
13113 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
13114 BOOST_CHECK_EQUAL( checkable, 1 );
13115 BOOST_CHECK_EQUAL( consistent, 1 );
13116}
13117
13118
13119BOOST_AUTO_TEST_CASE( PowerAliasConnectsAcrossPages )
13120{
13121 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13122
13123 if( !corpusEnv || !*corpusEnv )
13124 return;
13125
13126 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "710-DC2222A_REV07_PCA_SCHEMATIC.DSN" );
13127
13128 if( dsn.empty() )
13129 return;
13130
13131 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13132 SETTINGS_MANAGER manager;
13133 manager.LoadProject( "" );
13134 schematic->SetProject( &manager.Prj() );
13135 schematic->CurrentSheet().clear();
13136 schematic->CurrentSheet().push_back( &schematic->Root() );
13137
13138 SCH_IO_ORCAD plugin;
13139 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13140
13141 std::vector<std::set<std::string>> expected = { { terminalToken( "C10", "2" ), terminalToken( "C29", "1" ),
13142 terminalToken( "C30", "1" ), terminalToken( "C9", "2" ),
13143 terminalToken( "E2", "1" ), terminalToken( "R19", "1" ),
13144 terminalToken( "U1", "3" ), terminalToken( "U14", "1" ) } };
13145 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
13146 BOOST_CHECK_EQUAL( checkable, 1 );
13147 BOOST_CHECK_EQUAL( consistent, 1 );
13148}
13149
13150
13151BOOST_AUTO_TEST_CASE( PhysicalConnectorPinsUseDefinitionNumbers )
13152{
13153 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13154
13155 if( !corpusEnv || !*corpusEnv )
13156 return;
13157
13158 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2382A-1.DSN" );
13159
13160 if( dsn.empty() )
13161 return;
13162
13163 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13164 SETTINGS_MANAGER manager;
13165 manager.LoadProject( "" );
13166 schematic->SetProject( &manager.Prj() );
13167 schematic->CurrentSheet().clear();
13168 schematic->CurrentSheet().push_back( &schematic->Root() );
13169
13170 SCH_IO_ORCAD plugin;
13171 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13172
13173 std::set<wxString> pinNumbers;
13174
13175 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
13176 {
13177 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
13178 {
13179 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
13180
13181 if( symbol->GetRef( &path, false ) != wxS( "J3" ) )
13182 continue;
13183
13184 for( SCH_PIN* pin : symbol->GetPins( &path ) )
13185 pinNumbers.insert( pin->GetNumber() );
13186 }
13187 }
13188
13189 std::set<wxString> expected;
13190
13191 for( int pin = 1; pin <= 20; ++pin )
13192 expected.insert( wxString::Format( wxS( "%d" ), pin ) );
13193
13194 BOOST_CHECK_EQUAL_COLLECTIONS( pinNumbers.begin(), pinNumbers.end(), expected.begin(), expected.end() );
13195
13196 std::vector<std::set<std::string>> expectedNets = { { terminalToken( "J3", "1" ), terminalToken( "Q1", "3" ) },
13197 { terminalToken( "J3", "2" ), terminalToken( "J1", "2" ) },
13198 { terminalToken( "J3", "3" ), terminalToken( "J1", "3" ) },
13199 { terminalToken( "J3", "10" ), terminalToken( "J1", "10" ) },
13200 { terminalToken( "J3", "13" ), terminalToken( "R19", "1" ) } };
13201 auto [consistent, checkable] = checkConnectivity( *schematic, expectedNets );
13202 BOOST_CHECK_EQUAL( checkable, 5 );
13203 BOOST_CHECK_EQUAL( consistent, 5 );
13204}
13205
13206
13207BOOST_AUTO_TEST_CASE( ModernPackageStreamsSupplyEmbeddedSymbolGeometry )
13208{
13209 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13210
13211 if( !corpusEnv || !*corpusEnv )
13212 return;
13213
13214 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC2382A-1.DSN" );
13215
13216 if( dsn.empty() )
13217 return;
13218
13219 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13220 SETTINGS_MANAGER manager;
13221 manager.LoadProject( "" );
13222 schematic->SetProject( &manager.Prj() );
13223 schematic->CurrentSheet().clear();
13224 schematic->CurrentSheet().push_back( &schematic->Root() );
13225
13226 SCH_IO_ORCAD plugin;
13227 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13228
13229 SCH_SYMBOL* jumper = nullptr;
13230
13231 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
13232 {
13233 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
13234 {
13235 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
13236
13237 if( symbol->GetRef( &path, false ) == wxS( "JP4" ) )
13238 jumper = symbol;
13239 }
13240 }
13241
13242 BOOST_REQUIRE( jumper );
13243 BOOST_REQUIRE( jumper->GetLibSymbolRef() );
13244
13245 int bodyRectangles = 0;
13246
13247 for( const SCH_ITEM& item : jumper->GetLibSymbolRef()->GetDrawItems() )
13248 {
13249 if( item.Type() == SCH_SHAPE_T && static_cast<const SCH_SHAPE&>( item ).GetShape() == SHAPE_T::RECTANGLE )
13250 ++bodyRectangles;
13251 }
13252
13253 BOOST_CHECK_EQUAL( bodyRectangles, 3 );
13254}
13255
13256
13257BOOST_AUTO_TEST_CASE( LegacyDisplayTypesRemainVisible )
13258{
13259 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13260
13261 if( !corpusEnv || !*corpusEnv )
13262 return;
13263
13264 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "X375D_VER72.DSN" );
13265
13266 if( dsn.empty() )
13267 return;
13268
13269 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13270 SETTINGS_MANAGER manager;
13271 manager.LoadProject( "" );
13272 schematic->SetProject( &manager.Prj() );
13273 schematic->CurrentSheet().clear();
13274 schematic->CurrentSheet().push_back( &schematic->Root() );
13275
13276 SCH_IO_ORCAD plugin;
13277 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13278
13279 int checked = 0;
13280
13281 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
13282 {
13283 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
13284 {
13285 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
13286
13287 if( symbol->GetRef( &path, false ) != wxS( "CLKOUT0" ) )
13288 continue;
13289
13290 BOOST_CHECK( symbol->GetField( FIELD_T::REFERENCE )->IsVisible() );
13291 ++checked;
13292 }
13293 }
13294
13295 BOOST_CHECK_EQUAL( checked, 1 );
13296}
13297
13298
13299BOOST_AUTO_TEST_CASE( UnreferencedPagesDoNotOverwriteHierarchicalSheets )
13300{
13301 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13302
13303 if( !corpusEnv || !*corpusEnv )
13304 return;
13305
13306 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SI8281V2-EVB.DSN" );
13307
13308 if( dsn.empty() )
13309 return;
13310
13311 m_plugin.LoadSchematicFile( dsn.string(), m_schematic.get() );
13312 std::map<wxString, SCH_SCREEN*> screensByFile;
13313 size_t connectorPins = 0;
13314
13315 for( const SCH_SHEET_PATH& path : m_schematic->BuildSheetListSortedByPageNumbers() )
13316 {
13317 SCH_SCREEN* screen = path.LastScreen();
13318 auto [entry, inserted] = screensByFile.emplace( screen->GetFileName().Lower(), screen );
13319 BOOST_CHECK_MESSAGE( inserted || entry->second == screen,
13320 "Distinct sheets share output file " << screen->GetFileName() );
13321
13322 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
13323 {
13324 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
13325
13326 if( symbol->GetRef( &path, false ) == wxS( "J24" ) )
13327 connectorPins += symbol->GetPins().size();
13328 }
13329 }
13330
13331 BOOST_CHECK_EQUAL( connectorPins, 16 );
13332}
13333
13334
13335BOOST_AUTO_TEST_CASE( HierarchicalPortsUseVisibleNativeLabels )
13336{
13337 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13338
13339 if( !corpusEnv || !*corpusEnv )
13340 return;
13341
13342 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "M5275EVB.DSN" );
13343
13344 if( dsn.empty() )
13345 return;
13346
13347 m_plugin.LoadSchematicFile( dsn.string(), m_schematic.get() );
13348 size_t ports = 0;
13349
13350 for( const SCH_SHEET_PATH& path : m_schematic->BuildSheetListSortedByPageNumbers() )
13351 {
13352 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_HIER_LABEL_T ) )
13353 {
13354 SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( item );
13355 BOOST_CHECK_MESSAGE( label->GetTextColor() == KIGFX::COLOR4D::UNSPECIFIED
13356 || label->GetTextColor().a > 0,
13357 "Hidden hierarchical port " << label->GetText() );
13358 ++ports;
13359 }
13360 }
13361
13362 BOOST_CHECK_GT( ports, 0 );
13363}
13364
13365
13366BOOST_AUTO_TEST_CASE( LegacyHierarchicalBlockImport )
13367{
13368 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13369
13370 if( !corpusEnv || !*corpusEnv )
13371 {
13372 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping legacy hierarchy check." );
13373 return;
13374 }
13375
13376 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SCH-20380.DSN" );
13377
13378 if( dsn.empty() )
13379 {
13380 BOOST_TEST_MESSAGE( "SCH-20380.DSN not present in corpus; skipping legacy hierarchy check." );
13381 return;
13382 }
13383
13384 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13385 SETTINGS_MANAGER manager;
13386 manager.LoadProject( "" );
13387 schematic->SetProject( &manager.Prj() );
13388 schematic->CurrentSheet().clear();
13389 schematic->CurrentSheet().push_back( &schematic->Root() );
13390
13391 SCH_IO_ORCAD plugin;
13392 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13393 schematic->CurrentSheet().UpdateAllScreenReferences();
13394
13395 size_t pages = 0;
13396 size_t components = 0;
13397
13398 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
13399 {
13400 SCH_SCREEN* screen = path.LastScreen();
13401
13402 if( !screen )
13403 continue;
13404
13405 ++pages;
13406
13407 for( SCH_ITEM* item : screen->Items() )
13408 {
13409 if( item->Type() == SCH_SYMBOL_T
13410 && !static_cast<SCH_SYMBOL*>( item )->GetRef( &path, false ).StartsWith( wxS( "#" ) ) )
13411 {
13412 ++components;
13413 }
13414
13415 }
13416 }
13417
13418 BOOST_CHECK_EQUAL( pages, 16u );
13419 BOOST_CHECK_EQUAL( components, 491u );
13420 auto [consistent, checkable] = checkConnectivity(
13421 *schematic, { { "J7.54", "RP17.3", "U10.C13" },
13422 { "J10.35", "J12.9", "RP15.7", "U10.N10" } } );
13423 BOOST_CHECK_EQUAL( checkable, 2 );
13424 BOOST_CHECK_EQUAL( consistent, 2 );
13425}
13426
13427
13428BOOST_AUTO_TEST_CASE( LegacyDsnDiodePinsUseLogicalPolarity )
13429{
13430 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13431
13432 if( !corpusEnv || !*corpusEnv )
13433 return;
13434
13435 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SCH-20380.DSN" );
13436
13437 if( dsn.empty() )
13438 return;
13439
13440 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13441 SETTINGS_MANAGER manager;
13442 manager.LoadProject( "" );
13443 schematic->SetProject( &manager.Prj() );
13444 schematic->CurrentSheet().clear();
13445 schematic->CurrentSheet().push_back( &schematic->Root() );
13446
13447 SCH_IO_ORCAD plugin;
13448 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13449
13450 const std::vector<std::set<std::string>> expected = {
13451 { terminalToken( "D1", "2" ), terminalToken( "C37", "1" ) },
13452 { terminalToken( "D1", "1" ), terminalToken( "R15", "1" ) }
13453 };
13454 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
13455 BOOST_CHECK_EQUAL( checkable, expected.size() );
13456 BOOST_CHECK_EQUAL( consistent, expected.size() );
13457}
13458
13459
13460BOOST_AUTO_TEST_CASE( LegacyDsnEmbeddedSlashNetNameIsAuthoritative )
13461{
13462 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13463
13464 if( !corpusEnv || !*corpusEnv )
13465 return;
13466
13467 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SCH-20380.DSN" );
13468
13469 if( dsn.empty() )
13470 return;
13471
13472 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13473 SETTINGS_MANAGER manager;
13474 manager.LoadProject( "" );
13475 schematic->SetProject( &manager.Prj() );
13476 schematic->CurrentSheet().clear();
13477 schematic->CurrentSheet().push_back( &schematic->Root() );
13478
13479 SCH_IO_ORCAD plugin;
13480 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13481
13482 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
13483 schematic->ConnectionGraph()->Recalculate( sheets, true );
13484 wxString j1Pin7Net;
13485
13486 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
13487 {
13488 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
13489 {
13490 for( SCH_ITEM* item : subgraph->GetItems() )
13491 {
13492 if( item->Type() != SCH_PIN_T )
13493 continue;
13494
13495 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
13496 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
13497
13498 if( symbol && symbol->GetRef( &subgraph->GetSheet(), false ) == wxS( "J1" )
13499 && pin->GetNumber() == wxS( "7" ) )
13500 {
13501 j1Pin7Net = key.Name;
13502 }
13503 }
13504 }
13505 }
13506
13507 BOOST_CHECK( !j1Pin7Net.IsEmpty() );
13508 BOOST_CHECK_EQUAL( terminalNetName( *schematic, wxS( "RP13" ), wxS( "7" ) ), j1Pin7Net );
13509 BOOST_CHECK_EQUAL( terminalNetName( *schematic, wxS( "U26" ), wxS( "B" ) ), j1Pin7Net );
13510
13511 const IMPORT_NET_MAP* map = schematic->GetImportNetMap();
13512 BOOST_REQUIRE( map );
13513 auto mapped = std::find_if( map->entries.begin(), map->entries.end(),
13514 []( const IMPORT_NET_MAP_ENTRY& entry )
13515 {
13516 return entry.sourceNetId == 3221698
13517 && entry.originalName == wxS( "BDM_/RSTIN" );
13518 } );
13519 BOOST_REQUIRE( mapped != map->entries.end() );
13521 BOOST_CHECK_EQUAL( mapped->nameAtImport, j1Pin7Net );
13522
13523 bool sourcePort = false;
13524
13525 for( const SCH_SHEET_PATH& sheet : sheets )
13526 {
13527 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_HIER_LABEL_T ) )
13528 sourcePort |= static_cast<SCH_HIERLABEL*>( item )->GetText() == wxS( "BDM_/RSTIN" );
13529 }
13530
13531 BOOST_CHECK( sourcePort );
13532}
13533
13534
13535BOOST_AUTO_TEST_CASE( UninstantiatedLegacyPageIsExcludedFromBoard )
13536{
13537 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13538
13539 if( !corpusEnv || !*corpusEnv )
13540 return;
13541
13542 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SCH-21095.DSN" );
13543
13544 if( dsn.empty() )
13545 return;
13546
13547 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13548 SETTINGS_MANAGER manager;
13549 manager.LoadProject( "" );
13550 schematic->SetProject( &manager.Prj() );
13551 schematic->CurrentSheet().clear();
13552 schematic->CurrentSheet().push_back( &schematic->Root() );
13553
13554 SCH_IO_ORCAD plugin;
13555 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13556 schematic->CurrentSheet().UpdateAllScreenReferences();
13557
13558 SCH_SHEET* mram = nullptr;
13559 SCH_SHEET* reset = nullptr;
13560
13561 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
13562 {
13563 SCH_SHEET* sheet = path.Last();
13564
13565 if( path.LastScreen()->GetFileName().Upper().Contains( wxS( "MRAM" ) ) )
13566 mram = sheet;
13567 else if( path.LastScreen()->GetFileName().Upper().Contains( wxS( "RESET" ) ) )
13568 reset = sheet;
13569 }
13570
13571 BOOST_REQUIRE( mram );
13572 BOOST_REQUIRE( reset );
13573 BOOST_CHECK( mram->GetExcludedFromBoard() );
13574 BOOST_CHECK( !reset->GetExcludedFromBoard() );
13575}
13576
13577
13578BOOST_AUTO_TEST_CASE( LegacyFlatPageImport )
13579{
13580 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13581
13582 if( !corpusEnv || !*corpusEnv )
13583 {
13584 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping legacy flat-page check." );
13585 return;
13586 }
13587
13588 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "X375D_VER72.DSN" );
13589
13590 if( dsn.empty() )
13591 {
13592 BOOST_TEST_MESSAGE( "X375D_VER72.DSN not present in corpus; skipping legacy flat-page check." );
13593 return;
13594 }
13595
13596 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13597 SETTINGS_MANAGER manager;
13598 manager.LoadProject( "" );
13599 schematic->SetProject( &manager.Prj() );
13600 schematic->CurrentSheet().clear();
13601 schematic->CurrentSheet().push_back( &schematic->Root() );
13602
13603 SCH_IO_ORCAD plugin;
13605 plugin.SetReporter( &reporter );
13606
13607 try
13608 {
13609 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13610 }
13611 catch( const std::exception& e )
13612 {
13613 BOOST_FAIL( e.what() << "\n" << reporter.GetMessages() );
13614 return;
13615 }
13616
13617 size_t pages = 0;
13618 size_t components = 0;
13619
13620 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
13621 {
13622 SCH_SCREEN* screen = path.LastScreen();
13623
13624 if( !screen )
13625 continue;
13626
13627 ++pages;
13628
13629 for( SCH_ITEM* item : screen->Items() )
13630 {
13631 if( item->Type() == SCH_SYMBOL_T
13632 && !static_cast<SCH_SYMBOL*>( item )->GetRef( &path, false ).StartsWith( wxS( "#" ) ) )
13633 {
13634 ++components;
13635 }
13636
13637 }
13638 }
13639
13640 BOOST_CHECK_EQUAL( pages, 1u );
13641 BOOST_CHECK_EQUAL( components, 256u );
13642}
13643
13644
13645BOOST_AUTO_TEST_CASE( LegacyHierarchyPowerTableImport )
13646{
13647 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13648
13649 if( !corpusEnv || !*corpusEnv )
13650 {
13651 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping legacy power-table check." );
13652 return;
13653 }
13654
13655 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "DC1414B.DSN" );
13656
13657 if( dsn.empty() )
13658 {
13659 BOOST_TEST_MESSAGE( "DC1414B.DSN not present in corpus; skipping legacy power-table check." );
13660 return;
13661 }
13662
13663 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13664 SETTINGS_MANAGER manager;
13665 manager.LoadProject( "" );
13666 schematic->SetProject( &manager.Prj() );
13667 schematic->CurrentSheet().clear();
13668 schematic->CurrentSheet().push_back( &schematic->Root() );
13669
13670 SCH_IO_ORCAD plugin;
13671 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13672 schematic->CurrentSheet().UpdateAllScreenReferences();
13673
13674 size_t pages = 0;
13675 size_t components = 0;
13676
13677 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
13678 {
13679 SCH_SCREEN* screen = path.LastScreen();
13680
13681 if( !screen )
13682 continue;
13683
13684 ++pages;
13685
13686 for( SCH_ITEM* item : screen->Items() )
13687 {
13688 if( item->Type() == SCH_SYMBOL_T
13689 && !static_cast<SCH_SYMBOL*>( item )->GetRef( &path, false ).StartsWith( wxS( "#" ) ) )
13690 {
13691 ++components;
13692 }
13693
13694 }
13695 }
13696
13697 BOOST_CHECK_EQUAL( pages, 1u );
13698 BOOST_CHECK_EQUAL( components, 74u );
13699}
13700
13701
13702BOOST_AUTO_TEST_CASE( Issue25005Hierarchy )
13703{
13704 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13705
13706 if( !corpusEnv || !*corpusEnv )
13707 {
13708 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping issue 25005." );
13709 return;
13710 }
13711
13712 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CFW-002.DSN" );
13713
13714 if( dsn.empty() )
13715 {
13716 BOOST_TEST_MESSAGE( "CFW-002.DSN not present in corpus; skipping issue 25005." );
13717 return;
13718 }
13719
13720 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13721 SETTINGS_MANAGER manager;
13722 manager.LoadProject( "" );
13723 schematic->SetProject( &manager.Prj() );
13724 schematic->CurrentSheet().clear();
13725 schematic->CurrentSheet().push_back( &schematic->Root() );
13726
13727 SCH_IO_ORCAD plugin;
13728 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13729
13730 std::vector<SCH_SHEET*> topSheets = schematic->GetTopLevelSheets();
13731 BOOST_REQUIRE_EQUAL( topSheets.size(), 1u );
13732
13733 SCH_SCREEN* rootScreen = topSheets.front()->GetScreen();
13734 size_t sheets = 0;
13735 size_t sheetPins = 0;
13736
13737 for( SCH_ITEM* item : rootScreen->Items().OfType( SCH_SHEET_T ) )
13738 {
13739 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
13740 ++sheets;
13741 sheetPins += sheet->GetPins().size();
13742 }
13743
13744 const std::vector<wxString> expectedNames = { wxS( "PAG_2" ), wxS( "PAG_3" ), wxS( "PAG_4" ), wxS( "PAG_5" ),
13745 wxS( "PAG_6" ), wxS( "PAG_7" ), wxS( "PAG_8" ), wxS( "PAG_9" ) };
13746 const std::vector<size_t> expectedPinCounts = { 31, 24, 39, 47, 40, 33, 26, 10 };
13747 SCH_SHEET_LIST hierarchy = schematic->BuildSheetListSortedByPageNumbers();
13748 std::vector<wxString> sheetNames;
13749 std::vector<size_t> pinCounts;
13750
13751 for( auto it = std::next( hierarchy.begin() ); it != hierarchy.end(); ++it )
13752 {
13753 SCH_SHEET* sheet = it->Last();
13754 std::set<wxString> sheetPinNames;
13755 std::set<wxString> hierarchicalLabelNames;
13756
13757 sheetNames.push_back( sheet->GetField( FIELD_T::SHEET_NAME )->GetText() );
13758 pinCounts.push_back( sheet->GetPins().size() );
13759
13760 for( const SCH_SHEET_PIN* pin : sheet->GetPins() )
13761 sheetPinNames.insert( pin->GetText() );
13762
13763 for( SCH_ITEM* item : sheet->GetScreen()->Items().OfType( SCH_HIER_LABEL_T ) )
13764 hierarchicalLabelNames.insert( static_cast<SCH_HIERLABEL*>( item )->GetText() );
13765
13766 BOOST_CHECK_EQUAL_COLLECTIONS( sheetPinNames.begin(), sheetPinNames.end(), hierarchicalLabelNames.begin(),
13767 hierarchicalLabelNames.end() );
13768 }
13769
13770 schematic->ConnectionGraph()->Recalculate( hierarchy, true );
13771
13772 BOOST_CHECK_EQUAL( hierarchy.size(), 9u );
13773 BOOST_CHECK_EQUAL( sheets, 8u );
13774 BOOST_CHECK_EQUAL( sheetPins, 250u );
13775 BOOST_CHECK_EQUAL_COLLECTIONS( sheetNames.begin(), sheetNames.end(), expectedNames.begin(), expectedNames.end() );
13776 BOOST_CHECK_EQUAL_COLLECTIONS( pinCounts.begin(), pinCounts.end(), expectedPinCounts.begin(),
13777 expectedPinCounts.end() );
13778}
13779
13780
13781BOOST_AUTO_TEST_CASE( Issue25009PageOrderAndGraphics )
13782{
13783 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13784
13785 if( !corpusEnv || !*corpusEnv )
13786 {
13787 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping issue 25009." );
13788 return;
13789 }
13790
13791 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "SE_NGFOC-L_01.DSN" );
13792
13793 if( dsn.empty() )
13794 {
13795 BOOST_TEST_MESSAGE( "SE_NGFOC-L_01.DSN not present in corpus; skipping issue 25009." );
13796 return;
13797 }
13798
13799 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13800 SETTINGS_MANAGER manager;
13801 manager.LoadProject( "" );
13802 schematic->SetProject( &manager.Prj() );
13803 schematic->CurrentSheet().clear();
13804 schematic->CurrentSheet().push_back( &schematic->Root() );
13805
13806 SCH_IO_ORCAD plugin;
13807 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13808
13809 const std::vector<wxString> expectedNames = { wxS( "01.REV.HISTORY" ), wxS( "02.uC" ), wxS( "03.CAN" ),
13810 wxS( "04. Ethercat" ), wxS( "05.EtherSynch" ), wxS( "06.RS-485" ),
13811 wxS( "11.GPIO" ), wxS( "12.Analog" ), wxS( "13:IMU" ),
13812 wxS( "14.Bridge" ), wxS( "15.Encoder" ), wxS( "29.uCPower" ),
13813 wxS( "30.PowerSupply" ), wxS( "31.Expansion" ) };
13814
13815 std::vector<SCH_SHEET*> sheets = schematic->GetTopLevelSheets();
13816 BOOST_REQUIRE_EQUAL( sheets.size(), expectedNames.size() );
13817
13818 size_t wires = 0;
13819 size_t shapes = 0;
13820 size_t texts = 0;
13821 size_t tables = 0;
13822
13823 for( size_t i = 0; i < sheets.size(); ++i )
13824 {
13825 BOOST_CHECK_EQUAL( sheets[i]->GetField( FIELD_T::SHEET_NAME )->GetText(), expectedNames[i] );
13826
13827 for( SCH_ITEM* item : sheets[i]->GetScreen()->Items() )
13828 {
13829 if( item->Type() == SCH_LINE_T )
13830 {
13831 SCH_LINE* line = static_cast<SCH_LINE*>( item );
13832 BOOST_CHECK_EQUAL( line->GetLineWidth(), 0 );
13833 ++wires;
13834 }
13835 else if( item->Type() == SCH_SHAPE_T )
13836 {
13837 SCH_SHAPE* shape = static_cast<SCH_SHAPE*>( item );
13838
13839 if( i == 0 )
13840 BOOST_CHECK( shape->GetFillMode() == FILL_T::NO_FILL );
13841
13842 ++shapes;
13843 }
13844 else if( item->Type() == SCH_TEXT_T )
13845 {
13846 ++texts;
13847 }
13848 else if( item->Type() == SCH_TABLE_T )
13849 {
13850 ++tables;
13851 }
13852 }
13853 }
13854
13855 BOOST_CHECK_EQUAL( wires, 1921u );
13856 BOOST_CHECK_EQUAL( shapes, 168u );
13857 BOOST_CHECK_EQUAL( texts, 206u );
13858 BOOST_CHECK_EQUAL( tables, 0u );
13859}
13860
13861
13862// Set KICAD_ORCAD_CORPUS to verify that private OLB files yield pins or graphics.
13863
13864BOOST_AUTO_TEST_CASE( OlbLibraryImport )
13865{
13866 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13867
13868 if( !corpusEnv || !*corpusEnv )
13869 {
13870 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping OrCAD OLB library import." );
13871 return;
13872 }
13873
13874 namespace fs = std::filesystem;
13875 std::vector<fs::path> libs;
13876
13877 for( auto it = fs::recursive_directory_iterator( fs::path( corpusEnv ),
13878 fs::directory_options::skip_permission_denied );
13879 it != fs::recursive_directory_iterator(); ++it )
13880 {
13881 if( !it->is_regular_file() )
13882 continue;
13883
13884 std::string ext = it->path().extension().string();
13885 std::transform( ext.begin(), ext.end(), ext.begin(),
13886 []( unsigned char c )
13887 {
13888 return std::tolower( c );
13889 } );
13890
13891 if( ext == ".olb" )
13892 libs.push_back( it->path() );
13893 }
13894
13895 std::sort( libs.begin(), libs.end() );
13896 BOOST_TEST_MESSAGE( "OrCAD OLB libraries: " << libs.size() );
13897
13898 int totalSymbols = 0, emptySymbols = 0, checkedLibs = 0, rejectedLibs = 0, crashedLibs = 0;
13899
13900 for( const fs::path& lib : libs )
13901 {
13902 SCH_IO_ORCAD plugin;
13903 std::vector<LIB_SYMBOL*> symbols;
13904
13905 if( !plugin.CanReadLibrary( lib.string() ) )
13906 {
13907 ++rejectedLibs;
13908 continue;
13909 }
13910
13911 try
13912 {
13913 // Vector overload materializes all symbols O(n); per-name LoadSymbol rescans O(n^2).
13914 plugin.EnumerateSymbolLib( symbols, lib.string() );
13915 }
13916 catch( const std::exception& e )
13917 {
13918 ++crashedLibs;
13919 BOOST_TEST_MESSAGE( " THROW " << lib.filename().string() << " : " << e.what() );
13920 continue;
13921 }
13922
13923 ++checkedLibs;
13924 int withGeometry = 0;
13925
13926 for( LIB_SYMBOL* symbol : symbols )
13927 {
13928 BOOST_REQUIRE( symbol );
13929 ++totalSymbols;
13930
13931 if( symbol->GetPinCount() > 0 || !symbol->GetDrawItems().empty() )
13932 ++withGeometry;
13933 else
13934 ++emptySymbols;
13935 }
13936
13937 BOOST_TEST_MESSAGE( " " << lib.filename().string() << " : " << symbols.size() << " symbols, " << withGeometry
13938 << " with pins/graphics" );
13939 }
13940
13941 BOOST_TEST_MESSAGE( "OLB summary: " << checkedLibs << " libs, " << rejectedLibs << " rejected, " << crashedLibs
13942 << " crashed, " << totalSymbols << " symbols, " << emptySymbols << " empty" );
13943
13944 // Bad streams must degrade gracefully, not throw; wholesale empty result means decode broke.
13945 BOOST_CHECK_EQUAL( crashedLibs, 0 );
13946 BOOST_CHECK_GT( totalSymbols, 0 );
13947
13948 if( totalSymbols )
13949 BOOST_CHECK_LT( emptySymbols, totalSymbols / 2 );
13950}
13951
13952
13953// CutiePi (3 pages) imports as three sibling top-level sheets; off-page connectors keep own
13954// names; reference/value fields honor source display positions.
13955BOOST_AUTO_TEST_CASE( MultiPageFlatImport )
13956{
13957 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
13958
13959 if( !corpusEnv || !*corpusEnv )
13960 {
13961 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping OrCAD multi-page import." );
13962 return;
13963 }
13964
13965 namespace fs = std::filesystem;
13966 fs::path dsn = fs::path( corpusEnv ) / "cutiepi-board" / "CutiePi_V2.3-20210409.DSN";
13967
13968 if( !fs::exists( dsn ) )
13969 {
13970 BOOST_TEST_MESSAGE( "CutiePi design not present in corpus; skipping." );
13971 return;
13972 }
13973
13974 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
13975 SETTINGS_MANAGER manager;
13976 manager.LoadProject( "" );
13977 schematic->SetProject( &manager.Prj() );
13978 schematic->CurrentSheet().clear();
13979 schematic->CurrentSheet().push_back( &schematic->Root() );
13980
13981 SCH_IO_ORCAD plugin;
13982 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
13983
13984 BOOST_CHECK_EQUAL( schematic->Settings().m_DashedLineDashRatio, 3.0 );
13985 BOOST_CHECK_EQUAL( schematic->Settings().m_DashedLineGapRatio, 1.0 );
13986
13987 // Pages become flat ordered top-level sheets, not a stitching root w/ children; "N - " prefix orders them.
13988 std::vector<SCH_SHEET*> tops = schematic->GetTopLevelSheets();
13989 BOOST_REQUIRE_EQUAL( tops.size(), 3u );
13990
13991 BOOST_CHECK_EQUAL( tops[0]->GetField( FIELD_T::SHEET_NAME )->GetText(), wxS( "CONTENTS" ) );
13992 BOOST_CHECK_EQUAL( tops[1]->GetField( FIELD_T::SHEET_NAME )->GetText(), wxS( "CM4,USB HUB,AUDIO,MIC" ) );
13993 BOOST_CHECK_EQUAL( tops[2]->GetField( FIELD_T::SHEET_NAME )->GetText(), wxS( "CSI, DSI, HDMI, MCU" ) );
13994
13995 std::set<wxString> globalLabels;
13996
13997 for( SCH_SHEET* top : tops )
13998 {
13999 for( SCH_ITEM* item : top->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
14000 globalLabels.insert( static_cast<SCH_LABEL_BASE*>( item )->GetText() );
14001 }
14002
14003 // Off-page connectors carry own name (CAM0_IO1), not the local wire net (GPIO19) they sit on.
14004 BOOST_CHECK( globalLabels.count( wxS( "CAM0_IO1" ) ) );
14005 BOOST_CHECK( globalLabels.count( wxS( "AMP_SHUTDOWN" ) ) );
14006
14007 // R3197 reference honors OrCAD display position (left of body), not computed fallback (right).
14008 bool checkedField = false;
14009
14010 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
14011 {
14012 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
14013 {
14014 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
14015
14016 if( sym->GetRef( &path, false ) == wxS( "R3197" ) )
14017 {
14018 BOOST_CHECK_LT( sym->GetField( FIELD_T::REFERENCE )->GetPosition().x, sym->GetPosition().x );
14019 checkedField = true;
14020 }
14021 }
14022 }
14023
14024 BOOST_CHECK( checkedField );
14025}
14026
14027
14028BOOST_AUTO_TEST_CASE( HierarchicalSymbolInstancesUseCanonicalSheetPaths )
14029{
14030 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14031
14032 if( !corpusEnv || !*corpusEnv )
14033 {
14034 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping flat-page instance-path check." );
14035 return;
14036 }
14037
14038 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "OCTOPAES_10.DSN" );
14039
14040 if( dsn.empty() )
14041 {
14042 BOOST_TEST_MESSAGE( "OCTOPAES_10.DSN not present in corpus; skipping instance-path check." );
14043 return;
14044 }
14045
14046 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14047 SETTINGS_MANAGER manager;
14048 manager.LoadProject( "" );
14049 schematic->SetProject( &manager.Prj() );
14050 schematic->CurrentSheet().clear();
14051 schematic->CurrentSheet().push_back( &schematic->Root() );
14052
14053 SCH_IO_ORCAD plugin;
14054 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14055
14056 int checked = 0;
14057
14058 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
14059 {
14060 if( !path.LastScreen()->GetFileName().Contains( wxS( "CPLD Power" ) ) )
14061 continue;
14062
14063 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
14064 {
14065 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
14066
14067 if( symbol->GetRef( &path, false ) != wxS( "P?" ) )
14068 continue;
14069
14070 bool canonical = std::any_of( symbol->GetInstances().begin(), symbol->GetInstances().end(),
14071 [&]( const SCH_SYMBOL_INSTANCE& aInstance )
14072 {
14073 return aInstance.m_Path == path.Path();
14074 } );
14075 std::string stored;
14076
14077 for( const SCH_SYMBOL_INSTANCE& instance : symbol->GetInstances() )
14078 stored += instance.m_Path.AsString().ToStdString() + " ";
14079
14080 BOOST_CHECK_MESSAGE( canonical, "expected=" << path.Path().AsString() << " stored=" << stored );
14081 ++checked;
14082 }
14083 }
14084
14085 BOOST_CHECK_EQUAL( checked, 3 );
14086}
14087
14088
14089BOOST_AUTO_TEST_CASE( MultiPageHierarchyPreservesPortConnectivity )
14090{
14091 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14092
14093 if( !corpusEnv || !*corpusEnv )
14094 {
14095 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping multi-page hierarchy check." );
14096 return;
14097 }
14098
14099 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "HB1A-AAFM.DSN" );
14100
14101 if( dsn.empty() )
14102 {
14103 BOOST_TEST_MESSAGE( "HB1A-AAFM.DSN not present in corpus; skipping multi-page hierarchy check." );
14104 return;
14105 }
14106
14107 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14108 SETTINGS_MANAGER manager;
14109 manager.LoadProject( "" );
14110 schematic->SetProject( &manager.Prj() );
14111 schematic->CurrentSheet().clear();
14112 schematic->CurrentSheet().push_back( &schematic->Root() );
14113
14114 SCH_IO_ORCAD plugin;
14115 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14116
14117 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
14118 schematic->ConnectionGraph()->Recalculate( sheets, true );
14119
14120 std::map<std::tuple<std::string, std::string, std::string>, int> terminalNets;
14121 int netId = 0;
14122
14123 for( const auto& [key, subgraphs] : schematic->ConnectionGraph()->GetNetMap() )
14124 {
14125 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
14126 {
14127 std::string page = subgraph->GetSheet().LastScreen()->GetFileName().ToStdString();
14128
14129 for( SCH_ITEM* item : subgraph->GetItems() )
14130 {
14131 if( item->Type() != SCH_PIN_T )
14132 continue;
14133
14134 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
14135 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
14136
14137 if( symbol )
14138 {
14139 terminalNets[{ page, symbol->GetRef( &subgraph->GetSheet(), false ).ToStdString(),
14140 pin->GetNumber().ToStdString() }] = netId;
14141 }
14142 }
14143 }
14144
14145 ++netId;
14146 }
14147
14148 auto findNet = [&]( const std::string& aPage, const std::string& aRef, const std::string& aPin )
14149 {
14150 for( const auto& [terminal, id] : terminalNets )
14151 {
14152 if( std::get<0>( terminal ).find( aPage ) != std::string::npos && std::get<1>( terminal ) == aRef
14153 && std::get<2>( terminal ) == aPin )
14154 {
14155 return id;
14156 }
14157 }
14158
14159 return -1;
14160 };
14161
14162 int clockNet = findNet( "Clock Generator", "J1", "1" );
14163 int fmcNet = findNet( "FMC Connector", "P1", "H38" );
14164 BOOST_REQUIRE_NE( clockNet, -1 );
14165 BOOST_REQUIRE_NE( fmcNet, -1 );
14166 BOOST_CHECK_EQUAL( clockNet, fmcNet );
14167
14168 BOOST_CHECK_EQUAL( findNet( "Clock Generator", "J1", "2" ), findNet( "FMC Connector", "P1", "G37" ) );
14169 BOOST_CHECK_EQUAL( findNet( "Clock Generator", "U12", "7" ), findNet( "MAX II CPLD", "U9", "73" ) );
14170 BOOST_CHECK_EQUAL( findNet( "Current Sense", "U2", "2" ), findNet( "PROM & Misc", "U6", "4" ) );
14171
14172 int mvddUr = findNet( "Power & Control", "TP13", "1" );
14173 int mvddUl = findNet( "Power & Control", "TP30", "1" );
14174 int mvddLr = findNet( "Power & Control", "TP14", "1" );
14175 int mvddLl = findNet( "Power & Control", "TP31", "1" );
14176 BOOST_REQUIRE_NE( mvddUr, -1 );
14177 BOOST_REQUIRE_NE( mvddUl, -1 );
14178 BOOST_REQUIRE_NE( mvddLr, -1 );
14179 BOOST_REQUIRE_NE( mvddLl, -1 );
14180 BOOST_CHECK_NE( mvddUr, mvddUl );
14181 BOOST_CHECK_NE( mvddUr, mvddLr );
14182 BOOST_CHECK_NE( mvddUr, mvddLl );
14183 BOOST_CHECK_NE( mvddUl, mvddLr );
14184 BOOST_CHECK_NE( mvddUl, mvddLl );
14185 BOOST_CHECK_NE( mvddLr, mvddLl );
14186
14187 int urLclk = findNet( "Link Ports NORTH_SOUTH", "U4", "V6" );
14188 int lrLclk = findNet( "Link Ports NORTH_SOUTH", "U8", "A13" );
14189 BOOST_REQUIRE_NE( urLclk, -1 );
14190 BOOST_REQUIRE_NE( lrLclk, -1 );
14191 BOOST_CHECK_EQUAL( urLclk, lrLclk );
14192 BOOST_CHECK_NE( urLclk, findNet( "Link Ports NORTH_SOUTH", "U4", "V7" ) );
14193
14194 int p1c35 = findNet( "FMC Connector", "P1", "C35" );
14195 int p1c37 = findNet( "FMC Connector", "P1", "C37" );
14196 BOOST_REQUIRE_NE( p1c35, -1 );
14197 BOOST_REQUIRE_NE( p1c37, -1 );
14198 BOOST_CHECK_EQUAL( p1c35, p1c37 );
14199 BOOST_CHECK_EQUAL( terminalNetName( *schematic, wxS( "R265" ), wxS( "1" ) ).Lower(),
14200 wxString( wxS( "ll_ul_ns_data_p_0" ) ) );
14201}
14202
14203
14204BOOST_AUTO_TEST_CASE( PowerSymbolPinSharesPartNet )
14205{
14206 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14207
14208 if( !corpusEnv || !*corpusEnv )
14209 {
14210 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping power-symbol connectivity check." );
14211 return;
14212 }
14213
14214 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "POWER_SOURCE_BOARD_20180717.DSN" );
14215
14216 if( dsn.empty() )
14217 {
14218 BOOST_TEST_MESSAGE( "POWER_SOURCE_BOARD_20180717.DSN not present; skipping power-symbol connectivity check." );
14219 return;
14220 }
14221
14222 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14223 SETTINGS_MANAGER manager;
14224 manager.LoadProject( "" );
14225 schematic->SetProject( &manager.Prj() );
14226 schematic->CurrentSheet().clear();
14227 schematic->CurrentSheet().push_back( &schematic->Root() );
14228
14229 SCH_IO_ORCAD plugin;
14230 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14231
14232 std::vector<std::set<std::string>> expected = {
14233 { terminalToken( "R410", "2" ), terminalToken( "R122", "1" ) }
14234 };
14235 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
14236 BOOST_CHECK_EQUAL( checkable, 1 );
14237 BOOST_CHECK_EQUAL( consistent, 1 );
14238}
14239
14240
14241BOOST_AUTO_TEST_CASE( RepeatedHierarchicalBusPinsRemainScoped )
14242{
14243 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14244
14245 if( !corpusEnv || !*corpusEnv )
14246 return;
14247
14248 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "meta_carrier_sch_rev1.dsn" );
14249
14250 if( dsn.empty() )
14251 {
14252 BOOST_TEST_MESSAGE( "meta_carrier_sch_rev1.dsn not present in corpus; skipping repeated bus check." );
14253 return;
14254 }
14255
14256 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14257 SETTINGS_MANAGER manager;
14258 manager.LoadProject( "" );
14259 schematic->SetProject( &manager.Prj() );
14260 schematic->CurrentSheet().clear();
14261 schematic->CurrentSheet().push_back( &schematic->Root() );
14262
14263 SCH_IO_ORCAD plugin;
14264 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14265
14266 std::vector<std::set<std::string>> expected = {
14267 { "J1.1", "J11.1" },
14268 { "J12.239", "J5.239" },
14269 { "J15.239", "J3.239" },
14270 { "J4.1", "J6.1" },
14271 };
14272
14273 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
14274 BOOST_CHECK_EQUAL( checkable, 4 );
14275 BOOST_CHECK_EQUAL( consistent, 4 );
14276 wxString netName = terminalNetName( *schematic, wxS( "C103" ), wxS( "1" ) );
14277 BOOST_CHECK_EQUAL( netName.AfterLast( '/' ), wxString( "LF2_EXT_CAP" ) );
14278 BOOST_CHECK_EQUAL( terminalNetName( *schematic, wxS( "U7" ), wxS( "11" ) ), netName );
14279 const IMPORT_NET_MAP* map = schematic->GetImportNetMap();
14280 BOOST_REQUIRE( map );
14281 std::set<wxString> occurrenceNames;
14282
14283 for( const IMPORT_NET_MAP_ENTRY& entry : map->entries )
14284 {
14285 if( entry.sourceNetId == 9438967 && entry.originalName == wxS( "LF2_EXT_CAP" ) )
14286 {
14288 occurrenceNames.insert( entry.nameAtImport );
14289 }
14290 }
14291
14292 BOOST_CHECK( occurrenceNames.contains( netName ) );
14293 BOOST_CHECK_EQUAL( occurrenceNames.size(), 2u );
14294}
14295
14296
14297BOOST_AUTO_TEST_CASE( NestedHierarchicalBusRangesPreserveConnectivity )
14298{
14299 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14300
14301 if( !corpusEnv || !*corpusEnv )
14302 return;
14303
14304 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "meta_module_sch_rev1.dsn" );
14305
14306 if( dsn.empty() )
14307 {
14308 BOOST_TEST_MESSAGE( "meta_module_sch_rev1.dsn not present in corpus; skipping nested bus check." );
14309 return;
14310 }
14311
14312 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14313 SETTINGS_MANAGER manager;
14314 manager.LoadProject( "" );
14315 schematic->SetProject( &manager.Prj() );
14316 schematic->CurrentSheet().clear();
14317 schematic->CurrentSheet().push_back( &schematic->Root() );
14318
14319 SCH_IO_ORCAD plugin;
14320 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14321
14322 std::vector<std::set<std::string>> expected = {
14323 { terminalToken( "J1", "10" ), terminalToken( "U5", "B15" ) },
14324 { terminalToken( "J1", "100" ), terminalToken( "U7", "D11" ) },
14325 };
14326
14327 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
14328 BOOST_CHECK_EQUAL( checkable, 2 );
14329 BOOST_CHECK_EQUAL( consistent, 2 );
14330 wxString netName = terminalNetName( *schematic, wxS( "R138" ), wxS( "1" ) );
14331 BOOST_CHECK_EQUAL( netName.AfterLast( '/' ), wxString( "WE_WAIT_WR_P0" ) );
14332 BOOST_CHECK_EQUAL( terminalNetName( *schematic, wxS( "U1" ), wxS( "K3" ) ), netName );
14333 const IMPORT_NET_MAP* map = schematic->GetImportNetMap();
14334 BOOST_REQUIRE( map );
14335 std::set<wxString> occurrenceNames;
14336
14337 for( const IMPORT_NET_MAP_ENTRY& entry : map->entries )
14338 {
14339 if( entry.sourceNetId == 7937073 && entry.originalName == wxS( "WE_WAIT_WR_P0" ) )
14340 {
14342 occurrenceNames.insert( entry.nameAtImport );
14343 }
14344 }
14345
14346 BOOST_CHECK( occurrenceNames.contains( netName ) );
14347 BOOST_CHECK_EQUAL( occurrenceNames.size(), 4u );
14348}
14349
14350
14351BOOST_AUTO_TEST_CASE( RenamedHierarchicalBusMembersPreserveConnectivity )
14352{
14353 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14354
14355 if( !corpusEnv || !*corpusEnv )
14356 return;
14357
14358 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "HB1A-AAFM.DSN" );
14359
14360 if( dsn.empty() )
14361 {
14362 BOOST_TEST_MESSAGE( "HB1A-AAFM.DSN not present in corpus; skipping renamed bus check." );
14363 return;
14364 }
14365
14366 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14367 SETTINGS_MANAGER manager;
14368 manager.LoadProject( "" );
14369 schematic->SetProject( &manager.Prj() );
14370 schematic->CurrentSheet().clear();
14371 schematic->CurrentSheet().push_back( &schematic->Root() );
14372
14373 SCH_IO_ORCAD plugin;
14374 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14375
14376 std::vector<std::set<std::string>> expected = {
14377 { terminalToken( "P1", "G36" ), terminalToken( "U9", "36" ) },
14378 { terminalToken( "P1", "H37" ), terminalToken( "U9", "35" ) },
14379 };
14380
14381 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
14382 BOOST_CHECK_EQUAL( checkable, 2 );
14383 BOOST_CHECK_EQUAL( consistent, 2 );
14384}
14385
14386
14387BOOST_AUTO_TEST_CASE( RepeatedMultiPageFoldersKeepLeafNetsScoped )
14388{
14389 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14390
14391 if( !corpusEnv || !*corpusEnv )
14392 return;
14393
14394 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "HB1A-AAFM.DSN" );
14395
14396 if( dsn.empty() )
14397 return;
14398
14399 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14400 SETTINGS_MANAGER manager;
14401 manager.LoadProject( "" );
14402 schematic->SetProject( &manager.Prj() );
14403 schematic->CurrentSheet().clear();
14404 schematic->CurrentSheet().push_back( &schematic->Root() );
14405
14406 SCH_IO_ORCAD plugin;
14407 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14408
14409 auto [consistent, checkable] =
14410 checkConnectivity( *schematic, { { terminalToken( "P1", "C10" ), terminalToken( "U4", "E16" ) },
14411 { terminalToken( "P1", "K10" ), terminalToken( "U8", "E16" ) },
14412 { terminalToken( "P1", "C11" ), terminalToken( "U4", "D16" ) },
14413 { terminalToken( "P1", "K11" ), terminalToken( "U8", "D16" ) } } );
14414 BOOST_CHECK_EQUAL( checkable, 4 );
14415 BOOST_CHECK_EQUAL( consistent, 4 );
14416}
14417
14418
14419BOOST_AUTO_TEST_CASE( DegenerateHierarchicalPinPlacementsUseDefinitionGeometry )
14420{
14421 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14422
14423 if( !corpusEnv || !*corpusEnv )
14424 return;
14425
14426 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "buddy_sch_rev1.dsn" );
14427
14428 if( dsn.empty() )
14429 {
14430 BOOST_TEST_MESSAGE( "buddy_sch_rev1.dsn not present in corpus; skipping block-pin geometry check." );
14431 return;
14432 }
14433
14434 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14435 SETTINGS_MANAGER manager;
14436 manager.LoadProject( "" );
14437 schematic->SetProject( &manager.Prj() );
14438 schematic->CurrentSheet().clear();
14439 schematic->CurrentSheet().push_back( &schematic->Root() );
14440
14441 SCH_IO_ORCAD plugin;
14442 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14443
14444 std::vector<std::set<std::string>> expected = {
14445 { "J1.C10", "U1.J19" }, { "J1.C11", "U1.K19" }, { "J3.C10", "U1.W33" },
14446 { "J3.F28", "U1.J35" }, { "J1.E33", "U1.B38" }, { "J3.E33", "U1.AV40" },
14447 };
14448
14449 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
14450 BOOST_CHECK_EQUAL( checkable, 6 );
14451 BOOST_CHECK_EQUAL( consistent, 6 );
14452 wxString netName = terminalNetName( *schematic, wxS( "J6" ), wxS( "F35" ) );
14453 BOOST_CHECK( !netName.IsEmpty() );
14454 const IMPORT_NET_MAP* map = schematic->GetImportNetMap();
14455 BOOST_REQUIRE( map );
14456 auto mapped = std::find_if( map->entries.begin(), map->entries.end(),
14457 [&]( const IMPORT_NET_MAP_ENTRY& entry )
14458 {
14459 return entry.sourceNetId == 9578237
14460 && entry.originalName == wxS( "CTRL_N3" )
14461 && entry.nameAtImport == netName;
14462 } );
14463 BOOST_REQUIRE( mapped != map->entries.end() );
14465 BOOST_REQUIRE_EQUAL( mapped->occurrence.size(), 3u );
14466 BOOST_CHECK_EQUAL( mapped->occurrence[1], wxString( "10280601" ) );
14467}
14468
14469
14470BOOST_AUTO_TEST_CASE( PlacedUnitsSelectPackagePinMaps )
14471{
14472 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14473
14474 if( !corpusEnv || !*corpusEnv )
14475 {
14476 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping placed-unit check." );
14477 return;
14478 }
14479
14480 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "MC2_REV1_16_2.DSN" );
14481
14482 if( dsn.empty() )
14483 {
14484 BOOST_TEST_MESSAGE( "MC2_REV1_16_2.DSN not present; skipping placed-unit check." );
14485 return;
14486 }
14487
14488 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14489 SETTINGS_MANAGER manager;
14490 manager.LoadProject( "" );
14491 schematic->SetProject( &manager.Prj() );
14492 schematic->CurrentSheet().clear();
14493 schematic->CurrentSheet().push_back( &schematic->Root() );
14494
14495 SCH_IO_ORCAD plugin;
14496 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14497
14498 std::vector<std::set<std::string>> expected = { { terminalToken( "CC2", "5" ), terminalToken( "FL11", "1" ) },
14499 { terminalToken( "CC2", "6" ), terminalToken( "FL11", "2" ) } };
14500 auto [consistent, checkable] = checkConnectivity( *schematic, expected );
14501 BOOST_CHECK_EQUAL( checkable, 2 );
14502 BOOST_CHECK_EQUAL( consistent, 2 );
14503}
14504
14505
14506BOOST_AUTO_TEST_CASE( Dc2693aSmaOnlyDisplaysCenterPinNumber )
14507{
14508 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14509
14510 if( !corpusEnv || !*corpusEnv )
14511 return;
14512
14513 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "710-DC2693A_REV02_PCA_SCHEMATIC.DSN" );
14514
14515 if( dsn.empty() )
14516 return;
14517
14518 SETTINGS_MANAGER manager;
14519 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14520 manager.LoadProject( "" );
14521 schematic->SetProject( &manager.Prj() );
14522 schematic->CurrentSheet().clear();
14523 schematic->CurrentSheet().push_back( &schematic->Root() );
14524
14525 SCH_IO_ORCAD plugin;
14526 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14527
14528 SCH_SYMBOL* j1 = nullptr;
14529
14530 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
14531 {
14532 j1 = findConvertedSymbol( *path.LastScreen(), path, wxS( "J1" ) );
14533
14534 if( j1 )
14535 break;
14536 }
14537
14538 BOOST_REQUIRE( j1 );
14539 BOOST_REQUIRE_EQUAL( j1->GetPins().size(), 5u );
14540
14541 std::vector<wxString> displayedNumbers;
14542
14543 for( const SCH_PIN* pin : j1->GetPins() )
14544 {
14545 if( j1->GetShowPinNumbers() && pin->IsVisible() && pin->GetNumberTextSize() > 0 )
14546 displayedNumbers.push_back( pin->GetNumber() );
14547 }
14548
14549 BOOST_REQUIRE_EQUAL( displayedNumbers.size(), 1u );
14550 BOOST_CHECK_EQUAL( displayedNumbers.front(), wxS( "1" ) );
14551}
14552
14553
14554BOOST_AUTO_TEST_CASE( Cy8cproto040tDisplaysComponentPinNumbers )
14555{
14556 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14557
14558 if( !corpusEnv || !*corpusEnv )
14559 return;
14560
14561 std::filesystem::path dsn = findCorpusDesign(
14562 corpusEnv, "CY8CPROTO-040T_PSoC_4000T_CapSense_Prototyping_Board_Schematic.DSN" );
14563
14564 if( dsn.empty() )
14565 return;
14566
14567 SETTINGS_MANAGER manager;
14568 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14569 manager.LoadProject( "" );
14570 schematic->SetProject( &manager.Prj() );
14571 schematic->CurrentSheet().clear();
14572 schematic->CurrentSheet().push_back( &schematic->Root() );
14573
14574 SCH_IO_ORCAD plugin;
14575 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14576
14577 auto displayedPinNumbers = []( SCH_SYMBOL& aSymbol )
14578 {
14579 std::set<wxString> numbers;
14580
14581 if( aSymbol.GetShowPinNumbers() )
14582 {
14583 for( const SCH_PIN* pin : aSymbol.GetPins() )
14584 {
14585 if( pin->IsVisible() && pin->GetNumberTextSize() > 0 )
14586 numbers.insert( pin->GetNumber() );
14587 }
14588 }
14589
14590 return numbers;
14591 };
14592
14593 SCH_SYMBOL* j1 = nullptr;
14594 SCH_SYMBOL* u1 = nullptr;
14595 SCH_SYMBOL* j11 = nullptr;
14596
14597 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
14598 {
14599 if( !j1 )
14600 j1 = findConvertedSymbol( *path.LastScreen(), path, wxS( "J1" ) );
14601
14602 if( !u1 )
14603 u1 = findConvertedSymbol( *path.LastScreen(), path, wxS( "U1" ) );
14604
14605 if( !j11 )
14606 j11 = findConvertedSymbol( *path.LastScreen(), path, wxS( "J11" ) );
14607 }
14608
14609 BOOST_REQUIRE( j1 );
14610 BOOST_REQUIRE( u1 );
14611 BOOST_REQUIRE( j11 );
14612
14613 BOOST_CHECK( j1->GetShowPinNames() );
14614 BOOST_CHECK( j1->GetShowPinNumbers() );
14615 BOOST_REQUIRE_EQUAL( j1->GetPins().size(), 20u );
14616
14617 std::set<wxString> j1PinText;
14618
14619 for( const SCH_PIN* pin : j1->GetPins() )
14620 {
14621 BOOST_CHECK( !pin->GetName().IsEmpty() );
14622 BOOST_CHECK( !pin->GetNumber().IsEmpty() );
14623 BOOST_CHECK_GT( pin->GetNameTextSize(), 0 );
14624 BOOST_CHECK_GT( pin->GetNumberTextSize(), 0 );
14625 j1PinText.insert( pin->GetName() );
14626 j1PinText.insert( pin->GetNumber() );
14627 }
14628
14629 for( const SCH_ITEM& item : j1->GetLibSymbolRef()->GetDrawItems() )
14630 {
14631 if( item.Type() == SCH_TEXT_T )
14632 BOOST_CHECK( !j1PinText.contains( static_cast<const SCH_TEXT&>( item ).GetText() ) );
14633 }
14634
14635 BOOST_CHECK_EQUAL( displayedPinNumbers( *u1 ).size(), 25u );
14636 BOOST_CHECK_EQUAL( displayedPinNumbers( *j11 ).size(), 10u );
14637 BOOST_CHECK( displayedPinNumbers( *u1 ).contains( wxS( "23" ) ) );
14638 BOOST_CHECK( displayedPinNumbers( *u1 ).contains( wxS( "H" ) ) );
14639 BOOST_CHECK( displayedPinNumbers( *j11 ).contains( wxS( "1" ) ) );
14640 BOOST_CHECK( displayedPinNumbers( *j11 ).contains( wxS( "10" ) ) );
14641}
14642
14643
14644BOOST_AUTO_TEST_CASE( Cy8ckit149EmbeddedBlockDiagramIsComplete )
14645{
14646 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14647
14648 if( !corpusEnv || !*corpusEnv )
14649 return;
14650
14651 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "CY8CKIT-149 Schematic.DSN" );
14652
14653 if( dsn.empty() )
14654 return;
14655
14656 SETTINGS_MANAGER manager;
14657 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14658 manager.LoadProject( "" );
14659 schematic->SetProject( &manager.Prj() );
14660 schematic->CurrentSheet().clear();
14661 schematic->CurrentSheet().push_back( &schematic->Root() );
14662
14663 SCH_IO_ORCAD plugin;
14664 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14665
14666 const wxImage* blockDiagram = nullptr;
14667 size_t redShapesOnBlockDiagram = 0;
14668 size_t elephantNotes = 0;
14669
14670 for( const SCH_SHEET_PATH& path : schematic->BuildSheetListSortedByPageNumbers() )
14671 {
14672 if( path.Last()->GetName().Contains( wxS( "Block Diagram" ) ) )
14673 {
14674 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SHAPE_T ) )
14675 {
14676 const SCH_SHAPE& shape = static_cast<const SCH_SHAPE&>( *item );
14677
14678 if( shape.GetStroke().GetColor() == KIGFX::COLOR4D( 1.0, 0.0, 0.0, 1.0 ) )
14679 ++redShapesOnBlockDiagram;
14680 }
14681 }
14682
14683 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_TEXT_T ) )
14684 {
14685 const SCH_TEXT& text = static_cast<const SCH_TEXT&>( *item );
14686
14687 if( text.GetText() == wxS( "*All Test Points are No Load" ) )
14688 {
14689 BOOST_REQUIRE( text.GetFont() );
14690 BOOST_CHECK_EQUAL( text.GetFont()->GetName(), wxS( "KiCad OrCAD Elephant" ) );
14691 int sourceBottom = path.Last()->GetName().Contains( wxS( "PSoC 4100S" ) ) ? 623 : 613;
14692 BOX2I ink = text.GetEffectiveTextShape( false, BOX2I(), ANGLE_0 )->BBox();
14693 ink.Offset( text.GetSchematicTextOffset( nullptr )
14694 + text.GetOffsetToMatchSCH_FIELD( nullptr ) );
14695 BOOST_CHECK_SMALL( ink.GetY() - OrcadDbuToIu( 0, sourceBottom - 10 ).y,
14696 OrcadDbuToIu( 0, 1 ).y );
14697 BOOST_CHECK_SMALL( ink.GetBottom() - OrcadDbuToIu( 0, sourceBottom ).y,
14698 OrcadDbuToIu( 0, 1 ).y );
14699 ++elephantNotes;
14700 }
14701 }
14702
14703 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_BITMAP_T ) )
14704 {
14705 const wxImage* image = static_cast<SCH_BITMAP*>( item )->GetReferenceImage().GetImage().GetImageData();
14706
14707 if( image && image->IsOk() && image->GetWidth() >= 2500 && image->GetHeight() >= 1000
14708 && ( !blockDiagram
14709 || image->GetWidth() * image->GetHeight()
14710 > blockDiagram->GetWidth() * blockDiagram->GetHeight() ) )
14711 {
14712 blockDiagram = image;
14713 }
14714 }
14715 }
14716
14717 BOOST_REQUIRE_MESSAGE( blockDiagram, "CY8CKIT-149 block diagram was not rendered at full size" );
14718 BOOST_CHECK_EQUAL( redShapesOnBlockDiagram, 0u );
14719 BOOST_CHECK_EQUAL( elephantNotes, 2u );
14720 BOOST_CHECK( schematic->GetEmbeddedFiles()->HasFile( wxS( "KiCadOrCADElephant-Black.ttf" ) ) );
14721 BOOST_CHECK( schematic->GetAreFontsEmbedded() );
14722
14723 std::array<size_t, 3> blueColumns{};
14724 std::array<size_t, 3> blueRows{};
14725 const int width = blockDiagram->GetWidth();
14726 const int height = blockDiagram->GetHeight();
14727
14728 for( int y = 0; y < height; ++y )
14729 {
14730 for( int x = 0; x < width; ++x )
14731 {
14732 int red = blockDiagram->GetRed( x, y );
14733 int green = blockDiagram->GetGreen( x, y );
14734 int blue = blockDiagram->GetBlue( x, y );
14735
14736 if( blue > red + 30 && blue > green + 20 )
14737 {
14738 ++blueColumns[std::min( 2, x * 3 / width )];
14739 ++blueRows[std::min( 2, y * 3 / height )];
14740 }
14741 }
14742 }
14743
14744 const size_t minimumBluePixels = static_cast<size_t>( width ) * height / 500;
14745
14746 BOOST_TEST_MESSAGE( "block diagram size=" << width << 'x' << height << " columns=" << blueColumns[0] << ','
14747 << blueColumns[1] << ',' << blueColumns[2] << " rows="
14748 << blueRows[0] << ',' << blueRows[1] << ',' << blueRows[2] );
14749
14750 for( size_t count : blueColumns )
14751 BOOST_CHECK_GE( count, minimumBluePixels );
14752
14753 for( size_t count : blueRows )
14754 BOOST_CHECK_GE( count, minimumBluePixels );
14755}
14756
14757
14758// CutiePi component fidelity: pin number/name visibility, off-page label orientation, hidden
14759// fields, no-connect markers.
14760BOOST_AUTO_TEST_CASE( ComponentDetailImport )
14761{
14762 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14763
14764 if( !corpusEnv || !*corpusEnv )
14765 {
14766 BOOST_TEST_MESSAGE( "KICAD_ORCAD_CORPUS not set; skipping OrCAD component detail." );
14767 return;
14768 }
14769
14770 namespace fs = std::filesystem;
14771 fs::path dsn = fs::path( corpusEnv ) / "cutiepi-board" / "CutiePi_V2.3-20210409.DSN";
14772
14773 if( !fs::exists( dsn ) )
14774 {
14775 BOOST_TEST_MESSAGE( "CutiePi design not present in corpus; skipping." );
14776 return;
14777 }
14778
14779 std::unique_ptr<SCHEMATIC> schematic( new SCHEMATIC( nullptr ) );
14780 SETTINGS_MANAGER manager;
14781 manager.LoadProject( "" );
14782 schematic->SetProject( &manager.Prj() );
14783 schematic->CurrentSheet().clear();
14784 schematic->CurrentSheet().push_back( &schematic->Root() );
14785
14786 SCH_IO_ORCAD plugin;
14787 plugin.LoadSchematicFile( dsn.string(), schematic.get() );
14788
14789 std::map<wxString, SCH_SYMBOL*> symbols;
14790 std::multimap<wxString, int> labelSpins;
14791 int noConnects = 0;
14792
14793 for( SCH_SHEET* top : schematic->GetTopLevelSheets() )
14794 {
14796 path.push_back( top );
14797
14798 for( SCH_ITEM* item : top->GetScreen()->Items() )
14799 {
14800 if( item->Type() == SCH_SYMBOL_T )
14801 {
14802 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
14803 symbols[sym->GetRef( &path, false )] = sym;
14804 }
14805 else if( item->Type() == SCH_GLOBAL_LABEL_T )
14806 {
14807 SCH_LABEL_BASE* lbl = static_cast<SCH_LABEL_BASE*>( item );
14808 labelSpins.emplace( lbl->GetText(), (int) lbl->GetSpinStyle() );
14809 }
14810 else if( item->Type() == SCH_NO_CONNECT_T )
14811 {
14812 ++noConnects;
14813 }
14814 }
14815 }
14816
14817 // Pin numbers/names show on ICs (flags 0x3), hide on passives (0x6)
14818 BOOST_REQUIRE( symbols.count( wxS( "U3" ) ) );
14819 BOOST_CHECK( symbols[wxS( "U3" )]->GetShowPinNumbers() );
14820 BOOST_CHECK( symbols[wxS( "U3" )]->GetShowPinNames() );
14821 BOOST_REQUIRE( symbols.count( wxS( "R3174" ) ) );
14822 BOOST_CHECK( !symbols[wxS( "R3174" )]->GetShowPinNumbers() );
14823 BOOST_CHECK( !symbols[wxS( "R3174" )]->GetShowPinNames() );
14824
14825 // Ferrite bead value hidden, reference visible
14826 BOOST_REQUIRE( symbols.count( wxS( "FB8" ) ) );
14827 BOOST_CHECK( !symbols[wxS( "FB8" )]->GetField( FIELD_T::VALUE )->IsVisible() );
14828 BOOST_CHECK( symbols[wxS( "FB8" )]->GetField( FIELD_T::REFERENCE )->IsVisible() );
14829
14830 // Display-prop field positions are canvas-space (anchor + offset), not through body-orientation
14831 // transform. FB8 (90-deg ferrite) reference lands right of origin; rotation transform would flip left.
14832 SCH_FIELD* fb8Ref = symbols[wxS( "FB8" )]->GetField( FIELD_T::REFERENCE );
14833 BOOST_CHECK_GT( fb8Ref->GetPosition().x, symbols[wxS( "FB8" )]->GetPosition().x );
14834 BOOST_CHECK( fb8Ref->GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT );
14835
14836 // FB8 stored angle compensates for KiCad re-rotating fields on 90-deg symbol, so text stays horizontal.
14837 BOOST_CHECK( fb8Ref->GetDrawRotation() == ANGLE_HORIZONTAL );
14838
14839 // References render horizontal even on rotated symbols (ferrites, vertical R/C).
14840 for( const wxString& ref : { wxS( "R3186" ), wxS( "C2517" ), wxS( "R3189" ), wxS( "FB13" ), wxS( "FB9" ) } )
14841 {
14842 BOOST_REQUIRE_MESSAGE( symbols.count( ref ), ref );
14843 BOOST_CHECK( symbols[ref]->GetField( FIELD_T::REFERENCE )->GetDrawRotation() == ANGLE_HORIZONTAL );
14844 }
14845
14846 // Value rotation is per-field from source: FB9 part number horizontal, C2517 "47pF" stays vertical.
14847 BOOST_CHECK( symbols[wxS( "FB9" )]->GetField( FIELD_T::VALUE )->GetDrawRotation() == ANGLE_HORIZONTAL );
14848 BOOST_CHECK( symbols[wxS( "C2517" )]->GetField( FIELD_T::VALUE )->GetDrawRotation() == ANGLE_VERTICAL );
14849
14850 // Power net names read horizontal even on rotated power symbols (REG1V8/REG3V3).
14851 bool checkedPower = false;
14852
14853 for( SCH_SHEET* top : schematic->GetTopLevelSheets() )
14854 {
14855 for( SCH_ITEM* item : top->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
14856 {
14857 SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
14858 wxString val = sym->GetField( FIELD_T::VALUE )->GetText();
14859
14860 if( val == wxS( "REG1V8" ) || val == wxS( "REG3V3" ) )
14861 {
14862 BOOST_CHECK( sym->GetField( FIELD_T::VALUE )->GetDrawRotation() == ANGLE_HORIZONTAL );
14863 checkedPower = true;
14864 }
14865 }
14866 }
14867
14868 BOOST_CHECK( checkedPower );
14869
14870 // Unconnected IC pins get no-connect markers (U3 15 NC + U580 NC/ORG)
14871 BOOST_CHECK_GE( noConnects, 17 );
14872
14873 // Off-page connectors on vertical wires point up/down, not left/right.
14874 auto hasSpin = [&]( const wxString& aText, SPIN_STYLE::SPIN aSpin )
14875 {
14876 auto range = labelSpins.equal_range( aText );
14877
14878 for( auto it = range.first; it != range.second; ++it )
14879 {
14880 if( it->second == (int) aSpin )
14881 return true;
14882 }
14883
14884 return false;
14885 };
14886
14887 BOOST_CHECK( hasSpin( wxS( "VOLDN" ), SPIN_STYLE::UP ) );
14888 BOOST_CHECK( hasSpin( wxS( "MUTEP" ), SPIN_STYLE::BOTTOM ) );
14889}
14890
14891
14892BOOST_AUTO_TEST_CASE( HierarchicalBusAliasesConnectWithoutHiddenLabels )
14893{
14894 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14895
14896 if( !corpusEnv || !*corpusEnv )
14897 return;
14898
14899 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "buddy_sch_rev1.dsn" );
14900 BOOST_REQUIRE_MESSAGE( !dsn.empty(), "buddy_sch_rev1.dsn not present in corpus." );
14901
14902 SETTINGS_MANAGER manager;
14903 manager.LoadProject( "" );
14904 SCHEMATIC schematic( &manager.Prj() );
14905 SCH_IO_ORCAD plugin;
14906 plugin.LoadSchematicFile( dsn.string(), &schematic );
14907
14908 std::set<SCH_SCREEN*> visited;
14909
14910 for( const SCH_SHEET_PATH& path : schematic.BuildSheetListSortedByPageNumbers() )
14911 {
14912 SCH_SCREEN* screen = path.LastScreen();
14913
14914 if( !visited.insert( screen ).second )
14915 continue;
14916
14917 std::vector<SCH_ITEM*> hidden;
14918
14919 for( SCH_ITEM* item : screen->Items() )
14920 {
14921 if( item->Type() != SCH_LABEL_T && item->Type() != SCH_GLOBAL_LABEL_T )
14922 continue;
14923
14924 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( item );
14925
14926 if( label->GetTextColor() != KIGFX::COLOR4D::UNSPECIFIED && label->GetTextColor().a == 0 )
14927 hidden.push_back( item );
14928 }
14929
14930 for( SCH_ITEM* item : hidden )
14931 screen->DeleteItem( item );
14932 }
14933
14934 auto [consistent, checkable] =
14935 checkConnectivity( schematic, { { "U1.BB5", "J6.J30" }, { "U1.BA5", "J6.K31" } } );
14936 BOOST_CHECK_EQUAL( checkable, 2 );
14937 BOOST_CHECK_EQUAL( consistent, 2 );
14938}
14939
14940
14941BOOST_AUTO_TEST_CASE( EscapedHierarchicalBusMembersConnectWithoutGlobalHelpers )
14942{
14943 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
14944
14945 if( !corpusEnv || !*corpusEnv )
14946 return;
14947
14948 std::filesystem::path dsn = findCorpusDesign( corpusEnv, "M5275EVB.DSN" );
14949 BOOST_REQUIRE_MESSAGE( !dsn.empty(), "M5275EVB.DSN not present in corpus." );
14950
14951 SETTINGS_MANAGER manager;
14952 manager.LoadProject( "" );
14953 SCHEMATIC schematic( &manager.Prj() );
14954 SCH_IO_ORCAD plugin;
14955 plugin.LoadSchematicFile( dsn.string(), &schematic );
14956
14957 std::set<SCH_SCREEN*> visited;
14958
14959 for( const SCH_SHEET_PATH& path : schematic.BuildSheetListSortedByPageNumbers() )
14960 {
14961 SCH_SCREEN* screen = path.LastScreen();
14962
14963 if( !visited.insert( screen ).second )
14964 continue;
14965
14966 std::vector<SCH_LABEL_BASE*> hidden;
14967
14968 for( SCH_ITEM* item : screen->Items().OfType( SCH_GLOBAL_LABEL_T ) )
14969 {
14970 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( item );
14971
14972 if( label->GetTextColor() != KIGFX::COLOR4D::UNSPECIFIED && label->GetTextColor().a == 0 )
14973 hidden.push_back( label );
14974 }
14975
14976 for( SCH_LABEL_BASE* label : hidden )
14977 {
14978 screen->Append( new SCH_LABEL( label->GetPosition(), label->GetText() ) );
14979 screen->DeleteItem( label );
14980 }
14981 }
14982
14983 const std::vector<std::set<std::string>> expected = {
14984 { "J3.38", "RP49.7", "U6.D10" },
14985 { "J3.40", "RP49.5", "U6.D11" },
14986 { "J3.42", "RP49.3", "U6.D12" },
14987 { "J3.44", "RP49.1", "U6.D13" },
14988 { "J4.43", "RP16.4", "RP45.5", "RP9.8", "TP33.1", "U1.40", "U7.47" },
14989 { "J4.45", "RP16.8", "RP45.3", "RP9.6", "TP35.1", "U1.39", "U7.20" },
14990 { "J4.47", "RP9.4" },
14991 { "J4.49", "RP15.2", "RP9.2", "TP31.1", "U7.24" },
14992 { "J5.21", "R37.2", "RP50.1", "U6.G13" },
14993 { "J5.22", "RP47.3", "U6.E13" },
14994 { "J5.23", "RP50.3", "U6.H16" },
14995 { "J5.24", "RP47.1", "U2.6", "U6.F13" },
14996 { "J5.25", "RP50.5", "U6.H15" },
14997 { "J5.27", "RP50.7", "U6.H14" },
14998 { "J5.29", "J9.11", "RP48.3", "U6.J14" },
14999 { "J5.31", "RP48.5", "U6.J13", "U8.25" },
15000 { "J5.33", "RP48.7", "U6.K13", "U9.25" },
15001 { "J6.15", "RP47.7", "TP4.1", "U11.12", "U12.A8", "U2.1", "U6.R6" },
15002 { "J6.21", "RP47.5", "U1.6", "U2.3", "U6.N7" },
15003 };
15004
15005 auto [consistent, checkable] = checkConnectivity( schematic, expected );
15006 BOOST_CHECK_EQUAL( checkable, expected.size() );
15007 BOOST_CHECK_EQUAL( consistent, expected.size() );
15008}
15009
15010
15011BOOST_AUTO_TEST_CASE( GeneratedRepairNamesAvoidExplicitNetNames )
15012{
15013 for( int mode : { 0, 1, 2 } )
15014 {
15015 const bool collide = mode != 0;
15016 const bool remoteGlobal = mode == 2;
15017
15018 BOOST_TEST_CONTEXT( "collision mode=" << mode )
15019 {
15020 ORCAD_SYMBOL_DEF definition;
15021 definition.typeId = ORCAD_ST_LIBRARY_PART;
15022 definition.name = "LOAD.Normal";
15023 definition.bbox = ORCAD_BBOX{ 0, 0, 10, 10 };
15024 definition.pins.push_back( ORCAD_SYMBOL_PIN{ .name = "1", .position = 0 } );
15025 ORCAD_RAW_PAGE page;
15026 page.name = "REPAIR NAMES";
15027 page.netmap[1] = "N1001";
15028 ORCAD_RAW_PAGE peerPage;
15029 peerPage.name = "OTHER PAGE";
15030
15031 for( int index = 0; index < ( collide ? 4 : 2 ); ++index )
15032 {
15033 const uint32_t net = index < 2 ? 1 : index;
15034 const int x = ( index + 1 ) * 100;
15035 ORCAD_PLACED_INSTANCE instance;
15036 instance.pkgName = definition.name;
15037 instance.sourcePackage = "LOAD";
15038 instance.reference = "R" + std::to_string( index + 1 );
15039 instance.dbId = index + 10;
15040 instance.x = x;
15041 instance.pins.push_back( ORCAD_PIN_INST{ .pinIndex = 1, .x = x, .wordB = net } );
15042 ORCAD_RAW_PAGE& target = remoteGlobal && index >= 2 ? peerPage : page;
15043 target.instances.push_back( std::move( instance ) );
15044
15045 if( index >= 2 )
15046 {
15047 const std::string name = index == 2 ? "Net-(R1-Pad1)" : "Net-(R1-Pad1)_2";
15048 target.netmap[net] = name;
15049 ORCAD_WIRE wire{ .id = net, .x1 = x, .x2 = x + 40 };
15050
15051 if( remoteGlobal )
15052 {
15053 ORCAD_GRAPHIC_INST connector;
15054 connector.logicalName = name;
15055 connector.x = x + 20;
15056 target.offpage.push_back( std::move( connector ) );
15057 }
15058 else
15059 {
15060 target.netAliases[net] = { name };
15061 wire.aliases.push_back( ORCAD_ALIAS{ .name = name, .x = x + 20 } );
15062 }
15063
15064 target.wires.push_back( std::move( wire ) );
15065 }
15066 }
15067
15068 ORCAD_DESIGN design;
15069 design.sourceId = "generated-repair-name-collision";
15070 design.symbols.emplace( definition.name, std::move( definition ) );
15071 design.pages.push_back( std::move( page ) );
15072
15073 if( remoteGlobal )
15074 design.pages.push_back( std::move( peerPage ) );
15075
15076 SETTINGS_MANAGER manager;
15077 manager.LoadProject( "" );
15078 SCHEMATIC schematic( &manager.Prj() );
15079 SCH_SHEET* root = convertRawDesign( design, schematic );
15081 path.push_back( root );
15082 SCH_SYMBOL* first = findConvertedSymbol( *root->GetScreen(), path, wxS( "R1" ) );
15083 SCH_SYMBOL* second = findConvertedSymbol( *root->GetScreen(), path, wxS( "R2" ) );
15084 BOOST_REQUIRE( first );
15085 BOOST_REQUIRE( second );
15086 BOOST_REQUIRE_EQUAL( first->GetPins( &path ).size(), 1u );
15087 BOOST_REQUIRE_EQUAL( second->GetPins( &path ).size(), 1u );
15088 SCH_CONNECTION* repaired = first->GetPins( &path ).front()->Connection( &path );
15089 SCH_CONNECTION* peer = second->GetPins( &path ).front()->Connection( &path );
15090 BOOST_REQUIRE( repaired );
15091 BOOST_REQUIRE( peer );
15092 BOOST_CHECK_EQUAL( repaired->NetCode(), peer->NetCode() );
15093 BOOST_CHECK_EQUAL( repaired->Name( true ),
15094 collide ? wxString( "Net-(R1-Pad1)_3" ) : wxString( "Net-(R1-Pad1)" ) );
15095 BOOST_CHECK( root->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ).empty() );
15096
15097 if( collide )
15098 {
15099 std::set<int> nets = { repaired->NetCode() };
15100 SCH_SHEET_PATH collisionPath = path;
15101
15102 if( remoteGlobal )
15103 {
15104 BOOST_REQUIRE_EQUAL( schematic.GetTopLevelSheets().size(), 2u );
15105 collisionPath.clear();
15106 collisionPath.push_back( schematic.GetTopLevelSheet( 1 ) );
15107 auto globals = collisionPath.LastScreen()->Items().OfType( SCH_GLOBAL_LABEL_T );
15108 BOOST_CHECK_EQUAL( std::distance( globals.begin(), globals.end() ), 2 );
15109 }
15110
15111 for( const wxString& reference : { wxString( "R3" ), wxString( "R4" ) } )
15112 {
15113 SCH_SYMBOL* symbol = findConvertedSymbol( *collisionPath.LastScreen(), collisionPath, reference );
15114 BOOST_REQUIRE( symbol );
15115 BOOST_REQUIRE_EQUAL( symbol->GetPins( &collisionPath ).size(), 1u );
15116 SCH_CONNECTION* connection = symbol->GetPins( &collisionPath ).front()->Connection( &collisionPath );
15117 BOOST_REQUIRE( connection );
15118 nets.insert( connection->NetCode() );
15119 BOOST_CHECK_EQUAL( connection->Name( true ), reference == wxS( "R3" )
15120 ? wxString( "Net-(R1-Pad1)" ) : wxString( "Net-(R1-Pad1)_2" ) );
15121 }
15122
15123 BOOST_CHECK_EQUAL( nets.size(), 3u );
15124 }
15125
15126 BOOST_REQUIRE( schematic.GetImportNetMap() );
15127 bool mapped = false;
15128
15129 for( const IMPORT_NET_MAP_ENTRY& entry : schematic.GetImportNetMap()->entries )
15130 {
15131 if( entry.originalName == wxS( "N1001" ) )
15132 {
15133 mapped = true;
15134 BOOST_CHECK_EQUAL( entry.nameAtImport, repaired->Name() );
15135 }
15136 }
15137
15138 BOOST_CHECK( mapped );
15139 }
15140 }
15141}
15142
15143
15144BOOST_AUTO_TEST_CASE( RepeatedSheetPinNamesDoNotJoinLocalRepairs )
15145{
15146 const char* corpusEnv = std::getenv( "KICAD_ORCAD_CORPUS" );
15147
15148 if( !corpusEnv || !*corpusEnv )
15149 return;
15150
15151 const std::filesystem::path dsn = findCorpusDesign( corpusEnv, "HB1A-AAFM.DSN" );
15152
15153 if( dsn.empty() )
15154 {
15155 BOOST_TEST_MESSAGE( "HB1A-AAFM.DSN not present in corpus; skipping sheet-pin repair check." );
15156 return;
15157 }
15158
15159 SETTINGS_MANAGER manager;
15160 manager.LoadProject( "" );
15161 SCHEMATIC schematic( &manager.Prj() );
15162 SCH_IO_ORCAD plugin;
15163 plugin.LoadSchematicFile( dsn.string(), &schematic );
15164 schematic.ConnectionGraph()->Recalculate( schematic.BuildSheetListSortedByPageNumbers(), true );
15165 std::set<std::set<std::string>> partitions;
15166
15167 for( const auto& [key, subgraphs] : schematic.ConnectionGraph()->GetNetMap() )
15168 {
15169 std::set<std::string> terminals;
15170
15171 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
15172 {
15173 for( SCH_ITEM* item : subgraph->GetItems() )
15174 {
15175 if( item->Type() != SCH_PIN_T )
15176 continue;
15177
15178 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
15179 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
15180 wxString reference = symbol->GetRef( &subgraph->GetSheet(), false );
15181
15182 if( !reference.IsEmpty() && !reference.StartsWith( wxS( "#" ) ) )
15183 {
15184 terminals.insert( terminalToken( reference.ToStdString( wxConvUTF8 ),
15185 pin->GetNumber().ToStdString( wxConvUTF8 ) ) );
15186 }
15187 }
15188 }
15189
15190 partitions.insert( std::move( terminals ) );
15191 }
15192
15193 // These distinct Capture clock nets share interface pin names across repeated ANEMONE instances.
15194 const std::map<std::string, std::set<std::string>> expected = {
15195 { "UL_UL_WW_LCLK_P", { terminalToken( "R278", "1" ), terminalToken( "U3", "F1" ),
15196 terminalToken( "U3", "M1" ) } },
15197 { "UR_UL_WE_LCLK_P", { terminalToken( "R175", "1" ), terminalToken( "U3", "N18" ),
15198 terminalToken( "U4", "F1" ) } }
15199 };
15200
15201 for( const auto& [name, terminals] : expected )
15202 {
15204 {
15205 BOOST_CHECK( partitions.count( terminals ) == 1 );
15206 }
15207 }
15208}
15209
15210
15211BOOST_AUTO_TEST_CASE( WireFreePinJunctionsRetainVisibleLocalConnections )
15212{
15213 const char* corpus = std::getenv( "KICAD_ORCAD_CORPUS" );
15214
15215 if( !corpus || !*corpus )
15216 return;
15217
15218 std::filesystem::path dsn = findCorpusDesign( corpus, "1822A.DSN" );
15219
15220 if( dsn.empty() )
15221 return;
15222
15223 SETTINGS_MANAGER manager;
15224 manager.LoadProject( "" );
15225 SCHEMATIC schematic( &manager.Prj() );
15226 SCH_IO_ORCAD plugin;
15227 BOOST_REQUIRE_NO_THROW( plugin.LoadSchematicFile( dsn.string(), &schematic ) );
15228 bool found = false;
15229
15230 for( const SCH_SHEET_PATH& path : schematic.BuildSheetListSortedByPageNumbers() )
15231 {
15232 SCH_SYMBOL* symbol = findConvertedSymbol( *path.LastScreen(), path, wxS( "COUT1" ) );
15233
15234 if( !symbol )
15235 continue;
15236
15237 for( SCH_PIN* pin : symbol->GetPins( &path ) )
15238 {
15239 if( pin->GetNumber() != wxS( "3" ) )
15240 continue;
15241
15242 found = true;
15243 bool labelled = false;
15244 bool junction = false;
15245
15246 for( SCH_ITEM* item : path.LastScreen()->Items() )
15247 {
15248 if( auto* wire = dynamic_cast<SCH_LINE*>( item ); wire && wire->GetLayer() == LAYER_WIRE )
15249 BOOST_CHECK( !wire->GetSeg().Contains( pin->GetPosition() ) );
15250
15251 if( item->GetPosition() != pin->GetPosition() )
15252 continue;
15253
15254 junction |= item->Type() == SCH_JUNCTION_T;
15255
15256 if( auto* label = dynamic_cast<SCH_LABEL*>( item ) )
15257 {
15258 labelled |= label->GetText() == wxS( "Agnd" );
15259 BOOST_CHECK( label->GetTextColor() == KIGFX::COLOR4D::UNSPECIFIED
15260 || label->GetTextColor().a > 0 );
15261 }
15262 }
15263
15264 BOOST_CHECK( junction );
15265 BOOST_CHECK( labelled );
15266 }
15267 }
15268
15269 BOOST_CHECK( found );
15270
15271 for( const wxString& reference : { wxString( "COUT1" ), wxString( "COUT2" ), wxString( "COUT3" ) } )
15272 BOOST_CHECK_EQUAL( terminalNetName( schematic, reference, wxS( "3" ) ), wxString( "Agnd" ) );
15273}
15274
15275
15276BOOST_AUTO_TEST_CASE( InterfaceAnchorsAvoidPinsAndBusEntries )
15277{
15278 for( int mode = 0; mode < 4; ++mode )
15279 {
15280 const bool port = mode & 1;
15281 const bool entryOnBranch = mode & 2;
15282
15283 BOOST_TEST_CONTEXT( "port=" << port << ", entryOnBranch=" << entryOnBranch )
15284 {
15285 ORCAD_SYMBOL_DEF definition;
15286 definition.typeId = ORCAD_ST_LIBRARY_PART;
15287 definition.name = "LOAD.Normal";
15288 definition.bbox = ORCAD_BBOX{ 0, 0, 10, 10 };
15289 definition.pins.push_back( ORCAD_SYMBOL_PIN{ .name = "1", .position = 0 } );
15290 ORCAD_RAW_PAGE page;
15291 page.name = "LATE PIN";
15292 page.netmap[1] = "SIGNAL";
15293 const int branchEnd = entryOnBranch ? 20 : 5;
15294 page.wires = { ORCAD_WIRE{ .id = 1, .x1 = 100, .x2 = 200 },
15295 ORCAD_WIRE{ .id = 1, .x1 = 100, .x2 = 100, .y2 = branchEnd } };
15296 page.busEntries.push_back( entryOnBranch
15297 ? ORCAD_BUS_ENTRY{ .x1 = 100, .y1 = 5, .x2 = 110, .y2 = -5 }
15298 : ORCAD_BUS_ENTRY{ .x1 = 150, .x2 = 160, .y2 = -10 } );
15299 ORCAD_GRAPHIC_INST connector;
15300 connector.logicalName = "SIGNAL";
15301 connector.x = 100;
15302
15303 if( port )
15304 page.ports.push_back( std::move( connector ) );
15305 else
15306 page.offpage.push_back( std::move( connector ) );
15307
15308 for( int index = 0; index < 2; ++index )
15309 {
15310 ORCAD_PLACED_INSTANCE instance;
15311 instance.pkgName = definition.name;
15312 instance.sourcePackage = "LOAD";
15313 instance.reference = "R" + std::to_string( index + 1 );
15314 instance.x = index ? 200 : 100;
15315 instance.y = index ? 0 : branchEnd;
15316 instance.pins.push_back( ORCAD_PIN_INST{ .pinIndex = 1, .x = instance.x,
15317 .y = instance.y, .wordB = 1 } );
15318 page.instances.push_back( std::move( instance ) );
15319 }
15320
15321 ORCAD_DESIGN design;
15322 design.sourceId = "interface-anchor-late-pin";
15323 design.symbols.emplace( definition.name, std::move( definition ) );
15324 design.pages.push_back( std::move( page ) );
15325 SETTINGS_MANAGER manager;
15326 manager.LoadProject( "" );
15327 SCHEMATIC schematic( &manager.Prj() );
15328 SCH_SHEET* root = convertRawDesign( design, schematic );
15330 path.push_back( root );
15331 SCH_SYMBOL* first = findConvertedSymbol( *root->GetScreen(), path, wxS( "R1" ) );
15332 SCH_SYMBOL* second = findConvertedSymbol( *root->GetScreen(), path, wxS( "R2" ) );
15333 BOOST_REQUIRE( first );
15334 BOOST_REQUIRE( second );
15335 BOOST_REQUIRE_EQUAL( first->GetPins( &path ).size(), 1u );
15336 BOOST_REQUIRE_EQUAL( second->GetPins( &path ).size(), 1u );
15337 SCH_PIN* firstPin = first->GetPins( &path ).front();
15338 SCH_PIN* secondPin = second->GetPins( &path ).front();
15339 SCH_LABEL_BASE* label = nullptr;
15340
15341 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_GLOBAL_LABEL_T ) )
15342 label = static_cast<SCH_LABEL_BASE*>( item );
15343
15344 BOOST_REQUIRE( label );
15345 BOOST_CHECK( label->GetPosition() != firstPin->GetPosition() );
15346 BOOST_CHECK( label->GetPosition() != secondPin->GetPosition() );
15347
15348 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_BUS_WIRE_ENTRY_T ) )
15349 {
15350 for( const VECTOR2I& point : item->GetConnectionPoints() )
15351 BOOST_CHECK( label->GetPosition() != point );
15352 }
15353
15354 size_t contacts = 0;
15355
15356 for( SCH_ITEM* item : root->GetScreen()->Items().OfType( SCH_LINE_T ) )
15357 contacts += static_cast<SCH_LINE*>( item )->GetSeg().Contains( label->GetPosition() );
15358
15359 BOOST_CHECK_EQUAL( contacts, 1u );
15360 BOOST_REQUIRE( label->Connection( &path ) );
15361 BOOST_REQUIRE( firstPin->Connection( &path ) );
15362 BOOST_REQUIRE( secondPin->Connection( &path ) );
15363 BOOST_CHECK_EQUAL( label->Connection( &path )->NetCode(), firstPin->Connection( &path )->NetCode() );
15364 BOOST_CHECK_EQUAL( label->Connection( &path )->NetCode(), secondPin->Connection( &path )->NetCode() );
15365 }
15366 }
15367}
15368
15369
int blue
int red
int green
int index
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
constexpr coord_type GetY() const
Definition box2.h:205
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr Vec Centre() const
Definition box2.h:94
constexpr coord_type GetX() const
Definition box2.h:204
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr const Vec & GetOrigin() const
Definition box2.h:207
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr void Offset(coord_type dx, coord_type dy)
Definition box2.h:256
constexpr coord_type GetBottom() const
Definition box2.h:219
const NET_MAP & GetNetMap() const
void Recalculate(const SCH_SHEET_LIST &aSheetList, bool aUnconditional=false, std::function< void(SCH_ITEM *)> *aChangedItemHandler=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Update the connection graph for the given list of sheets.
A subgraph is a set of items that are electrically connected on a single sheet.
bool IsVertical() const
Definition eda_angle.h:148
virtual VECTOR2I GetPosition() const
Definition eda_item.h:348
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:270
const KIID m_Uuid
Definition eda_item.h:597
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
virtual void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:329
FILL_T GetFillMode() const
Definition eda_shape.h:148
std::vector< VECTOR2I > GetPolyPoints() const
Duplicate the polygon outlines into a flat list of VECTOR2I points.
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:175
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:94
virtual VECTOR2I GetTextSize() const
Definition eda_text.h:301
COLOR4D GetTextColor() const
Definition eda_text.h:310
bool IsItalic() const
Definition eda_text.h:200
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:118
virtual bool IsVisible() const
Definition eda_text.h:226
virtual int GetTextHeight() const
Definition eda_text.h:307
KIFONT::FONT * GetFont() const
Definition eda_text.h:286
virtual EDA_ANGLE GetDrawRotation() const
Definition eda_text.h:419
BOX2I GetTextBox(const RENDER_SETTINGS *aSettings, int aLine=-1) const
Useful in multiline texts to calculate the full text or a line area (for zones filling,...
Definition eda_text.cpp:737
virtual int GetTextWidth() const
Definition eda_text.h:304
GR_TEXT_H_ALIGN_T GetHorizJustify() const
Definition eda_text.h:239
double GetLineSpacing() const
Definition eda_text.h:298
virtual EDA_ANGLE GetTextAngle() const
Definition eda_text.h:178
std::shared_ptr< SHAPE_COMPOUND > GetEffectiveTextShape(bool aTriangulate=true, const BOX2I &aBBox=BOX2I(), const EDA_ANGLE &aAngle=ANGLE_0) const
build a list of segments (SHAPE_SEGMENT) to describe a text shape.
bool IsBold() const
Definition eda_text.h:215
int GetInterline(const RENDER_SETTINGS *aSettings) const
Return the distance between two lines of text.
Definition eda_text.cpp:730
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
virtual void SetReporter(REPORTER *aReporter)
Set an optional reporter for warnings/errors.
Definition io_base.h:89
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
const wxString & GetName() const
Definition font.h:112
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
double a
Alpha component.
Definition color4d.h:393
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:399
Definition kiid.h:46
std::string AsStdString() const
Definition kiid.cpp:270
static KIID FromName(const std::string &aName)
Return a KIID derived from a name, the same name always gives the same KIID.
Definition kiid.cpp:237
Define a library symbol object.
Definition lib_symbol.h:119
LIB_ITEMS_CONTAINER & GetDrawItems()
Return a reference to the draw item list.
Definition lib_symbol.h:832
bool IsGlobalPower() const override
const SCH_PIN * GetPin(const wxString &aNumber, int aUnit=0, int aBodyStyle=0) const
Return pin object with the requested pin aNumber.
SCH_SHEET * Convert(SCH_SHEET *aRootSheet)
aRootSheet must have a screen and be registered on the schematic.
Limit reads to one record so a malformed body cannot consume the next record.
The caller owns the buffer.
size_t GetOffset() const
static constexpr uint8_t PREAMBLE[4]
Magic preceding every framed structure body: FF E4 5C 39.
uint32_t ReadU32()
ReadStructure can recover from a body error when the prefix supplies a valid end offset.
ORCAD_PREFIXES ReadPrefixes(int aExpectedType=-1, size_t aEnclosingEnd=ORCAD_STREAM::npos, size_t aLongPrefixCount=ORCAD_STREAM::npos)
aLongPrefixCount overrides the type depth; aEnclosingEnd bounds all stops.
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
double GetHeightMils() const
Definition page_info.h:143
int GetWidthIU(double aIUScale) const
Gets the page width in IU.
Definition page_info.h:155
double GetWidthMils() const
Definition page_info.h:138
const PAGE_SIZE_TYPE & GetType() const
Definition page_info.h:98
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
Holds all the data relating to one schematic.
Definition schematic.h:148
SCH_SHEET_LIST BuildSheetListSortedByPageNumbers() const
SCH_ITEM * ResolveItem(const KIID &aID, SCH_SHEET_PATH *aPathOut=nullptr, bool aAllowNullptrReturn=false) const
Definition schematic.h:193
SCH_SHEET_LIST Hierarchy() const
Return the full schematic flattened hierarchical sheet list.
SCH_SHEET * GetTopLevelSheet(int aIndex=0) const
const IMPORT_NET_MAP * GetImportNetMap() const
Definition schematic.h:162
void SetProject(PROJECT *aPrj)
CONNECTION_GRAPH * ConnectionGraph() const
Definition schematic.h:317
void SetTopLevelSheets(const std::vector< SCH_SHEET * > &aSheets)
Replace the top level sheets, rebuilding the hierarchy and connectivity around them.
std::vector< SCH_SHEET * > GetTopLevelSheets() const
Get the list of top-level sheets.
SCH_SHEET_PATH & CurrentSheet() const
Definition schematic.h:303
void RefreshHierarchy()
Object to handle a bitmap image that can be inserted in a schematic.
Definition sch_bitmap.h:36
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
wxString Name(bool aIgnoreSheet=false) const
int NetCode() const
GR_TEXT_V_ALIGN_T GetEffectiveVertJustify() const
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
VECTOR2I GetPosition() const override
EDA_ANGLE GetDrawRotation() const override
Adjusters to allow EDA_TEXT to draw/print/etc.
virtual const wxString & GetText() const override
Return the string associated with the text object.
Definition sch_field.h:138
wxString GetShownText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString, int aDepth=0) const
GR_TEXT_H_ALIGN_T GetEffectiveHorizJustify() const
A SCH_IO derivation for loading schematic files using the new s-expression file format.
void SaveSchematicFile(const wxString &aFileName, SCH_SHEET *aSheet, SCHEMATIC *aSchematic, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aSchematic to a storage file in a format that this SCH_IO implementation knows about,...
SCH_SHEET * LoadSchematicFile(const wxString &aFileName, SCHEMATIC *aSchematic, SCH_SHEET *aAppendToMe=nullptr, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load information from some input file format that this SCH_IO implementation knows about,...
bool CanReadSchematicFile(const wxString &aFileName) const override
The .dsn extension also identifies SPECCTRA files.
SCH_SHEET * LoadSchematicFile(const wxString &aFileName, SCHEMATIC *aSchematic, SCH_SHEET *aAppendToMe=nullptr, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load information from some input file format that this SCH_IO implementation knows about,...
bool CanReadLibrary(const wxString &aFileName) const override
Checks if this IO object can read the specified library file/directory.
void EnumerateSymbolLib(wxArrayString &aSymbolNameList, const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Populate a list of LIB_SYMBOL alias names contained within the library aLibraryPath.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
int GetUnit() const
Definition sch_item.h:237
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition sch_item.h:345
SCH_CONNECTION * Connection(const SCH_SHEET_PATH *aSheet=nullptr) const
Retrieve the connection associated with this object in the given sheet.
Definition sch_item.cpp:503
SPIN_STYLE GetSpinStyle() const
LABEL_FLAG_SHAPE GetShape() const
Definition sch_label.h:178
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
VECTOR2I GetEndPoint() const
Definition sch_line.h:145
VECTOR2I GetStartPoint() const
Definition sch_line.h:136
SEG GetSeg() const
Get the geometric aspect of the wire as a SEG.
Definition sch_line.h:155
int GetLineWidth() const
Definition sch_line.h:195
COLOR4D GetLineColor() const
Return COLOR4D::UNSPECIFIED if a custom color hasn't been set for this line.
Definition sch_line.cpp:395
VECTOR2I GetPosition() const override
int GetNumberTextSize() const
Definition sch_pin.cpp:865
const wxString & GetName() const
Definition sch_pin.cpp:503
VECTOR2I GetPosition() const override
Definition sch_pin.cpp:354
int GetNameTextSize() const
Definition sch_pin.cpp:839
const PAGE_INFO & GetPageSettings() const
Definition sch_screen.h:140
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:118
const wxString & GetFileName() const
Definition sch_screen.h:153
const KIID & GetUuid() const
Definition sch_screen.h:540
TITLE_BLOCK & GetTitleBlock()
Definition sch_screen.h:164
void DeleteItem(SCH_ITEM *aItem)
Remove aItem from the linked list and deletes the object.
void SetPosition(const VECTOR2I &aPos) override
Definition sch_shape.h:87
STROKE_PARAMS GetStroke() const override
Definition sch_shape.h:57
VECTOR2I GetPosition() const override
Definition sch_shape.h:86
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
SCH_SCREEN * LastScreen()
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
void clear()
Forwarded method from std::vector.
Define a sheet pin (label) used in sheets to create hierarchical schematics.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this sheet.
wxString GetName() const
Definition sch_sheet.h:142
void SetName(const wxString &aName)
Definition sch_sheet.h:143
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
bool GetExcludedFromBoard(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
Definition sch_sheet.h:475
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition sch_sheet.h:241
Schematic symbol object.
Definition sch_symbol.h:75
bool GetShowPinNumbers() const override
std::vector< std::unique_ptr< SCH_PIN > > & GetRawPins()
Definition sch_symbol.h:692
const std::vector< SCH_SYMBOL_INSTANCE > & GetInstances() const
Definition sch_symbol.h:134
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
bool GetShowPinNames() const override
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly) const override
Populate a std::vector with SCH_FIELDs, sorted in ordinal order.
VECTOR2I GetPosition() const override
Definition sch_symbol.h:934
const LIB_ID & GetLibId() const override
Definition sch_symbol.h:164
const wxString GetValue(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString) const override
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:183
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
VECTOR2I GetPosition() const override
Definition sch_text.h:143
VECTOR2I GetOffsetToMatchSCH_FIELD(SCH_RENDER_SETTINGS *aRenderSettings) const
Definition sch_text.cpp:497
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition sch_text.cpp:338
virtual VECTOR2I GetSchematicTextOffset(const RENDER_SETTINGS *aSettings) const
This offset depends on the orientation, the type of text, and the area required to draw the associate...
Definition sch_text.cpp:117
Definition seg.h:38
bool Contains(const SEG &aSeg) const
Definition seg.h:320
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
SPIN Spin() const
Definition sch_label.h:69
SPIN_STYLE RotateCCW()
int GetWidth() const
KIGFX::COLOR4D GetColor() const
const TRANSFORM & GetTransform() const
Definition symbol.h:243
const wxString & GetRevision() const
Definition title_block.h:83
VECTOR2I TransformCoordinate(const VECTOR2I &aPoint) const
Calculate a new coordinate according to the mirror/rotation transform.
Definition transform.cpp:40
A wrapper for reporting to a wxString object.
Definition reporter.h:242
@ FOR_CANVAS
Definition common.h:88
@ RAW_VALUE
Definition common.h:94
static bool empty(const wxTextEntryBase *aCtrl)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:422
static constexpr EDA_ANGLE ANGLE_VERTICAL
Definition eda_angle.h:419
static constexpr EDA_ANGLE ANGLE_HORIZONTAL
Definition eda_angle.h:418
@ FILLED_WITH_COLOR
Definition eda_fill.h:33
@ NO_FILL
Definition eda_fill.h:30
@ REVERSE_HATCH
Definition eda_fill.h:35
@ HATCH
Definition eda_fill.h:34
@ FILLED_SHAPE
Fill with object color.
Definition eda_fill.h:31
@ CROSS_HATCH
Definition eda_fill.h:36
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ BUS
the source net is carried by a bus
@ RESOLVED
exactly one physical net carries the source net
@ LAYER_DEVICE
Definition layer_ids.h:488
@ LAYER_WIRE
Definition layer_ids.h:474
@ LAYER_BUS
Definition layer_ids.h:475
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
bool OleRenderEmf(const std::vector< uint8_t > &aEmf, int aMaxWidth, int aMaxHeight, wxImage &aImage, double aTargetAspect)
Definition ole_emf.cpp:122
std::vector< uint8_t > OleExtractCiImage(const std::vector< uint8_t > &aPayload)
The CI marker follows the preview DIB; the raster has a counted decimal length.
VECTOR2I OleWmfRenderSize(int aNaturalWidth, int aNaturalHeight, int aMaxWidth, int aMaxHeight, double aTargetAspect)
bool OleRenderWmf(const std::vector< uint8_t > &aWmf, int aMaxWidth, int aMaxHeight, wxImage &aImage, double aTargetAspect)
OLE_IMAGE_PAYLOAD ExtractOleImageFromPayload(const std::vector< uint8_t > &aPayload)
Read the picture out of an OLE object payload that carries the 26-byte prologue.
bool OleRenderMetafilePreview(const std::vector< uint8_t > &aWmf, int aMaxWidth, int aMaxHeight, wxImage &aImage, double aTargetAspect, bool *aUsedEmbeddedEmf)
Render a metafile preview, preferring an EMF the WMF carries over the WMF itself.
std::vector< uint8_t > OleExtractEmbeddedEmf(const std::vector< uint8_t > &aWmf)
Reassemble an EMF carried by WMF META_ESCAPE_ENHANCED_METAFILE records.
wxString OleDescribeImagePayload(const std::vector< uint8_t > &aPayload)
Include leading bytes when the payload format is unknown.
OLE_IMAGE_PAYLOAD ExtractOleImage(const uint8_t *aCfb, size_t aSize)
Prefer CONTENTS, then OlePres000, then the native stream.
void OrcadMergeSymbolGeneralProperties(std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, const std::map< std::string, ORCAD_SYMBOL_DEF > &aMetadataSymbols)
std::optional< ORCAD_SYMBOL_PIN > OrcadReadSymbolPin(ORCAD_STRUCT_READER &aReader)
A zero byte marks an empty pin slot and returns nullopt.
std::optional< ORCAD_PRIMITIVE > OrcadReadPrimitive(ORCAD_STREAM &aStream)
Consumes one primitive and its optional preamble.
void OrcadParseCache(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &aWarn, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages)
The first entry is the default; later entries become variants.
ORCAD_SYMBOL_DEF OrcadReadSymbolDef(ORCAD_STRUCT_READER &aReader, const ORCAD_PREFIXES &aPrefixes, bool aWithPins)
Set aWithPins to read the trailing pin and property lists.
std::map< uint32_t, bool > OrcadCisParseMemberships(const std::vector< char > &aData)
std::map< uint32_t, std::map< std::string, std::string > > OrcadCisParsePropertyUpdates(const std::vector< char > &aData)
std::string OrcadCisSelectVariant(const std::vector< std::string > &aNames, const std::optional< std::string > &aRequested)
ORCAD_CIS_SCHEMATIC_INFO OrcadCisParseSchematicInfo(const std::vector< char > &aData)
std::vector< std::string > OrcadCisParseCountedList(const std::vector< char > &aData, uint8_t aSeparator)
Definition orcad_cis.cpp:92
VECTOR2I OrcadDbuToIu(int aX, int aY)
bool OrcadDisplayPropShowsName(const ORCAD_DISPLAY_PROP &aProp)
FILL_T OrcadFillType(int aFillStyle, int aHatchStyle)
int OrcadLineWidthIu(int aWidth)
std::pair< double, double > OrcadDashRatios(int aFormatVersionMajor)
int OrcadTextBaselineOffset(int aTextSize)
std::vector< SEG > OrcadHatchLines(const EDA_SHAPE &aShape, int aHatchStyle, int aPitch)
int OrcadPageOrder(wxString &aName)
Return a numeric page prefix (or -1); strip only the "N - title" convention.
int OrcadHatchPitchIu(uint32_t aModifyTimestamp)
bool OrcadDisplayPropShowsValue(const ORCAD_DISPLAY_PROP &aProp)
int OrcadHatchLineWidthIu(uint32_t aModifyTimestamp)
KIGFX::COLOR4D OrcadColor(int aColorIndex)
wxString OrcadPinNameMarkup(const wxString &aName)
constexpr int ORCAD_IU_PER_DBU
Schematic internal units per OrCAD DBU: 10 mil * 254 IU/mil.
bool OrcadDisplayPropVisible(const ORCAD_DISPLAY_PROP &aProp)
LINE_STYLE OrcadLineStyle(int aStyle)
int OrcadPageGraphicLineWidthIu(int aWidth)
VECTOR2I OrcadStretchedImageSize(int aWidth, int aHeight, int aBoxWidth, int aBoxHeight)
ORCAD_PAGE_SETTINGS OrcadParsePageSettings(ORCAD_STREAM &aStream)
Consumes 156 bytes; throws IO_ERROR on overrun.
ORCAD_LIBRARY_INFO OrcadParseLibrary(const std::vector< char > &aData)
The Library version selects the string-count width.
std::vector< std::string > OrcadParsePageOrderV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings)
Returns display order; throws IO_ERROR for invalid legacy framing.
void OrcadParseCacheV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &aWarn, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages)
Keep decoded entries if a framing error ends the legacy cache.
void OrcadParseOlbSymbolStreamV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, bool aShortDisplayProp)
aShortDisplayProp selects the version 1 display-property layout.
ORCAD_RAW_PAGE OrcadParsePageV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &, bool aShortDisplayProp)
Legacy records have no stop offsets.
void OrcadParseOlbPackageStreamV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages, bool aShortDisplayProp)
aShortDisplayProp selects the version 1 display-property layout.
std::vector< std::string > OrcadParsePageOrder(const std::vector< char > &aData)
Returns display order.
ORCAD_OCC_SCOPE OrcadReadOccurrenceTree(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &aWarn)
Returns occurrence references and child scopes.
ORCAD_OCC_SCOPE OrcadReadOccurrenceTreeV2(const std::vector< char > &aData, const std::vector< std::string > &aStrings)
Parse a short-prefix-only v2.0 Hierarchy stream without scan recovery.
std::vector< std::string > OrcadParseSchematicFolderOrder(const std::vector< char > &aData)
Unlisted Views storages can be stale.
std::function< void(const wxString &aMsg)> ORCAD_WARN_FN
Coordinates use DBU with Y down.
@ ORCAD_PRIM_COMMENT_TEXT
@ ORCAD_PRIM_LINE
@ ORCAD_PRIM_POLYGON
@ ORCAD_PRIM_RECT
@ ORCAD_PRIM_SYMBOL_VECTOR
nested prefix-framed vector graphic
@ ORCAD_ST_PORT_SYMBOL
@ ORCAD_ST_TITLEBLOCK_SYMBOL
@ ORCAD_ST_GRAPHIC_ARC_INST
@ ORCAD_ST_SYMBOL_PIN_SCALAR
@ ORCAD_ST_PART_CELL
@ ORCAD_ST_DRAWN_INSTANCE
hierarchical block instance
@ ORCAD_ST_GLOBAL
placed power symbol
@ ORCAD_ST_PAGE
@ ORCAD_ST_LIBRARY_PART
@ ORCAD_ST_OFFPAGE_CONNECTOR
@ ORCAD_ST_GLOBAL_SYMBOL
power symbol definition
@ ORCAD_ST_PACKAGE
@ ORCAD_ST_GRAPHIC_COMMENT_TEXT_INST
@ ORCAD_ST_GRAPHIC_ELLIPSE_INST
@ ORCAD_ST_ERC_OBJECT
saved design-rule-check marker
@ ORCAD_ST_SCH_LIB
@ ORCAD_ST_SYMBOL_DISPLAY_PROP
@ ORCAD_ST_GRAPHIC_OLE_INST
ORCAD_BUS_ENTRY OrcadReadBusEntryBody(ORCAD_STREAM &aStream)
std::optional< size_t > OrcadLongPrefixCount(int aTypeId)
Registered number of long prefixes for a modern framed structure type.
@ PT_POWER_IN
power input (GND, VCC for ICs). Must be connected to a power output.
Definition pin_type.h:42
@ PT_PASSIVE
pin for passive symbols: must be connected, and can be connected to any pin.
Definition pin_type.h:39
std::string OrcadNormalizeCfbName(const std::string &aName)
@ L_OUTPUT
Definition sch_label.h:99
@ L_INPUT
Definition sch_label.h:98
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
bool collide(T aObject, U aAnotherObject, int aLayer, int aMinDistance)
Used by SHAPE_INDEX to implement Query().
Definition shape_index.h:93
wxString originalName
std::vector< wxString > occurrence
wxString nameAtImport
std::vector< KIID > itemUuids
std::vector< IMPORT_NET_TERMINAL > terminals
wxString view
uint32_t sourceNetId
IMPORT_NET_STATUS status
Import provenance, held only for the lifetime of the importing SCHEMATIC.
std::vector< IMPORT_NET_MAP_ENTRY > entries
std::vector< uint8_t > data
Definition ole_image.h:48
OLE_IMAGE_TYPE type
Definition ole_image.h:47
The owning wire defines the connection.
std::string name
Axis-aligned box in OrCAD DBU; corner order as stored (not normalized).
One interface pin of a hierarchical block, at its absolute page position.
int y1
int color
int x2
int y2
int x1
Child-folder pages are instantiated for each occurrence, with that occurrence's references.
ORCAD_LIBRARY_INFO library
std::string sourceId
stable checksum of the input file
ORCAD_OCC_SCOPE occurrenceRoot
Occurrence references distinguish repeated placements of a child schematic.
std::map< std::string, ORCAD_SYMBOL_DEF > symbols
cache, keyed by cache name
std::map< std::string, ORCAD_PACKAGE > packages
keyed by package name
std::map< std::string, std::vector< ORCAD_RAW_PAGE > > childFolderPages
Child schematic folder pages, keyed by lower-cased folder name; instantiated once per hierarchical bl...
std::vector< ORCAD_RAW_PAGE > pages
root schematic folder pages
Device unit names omit the view suffix.
Display positions use symbol coordinates; rotFont combines the font index and quarter turns.
int rotation
0..3 quarter turns
std::string name
resolved property name (empty when index invalid)
The inline LibraryPart defines the block interface; placed pin records supply absolute positions.
std::string name
intrinsic Name property used for flat-net scoping
int x1
block rectangle top-left, page DBU
std::vector< ORCAD_BLOCK_PIN > pins
std::vector< ORCAD_DISPLAY_PROP > displayProps
Font indices are one-based; zero selects the default.
Free graphics use nested primitive coordinates.
std::unique_ptr< ORCAD_SYMBOL_DEF > nested
SthInPages0 body, else nullptr.
std::map< std::string, std::string > props
std::string name
cache symbol name
std::vector< ORCAD_DISPLAY_PROP > displayProps
std::string logicalName
ports: resolved net/port name
int typeId
ORCAD_ST value.
std::vector< std::string > strings
global string table
std::vector< ORCAD_FONT > fonts
int pinNumberFont
Design Template font ID (slot 11)
std::vector< int > templateFonts
template font ID -> 1-based LOGFONT index
int pinNameFont
Design Template font ID (slot 10)
Repeated child folders have separate scopes and reference designators.
uint32_t targetDbId
type-12 drawn-instance dbId on the parent page
ORCAD_OCC_SCOPE scope
the child's occurrences under this path
std::string childFolder
child schematic folder name
Each scope holds the references and child blocks for one instantiation path.
std::vector< ORCAD_OCC_BLOCK > blocks
hierarchical block occurrences
std::map< uint32_t, std::map< std::string, std::string > > partProps
dbId -> occurrence properties
std::map< uint32_t, std::string > netNames
occurrence net id -> effective net name
std::map< uint32_t, std::string > partRefs
type-13 dbId -> occurrence refdes
std::string refDes
std::map< std::string, std::string > props
Part-level properties shared by every placement (Description, Tolerance, ...).
std::vector< ORCAD_PACKAGE > variants
later same-name cache entries in stream order
std::string sourceLib
std::vector< ORCAD_DEVICE > devices
std::string pcbFootprint
std::string name
Dimensions use mils when isMetric is zero, otherwise micrometres.
Pin positions are absolute page connection points.
The placed box includes displayed text.
std::string sourcePackage
package base name
std::vector< ORCAD_DISPLAY_PROP > displayProps
int rotation
0..3 quarter turns
uint16_t unitIndex
zero-based package device index
std::map< std::string, std::string > props
short-prefix property pairs
bool mirror
orientation bit 2
std::string sourceLibrary
source library path from the placed-instance header
std::string value
resolved Part Value
std::vector< ORCAD_PIN_INST > pins
successfully parsed T0x10 records
Integer point in OrCAD DBU.
Prefix lengths supply structure bounds.
std::vector< size_t > stops
< (from the outermost long prefix); < 0 when unknown
int typeId
ORCAD_ST value (u8 in the stream)
std::vector< std::pair< uint32_t, uint32_t > > props
short prefix (nameIdx, valueIdx) pairs
std::vector< uint32_t > bodyLens
one per long prefix, outermost first
size_t end
offset right after the whole structure
Primitive byte lengths can include or exclude the eight-byte size envelope.
Wire IDs refer to netmap, which supplies the source net names.
uint32_t verticalWidth
uint32_t horizontalWidth
std::vector< ORCAD_GRAPHIC_INST > ports
size_t sourcePageCount
pages in the OrCAD folder
std::map< uint32_t, std::vector< std::string > > netAliases
every name recorded for a net db id
std::string name
std::vector< ORCAD_GRAPHIC_INST > globals
placed power symbols
std::map< uint32_t, std::string > netmap
net db id -> net name
std::vector< ORCAD_WIRE > wires
size_t sourcePageNumber
1-based within the OrCAD folder
uint32_t width
mils, or um when isMetric
std::vector< ORCAD_NET_GROUP > netGroups
bus net id -> member net ids
std::vector< ORCAD_DRAWN_INSTANCE > blocks
hierarchical blocks (detection only)
std::vector< ORCAD_PLACED_INSTANCE > instances
uint16_t horizontalCount
std::vector< ORCAD_GRAPHIC_INST > graphics
free comment text/shapes/images
std::vector< ORCAD_GRAPHIC_INST > offpage
off-page connectors
std::vector< ORCAD_GRAPHIC_INST > titleBlocks
std::vector< ORCAD_BUS_ENTRY > busEntries
uint16_t verticalCount
std::string pageSize
page-size name string, e.g. "B"
std::vector< ORCAD_GRAPHIC_INST > ercObjects
saved design-rule-check markers
uint32_t modifyTimestamp
SCH_SHEET * LoadOrcadSchematic(const std::string &aRelPath)
std::unique_ptr< SCHEMATIC > m_schematic
std::string dataPath(const std::string &aRelPath) const
The bounding box occupies the final eight bytes before the next prefix stop.
std::string name
cache name, e.g. "C.Normal"
std::vector< ORCAD_SYMBOL_PIN > pins
std::vector< ORCAD_PRIMITIVE > primitives
bool synthesized
placeholder built from T0x10 data
std::map< std::string, std::string > props
int typeId
ORCAD_ST value.
std::string sourceLib
std::optional< ORCAD_BBOX > bbox
symbol-space body box
std::vector< ORCAD_SYMBOL_DEF > variants
Variant zero is this entry.
int generalFlags
LibraryPart GeneralProperties flags (-1 = absent); bit0 = pin names visible, bit1 = pin text rotates ...
Pin coordinates use symbol space with Y down.
The wire ID refers to the page net table.
uint32_t id
uint32_t dbId
std::vector< ORCAD_ALIAS > aliases
A simple container for schematic symbol instance information.
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ DATASHEET
name of datasheet
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
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()
std::string path
IbisParser parser & reporter
KIBIS top(path, &reporter)
KIBIS_PIN * pin
VECTOR3I expected(15, 30, 45)
static std::vector< std::string > splitRefs(const std::string &aCell)
static std::filesystem::path findCorpusDesign(const std::filesystem::path &aRoot, const std::string &aFileName)
static std::set< std::string > parseBomRefs(const std::string &aPath)
static std::vector< std::set< std::string > > parseNetTerminals(const std::string &aPath)
Net terminal sets from .NET.
static std::set< std::string > expectedRefsFor(const std::filesystem::path &aDsn, std::string &aSource)
Ground-truth companion beside .DSN, .NET preferred over .BOM.
static std::string trimCell(std::string aText)
static SCH_SYMBOL * findConvertedSymbol(SCH_SCREEN &aScreen, const SCH_SHEET_PATH &aPath, const wxString &aReference)
static std::pair< int, int > checkConnectivity(SCHEMATIC &aSchematic, const std::vector< std::set< std::string > > &aNets, std::vector< std::set< std::string > > *aInconsistent=nullptr)
Count ground-truth nets whose resolvable terminals all land on one KiCad net after connectivity rebui...
static std::set< std::string > parseNetComs(const std::string &aPath)
static std::string terminalToken(const std::string &aRef, const std::string &aPin)
BOOST_AUTO_TEST_CASE(CisVariantFallbackSortsBomNamesBytewise)
static std::set< std::string > collectImportedRefs(SCHEMATIC &aSchematic)
Unique refdes of real (non-power) parts.
static wxString terminalNetName(SCHEMATIC &aSchematic, const wxString &aReference, const wxString &aPinNumber)
static SCH_SHEET * convertRawDesign(ORCAD_DESIGN &aDesign, SCHEMATIC &aSchematic, REPORTER *aReporter=nullptr)
static std::map< std::string, std::vector< std::string > > collectImportedUuids(const SCHEMATIC &aSchematic)
VECTOR2I end
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
int actual
BOOST_CHECK_EQUAL(result, "25.4")
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
@ SCH_TABLE_T
Definition typeinfo.h:161
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_NO_CONNECT_T
Definition typeinfo.h:156
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_LABEL_T
Definition typeinfo.h:163
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_SHAPE_T
Definition typeinfo.h:145
@ SCH_HIER_LABEL_T
Definition typeinfo.h:165
@ SCH_TEXT_T
Definition typeinfo.h:147
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:157
@ SCH_BITMAP_T
Definition typeinfo.h:160
@ SCH_TEXTBOX_T
Definition typeinfo.h:148
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:164
@ SCH_JUNCTION_T
Definition typeinfo.h:155
@ SCH_PIN_T
Definition typeinfo.h:149
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683