KiCad PCB EDA Suite
Loading...
Searching...
No Matches
orcad_converter_sheet.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 * Based on the dsn2kicad reference implementation and on OrCAD file format
7 * documentation from the OpenOrCadParser project (MIT licensed).
8 *
9 * This program is free software: you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your
12 * option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
23
24
26#include <sch_io/ole_image.h>
27
28#include <algorithm>
29#include <array>
30#include <charconv>
31#include <cctype>
32#include <chrono>
33#include <cmath>
34#include <functional>
35#include <numeric>
36#include <limits>
37#include <map>
38#include <memory>
39#include <optional>
40#include <set>
41#include <string>
42#include <string_view>
43#include <utility>
44#include <vector>
45
46#include <wx/buffer.h>
47#include <wx/filename.h>
48#include <wx/image.h>
49#include <wx/log.h>
50#include <wx/tokenzr.h>
51#include <wx/translation.h>
52
53#include <base_units.h>
54#include <bitmap_base.h>
55#include <connection_graph.h>
56#include <core/kicad_algo.h>
57#include <ki_exception.h>
58#include <kiid.h>
59#include <layer_ids.h>
60#include <lib_symbol.h>
61#include <math/util.h>
62#include <page_info.h>
63#include <progress_reporter.h>
64#include <project.h>
65#include <reference_image.h>
66#include <reporter.h>
67#include <schematic.h>
68#include <sch_bitmap.h>
69#include <sch_bus_entry.h>
70#include <sch_junction.h>
71#include <sch_label.h>
72#include <sch_line.h>
73#include <sch_no_connect.h>
74#include <sch_screen.h>
75#include <sch_shape.h>
76#include <sch_sheet.h>
77#include <sch_sheet_path.h>
78#include <sch_sheet_pin.h>
79#include <sch_symbol.h>
80#include <sch_pin.h>
81#include <import_net_map.h>
82#include <sch_text.h>
83#include <string_utils.h>
84#include <stroke_params.h>
85#include <template_fieldnames.h>
86#include <title_block.h>
87
89
90
91namespace
92{
93
95constexpr double DBU_TO_MM = 0.254;
96
98constexpr int COMMENT_COUNT = 9;
99
100
101std::string trimmed( const std::string& aText )
102{
103 size_t begin = aText.find_first_not_of( " \t\r\n\f\v" );
104
105 if( begin == std::string::npos )
106 return std::string();
107
108 size_t end = aText.find_last_not_of( " \t\r\n\f\v" );
109
110 return aText.substr( begin, end - begin + 1 );
111}
112
113
114std::optional<std::chrono::sys_days> orcadCalendarDay( uint32_t aTimestamp )
115{
116 if( aTimestamp == 0 )
117 return std::nullopt;
118
119 using namespace std::chrono;
120
121 // Use the reference installation's fixed UTC-8 offset for title-block dates.
122 sys_days date = floor<days>( sys_seconds( seconds( aTimestamp ) ) - hours( 8 ) );
123 year_month_day ymd( date );
124
125 if( !ymd.ok() )
126 return std::nullopt;
127
128 return date;
129}
130
131
132std::string orcadCalendarDate( uint32_t aTimestamp )
133{
134 using namespace std::chrono;
135
136 std::optional<sys_days> date = orcadCalendarDay( aTimestamp );
137
138 if( !date )
139 return {};
140
141 year_month_day ymd( *date );
142 weekday day( *date );
143
144 static constexpr std::array<const char*, 7> weekdays = { "Sunday", "Monday", "Tuesday", "Wednesday",
145 "Thursday", "Friday", "Saturday" };
146 static constexpr std::array<const char*, 12> months = { "January", "February", "March", "April",
147 "May", "June", "July", "August",
148 "September", "October", "November", "December" };
149
150 return std::string( weekdays[day.c_encoding()] ) + ", " + months[static_cast<unsigned>( ymd.month() ) - 1]
151 + wxString::Format( wxS( " %02u, %d" ), static_cast<unsigned>( ymd.day() ),
152 static_cast<int>( ymd.year() ) )
153 .ToStdString();
154}
155
156
157std::string orcadShortCalendarDate( uint32_t aTimestamp )
158{
159 using namespace std::chrono;
160
161 std::optional<sys_days> date = orcadCalendarDay( aTimestamp );
162
163 if( !date )
164 return {};
165
166 year_month_day ymd( *date );
167 static constexpr std::array<const char*, 12> months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
168 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
169
170 return months[static_cast<unsigned>( ymd.month() ) - 1]
171 + wxString::Format( wxS( " %02u, %d" ), static_cast<unsigned>( ymd.day() ),
172 static_cast<int>( ymd.year() ) )
173 .ToStdString();
174}
175
176
177std::string kicadBusName( std::string aName )
178{
179 size_t open = 0;
180
181 while( ( open = aName.find( '[', open ) ) != std::string::npos )
182 {
183 size_t close = aName.find( ']', open + 1 );
184
185 if( close == std::string::npos )
186 break;
187
188 size_t colon = aName.find( ':', open + 1 );
189
190 if( colon < close )
191 {
192 aName.replace( colon, 1, ".." );
193 ++close;
194 }
195
196 open = close + 1;
197 }
198
199 return aName;
200}
201
202
203std::string kicadElectricalNetName( std::string aName )
204{
205 aName = kicadBusName( std::move( aName ) );
206
207 if( !aName.empty() && aName.front() == '/' )
208 {
209 size_t lastSlash = aName.find_last_of( '/' );
210
211 if( lastSlash == 0 )
212 aName.replace( 0, 1, "{slash}" );
213 else
214 aName.erase( 0, lastSlash + 1 );
215 }
216
217 return aName;
218}
219
220
221std::string kicadOccurrenceNetName( std::string aName )
222{
223 if( !aName.empty() && aName.front() == '/' )
224 aName.erase( 0, aName.find_last_of( '/' ) + 1 );
225
226 return kicadElectricalNetName( std::move( aName ) );
227}
228
229
230static std::string generatedPinNetName( uint32_t aInstanceId, size_t aPinIndex )
231{
232 std::string instance = std::to_string( aInstanceId );
233
234 if( instance.size() < 5 )
235 instance.insert( 0, 5 - instance.size(), '0' );
236
237 return "N" + instance + std::to_string( aPinIndex );
238}
239
240
241std::optional<uint32_t> occurrenceNetObjectId( const std::string& aName )
242{
243 size_t begin = std::string::npos;
244 size_t separator = aName.find_last_of( '_' );
245
246 if( separator != std::string::npos && separator + 1 < aName.size()
247 && std::isdigit( static_cast<unsigned char>( aName[separator + 1] ) ) )
248 begin = separator + 1;
249 else if( aName.size() > 1 && aName[0] == 'N' )
250 begin = 1;
251
252 if( begin == std::string::npos )
253 return std::nullopt;
254
255 size_t end = begin;
256
257 while( end < aName.size() && std::isdigit( static_cast<unsigned char>( aName[end] ) ) )
258 ++end;
259
260 if( end == begin || ( end != aName.size() && aName[end] != '_' ) )
261 return std::nullopt;
262
263 uint64_t objectId = 0;
264 auto [next, error] = std::from_chars( aName.data() + begin, aName.data() + end, objectId );
265
266 if( error != std::errc() || next != aName.data() + end || objectId == 0
267 || objectId > std::numeric_limits<uint32_t>::max() )
268 {
269 return std::nullopt;
270 }
271
272 return static_cast<uint32_t>( objectId );
273}
274
275
276void pollProgress( PROGRESS_REPORTER* aReporter, const std::string& aPageName )
277{
278 if( !aReporter )
279 return;
280
281 aReporter->Report( wxString::Format( _( "Converting page '%s'..." ), FromOrcadString( aPageName ) ) );
282
283 if( !aReporter->KeepRefreshing() )
285}
286
287
288SCH_SHAPE* makeSheetPoly( const std::vector<VECTOR2I>& aPoints, const ORCAD_PRIMITIVE& aPrimitive,
289 const KIGFX::COLOR4D& aColor, bool aCanFill = false,
290 bool aUseSymbolLineWidths = false )
291{
293
294 for( const VECTOR2I& pt : aPoints )
295 poly->AddPoint( pt );
296
297 int lineWidth = aUseSymbolLineWidths ? OrcadLineWidthIu( aPrimitive.lineWidth )
298 : OrcadPageGraphicLineWidthIu( aPrimitive.lineWidth );
299 poly->SetStroke( STROKE_PARAMS( lineWidth, OrcadLineStyle( aPrimitive.lineStyle ), aColor ) );
300
301 // Page graphics have no symbol body color, so solid fill uses the foreground color.
302 if( aCanFill && aPrimitive.fillStyle == 0 )
304 else
305 poly->SetFillMode( aCanFill ? OrcadFillType( aPrimitive.fillStyle, aPrimitive.hatchStyle ) : FILL_T::NO_FILL );
306
307 return poly;
308}
309
310
311VECTOR2I dbuPointToIu( double aX, double aY )
312{
313 return VECTOR2I( KiROUND( aX * ORCAD_IU_PER_DBU ), KiROUND( aY * ORCAD_IU_PER_DBU ) );
314}
315
316} // namespace
317
318
319VECTOR2I OrcadStretchedImageSize( int aWidth, int aHeight, int aBoxWidth, int aBoxHeight )
320{
321 if( aWidth <= 0 || aHeight <= 0 || aBoxWidth <= 0 || aBoxHeight <= 0 )
322 return VECTOR2I( aWidth, aHeight );
323
324 int64_t imageAspect = static_cast<int64_t>( aWidth ) * aBoxHeight;
325 int64_t boxAspect = static_cast<int64_t>( aBoxWidth ) * aHeight;
326
327 if( imageAspect == boxAspect )
328 return VECTOR2I( aWidth, aHeight );
329
330 auto roundedRatio = []( int64_t aNumerator, int64_t aDenominator )
331 {
332 int64_t value = ( aNumerator + aDenominator / 2 ) / aDenominator;
333 return static_cast<int>( std::clamp<int64_t>( value, 1, std::numeric_limits<int>::max() ) );
334 };
335
336 if( imageAspect < boxAspect )
337 return VECTOR2I( roundedRatio( static_cast<int64_t>( aHeight ) * aBoxWidth, aBoxHeight ), aHeight );
338
339 return VECTOR2I( aWidth, roundedRatio( static_cast<int64_t>( aWidth ) * aBoxHeight, aBoxWidth ) );
340}
341
342
343KIGFX::COLOR4D OrcadColor( int aColorIndex )
344{
345 static constexpr uint8_t palette[][3] = {
346 { 0, 0, 0 }, { 255, 255, 128 }, { 128, 255, 128 }, { 0, 255, 128 }, { 128, 255, 255 }, { 0, 128, 255 },
347 { 255, 128, 192 }, { 255, 128, 255 }, { 255, 0, 0 }, { 255, 255, 0 }, { 128, 255, 0 }, { 0, 255, 64 },
348 { 0, 255, 255 }, { 0, 128, 192 }, { 128, 128, 192 }, { 255, 0, 255 }, { 128, 64, 64 }, { 255, 128, 64 },
349 { 0, 255, 0 }, { 0, 128, 128 }, { 0, 64, 128 }, { 128, 128, 255 }, { 128, 0, 64 }, { 255, 0, 128 },
350 { 128, 0, 0 }, { 255, 128, 0 }, { 0, 128, 0 }, { 0, 128, 64 }, { 0, 0, 255 }, { 0, 0, 160 },
351 { 128, 0, 128 }, { 128, 0, 255 }, { 64, 0, 0 }, { 128, 64, 0 }, { 0, 64, 0 }, { 0, 64, 64 },
352 { 0, 0, 128 }, { 0, 0, 64 }, { 64, 0, 64 }, { 64, 0, 128 }, { 0, 0, 0 }, { 128, 128, 0 },
353 { 128, 128, 64 }, { 128, 128, 128 }, { 64, 128, 128 }, { 192, 192, 192 }, { 64, 0, 64 }, { 255, 255, 255 }
354 };
355
356 if( aColorIndex < 1 || aColorIndex >= static_cast<int>( std::size( palette ) ) )
358
359 return KIGFX::COLOR4D( palette[aColorIndex][0] / 255.0, palette[aColorIndex][1] / 255.0,
360 palette[aColorIndex][2] / 255.0, 1.0 );
361}
362
363
365 PROGRESS_REPORTER* aProgressReporter ) :
366 m_design( aDesign ),
367 m_schematic( aSchematic ),
368 m_reporter( aReporter ),
369 m_progressReporter( aProgressReporter ),
370 m_rootSheet( nullptr ),
371 m_powerCount( 0 ),
373{
374}
375
376
378
379
380KIID ORCAD_CONVERTER::deterministicUuid( const std::string& aRole, size_t aOrdinal ) const
381{
382 return KIID::FromName( "orcad-import:" + m_design.sourceId + ":" + aRole + ":" + std::to_string( aOrdinal ) );
383}
384
385
387{
388 if( aScreen == m_pageItemScreen )
389 m_pageItems.push_back( aItem );
390
391 aScreen->Append( aItem );
392}
393
394
395void ORCAD_CONVERTER::assignPageItemUuids( size_t aPageOrdinal )
396{
397 std::map<KICAD_T, size_t> ordinals;
398
399 for( SCH_ITEM* item : m_pageItems )
400 {
401 if( item->Type() == SCH_SHEET_T )
402 continue;
403
404 size_t ordinal = ordinals[item->Type()]++;
405 std::string role = "page:" + std::to_string( aPageOrdinal )
406 + ":item:" + std::to_string( static_cast<int>( item->Type() ) );
407 const_cast<KIID&>( item->m_Uuid ) = deterministicUuid( role, ordinal );
408
409 if( item->Type() == SCH_SYMBOL_T )
410 {
411 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
412 std::map<std::string, size_t> pinOrdinals;
413
414 for( const std::unique_ptr<SCH_PIN>& ownedPin : symbol->GetRawPins() )
415 {
416 SCH_PIN* pin = ownedPin.get();
417 VECTOR2I position = pin->GetPosition();
418 std::string pinRole = std::string( pin->GetNumber().ToUTF8() ) + ":"
419 + std::string( pin->GetName().ToUTF8() ) + ":" + std::to_string( position.x )
420 + ":" + std::to_string( position.y );
421 size_t pinOrdinal = pinOrdinals[pinRole]++;
422 const_cast<KIID&>( pin->m_Uuid ) =
423 deterministicUuid( role + ":" + std::to_string( ordinal ) + ":pin:" + pinRole, pinOrdinal );
424 }
425
426 std::sort( symbol->GetRawPins().begin(), symbol->GetRawPins().end(),
427 []( const std::unique_ptr<SCH_PIN>& a, const std::unique_ptr<SCH_PIN>& b )
428 {
429 return a->m_Uuid < b->m_Uuid;
430 } );
431 }
432 }
433}
434
435
436void ORCAD_CONVERTER::warn( const wxString& aMsg )
437{
438 if( m_reporter )
439 m_reporter->Report( aMsg, RPT_SEVERITY_WARNING );
440}
441
442
443void ORCAD_CONVERTER::note( const wxString& aMsg )
444{
445 if( m_reporter )
446 m_reporter->Report( aMsg, RPT_SEVERITY_INFO );
447}
448
449
451{
452 auto lowerName = []( std::string aName )
453 {
454 std::transform( aName.begin(), aName.end(), aName.begin(),
455 []( unsigned char c )
456 {
457 return static_cast<char>( std::tolower( c ) );
458 } );
459 return aName;
460 };
461
462 auto addName = [&]( const std::string& aName )
463 {
464 std::string name = trimmed( aName );
465 std::string key = lowerName( name );
466
467 if( !key.empty() )
468 m_globalNetNames.emplace( std::move( key ), std::move( name ) );
469 };
470
471 auto addPage = [&]( const ORCAD_RAW_PAGE& aPage )
472 {
473 for( const ORCAD_GRAPHIC_INST& global : aPage.globals )
474 {
475 auto name = global.props.find( "Name" );
476 std::string displayName = name != global.props.end() ? trimmed( name->second ) : std::string();
477 std::string logicalName = trimmed( global.logicalName );
478 std::string powerName = !displayName.empty() ? displayName : logicalName;
479 addName( powerName );
480
481 powerName = lowerName( powerName );
482
483 if( !powerName.empty() )
484 m_powerNetNames.insert( std::move( powerName ) );
485 }
486
487 for( const ORCAD_GRAPHIC_INST& offpage : aPage.offpage )
488 {
489 addName( offpage.logicalName );
490
491 std::string key = trimmed( offpage.logicalName );
492 std::transform( key.begin(), key.end(), key.begin(),
493 []( unsigned char c )
494 {
495 return static_cast<char>( std::tolower( c ) );
496 } );
497
498 if( !key.empty() )
499 m_offpageNetNames.insert( std::move( key ) );
500 }
501
502 for( const ORCAD_GRAPHIC_INST& port : aPage.ports )
503 addName( port.logicalName.empty() ? port.name : port.logicalName );
504 };
505
506 for( const ORCAD_RAW_PAGE& page : m_design.pages )
507 addPage( page );
508
509 for( const auto& [folder, pages] : m_design.childFolderPages )
510 {
511 for( const ORCAD_RAW_PAGE& page : pages )
512 addPage( page );
513 }
514
515}
516
517
518std::string ORCAD_CONVERTER::canonicalGlobalNetName( const std::string& aName ) const
519{
520 std::string name = trimmed( aName );
521 std::string key = name;
522 std::transform( key.begin(), key.end(), key.begin(),
523 []( unsigned char c )
524 {
525 return static_cast<char>( std::tolower( c ) );
526 } );
527
528 auto alias = m_globalNetAliases.find( key );
529
530 if( alias != m_globalNetAliases.end() )
531 return kicadElectricalNetName( alias->second );
532
533 auto canonical = m_globalNetNames.find( key );
534 return kicadElectricalNetName( canonical != m_globalNetNames.end() ? canonical->second : name );
535}
536
537
538std::string ORCAD_CONVERTER::effectiveInterfaceNetName( const std::string& aName ) const
539{
540 std::string name = canonicalGlobalNetName( aName );
541 std::string key = name;
542 std::transform( key.begin(), key.end(), key.begin(),
543 []( unsigned char c )
544 {
545 return static_cast<char>( std::tolower( c ) );
546 } );
547
548 auto effective = m_currentInterfaceNetAliases.find( key );
549 return effective != m_currentInterfaceNetAliases.end() ? effective->second : name;
550}
551
552
553std::string ORCAD_CONVERTER::occurrenceElectricalNetName( uint32_t aOccurrenceId,
554 const std::string& aName ) const
555{
556 auto lower = []( std::string aValue )
557 {
558 std::transform( aValue.begin(), aValue.end(), aValue.begin(),
559 []( unsigned char c )
560 {
561 return static_cast<char>( std::tolower( c ) );
562 } );
563 return aValue;
564 };
565
566 auto baseName = [&]( const std::string& aValue )
567 {
568 std::string name = kicadOccurrenceNetName( aValue );
569 std::string key = lower( name );
570 auto occurrenceAlias = m_currentOccurrenceNetAliases.find( key );
571
572 if( occurrenceAlias != m_currentOccurrenceNetAliases.end() )
573 return occurrenceAlias->second;
574
575 auto interfaceAlias = m_currentInterfaceNetAliases.find( key );
576 return interfaceAlias != m_currentInterfaceNetAliases.end() ? interfaceAlias->second : name;
577 };
578
579 std::string electricalName = baseName( aName );
580 std::string electricalKey = lower( electricalName );
581 size_t peerCount = 0;
582
584 {
585 peerCount = std::count_if( m_currentOccNetNames->begin(), m_currentOccNetNames->end(),
586 [&]( const auto& aOccurrence )
587 {
588 return lower( baseName( aOccurrence.second ) ) == electricalKey;
589 } );
590 }
591
592 if( peerCount > 1 )
593 {
594 std::string suffix = std::to_string( aOccurrenceId );
595
596 if( suffix.size() < 6 )
597 suffix.insert( 0, 6 - suffix.size(), '0' );
598
599 electricalName += "_" + suffix;
600 }
601
602 return electricalName;
603}
604
605
606bool ORCAD_CONVERTER::isOffpageNetName( const std::string& aName ) const
607{
608 std::string key = trimmed( aName );
609 std::transform( key.begin(), key.end(), key.begin(),
610 []( unsigned char c )
611 {
612 return static_cast<char>( std::tolower( c ) );
613 } );
614 return m_offpageNetNames.count( key );
615}
616
617
618bool ORCAD_CONVERTER::isPowerNetName( const std::string& aName ) const
619{
620 std::string key = trimmed( aName );
621 std::transform( key.begin(), key.end(), key.begin(),
622 []( unsigned char c )
623 {
624 return static_cast<char>( std::tolower( c ) );
625 } );
626 return m_powerNetNames.count( key );
627}
628
629
631int OrcadPageOrder( wxString& aName )
632{
633 size_t digitStart = 0;
634 wxString upper = aName.Upper();
635
636 for( const wxString& prefix : { wxString( "PAGE" ), wxString( "SCH" ), wxString( "PAG" ) } )
637 {
638 if( upper.StartsWith( prefix )
639 && ( aName.length() == prefix.length() || wxIsdigit( aName[prefix.length()] )
640 || wxIsspace( aName[prefix.length()] ) || aName[prefix.length()] == '_' ) )
641 {
642 digitStart = prefix.length();
643
644 while( digitStart < aName.length() && ( wxIsspace( aName[digitStart] ) || aName[digitStart] == '_' ) )
645 {
646 digitStart++;
647 }
648
649 break;
650 }
651 }
652
653 size_t digitEnd = digitStart;
654
655 while( digitEnd < aName.length() && wxIsdigit( aName[digitEnd] ) )
656 digitEnd++;
657
658 long order = 0;
659
660 if( digitEnd == digitStart || !aName.Mid( digitStart, digitEnd - digitStart ).ToLong( &order ) )
661 {
662 return -1;
663 }
664
665 size_t separator = digitEnd;
666
667 while( separator < aName.length() && wxIsspace( aName[separator] ) )
668 separator++;
669
670 bool separatedBySpace = separator > digitEnd;
671
672 if( !separatedBySpace && separator < aName.length() && aName[separator] != '-' && aName[separator] != '.'
673 && aName[separator] != ':' && aName[separator] != '_' )
674 return -1;
675
676 if( separator < aName.length() && aName[separator] == '-' )
677 {
678 wxString rest = aName.Mid( separator + 1 );
679 rest.Trim( false );
680 aName = rest;
681 }
682
683 return static_cast<int>( order );
684}
685
686
687static std::string scopedHierBusName( const std::string& aName, uint32_t aOccurrenceId )
688{
689 size_t range = aName.find( '[' );
690
691 if( range == std::string::npos || aName.find( "..", range ) == std::string::npos )
692 return aName;
693
694 return aName.substr( 0, range ) + "_ORCAD_" + std::to_string( aOccurrenceId ) + aName.substr( range );
695}
696
697
698static bool parseVectorBusName( const std::string& aName, std::string& aPrefix, int& aFirst, int& aLast )
699{
700 size_t open = aName.find( '[' );
701 size_t dots = open == std::string::npos ? std::string::npos : aName.find( "..", open + 1 );
702 size_t separatorSize = 2;
703
704 if( dots == std::string::npos && open != std::string::npos )
705 {
706 dots = aName.find( ':', open + 1 );
707 separatorSize = 1;
708 }
709
710 size_t close = dots == std::string::npos ? std::string::npos : aName.find( ']', dots + separatorSize );
711
712 if( open == std::string::npos || dots == std::string::npos || close != aName.size() - 1 )
713 return false;
714
715 auto parseIndex = []( std::string_view aText, int& aValue )
716 {
717 const char* begin = aText.data();
718 const char* end = begin + aText.size();
719 auto [next, error] = std::from_chars( begin, end, aValue );
720 return error == std::errc() && next == end;
721 };
722
723 if( !parseIndex( std::string_view( aName ).substr( open + 1, dots - open - 1 ), aFirst )
724 || !parseIndex( std::string_view( aName ).substr( dots + separatorSize, close - dots - separatorSize ),
725 aLast ) )
726 {
727 return false;
728 }
729
730 aPrefix = aName.substr( 0, open );
731 return true;
732}
733
734
735static std::string scopedHierBusMember( const std::string& aName,
736 const std::map<std::string, std::string>& aBusNames )
737{
738 for( const auto& [sourceBus, scopedBus] : aBusNames )
739 {
740 std::string sourcePrefix;
741 std::string scopedPrefix;
742 int sourceFirst;
743 int sourceLast;
744 int scopedFirst;
745 int scopedLast;
746
747 if( !parseVectorBusName( sourceBus, sourcePrefix, sourceFirst, sourceLast )
748 || !parseVectorBusName( scopedBus, scopedPrefix, scopedFirst, scopedLast )
749 || aName.compare( 0, sourcePrefix.size(), sourcePrefix ) != 0 )
750 {
751 continue;
752 }
753
754 int member;
755 std::string_view suffix( aName.data() + sourcePrefix.size(), aName.size() - sourcePrefix.size() );
756 auto [next, error] = std::from_chars( suffix.data(), suffix.data() + suffix.size(), member );
757
758 if( suffix.empty() || error != std::errc() || next != suffix.data() + suffix.size() )
759 continue;
760
761 int sourceStep = sourceLast >= sourceFirst ? 1 : -1;
762 int scopedStep = scopedLast >= scopedFirst ? 1 : -1;
763 int64_t ordinal = ( static_cast<int64_t>( member ) - sourceFirst ) * sourceStep;
764 int64_t sourceCount = std::abs( static_cast<int64_t>( sourceLast ) - sourceFirst ) + 1;
765 int64_t scopedCount = std::abs( static_cast<int64_t>( scopedLast ) - scopedFirst ) + 1;
766
767 if( ordinal < 0 || ordinal >= sourceCount || sourceCount != scopedCount )
768 continue;
769
770 return scopedPrefix + std::to_string( scopedFirst + ordinal * scopedStep );
771 }
772
773 return aName;
774}
775
776
777static std::string scopedHierBusRange( const std::string& aName, const std::map<std::string, std::string>& aBusNames )
778{
779 std::string memberPrefix;
780 int memberFirst;
781 int memberLast;
782
783 if( !parseVectorBusName( aName, memberPrefix, memberFirst, memberLast ) )
784 return aName;
785
786 for( const auto& [sourceBus, scopedBus] : aBusNames )
787 {
788 std::string sourcePrefix;
789 std::string scopedPrefix;
790 int sourceFirst;
791 int sourceLast;
792 int scopedFirst;
793 int scopedLast;
794
795 if( !parseVectorBusName( sourceBus, sourcePrefix, sourceFirst, sourceLast )
796 || !parseVectorBusName( scopedBus, scopedPrefix, scopedFirst, scopedLast ) || memberPrefix != sourcePrefix )
797 {
798 continue;
799 }
800
801 int sourceStep = sourceLast >= sourceFirst ? 1 : -1;
802 int scopedStep = scopedLast >= scopedFirst ? 1 : -1;
803 int64_t sourceCount = std::abs( static_cast<int64_t>( sourceLast ) - sourceFirst ) + 1;
804 int64_t scopedCount = std::abs( static_cast<int64_t>( scopedLast ) - scopedFirst ) + 1;
805 int64_t firstOrdinal = ( static_cast<int64_t>( memberFirst ) - sourceFirst ) * sourceStep;
806 int64_t lastOrdinal = ( static_cast<int64_t>( memberLast ) - sourceFirst ) * sourceStep;
807
808 if( sourceCount != scopedCount || firstOrdinal < 0 || firstOrdinal >= sourceCount || lastOrdinal < 0
809 || lastOrdinal >= sourceCount )
810 {
811 continue;
812 }
813
814 int64_t mappedFirst = scopedFirst + firstOrdinal * scopedStep;
815 int64_t mappedLast = scopedFirst + lastOrdinal * scopedStep;
816 return scopedPrefix + "[" + std::to_string( mappedFirst ) + ".." + std::to_string( mappedLast ) + "]";
817 }
818
819 return aName;
820}
821
822
823static bool rawPointOnSegment( int aX, int aY, const ORCAD_WIRE& aWire )
824{
825 int64_t cross = static_cast<int64_t>( aX - aWire.x1 ) * ( aWire.y2 - aWire.y1 )
826 - static_cast<int64_t>( aY - aWire.y1 ) * ( aWire.x2 - aWire.x1 );
827 return cross == 0 && aX >= std::min( aWire.x1, aWire.x2 ) && aX <= std::max( aWire.x1, aWire.x2 )
828 && aY >= std::min( aWire.y1, aWire.y2 ) && aY <= std::max( aWire.y1, aWire.y2 );
829}
830
831
832static std::optional<VECTOR2I> rawWireIntersection( const ORCAD_WIRE& aFirst, const ORCAD_WIRE& aSecond )
833{
834 int64_t firstDx = static_cast<int64_t>( aFirst.x2 ) - aFirst.x1;
835 int64_t firstDy = static_cast<int64_t>( aFirst.y2 ) - aFirst.y1;
836 int64_t secondDx = static_cast<int64_t>( aSecond.x2 ) - aSecond.x1;
837 int64_t secondDy = static_cast<int64_t>( aSecond.y2 ) - aSecond.y1;
838 int64_t deltaX = static_cast<int64_t>( aSecond.x1 ) - aFirst.x1;
839 int64_t deltaY = static_cast<int64_t>( aSecond.y1 ) - aFirst.y1;
840 int64_t denominator = firstDx * secondDy - firstDy * secondDx;
841
842 if( denominator == 0 )
843 return std::nullopt;
844
845 int64_t firstNumerator = deltaX * secondDy - deltaY * secondDx;
846 int64_t secondNumerator = deltaX * firstDy - deltaY * firstDx;
847
848 if( denominator < 0 )
849 {
850 denominator = -denominator;
851 firstNumerator = -firstNumerator;
852 secondNumerator = -secondNumerator;
853 }
854
855 if( firstNumerator < 0 || firstNumerator > denominator || secondNumerator < 0
856 || secondNumerator > denominator )
857 {
858 return std::nullopt;
859 }
860
861 int64_t xNumerator = static_cast<int64_t>( aFirst.x1 ) * denominator + firstDx * firstNumerator;
862 int64_t yNumerator = static_cast<int64_t>( aFirst.y1 ) * denominator + firstDy * firstNumerator;
863
864 if( xNumerator % denominator != 0 || yNumerator % denominator != 0 )
865 return std::nullopt;
866
867 return VECTOR2I( static_cast<int>( xNumerator / denominator ), static_cast<int>( yNumerator / denominator ) );
868}
869
870
871static bool rawBusSegmentsTouch( const ORCAD_WIRE& aFirst, const ORCAD_WIRE& aSecond )
872{
873 return rawPointOnSegment( aFirst.x1, aFirst.y1, aSecond ) || rawPointOnSegment( aFirst.x2, aFirst.y2, aSecond )
874 || rawPointOnSegment( aSecond.x1, aSecond.y1, aFirst )
875 || rawPointOnSegment( aSecond.x2, aSecond.y2, aFirst );
876}
877
878
879static std::set<std::string> busPrefixTokens( const std::string& aPrefix )
880{
881 std::set<std::string> result;
882 size_t start = 0;
883
884 while( start < aPrefix.size() )
885 {
886 while( start < aPrefix.size() && !std::isalnum( static_cast<unsigned char>( aPrefix[start] ) ) )
887 ++start;
888
889 size_t end = start;
890
891 while( end < aPrefix.size() && std::isalnum( static_cast<unsigned char>( aPrefix[end] ) ) )
892 ++end;
893
894 if( end - start > 1 )
895 result.insert( aPrefix.substr( start, end - start ) );
896
897 start = end;
898 }
899
900 return result;
901}
902
903
904static std::string firstBusPrefixToken( const std::string& aPrefix )
905{
906 size_t start = 0;
907
908 while( start < aPrefix.size() && !std::isalnum( static_cast<unsigned char>( aPrefix[start] ) ) )
909 ++start;
910
911 size_t end = start;
912
913 while( end < aPrefix.size() && std::isalnum( static_cast<unsigned char>( aPrefix[end] ) ) )
914 ++end;
915
916 return end - start > 1 ? aPrefix.substr( start, end - start ) : std::string();
917}
918
919
920static std::string connectedBusName( const ORCAD_RAW_PAGE& aPage, const ORCAD_BLOCK_PIN& aPin,
921 const std::string& aFallback )
922{
923 std::string sourcePrefix;
924 int sourceFirst;
925 int sourceLast;
926
927 if( !parseVectorBusName( aFallback, sourcePrefix, sourceFirst, sourceLast ) )
928 return aFallback;
929
930 int sourceCount = std::abs( sourceLast - sourceFirst ) + 1;
931 std::vector<size_t> pending;
932 std::set<size_t> seen;
933
934 for( size_t i = 0; i < aPage.wires.size(); ++i )
935 {
936 if( aPage.wires[i].isBus && rawPointOnSegment( aPin.x, aPin.y, aPage.wires[i] ) )
937 {
938 pending.push_back( i );
939 seen.insert( i );
940 }
941 }
942
943 for( size_t cursor = 0; cursor < pending.size(); ++cursor )
944 {
945 const ORCAD_WIRE& wire = aPage.wires[pending[cursor]];
946
947 for( size_t i = 0; i < aPage.wires.size(); ++i )
948 {
949 if( !seen.count( i ) && aPage.wires[i].isBus && rawBusSegmentsTouch( wire, aPage.wires[i] ) )
950 {
951 seen.insert( i );
952 pending.push_back( i );
953 }
954 }
955 }
956
957 std::set<std::string> names;
958
959 for( size_t wireIndex : pending )
960 {
961 const ORCAD_WIRE& wire = aPage.wires[wireIndex];
962
963 for( const ORCAD_ALIAS& alias : wire.aliases )
964 names.insert( alias.name );
965
966 auto aliases = aPage.netAliases.find( wire.id );
967
968 if( aliases != aPage.netAliases.end() )
969 names.insert( aliases->second.begin(), aliases->second.end() );
970
971 auto net = aPage.netmap.find( wire.id );
972
973 if( net != aPage.netmap.end() )
974 names.insert( net->second );
975 }
976
977 for( const ORCAD_GRAPHIC_INST& port : aPage.ports )
978 {
979 if( std::any_of( pending.begin(), pending.end(),
980 [&]( size_t aWireIndex )
981 {
982 return rawPointOnSegment( port.x, port.y, aPage.wires[aWireIndex] );
983 } ) )
984 {
985 names.insert( port.name );
986 names.insert( port.logicalName );
987 }
988 }
989
990 for( const ORCAD_GRAPHIC_INST& connector : aPage.offpage )
991 {
992 if( std::any_of( pending.begin(), pending.end(),
993 [&]( size_t aWireIndex )
994 {
995 return rawPointOnSegment( connector.x, connector.y, aPage.wires[aWireIndex] );
996 } ) )
997 {
998 names.insert( connector.name );
999 names.insert( connector.logicalName );
1000 }
1001 }
1002
1003 if( pending.empty() )
1004 {
1005 for( const ORCAD_GRAPHIC_INST& port : aPage.ports )
1006 {
1007 names.insert( port.name );
1008 names.insert( port.logicalName );
1009 }
1010
1011 for( const ORCAD_GRAPHIC_INST& connector : aPage.offpage )
1012 {
1013 names.insert( connector.name );
1014 names.insert( connector.logicalName );
1015 }
1016 }
1017
1018 if( names.count( aFallback ) )
1019 return aFallback;
1020
1021 std::set<uint32_t> connectedNetIds;
1022
1023 for( size_t wireIndex : pending )
1024 connectedNetIds.insert( aPage.wires[wireIndex].id );
1025
1026 std::set<std::string> sourceMembers;
1027 int sourceStep = sourceLast >= sourceFirst ? 1 : -1;
1028
1029 for( int member = sourceFirst;; member += sourceStep )
1030 {
1031 sourceMembers.insert( sourcePrefix + std::to_string( member ) );
1032
1033 if( member == sourceLast )
1034 break;
1035 }
1036
1037 for( const ORCAD_NET_GROUP& group : aPage.netGroups )
1038 {
1039 if( !connectedNetIds.count( group.id ) || static_cast<int>( group.members.size() ) != sourceCount )
1040 continue;
1041
1042 std::set<std::string> memberNames;
1043
1044 for( uint32_t memberId : group.members )
1045 {
1046 auto aliases = aPage.netAliases.find( memberId );
1047
1048 if( aliases != aPage.netAliases.end() )
1049 memberNames.insert( aliases->second.begin(), aliases->second.end() );
1050
1051 auto net = aPage.netmap.find( memberId );
1052
1053 if( net != aPage.netmap.end() )
1054 memberNames.insert( net->second );
1055 }
1056
1057 if( std::includes( memberNames.begin(), memberNames.end(), sourceMembers.begin(), sourceMembers.end() ) )
1058 return aFallback;
1059 }
1060
1061 std::set<std::string> candidates;
1062
1063 for( const std::string& name : names )
1064 {
1065 std::string targetPrefix;
1066 int targetFirst;
1067 int targetLast;
1068
1069 if( parseVectorBusName( name, targetPrefix, targetFirst, targetLast )
1070 && std::abs( targetLast - targetFirst ) + 1 == sourceCount )
1071 {
1072 candidates.insert( kicadBusName( name ) );
1073 }
1074 }
1075
1076 std::set<std::string> sourceTokens = busPrefixTokens( sourcePrefix );
1077
1078 for( const std::string& candidate : candidates )
1079 {
1080 std::string candidatePrefix;
1081 int candidateFirst;
1082 int candidateLast;
1083
1084 if( !parseVectorBusName( candidate, candidatePrefix, candidateFirst, candidateLast ) )
1085 continue;
1086
1087 std::set<std::string> candidateTokens = busPrefixTokens( candidatePrefix );
1088 std::vector<std::string> sharedTokens;
1089 std::set_intersection( sourceTokens.begin(), sourceTokens.end(), candidateTokens.begin(), candidateTokens.end(),
1090 std::back_inserter( sharedTokens ) );
1091
1092 // KiCad maps ranges positionally within a bus family. Renaming such a pin
1093 // translates nested ranges twice; only unrelated, unambiguous families need it.
1094 bool leadingNamespaceOnly = sharedTokens.size() == 1 && sourceTokens.size() > 1 && candidateTokens.size() > 1
1095 && sharedTokens.front() == firstBusPrefixToken( sourcePrefix )
1096 && sharedTokens.front() == firstBusPrefixToken( candidatePrefix );
1097
1098 if( !sharedTokens.empty() && !leadingNamespaceOnly )
1099 return aFallback;
1100 }
1101
1102 if( candidates.size() == 1 )
1103 return *candidates.begin();
1104
1105 return aFallback;
1106}
1107
1108
1120
1121
1122
1123
1124static uint32_t busNetAt( const ORCAD_RAW_PAGE& aPage, const ORCAD_BLOCK_PIN& aPin )
1125{
1126 uint32_t endpointNet = 0;
1127 uint32_t throughNet = 0;
1128
1129 for( const ORCAD_WIRE& wire : aPage.wires )
1130 {
1131 int64_t cross = static_cast<int64_t>( aPin.x - wire.x1 ) * ( wire.y2 - wire.y1 )
1132 - static_cast<int64_t>( aPin.y - wire.y1 ) * ( wire.x2 - wire.x1 );
1133 bool onSegment = cross == 0 && aPin.x >= std::min( wire.x1, wire.x2 ) && aPin.x <= std::max( wire.x1, wire.x2 )
1134 && aPin.y >= std::min( wire.y1, wire.y2 ) && aPin.y <= std::max( wire.y1, wire.y2 );
1135
1136 if( !wire.isBus || !onSegment )
1137 continue;
1138
1139 bool endpoint = ( aPin.x == wire.x1 && aPin.y == wire.y1 ) || ( aPin.x == wire.x2 && aPin.y == wire.y2 );
1140 uint32_t& candidate = endpoint ? endpointNet : throughNet;
1141
1142 if( candidate == 0 || wire.id < candidate )
1143 candidate = wire.id;
1144 }
1145
1146 return endpointNet ? endpointNet : throughNet;
1147}
1148
1149
1150static std::string flatNetSuffix( const ORCAD_DRAWN_INSTANCE& aBlock )
1151{
1152 if( !aBlock.name.empty() )
1153 return aBlock.name;
1154
1155 auto name = aBlock.props.find( "Name" );
1156
1157 return name != aBlock.props.end() ? name->second : std::string();
1158}
1159
1160
1162{
1163 m_rootSheet = aRootSheet;
1164
1167
1168 struct SLASH_NAMES
1169 {
1170 bool leading = false;
1171 std::set<std::string> embedded;
1172 };
1173
1174 std::map<std::string, SLASH_NAMES> slashNames;
1175 auto collectSlashName = [&]( const std::string& aName )
1176 {
1177 if( aName.empty() || aName.find( '/' ) == std::string::npos )
1178 return;
1179
1180 std::string key = aName;
1181 std::erase( key, '/' );
1182 std::transform( key.begin(), key.end(), key.begin(),
1183 []( unsigned char c )
1184 {
1185 return static_cast<char>( std::tolower( c ) );
1186 } );
1187
1188 if( aName.front() == '/' )
1189 slashNames[key].leading = true;
1190 else
1191 slashNames[key].embedded.insert( kicadOccurrenceNetName( aName ) );
1192 };
1193 auto collectPageSlashNames = [&]( const ORCAD_RAW_PAGE& aPage )
1194 {
1195 for( const auto& [netId, name] : aPage.netmap )
1196 collectSlashName( name );
1197
1198 for( const auto& [netId, aliases] : aPage.netAliases )
1199 {
1200 for( const std::string& alias : aliases )
1201 collectSlashName( alias );
1202 }
1203 };
1204
1205 for( const ORCAD_RAW_PAGE& page : m_design.pages )
1206 collectPageSlashNames( page );
1207
1208 for( const auto& [folder, pages] : m_design.childFolderPages )
1209 {
1210 for( const ORCAD_RAW_PAGE& page : pages )
1211 collectPageSlashNames( page );
1212 }
1213
1214 std::map<std::string, std::string> baseOccurrenceNetAliases;
1215
1216 for( const auto& [key, names] : slashNames )
1217 {
1218 if( names.leading && names.embedded.size() == 1 )
1219 baseOccurrenceNetAliases[key] = *names.embedded.begin();
1220 }
1221
1222 std::map<const std::map<uint32_t, std::string>*, std::map<std::string, std::string>> occurrenceAliasesByScope;
1223
1224 auto selectOccurrenceAliases = [&]( const std::map<uint32_t, std::string>* aScope )
1225 {
1226 m_currentOccurrenceNetAliases = baseOccurrenceNetAliases;
1227 auto aliases = occurrenceAliasesByScope.find( aScope );
1228
1229 if( aliases != occurrenceAliasesByScope.end() )
1230 m_currentOccurrenceNetAliases.insert( aliases->second.begin(), aliases->second.end() );
1231 };
1232
1235 std::function<void( const ORCAD_OCC_SCOPE&, size_t )> countOccurrenceNetNameScopes =
1236 [&]( const ORCAD_OCC_SCOPE& aScope, size_t aDepth )
1237 {
1238 std::set<std::string> scopeNames;
1239
1240 for( const auto& occurrence : aScope.netNames )
1241 {
1242 const std::string& name = occurrence.second;
1243 std::string key = kicadOccurrenceNetName( name );
1244 std::transform( key.begin(), key.end(), key.begin(),
1245 []( unsigned char c )
1246 {
1247 return static_cast<char>( std::tolower( c ) );
1248 } );
1249 scopeNames.insert( key );
1250
1251 auto depth = m_occurrenceNetNameMinDepth.find( key );
1252
1253 if( depth == m_occurrenceNetNameMinDepth.end() || aDepth < depth->second )
1254 m_occurrenceNetNameMinDepth[key] = aDepth;
1255 }
1256
1257 for( const std::string& name : scopeNames )
1259
1260 for( const ORCAD_OCC_BLOCK& block : aScope.blocks )
1261 countOccurrenceNetNameScopes( block.scope, aDepth + 1 );
1262 };
1263 countOccurrenceNetNameScopes( m_design.occurrenceRoot, 0 );
1264
1265 SCH_SCREEN* rootScreen = aRootSheet->GetScreen();
1266
1267 SCH_SHEET_PATH rootPath;
1268 rootPath.push_back( aRootSheet );
1269 rootPath.SetPageNumber( wxS( "1" ) );
1270
1271 std::map<std::string, size_t> occurrenceFolderCounts;
1272 std::function<void( const ORCAD_OCC_SCOPE& )> countOccurrenceFolders = [&]( const ORCAD_OCC_SCOPE& aScope )
1273 {
1274 for( const ORCAD_OCC_BLOCK& block : aScope.blocks )
1275 {
1276 std::string key = block.childFolder;
1277 std::transform( key.begin(), key.end(), key.begin(),
1278 []( unsigned char c )
1279 {
1280 return static_cast<char>( std::tolower( c ) );
1281 } );
1282 ++occurrenceFolderCounts[key];
1283 countOccurrenceFolders( block.scope );
1284 }
1285 };
1286 countOccurrenceFolders( m_design.occurrenceRoot );
1287
1288 std::set<std::string> rootChildFolders;
1289 bool simpleRepeatedLeafDesign = !m_design.occurrenceRoot.blocks.empty();
1290
1291 for( const ORCAD_OCC_BLOCK& block : m_design.occurrenceRoot.blocks )
1292 {
1293 std::string key = block.childFolder;
1294 std::transform( key.begin(), key.end(), key.begin(),
1295 []( unsigned char c )
1296 {
1297 return static_cast<char>( std::tolower( c ) );
1298 } );
1299 rootChildFolders.insert( key );
1300 auto pages = m_design.childFolderPages.find( key );
1301
1302 if( pages == m_design.childFolderPages.end()
1303 || !std::all_of( pages->second.begin(), pages->second.end(),
1304 []( const ORCAD_RAW_PAGE& aPage )
1305 {
1306 return aPage.blocks.empty();
1307 } ) )
1308 {
1309 simpleRepeatedLeafDesign = false;
1310 }
1311 }
1312
1313 simpleRepeatedLeafDesign &= rootChildFolders.size() == 1 && m_design.occurrenceRoot.blocks.size() > 1;
1314
1316 auto collectConnectedBlockInterfaceNames = [&]( const ORCAD_RAW_PAGE& aPage )
1317 {
1318 for( const ORCAD_DRAWN_INSTANCE& block : aPage.blocks )
1319 {
1320 for( const ORCAD_BLOCK_PIN& pin : block.pins )
1321 {
1322 if( pin.noConnect || pin.name.empty() )
1323 continue;
1324
1325 std::string pinName = canonicalGlobalNetName( pin.name );
1326 std::transform( pinName.begin(), pinName.end(), pinName.begin(),
1327 []( unsigned char c )
1328 {
1329 return static_cast<char>( std::tolower( c ) );
1330 } );
1331
1332 for( const ORCAD_WIRE& wire : aPage.wires )
1333 {
1334 if( wire.isBus || !rawPointOnSegment( pin.x, pin.y, wire ) )
1335 continue;
1336
1337 auto netName = aPage.netmap.find( wire.id );
1338
1339 if( netName == aPage.netmap.end() )
1340 continue;
1341
1342 std::string wireName = canonicalGlobalNetName( netName->second );
1343 std::transform( wireName.begin(), wireName.end(), wireName.begin(),
1344 []( unsigned char c )
1345 {
1346 return static_cast<char>( std::tolower( c ) );
1347 } );
1348
1349 if( wireName == pinName )
1350 m_connectedBlockInterfaceNames.insert( pinName );
1351 }
1352 }
1353 }
1354 };
1355
1356 for( const ORCAD_RAW_PAGE& page : m_design.pages )
1357 collectConnectedBlockInterfaceNames( page );
1358
1359 for( const auto& [folder, pages] : m_design.childFolderPages )
1360 {
1361 for( const ORCAD_RAW_PAGE& page : pages )
1362 collectConnectedBlockInterfaceNames( page );
1363 }
1364
1365 auto unconnectedInterfaceNetNames = [&]( const ORCAD_DRAWN_INSTANCE& aDrawn, const std::string& aFlatNetSuffix )
1366 {
1367 std::map<std::string, std::string> result;
1368
1369 for( const ORCAD_BLOCK_PIN& pin : aDrawn.pins )
1370 {
1371 if( !pin.noConnect || pin.name.empty() || aFlatNetSuffix.empty() )
1372 continue;
1373
1374 std::string localName = canonicalGlobalNetName( pin.name );
1375 std::string localKey = localName;
1376 std::transform( localKey.begin(), localKey.end(), localKey.begin(),
1377 []( unsigned char c )
1378 {
1379 return static_cast<char>( std::tolower( c ) );
1380 } );
1381
1382 if( !m_connectedBlockInterfaceNames.count( localKey ) )
1383 continue;
1384
1385 result.emplace( std::move( localKey ), localName + "_" + aFlatNetSuffix );
1386 }
1387
1388 return result;
1389 };
1390
1391 std::function<bool( const ORCAD_RAW_PAGE&, const ORCAD_OCC_SCOPE& )> canBuildHierarchy =
1392 [&]( const ORCAD_RAW_PAGE& aPage, const ORCAD_OCC_SCOPE& aScope )
1393 {
1394 if( aPage.blocks.size() != aScope.blocks.size() )
1395 return false;
1396
1397 std::set<uint32_t> matchedBlocks;
1398
1399 for( const ORCAD_OCC_BLOCK& occurrence : aScope.blocks )
1400 {
1401 auto drawn = std::find_if( aPage.blocks.begin(), aPage.blocks.end(),
1402 [&]( const ORCAD_DRAWN_INSTANCE& aBlock )
1403 {
1404 return aBlock.dbId == occurrence.targetDbId;
1405 } );
1406
1407 std::string key = occurrence.childFolder;
1408 std::transform( key.begin(), key.end(), key.begin(),
1409 []( unsigned char c )
1410 {
1411 return static_cast<char>( std::tolower( c ) );
1412 } );
1413
1414 auto pages = m_design.childFolderPages.find( key );
1415
1416 if( !matchedBlocks.insert( occurrence.targetDbId ).second || drawn == aPage.blocks.end()
1417 || pages == m_design.childFolderPages.end() || pages->second.size() != 1
1418 || !canBuildHierarchy( pages->second.front(), occurrence.scope ) )
1419 {
1420 return false;
1421 }
1422 }
1423
1424 return true;
1425 };
1426
1427 bool nativeHierarchy = m_design.pages.size() == 1 && !m_design.occurrenceRoot.blocks.empty()
1428 && canBuildHierarchy( m_design.pages.front(), m_design.occurrenceRoot );
1429
1430 if( nativeHierarchy )
1431 {
1432 ORCAD_RAW_PAGE& rootPage = m_design.pages.front();
1433
1434 pollProgress( m_progressReporter, rootPage.name );
1435 m_currentOccRefs = &m_design.occurrenceRoot.partRefs;
1436 m_currentOccUnitRefs = &m_design.occurrenceRoot.partUnitRefs;
1437 m_currentOccProps = &m_design.occurrenceRoot.partProps;
1438 m_currentOccNetNames = &m_design.occurrenceRoot.netNames;
1439 selectOccurrenceAliases( m_currentOccNetNames );
1440 m_currentFlatNetSuffix.clear();
1441 m_scopeNamedFlatNets = false;
1444 applyPageSettings( rootPage, rootScreen );
1445 convertPage( rootPage, rootScreen, rootPath );
1446
1447 int pageIndex = 1;
1448
1449 std::function<void( ORCAD_RAW_PAGE&, const ORCAD_OCC_SCOPE&, SCH_SHEET*, const SCH_SHEET_PATH& )>
1450 placeChildren = [&]( ORCAD_RAW_PAGE& aParentPage, const ORCAD_OCC_SCOPE& aScope,
1451 SCH_SHEET* aParentSheet, const SCH_SHEET_PATH& aParentPath )
1452 {
1453 std::vector<const ORCAD_OCC_BLOCK*> occurrences;
1454
1455 for( const ORCAD_OCC_BLOCK& occurrence : aScope.blocks )
1456 occurrences.push_back( &occurrence );
1457
1458 std::stable_sort( occurrences.begin(), occurrences.end(),
1459 []( const ORCAD_OCC_BLOCK* a, const ORCAD_OCC_BLOCK* b )
1460 {
1461 wxString aName = FromOrcadString( a->childFolder );
1462 wxString bName = FromOrcadString( b->childFolder );
1463 int aOrder = OrcadPageOrder( aName );
1464 int bOrder = OrcadPageOrder( bName );
1465 int aKey = aOrder >= 0 ? aOrder : std::numeric_limits<int>::max();
1466 int bKey = bOrder >= 0 ? bOrder : std::numeric_limits<int>::max();
1467
1468 if( aKey != bKey )
1469 return aKey < bKey;
1470
1471 return aName.CmpNoCase( bName ) < 0;
1472 } );
1473
1474 for( const ORCAD_OCC_BLOCK* occurrencePtr : occurrences )
1475 {
1476 const ORCAD_OCC_BLOCK& occurrence = *occurrencePtr;
1477 auto drawn = std::find_if( aParentPage.blocks.begin(), aParentPage.blocks.end(),
1478 [&]( const ORCAD_DRAWN_INSTANCE& aBlock )
1479 {
1480 return aBlock.dbId == occurrence.targetDbId;
1481 } );
1482
1483 std::string key = occurrence.childFolder;
1484 std::transform( key.begin(), key.end(), key.begin(),
1485 []( unsigned char c )
1486 {
1487 return static_cast<char>( std::tolower( c ) );
1488 } );
1489
1490 ORCAD_RAW_PAGE& childPage = m_design.childFolderPages.at( key ).front();
1491 SCH_SCREEN* childScreen = new SCH_SCREEN( m_schematic );
1492 const_cast<KIID&>( childScreen->GetUuid() ) = deterministicUuid( "screen", m_screenOrdinal++ );
1493 SCH_SHEET* childSheet = new SCH_SHEET( aParentSheet, OrcadDbuToIu( drawn->x1, drawn->y1 ),
1494 OrcadDbuToIu( drawn->w, drawn->h ) );
1495 wxString sheetName = FromOrcadString( drawn->reference );
1496
1497 if( sheetName.IsEmpty() )
1498 sheetName = FromOrcadString( childPage.name );
1499
1500 wxString base = sheetName;
1501
1502 for( int suffix = 2; !m_usedSheetNames.insert( sheetName.Lower() ).second; ++suffix )
1503 sheetName = wxString::Format( wxS( "%s (%d)" ), base, suffix );
1504
1505 wxString fileName = MakePageFileName( ++pageIndex, childPage.name );
1506 const_cast<KIID&>( childSheet->m_Uuid ) = deterministicUuid( "sheet", pageIndex );
1507 childSheet->GetField( FIELD_T::SHEET_NAME )->SetText( sheetName );
1508 childSheet->GetField( FIELD_T::SHEET_FILENAME )->SetText( fileName );
1509 placeHierarchicalBlockFields( childSheet, *drawn, occurrence.childFolder );
1510 childSheet->SetScreen( childScreen );
1511 childScreen->SetFileName( m_schematic->Project().GetProjectPath() + fileName );
1512
1513 auto implementation = drawn->props.find( "Implementation" );
1514
1515 if( implementation != drawn->props.end() && !implementation->second.empty()
1516 && FromOrcadString( implementation->second )
1517 .CmpNoCase( FromOrcadString( occurrence.childFolder ) )
1518 != 0 )
1519 {
1520 childSheet->SetExcludedFromBoard( true );
1521 }
1522
1523 size_t pinOrdinal = 0;
1524
1525 for( const ORCAD_BLOCK_PIN& sourcePin : drawn->pins )
1526 {
1527 VECTOR2I position = OrcadDbuToIu( sourcePin.x, sourcePin.y );
1528 std::string sourceName = canonicalGlobalNetName( sourcePin.name );
1529 uint32_t busNetId = busNetAt( aParentPage, sourcePin );
1530 std::string pinName = busNetId ? connectedBusName( aParentPage, sourcePin, sourceName )
1531 : scopedHierBusName( sourceName, occurrence.targetDbId );
1532
1533 if( busNetId && pinName != sourceName )
1534 {
1535 auto parentBusNames =
1536 m_hierBusNamesByScreen.find( aParentSheet->GetScreen()->GetUuid().AsStdString() );
1537
1538 if( parentBusNames != m_hierBusNamesByScreen.end() )
1539 pinName = scopedHierBusRange( pinName, parentBusNames->second );
1540 }
1541
1542 if( pinName != sourceName )
1543 m_hierBusNamesByScreen[childScreen->GetUuid().AsStdString()][sourceName] = pinName;
1544
1545 SCH_SHEET_PIN* pin = new SCH_SHEET_PIN( childSheet, position, FromOrcadString( pinName ) );
1546 const_cast<KIID&>( pin->m_Uuid ) =
1547 deterministicUuid( "sheet-pin:" + childScreen->GetUuid().AsStdString(), pinOrdinal++ );
1548 std::array<std::pair<int, SHEET_SIDE>, 4> sides = {
1549 std::pair{ std::abs( sourcePin.x - drawn->x1 ), SHEET_SIDE::LEFT },
1550 std::pair{ std::abs( sourcePin.x - drawn->x1 - drawn->w ), SHEET_SIDE::RIGHT },
1551 std::pair{ std::abs( sourcePin.y - drawn->y1 ), SHEET_SIDE::TOP },
1552 std::pair{ std::abs( sourcePin.y - drawn->y1 - drawn->h ), SHEET_SIDE::BOTTOM }
1553 };
1554
1555 pin->SetSide( std::min_element( sides.begin(), sides.end(),
1556 []( const auto& a, const auto& b )
1557 {
1558 return a.first < b.first;
1559 } )
1560 ->second );
1561 pin->SetPosition( position );
1562
1563 pin->SetShape( hierarchicalPinShape( sourcePin.portType ) );
1564
1565 childSheet->AddPin( pin );
1566 placeHierarchicalBlockPinFill( aParentSheet->GetScreen(), pin );
1567 }
1568
1569 aParentSheet->GetScreen()->Append( childSheet );
1570
1571 SCH_SHEET_PATH childPath = aParentPath;
1572 childPath.push_back( childSheet );
1573 childPath.SetPageNumber( wxString::Format( wxS( "%d" ), pageIndex ) );
1574
1575 std::map<std::string, std::string> childInterfaceAliases;
1576
1577 for( const ORCAD_BLOCK_PIN& sourcePin : drawn->pins )
1578 {
1579 std::set<std::string> sourceNames;
1580 std::set<uint32_t> wireObjectIds;
1581 std::string pinName = canonicalGlobalNetName( sourcePin.name );
1582 std::string pinKey = pinName;
1583 std::transform( pinKey.begin(), pinKey.end(), pinKey.begin(),
1584 []( unsigned char c )
1585 {
1586 return static_cast<char>( std::tolower( c ) );
1587 } );
1588 sourceNames.insert( pinKey );
1589
1590 for( const ORCAD_WIRE& wire : aParentPage.wires )
1591 {
1592 if( wire.isBus || !rawPointOnSegment( sourcePin.x, sourcePin.y, wire ) )
1593 continue;
1594
1595 wireObjectIds.insert( wire.dbId );
1596 auto pageName = aParentPage.netmap.find( wire.id );
1597
1598 if( pageName != aParentPage.netmap.end() && !pageName->second.empty() )
1599 {
1600 std::string name = canonicalGlobalNetName( pageName->second );
1601 std::transform( name.begin(), name.end(), name.begin(),
1602 []( unsigned char c )
1603 {
1604 return static_cast<char>( std::tolower( c ) );
1605 } );
1606 sourceNames.insert( std::move( name ) );
1607 }
1608
1609 auto aliases = aParentPage.netAliases.find( wire.id );
1610
1611 if( aliases != aParentPage.netAliases.end() )
1612 {
1613 for( const std::string& alias : aliases->second )
1614 {
1615 std::string name = canonicalGlobalNetName( alias );
1616 std::transform( name.begin(), name.end(), name.begin(),
1617 []( unsigned char c )
1618 {
1619 return static_cast<char>( std::tolower( c ) );
1620 } );
1621 sourceNames.insert( std::move( name ) );
1622 }
1623 }
1624 }
1625
1626 std::set<const std::string*> targets;
1627
1628 for( const auto& [occurrenceId, occurrenceName] : aScope.netNames )
1629 {
1630 std::string occurrenceKey = canonicalGlobalNetName( occurrenceName );
1631 std::transform( occurrenceKey.begin(), occurrenceKey.end(), occurrenceKey.begin(),
1632 []( unsigned char c )
1633 {
1634 return static_cast<char>( std::tolower( c ) );
1635 } );
1636 bool matches = sourceNames.count( occurrenceKey );
1637
1638 if( std::optional<uint32_t> objectId = occurrenceNetObjectId( occurrenceName ) )
1639 matches = matches || wireObjectIds.count( *objectId );
1640
1641 if( matches )
1642 targets.insert( &occurrenceName );
1643 }
1644
1645 if( targets.size() == 1 )
1646 childInterfaceAliases[pinKey] = canonicalGlobalNetName( **targets.begin() );
1647 }
1648
1649 pollProgress( m_progressReporter, childPage.name );
1650 m_currentOccRefs = &occurrence.scope.partRefs;
1652 m_currentOccProps = &occurrence.scope.partProps;
1653 m_currentOccNetNames = &occurrence.scope.netNames;
1654 selectOccurrenceAliases( m_currentOccNetNames );
1655 m_currentFlatNetSuffix = simpleRepeatedLeafDesign ? flatNetSuffix( *drawn ) : std::string();
1656 m_scopeNamedFlatNets = simpleRepeatedLeafDesign;
1657 m_scopeGeneratedFlatNets = occurrenceFolderCounts[key] > 1;
1658 m_currentUnconnectedInterfaceNetNames = unconnectedInterfaceNetNames( *drawn, m_currentFlatNetSuffix );
1659 m_currentInterfaceNetAliases = std::move( childInterfaceAliases );
1661 applyPageSettings( childPage, childScreen );
1662 convertPage( childPage, childScreen, childPath, true, drawn->pins.empty() );
1663 placeChildren( childPage, occurrence.scope, childSheet, childPath );
1664 }
1665 };
1666
1667 placeChildren( rootPage, m_design.occurrenceRoot, aRootSheet, rootPath );
1668 m_currentOccRefs = nullptr;
1669 m_currentOccUnitRefs = nullptr;
1670 m_currentOccProps = nullptr;
1671 m_currentOccNetNames = nullptr;
1672 m_currentFlatNetSuffix.clear();
1673 m_scopeNamedFlatNets = false;
1677 return aRootSheet;
1678 }
1679
1680 std::function<bool( const std::vector<ORCAD_RAW_PAGE>&, const ORCAD_OCC_SCOPE& )> canBuildFolderHierarchy =
1681 [&]( const std::vector<ORCAD_RAW_PAGE>& aPages, const ORCAD_OCC_SCOPE& aScope )
1682 {
1683 size_t blockCount = 0;
1684
1685 for( const ORCAD_RAW_PAGE& page : aPages )
1686 blockCount += page.blocks.size();
1687
1688 if( blockCount != aScope.blocks.size() )
1689 return false;
1690
1691 std::set<uint32_t> matchedBlocks;
1692
1693 for( const ORCAD_OCC_BLOCK& occurrence : aScope.blocks )
1694 {
1695 size_t matches = 0;
1696
1697 for( const ORCAD_RAW_PAGE& page : aPages )
1698 {
1699 matches += std::count_if( page.blocks.begin(), page.blocks.end(),
1700 [&]( const ORCAD_DRAWN_INSTANCE& aBlock )
1701 {
1702 return aBlock.dbId == occurrence.targetDbId;
1703 } );
1704 }
1705
1706 std::string key = occurrence.childFolder;
1707 std::transform( key.begin(), key.end(), key.begin(),
1708 []( unsigned char c )
1709 {
1710 return static_cast<char>( std::tolower( c ) );
1711 } );
1712
1713 auto pages = m_design.childFolderPages.find( key );
1714
1715 if( matches != 1 || !matchedBlocks.insert( occurrence.targetDbId ).second
1716 || pages == m_design.childFolderPages.end() || pages->second.empty()
1717 || !canBuildFolderHierarchy( pages->second, occurrence.scope ) )
1718 {
1719 return false;
1720 }
1721 }
1722
1723 return true;
1724 };
1725
1726 bool folderHierarchy = !m_design.occurrenceRoot.blocks.empty()
1727 && canBuildFolderHierarchy( m_design.pages, m_design.occurrenceRoot );
1728
1729 struct POWER_ALIAS_EVIDENCE
1730 {
1731 size_t placements = 0;
1732 std::map<std::string, std::pair<std::string, size_t>> targets;
1733 };
1734
1735 auto lowerPowerName = []( std::string aName )
1736 {
1737 std::transform( aName.begin(), aName.end(), aName.begin(),
1738 []( unsigned char c )
1739 {
1740 return static_cast<char>( std::tolower( c ) );
1741 } );
1742 return aName;
1743 };
1744
1745 auto recordPowerAlias = [&]( std::map<std::string, POWER_ALIAS_EVIDENCE>& aCandidates,
1746 const std::string& aSourceName, const std::string& aElectricalName )
1747 {
1748 if( aSourceName.empty() || aElectricalName.empty() )
1749 return;
1750
1751 POWER_ALIAS_EVIDENCE& evidence = aCandidates[lowerPowerName( aSourceName )];
1752 ++evidence.placements;
1753
1754 if( !isPowerNetName( aElectricalName )
1755 || wxString::FromUTF8( aSourceName ).CmpNoCase( wxString::FromUTF8( aElectricalName ) ) == 0 )
1756 {
1757 return;
1758 }
1759
1760 auto& target = evidence.targets[lowerPowerName( aElectricalName )];
1761 target.first = aElectricalName;
1762 ++target.second;
1763 };
1764
1765 auto acceptPowerAliases = [&]( const std::map<std::string, POWER_ALIAS_EVIDENCE>& aCandidates )
1766 {
1767 for( const auto& [sourceName, evidence] : aCandidates )
1768 {
1769 if( evidence.targets.size() != 1 )
1770 continue;
1771
1772 const auto& target = evidence.targets.begin()->second;
1773
1774 if( target.second * 2 > evidence.placements )
1775 m_globalNetAliases[sourceName] = target.first;
1776 }
1777 };
1778
1779 if( folderHierarchy )
1780 {
1781 int pageIndex = 1;
1782 size_t sourcePageIndex = 0;
1783
1784 std::function<size_t( const std::vector<ORCAD_RAW_PAGE>&, const ORCAD_OCC_SCOPE& )> countSourcePages =
1785 [&]( const std::vector<ORCAD_RAW_PAGE>& aPages, const ORCAD_OCC_SCOPE& aScope )
1786 {
1787 size_t count = aPages.size();
1788
1789 for( const ORCAD_OCC_BLOCK& occurrence : aScope.blocks )
1790 {
1791 std::string key = occurrence.childFolder;
1792 std::transform( key.begin(), key.end(), key.begin(),
1793 []( unsigned char c )
1794 {
1795 return static_cast<char>( std::tolower( c ) );
1796 } );
1797
1798 if( auto pages = m_design.childFolderPages.find( key ); pages != m_design.childFolderPages.end() )
1799 count += countSourcePages( pages->second, occurrence.scope );
1800 }
1801
1802 return count;
1803 };
1804
1805 size_t sourcePageCount = countSourcePages( m_design.pages, m_design.occurrenceRoot );
1806 auto numberSourcePage = [&]( ORCAD_RAW_PAGE& aPage )
1807 {
1808 aPage.sourcePageNumber = ++sourcePageIndex;
1809 aPage.sourcePageCount = sourcePageCount;
1810 };
1811
1812 auto uniqueSheetName = [&]( const std::string& aName )
1813 {
1814 wxString name = FromOrcadString( aName );
1815
1816 if( name.IsEmpty() )
1817 name = wxS( "PAGE" );
1818
1819 wxString base = name;
1820
1821 for( int suffix = 2; !m_usedSheetNames.insert( name.Lower() ).second; ++suffix )
1822 name = wxString::Format( wxS( "%s (%d)" ), base, suffix );
1823
1824 return name;
1825 };
1826
1827 auto interfaceNames = [&]( const ORCAD_RAW_PAGE& aPage )
1828 {
1829 std::vector<std::string> names;
1830 std::set<std::string> seen;
1831
1832 auto collect = [&]( const std::vector<ORCAD_GRAPHIC_INST>& aConnectors )
1833 {
1834 for( const ORCAD_GRAPHIC_INST& connector : aConnectors )
1835 {
1836 std::string name = canonicalGlobalNetName( connector.logicalName );
1837
1838 if( name.empty() )
1839 name = canonicalGlobalNetName( connector.name );
1840
1841 std::string key = name;
1842 std::transform( key.begin(), key.end(), key.begin(),
1843 []( unsigned char c )
1844 {
1845 return static_cast<char>( std::tolower( c ) );
1846 } );
1847
1848 if( !name.empty() && seen.insert( key ).second )
1849 names.push_back( std::move( name ) );
1850 }
1851 };
1852
1853 collect( aPage.ports );
1854 collect( aPage.offpage );
1855 return names;
1856 };
1857
1858 auto addContainerLabel =
1859 [&]( SCH_SCREEN* aScreen, const wxString& aName, const VECTOR2I& aPosition, bool aHierarchical )
1860 {
1861 SCH_LABEL_BASE* label = aHierarchical
1862 ? static_cast<SCH_LABEL_BASE*>( new SCH_HIERLABEL( aPosition, aName ) )
1863 : static_cast<SCH_LABEL_BASE*>( new SCH_LABEL( aPosition, aName ) );
1865 appendPageItem( aScreen, label );
1866 };
1867
1868 auto addChildOccurrenceAliases = [&]( const ORCAD_RAW_PAGE& aParentPage, const ORCAD_OCC_SCOPE& aParentScope,
1869 const ORCAD_DRAWN_INSTANCE& aDrawn, const ORCAD_OCC_SCOPE& aChildScope )
1870 {
1871 auto lower = []( std::string aName )
1872 {
1873 std::transform( aName.begin(), aName.end(), aName.begin(),
1874 []( unsigned char c )
1875 {
1876 return static_cast<char>( std::tolower( c ) );
1877 } );
1878 return aName;
1879 };
1880 auto generated = []( const std::string& aName )
1881 {
1882 return aName.size() > 1 && aName.front() == 'N'
1883 && std::all_of( aName.begin() + 1, aName.end(),
1884 []( unsigned char c )
1885 {
1886 return std::isdigit( c );
1887 } );
1888 };
1889
1890 auto& childAliases = occurrenceAliasesByScope[&aChildScope.netNames];
1891
1892 for( const ORCAD_BLOCK_PIN& pin : aDrawn.pins )
1893 {
1894 std::set<std::string> sourceNames = { lower( canonicalGlobalNetName( pin.name ) ) };
1895 std::set<uint32_t> wireObjectIds;
1896
1897 for( const ORCAD_WIRE& wire : aParentPage.wires )
1898 {
1899 if( wire.isBus || !rawPointOnSegment( pin.x, pin.y, wire ) )
1900 continue;
1901
1902 wireObjectIds.insert( wire.dbId );
1903 auto pageName = aParentPage.netmap.find( wire.id );
1904
1905 if( pageName != aParentPage.netmap.end() && !pageName->second.empty() )
1906 sourceNames.insert( lower( canonicalGlobalNetName( pageName->second ) ) );
1907
1908 auto aliases = aParentPage.netAliases.find( wire.id );
1909
1910 if( aliases != aParentPage.netAliases.end() )
1911 {
1912 for( const std::string& alias : aliases->second )
1913 sourceNames.insert( lower( canonicalGlobalNetName( alias ) ) );
1914 }
1915 }
1916
1917 std::set<const std::string*> targets;
1918
1919 for( const auto& [occurrenceId, occurrenceName] : aParentScope.netNames )
1920 {
1921 bool matches = sourceNames.count( lower( canonicalGlobalNetName( occurrenceName ) ) );
1922
1923 if( std::optional<uint32_t> objectId = occurrenceNetObjectId( occurrenceName ) )
1924 matches = matches || wireObjectIds.count( *objectId );
1925
1926 if( matches && !generated( occurrenceName ) )
1927 targets.insert( &occurrenceName );
1928 }
1929
1930 if( targets.size() == 1 )
1931 {
1932 std::string sourceName = lower( kicadOccurrenceNetName( pin.name ) );
1933 std::string targetName = kicadOccurrenceNetName( **targets.begin() );
1934
1935 if( sourceName != lower( targetName ) )
1936 childAliases[std::move( sourceName )] = std::move( targetName );
1937 }
1938 }
1939 };
1940
1941 std::function<void( std::vector<ORCAD_RAW_PAGE>&, const ORCAD_OCC_SCOPE&, SCH_SHEET*, const SCH_SHEET_PATH&,
1942 const std::string&, bool, const std::map<std::string, std::string>& )>
1943 placeFolder;
1944
1945 placeFolder = [&]( std::vector<ORCAD_RAW_PAGE>& aPages, const ORCAD_OCC_SCOPE& aScope, SCH_SHEET* aFolderSheet,
1946 const SCH_SHEET_PATH& aFolderPath, const std::string& aOccurrenceSuffix,
1947 bool aRepeatedFolder,
1948 const std::map<std::string, std::string>& aUnconnectedInterfaceNetNames )
1949 {
1950 struct PAGE_PLACEMENT
1951 {
1952 ORCAD_RAW_PAGE* page;
1953 SCH_SHEET* sheet;
1954 SCH_SCREEN* screen;
1956 };
1957
1958 std::vector<PAGE_PLACEMENT> placements;
1959 bool leafFolder = std::all_of( aPages.begin(), aPages.end(),
1960 []( const ORCAD_RAW_PAGE& aPage )
1961 {
1962 return aPage.blocks.empty();
1963 } );
1964 m_currentFlatNetSuffix = aOccurrenceSuffix;
1965 m_scopeNamedFlatNets = simpleRepeatedLeafDesign && leafFolder;
1966 m_scopeGeneratedFlatNets = aRepeatedFolder;
1968 leafFolder ? aUnconnectedInterfaceNetNames : std::map<std::string, std::string>();
1969
1970 if( aPages.size() == 1 )
1971 {
1972 ORCAD_RAW_PAGE& page = aPages.front();
1973 pollProgress( m_progressReporter, page.name );
1974 m_currentOccRefs = &aScope.partRefs;
1975 m_currentOccUnitRefs = &aScope.partUnitRefs;
1976 m_currentOccProps = &aScope.partProps;
1977 m_currentOccNetNames = &aScope.netNames;
1978 selectOccurrenceAliases( m_currentOccNetNames );
1979
1980 numberSourcePage( page );
1981 applyPageSettings( page, aFolderSheet->GetScreen() );
1982 convertPage( page, aFolderSheet->GetScreen(), aFolderPath, aFolderSheet != aRootSheet,
1983 aFolderSheet->GetPins().empty() );
1984 placements.push_back( { &page, aFolderSheet, aFolderSheet->GetScreen(), aFolderPath } );
1985 }
1986 else if( aFolderSheet == aRootSheet )
1987 {
1988 std::vector<SCH_SHEET*> topSheets;
1989 std::vector<SCH_SCREEN*> topScreens;
1990
1991 for( size_t i = 0; i < aPages.size(); ++i )
1992 {
1993 ORCAD_RAW_PAGE& page = aPages[i];
1994 SCH_SHEET* pageSheet = i == 0 ? aRootSheet : new SCH_SHEET( m_schematic );
1995 SCH_SCREEN* pageScreen = i == 0 ? aRootSheet->GetScreen() : new SCH_SCREEN( m_schematic );
1996 int currentPage = static_cast<int>( i + 1 );
1997
1998 if( i != 0 )
1999 {
2000 const_cast<KIID&>( pageScreen->GetUuid() ) = deterministicUuid( "screen", m_screenOrdinal++ );
2001 const_cast<KIID&>( pageSheet->m_Uuid ) = deterministicUuid( "sheet", currentPage );
2002 pageSheet->SetScreen( pageScreen );
2003 }
2004
2005 wxString fileName = MakePageFileName( currentPage, page.name );
2006 pageSheet->GetField( FIELD_T::SHEET_NAME )->SetText( uniqueSheetName( page.name ) );
2007 pageSheet->GetField( FIELD_T::SHEET_FILENAME )->SetText( fileName );
2008 pageScreen->SetFileName( m_schematic->Project().GetProjectPath() + fileName );
2009 topSheets.push_back( pageSheet );
2010 topScreens.push_back( pageScreen );
2011 }
2012
2013 m_schematic->SetTopLevelSheets( topSheets );
2014 pageIndex = static_cast<int>( aPages.size() );
2015
2016 for( size_t i = 0; i < aPages.size(); ++i )
2017 {
2018 ORCAD_RAW_PAGE& page = aPages[i];
2019 SCH_SHEET_PATH pagePath;
2020
2021 for( const SCH_SHEET_PATH& candidate : m_schematic->Hierarchy() )
2022 {
2023 if( candidate.Last() == topSheets[i] )
2024 {
2025 pagePath = candidate;
2026 break;
2027 }
2028 }
2029
2030 if( pagePath.empty() )
2031 pagePath.push_back( topSheets[i] );
2032
2033 pagePath.SetPageNumber( wxString::Format( wxS( "%zu" ), i + 1 ) );
2034 pollProgress( m_progressReporter, page.name );
2035 m_currentOccRefs = &aScope.partRefs;
2036 m_currentOccUnitRefs = &aScope.partUnitRefs;
2037 m_currentOccProps = &aScope.partProps;
2038 m_currentOccNetNames = &aScope.netNames;
2039 selectOccurrenceAliases( m_currentOccNetNames );
2040 numberSourcePage( page );
2041 applyPageSettings( page, topScreens[i] );
2042 convertPage( page, topScreens[i], pagePath, false, true );
2043 placements.push_back( { &page, topSheets[i], topScreens[i], pagePath } );
2044 }
2045 }
2046 else
2047 {
2048 SCH_SCREEN* container = aFolderSheet->GetScreen();
2050 size_t outerOrdinal = 0;
2051 auto folderBusNames = m_hierBusNamesByScreen.find( container->GetUuid().AsStdString() );
2052
2053 for( const SCH_SHEET_PIN* pin : aFolderSheet->GetPins() )
2054 {
2055 VECTOR2I position( schIUScale.mmToIU( 20 ),
2056 schIUScale.mmToIU( 20 + 5 * static_cast<int>( outerOrdinal++ ) ) );
2057 addContainerLabel( container, pin->GetText(), position, true );
2058 }
2059
2060 for( size_t i = 0; i < aPages.size(); ++i )
2061 {
2062 ORCAD_RAW_PAGE& page = aPages[i];
2063 int column = static_cast<int>( i % 3 );
2064 int row = static_cast<int>( i / 3 );
2065 std::vector<std::string> names = interfaceNames( page );
2066
2067 if( folderBusNames != m_hierBusNamesByScreen.end() )
2068 {
2069 for( std::string& name : names )
2070 {
2071 auto renamed = folderBusNames->second.find( name );
2072
2073 if( renamed != folderBusNames->second.end() )
2074 name = renamed->second;
2075 }
2076 }
2077
2078 int heightMm = std::max( 25, 10 + 5 * static_cast<int>( names.size() ) );
2079 VECTOR2I position( schIUScale.mmToIU( 55 + column * 70 ), schIUScale.mmToIU( 15 + row * 70 ) );
2080 VECTOR2I size( schIUScale.mmToIU( 55 ), schIUScale.mmToIU( heightMm ) );
2081 SCH_SCREEN* pageScreen = new SCH_SCREEN( m_schematic );
2082 const_cast<KIID&>( pageScreen->GetUuid() ) = deterministicUuid( "screen", m_screenOrdinal++ );
2083
2084 if( folderBusNames != m_hierBusNamesByScreen.end() )
2085 m_hierBusNamesByScreen[pageScreen->GetUuid().AsStdString()] = folderBusNames->second;
2086
2087 SCH_SHEET* pageSheet = new SCH_SHEET( aFolderSheet, position, size );
2088 int currentPage = ++pageIndex;
2089 const_cast<KIID&>( pageSheet->m_Uuid ) = deterministicUuid( "sheet", currentPage );
2090 wxString fileName = MakePageFileName( currentPage, page.name );
2091 pageSheet->GetField( FIELD_T::SHEET_NAME )->SetText( uniqueSheetName( page.name ) );
2092 pageSheet->GetField( FIELD_T::SHEET_FILENAME )->SetText( fileName );
2093 pageSheet->SetScreen( pageScreen );
2094 pageScreen->SetFileName( m_schematic->Project().GetProjectPath() + fileName );
2095
2096 for( size_t pinIndex = 0; pinIndex < names.size(); ++pinIndex )
2097 {
2098 VECTOR2I pinPosition( position.x,
2099 position.y + schIUScale.mmToIU( 5 + 5 * static_cast<int>( pinIndex ) ) );
2100 wxString name = FromOrcadString( names[pinIndex] );
2101 SCH_SHEET_PIN* pin = new SCH_SHEET_PIN( pageSheet, pinPosition, name );
2102 const_cast<KIID&>( pin->m_Uuid ) =
2103 deterministicUuid( "container-pin:" + pageScreen->GetUuid().AsStdString(), pinIndex );
2104 pin->SetSide( SHEET_SIDE::LEFT );
2105 pin->SetShape( LABEL_FLAG_SHAPE::L_BIDI );
2106 pageSheet->AddPin( pin );
2107 addContainerLabel( container, name, pinPosition, false );
2108 }
2109
2110 container->Append( pageSheet );
2111 SCH_SHEET_PATH pagePath = aFolderPath;
2112 pagePath.push_back( pageSheet );
2113 pagePath.SetPageNumber( wxString::Format( wxS( "%d" ), currentPage ) );
2114 pollProgress( m_progressReporter, page.name );
2115 m_currentOccRefs = &aScope.partRefs;
2116 m_currentOccUnitRefs = &aScope.partUnitRefs;
2117 m_currentOccProps = &aScope.partProps;
2118 m_currentOccNetNames = &aScope.netNames;
2119 selectOccurrenceAliases( m_currentOccNetNames );
2120 numberSourcePage( page );
2121 applyPageSettings( page, pageScreen );
2122 convertPage( page, pageScreen, pagePath, true, true );
2123 placements.push_back( { &page, pageSheet, pageScreen, pagePath } );
2124 }
2125 }
2126
2127 std::vector<const ORCAD_OCC_BLOCK*> occurrences;
2128
2129 for( const ORCAD_OCC_BLOCK& occurrence : aScope.blocks )
2130 occurrences.push_back( &occurrence );
2131
2132 std::stable_sort( occurrences.begin(), occurrences.end(),
2133 []( const ORCAD_OCC_BLOCK* a, const ORCAD_OCC_BLOCK* b )
2134 {
2135 wxString aName = FromOrcadString( a->childFolder );
2136 wxString bName = FromOrcadString( b->childFolder );
2137 int aOrder = OrcadPageOrder( aName );
2138 int bOrder = OrcadPageOrder( bName );
2139 int aKey = aOrder >= 0 ? aOrder : std::numeric_limits<int>::max();
2140 int bKey = bOrder >= 0 ? bOrder : std::numeric_limits<int>::max();
2141
2142 if( aKey != bKey )
2143 return aKey < bKey;
2144
2145 return aName.CmpNoCase( bName ) < 0;
2146 } );
2147
2148 for( const ORCAD_OCC_BLOCK* occurrencePtr : occurrences )
2149 {
2150 const ORCAD_OCC_BLOCK& occurrence = *occurrencePtr;
2151 PAGE_PLACEMENT* parent = nullptr;
2152 ORCAD_DRAWN_INSTANCE* drawn = nullptr;
2153
2154 for( PAGE_PLACEMENT& placement : placements )
2155 {
2156 auto found = std::find_if( placement.page->blocks.begin(), placement.page->blocks.end(),
2157 [&]( const ORCAD_DRAWN_INSTANCE& aBlock )
2158 {
2159 return aBlock.dbId == occurrence.targetDbId;
2160 } );
2161
2162 if( found != placement.page->blocks.end() )
2163 {
2164 parent = &placement;
2165 drawn = &*found;
2166 break;
2167 }
2168 }
2169
2170 if( !parent || !drawn )
2171 continue;
2172
2173 std::string key = occurrence.childFolder;
2174 std::transform( key.begin(), key.end(), key.begin(),
2175 []( unsigned char c )
2176 {
2177 return static_cast<char>( std::tolower( c ) );
2178 } );
2179 std::vector<ORCAD_RAW_PAGE>& childPages = m_design.childFolderPages.at( key );
2180 SCH_SCREEN* childScreen = new SCH_SCREEN( m_schematic );
2181 const_cast<KIID&>( childScreen->GetUuid() ) = deterministicUuid( "screen", m_screenOrdinal++ );
2182 SCH_SHEET* childSheet = new SCH_SHEET( parent->sheet, OrcadDbuToIu( drawn->x1, drawn->y1 ),
2183 OrcadDbuToIu( drawn->w, drawn->h ) );
2184 int currentPage = ++pageIndex;
2185 const_cast<KIID&>( childSheet->m_Uuid ) = deterministicUuid( "sheet", currentPage );
2186 wxString fileName =
2187 MakePageFileName( currentPage, childPages.size() == 1 ? childPages.front().name
2188 : occurrence.childFolder + " container" );
2189 childSheet->GetField( FIELD_T::SHEET_NAME )
2190 ->SetText( uniqueSheetName( drawn->reference.empty() ? occurrence.childFolder
2191 : drawn->reference ) );
2192 childSheet->GetField( FIELD_T::SHEET_FILENAME )->SetText( fileName );
2193 placeHierarchicalBlockFields( childSheet, *drawn, occurrence.childFolder );
2194 childSheet->SetScreen( childScreen );
2195 childScreen->SetFileName( m_schematic->Project().GetProjectPath() + fileName );
2196
2197 auto implementation = drawn->props.find( "Implementation" );
2198
2199 if( implementation != drawn->props.end() && !implementation->second.empty()
2200 && FromOrcadString( implementation->second )
2201 .CmpNoCase( FromOrcadString( occurrence.childFolder ) )
2202 != 0 )
2203 {
2204 childSheet->SetExcludedFromBoard( true );
2205 }
2206
2207 for( size_t pinIndex = 0; pinIndex < drawn->pins.size(); ++pinIndex )
2208 {
2209 const ORCAD_BLOCK_PIN& sourcePin = drawn->pins[pinIndex];
2210 VECTOR2I position = OrcadDbuToIu( sourcePin.x, sourcePin.y );
2211 std::string sourceName = canonicalGlobalNetName( sourcePin.name );
2212 uint32_t busNetId = busNetAt( *parent->page, sourcePin );
2213 std::string pinName = busNetId ? connectedBusName( *parent->page, sourcePin, sourceName )
2214 : scopedHierBusName( sourceName, occurrence.targetDbId );
2215
2216 if( busNetId && pinName != sourceName )
2217 {
2218 auto parentBusNames = m_hierBusNamesByScreen.find( parent->screen->GetUuid().AsStdString() );
2219
2220 if( parentBusNames != m_hierBusNamesByScreen.end() )
2221 pinName = scopedHierBusRange( pinName, parentBusNames->second );
2222 }
2223
2224 if( pinName != sourceName )
2225 m_hierBusNamesByScreen[childScreen->GetUuid().AsStdString()][sourceName] = pinName;
2226
2227 SCH_SHEET_PIN* pin = new SCH_SHEET_PIN( childSheet, position, FromOrcadString( pinName ) );
2228 const_cast<KIID&>( pin->m_Uuid ) =
2229 deterministicUuid( "sheet-pin:" + childScreen->GetUuid().AsStdString(), pinIndex );
2230 std::array<std::pair<int, SHEET_SIDE>, 4> sides = {
2231 std::pair{ std::abs( sourcePin.x - drawn->x1 ), SHEET_SIDE::LEFT },
2232 std::pair{ std::abs( sourcePin.x - drawn->x1 - drawn->w ), SHEET_SIDE::RIGHT },
2233 std::pair{ std::abs( sourcePin.y - drawn->y1 ), SHEET_SIDE::TOP },
2234 std::pair{ std::abs( sourcePin.y - drawn->y1 - drawn->h ), SHEET_SIDE::BOTTOM }
2235 };
2236 pin->SetSide( std::min_element( sides.begin(), sides.end(),
2237 []( const auto& a, const auto& b )
2238 {
2239 return a.first < b.first;
2240 } )
2241 ->second );
2242 pin->SetPosition( position );
2243 pin->SetShape( hierarchicalPinShape( sourcePin.portType ) );
2244 childSheet->AddPin( pin );
2245 placeHierarchicalBlockPinFill( parent->screen, pin );
2246 }
2247
2248 parent->screen->Append( childSheet );
2249 SCH_SHEET_PATH childPath = parent->path;
2250 childPath.push_back( childSheet );
2251 childPath.SetPageNumber( wxString::Format( wxS( "%d" ), currentPage ) );
2252 std::string childSuffix = aOccurrenceSuffix;
2253 std::string childKey = occurrence.childFolder;
2254 std::transform( childKey.begin(), childKey.end(), childKey.begin(),
2255 []( unsigned char c )
2256 {
2257 return static_cast<char>( std::tolower( c ) );
2258 } );
2259
2260 if( !flatNetSuffix( *drawn ).empty() )
2261 {
2262 if( !childSuffix.empty() )
2263 childSuffix += '_';
2264
2265 childSuffix += flatNetSuffix( *drawn );
2266 }
2267
2268 auto unconnectedNames = unconnectedInterfaceNetNames( *drawn, childSuffix );
2269 addChildOccurrenceAliases( *parent->page, aScope, *drawn, occurrence.scope );
2270 placeFolder( childPages, occurrence.scope, childSheet, childPath, childSuffix,
2271 occurrenceFolderCounts[childKey] > 1, unconnectedNames );
2272 }
2273 };
2274
2275 std::map<std::string, POWER_ALIAS_EVIDENCE> powerAliasCandidates;
2276 std::function<void( std::vector<ORCAD_RAW_PAGE>&, const ORCAD_OCC_SCOPE& )> collectPowerAliases =
2277 [&]( std::vector<ORCAD_RAW_PAGE>& aPages, const ORCAD_OCC_SCOPE& aScope )
2278 {
2279 m_currentOccRefs = &aScope.partRefs;
2280 m_currentOccUnitRefs = &aScope.partUnitRefs;
2281 m_currentOccProps = &aScope.partProps;
2282 m_currentOccNetNames = &aScope.netNames;
2283 selectOccurrenceAliases( m_currentOccNetNames );
2284
2285 for( ORCAD_RAW_PAGE& page : aPages )
2286 {
2287 buildNetLookup( page );
2288
2289 for( const ORCAD_GRAPHIC_INST& global : page.globals )
2290 {
2291 std::string sourceName = trimmed( global.logicalName );
2292 std::string electricalName = powerNet( page, global );
2293 recordPowerAlias( powerAliasCandidates, sourceName, electricalName );
2294 }
2295 }
2296
2297 for( const ORCAD_OCC_BLOCK& occurrence : aScope.blocks )
2298 {
2299 ORCAD_RAW_PAGE* parentPage = nullptr;
2300 ORCAD_DRAWN_INSTANCE* drawn = nullptr;
2301
2302 for( ORCAD_RAW_PAGE& page : aPages )
2303 {
2304 auto found = std::find_if( page.blocks.begin(), page.blocks.end(),
2305 [&]( const ORCAD_DRAWN_INSTANCE& aBlock )
2306 {
2307 return aBlock.dbId == occurrence.targetDbId;
2308 } );
2309
2310 if( found != page.blocks.end() )
2311 {
2312 parentPage = &page;
2313 drawn = &*found;
2314 break;
2315 }
2316 }
2317
2318 std::string key = lowerPowerName( occurrence.childFolder );
2319 auto childPages = m_design.childFolderPages.find( key );
2320
2321 if( parentPage && drawn && childPages != m_design.childFolderPages.end() )
2322 {
2323 addChildOccurrenceAliases( *parentPage, aScope, *drawn, occurrence.scope );
2324 collectPowerAliases( childPages->second, occurrence.scope );
2325 }
2326 }
2327 };
2328
2329 collectPowerAliases( m_design.pages, m_design.occurrenceRoot );
2330 acceptPowerAliases( powerAliasCandidates );
2331
2332 m_currentOccRefs = nullptr;
2333 m_currentOccUnitRefs = nullptr;
2334 m_currentOccProps = nullptr;
2335 m_currentOccNetNames = nullptr;
2337
2338 placeFolder( m_design.pages, m_design.occurrenceRoot, aRootSheet, rootPath, {}, false, {} );
2339 m_currentOccRefs = nullptr;
2340 m_currentOccUnitRefs = nullptr;
2341 m_currentOccProps = nullptr;
2342 m_currentOccNetNames = nullptr;
2343 m_currentFlatNetSuffix.clear();
2344 m_scopeNamedFlatNets = false;
2349 return aRootSheet;
2350 }
2351
2352 // Page list = root pages + each block occurrence's child pages, tagged w/ scope refs.
2353 // Child schematic reused N times yields N jobs, each w/ own designators.
2354 struct PAGE_JOB
2355 {
2356 ORCAD_RAW_PAGE* page;
2357 const std::map<uint32_t, std::string>* refs;
2358 const std::map<uint32_t, std::string>* unitRefs;
2359 const std::map<uint32_t, std::map<std::string, std::string>>* props;
2360 const std::map<uint32_t, std::string>* netNames;
2361 std::string flatNetSuffix;
2362 bool scopeNamedFlatNets;
2363 bool scopeGeneratedFlatNets;
2364 std::map<std::string, std::string> unconnectedInterfaceNetNames;
2365 };
2366
2367 std::vector<PAGE_JOB> jobs;
2368
2369 for( ORCAD_RAW_PAGE& page : m_design.pages )
2370 jobs.push_back( { &page,
2371 &m_design.occurrenceRoot.partRefs,
2372 &m_design.occurrenceRoot.partUnitRefs,
2373 &m_design.occurrenceRoot.partProps,
2374 &m_design.occurrenceRoot.netNames,
2375 {},
2376 false,
2377 false,
2378 {} } );
2379
2380 auto findBlock = [&]( uint32_t aDbId ) -> std::pair<const ORCAD_DRAWN_INSTANCE*, const ORCAD_RAW_PAGE*>
2381 {
2382 auto findInPages = [&]( const std::vector<ORCAD_RAW_PAGE>& aPages )
2383 -> std::pair<const ORCAD_DRAWN_INSTANCE*, const ORCAD_RAW_PAGE*>
2384 {
2385 for( const ORCAD_RAW_PAGE& page : aPages )
2386 {
2387 for( const ORCAD_DRAWN_INSTANCE& block : page.blocks )
2388 {
2389 if( block.dbId == aDbId )
2390 return { &block, &page };
2391 }
2392 }
2393
2394 return {};
2395 };
2396
2397 auto result = findInPages( m_design.pages );
2398
2399 if( result.first )
2400 return result;
2401
2402 for( const auto& [folder, pages] : m_design.childFolderPages )
2403 {
2404 result = findInPages( pages );
2405
2406 if( result.first )
2407 return result;
2408 }
2409
2410 return {};
2411 };
2412
2413 std::function<void( const ORCAD_OCC_SCOPE&, const std::string& )> expand =
2414 [&]( const ORCAD_OCC_SCOPE& aScope, const std::string& aParentSuffix )
2415 {
2416 for( const ORCAD_OCC_BLOCK& block : aScope.blocks )
2417 {
2418 std::string key = block.childFolder;
2419 std::transform( key.begin(), key.end(), key.begin(),
2420 []( unsigned char c )
2421 {
2422 return static_cast<char>( std::tolower( c ) );
2423 } );
2424
2425 auto it = m_design.childFolderPages.find( key );
2426 const ORCAD_DRAWN_INSTANCE* drawn = findBlock( block.targetDbId ).first;
2427 std::string occurrenceSuffix = aParentSuffix;
2428
2429 if( drawn && !flatNetSuffix( *drawn ).empty() )
2430 {
2431 if( !occurrenceSuffix.empty() )
2432 occurrenceSuffix += '_';
2433
2434 occurrenceSuffix += flatNetSuffix( *drawn );
2435 }
2436
2437 if( it != m_design.childFolderPages.end() )
2438 {
2439 bool leafFolder = std::all_of( it->second.begin(), it->second.end(),
2440 []( const ORCAD_RAW_PAGE& aPage )
2441 {
2442 return aPage.blocks.empty();
2443 } );
2444 std::map<std::string, std::string> unconnectedNames;
2445
2446 if( drawn )
2447 unconnectedNames = unconnectedInterfaceNetNames( *drawn, occurrenceSuffix );
2448
2449 for( ORCAD_RAW_PAGE& childPage : it->second )
2450 {
2451 jobs.push_back( { &childPage, &block.scope.partRefs, &block.scope.partUnitRefs,
2452 &block.scope.partProps, &block.scope.netNames,
2453 leafFolder ? occurrenceSuffix : std::string(),
2454 simpleRepeatedLeafDesign && leafFolder,
2455 leafFolder && occurrenceFolderCounts[key] > 1, unconnectedNames } );
2456 }
2457 }
2458
2459 expand( block.scope, occurrenceSuffix );
2460 }
2461 };
2462
2463 expand( m_design.occurrenceRoot, {} );
2464
2465 std::map<const std::map<uint32_t, std::string>*, std::map<std::string, std::set<std::string>>>
2466 interfaceAliasCandidates;
2467 std::map<const std::map<uint32_t, std::string>*, std::map<std::string, std::set<std::string>>>
2468 connectorAliasCandidates;
2469
2470 auto lowerName = []( std::string aName )
2471 {
2472 std::transform( aName.begin(), aName.end(), aName.begin(),
2473 []( unsigned char c )
2474 {
2475 return static_cast<char>( std::tolower( c ) );
2476 } );
2477 return aName;
2478 };
2479
2480 using NET_NAME_SCOPE = const std::map<uint32_t, std::string>*;
2481 std::map<NET_NAME_SCOPE, std::map<std::string, std::set<std::string>>> interfaceNameGraphs;
2482
2483 for( const PAGE_JOB& job : jobs )
2484 {
2485 for( const auto& [netId, aliases] : job.page->netAliases )
2486 {
2487 std::set<std::string> names;
2488 auto primary = job.page->netmap.find( netId );
2489 bool allPowerNames = true;
2490 bool anyPowerName = false;
2491
2492 if( primary != job.page->netmap.end() && !primary->second.empty() )
2493 {
2494 std::string name = lowerName( canonicalGlobalNetName( primary->second ) );
2495 names.insert( name );
2496 allPowerNames = isPowerNetName( primary->second );
2497 anyPowerName = allPowerNames;
2498 }
2499
2500 for( const std::string& alias : aliases )
2501 {
2502 if( !alias.empty() )
2503 {
2504 names.insert( lowerName( canonicalGlobalNetName( alias ) ) );
2505 allPowerNames = allPowerNames && isPowerNetName( alias );
2506 anyPowerName = anyPowerName || isPowerNetName( alias );
2507 }
2508 }
2509
2510 if( anyPowerName && !allPowerNames )
2511 continue;
2512
2513 for( const std::string& name : names )
2514 {
2515 auto& neighbors = interfaceNameGraphs[job.netNames][name];
2516 neighbors.insert( names.begin(), names.end() );
2517 }
2518 }
2519 }
2520
2521 for( const PAGE_JOB& job : jobs )
2522 {
2523 for( const auto& [netId, aliases] : job.page->netAliases )
2524 {
2525 std::set<std::string> distinctAliases;
2526
2527 for( const std::string& alias : aliases )
2528 {
2529 if( !alias.empty() )
2530 distinctAliases.insert( lowerName( alias ) );
2531 }
2532
2533 auto primary = job.page->netmap.find( netId );
2534
2535 if( distinctAliases.size() < 2 || primary == job.page->netmap.end() || primary->second.empty() )
2536 continue;
2537
2538 std::string effectiveName;
2539 int64_t bestConnectorAliasDistance = std::numeric_limits<int64_t>::max();
2540 bool connectorAlias = false;
2541 auto firstAlias = std::find_if( aliases.begin(), aliases.end(),
2542 []( const std::string& aName )
2543 {
2544 return !aName.empty();
2545 } );
2546
2547 for( const auto& [occurrenceId, occurrenceName] : *job.netNames )
2548 {
2549 if( lowerName( primary->second ) == lowerName( occurrenceName ) )
2550 effectiveName = canonicalGlobalNetName( occurrenceName );
2551 }
2552
2553 if( effectiveName.empty() )
2554 {
2555 for( const ORCAD_PLACED_INSTANCE& instance : job.page->instances )
2556 {
2557 if( instance.reference.empty()
2558 || std::toupper( static_cast<unsigned char>( instance.reference.front() ) ) != 'J' )
2559 {
2560 continue;
2561 }
2562
2563 for( const ORCAD_PIN_INST& pin : instance.pins )
2564 {
2565 for( const ORCAD_WIRE& wire : job.page->wires )
2566 {
2567 if( wire.id != netId
2568 || ( pin.wordA != wire.dbId && pin.wordB != wire.dbId
2569 && !rawPointOnSegment( pin.x, pin.y, wire ) ) )
2570 {
2571 continue;
2572 }
2573
2574 size_t localAliasCount = std::count_if(
2575 wire.aliases.begin(), wire.aliases.end(),
2576 [&]( const ORCAD_ALIAS& aAlias )
2577 {
2578 if( aAlias.name.empty() || isOffpageNetName( aAlias.name ) )
2579 {
2580 return false;
2581 }
2582
2583 std::string aliasName = lowerName( aAlias.name );
2584
2585 return std::none_of( aliases.begin(), aliases.end(),
2586 [&]( const std::string& aName )
2587 {
2588 return isOffpageNetName( aName )
2589 && lowerName( aName ).find( aliasName )
2590 != std::string::npos;
2591 } );
2592 } );
2593
2594 if( localAliasCount < 2 )
2595 continue;
2596
2597 for( const ORCAD_ALIAS& alias : wire.aliases )
2598 {
2599 if( alias.name.empty() || isOffpageNetName( alias.name ) || firstAlias == aliases.end()
2600 || lowerName( alias.name ) != lowerName( *firstAlias ) )
2601 {
2602 continue;
2603 }
2604
2605 int64_t dx = alias.x - pin.x;
2606 int64_t dy = alias.y - pin.y;
2607 int64_t distance = dx * dx + dy * dy;
2608
2609 if( distance < bestConnectorAliasDistance )
2610 {
2611 bestConnectorAliasDistance = distance;
2612 effectiveName = canonicalGlobalNetName( alias.name );
2613 connectorAlias = true;
2614 }
2615 }
2616 }
2617 }
2618 }
2619 }
2620
2621 if( !effectiveName.empty() && !connectorAlias )
2622 {
2623 connectorAlias = std::any_of(
2624 job.page->instances.begin(), job.page->instances.end(),
2625 [&]( const ORCAD_PLACED_INSTANCE& aInstance )
2626 {
2627 if( aInstance.reference.empty()
2628 || std::toupper( static_cast<unsigned char>( aInstance.reference.front() ) ) != 'J' )
2629 {
2630 return false;
2631 }
2632
2633 return std::any_of(
2634 aInstance.pins.begin(), aInstance.pins.end(),
2635 [&]( const ORCAD_PIN_INST& aPin )
2636 {
2637 return std::any_of(
2638 job.page->wires.begin(), job.page->wires.end(),
2639 [&]( const ORCAD_WIRE& aWire )
2640 {
2641 if( aWire.id != netId
2642 || ( aPin.wordA != aWire.dbId && aPin.wordB != aWire.dbId
2643 && !rawPointOnSegment( aPin.x, aPin.y, aWire ) ) )
2644 {
2645 return false;
2646 }
2647
2648 return std::any_of( aWire.aliases.begin(), aWire.aliases.end(),
2649 [&]( const ORCAD_ALIAS& aAlias )
2650 {
2651 return lowerName( canonicalGlobalNetName(
2652 aAlias.name ) )
2653 == lowerName( effectiveName );
2654 } );
2655 } );
2656 } );
2657 } );
2658 }
2659
2660 if( effectiveName.empty() )
2661 continue;
2662
2663 for( const std::string& alias : aliases )
2664 {
2665 if( isOffpageNetName( alias ) )
2666 {
2667 std::string sourceName = lowerName( canonicalGlobalNetName( alias ) );
2668 interfaceAliasCandidates[job.netNames][sourceName].insert( effectiveName );
2669
2670 if( connectorAlias )
2671 connectorAliasCandidates[job.netNames][sourceName].insert( effectiveName );
2672 }
2673 }
2674 }
2675 }
2676
2677 std::map<const std::map<uint32_t, std::string>*, std::map<std::string, std::string>> interfaceAliasesByScope;
2678 std::map<const std::map<uint32_t, std::string>*, std::set<std::string>> connectorAliasesByScope;
2679
2680 for( const auto& [scope, aliases] : interfaceAliasCandidates )
2681 {
2682 for( const auto& [sourceName, effectiveNames] : aliases )
2683 {
2684 auto selected = std::max_element( effectiveNames.begin(), effectiveNames.end(),
2685 []( const std::string& aLeft, const std::string& aRight )
2686 {
2687 return aLeft.size() < aRight.size();
2688 } );
2689
2690 if( selected == effectiveNames.end() )
2691 continue;
2692
2693 bool sourceIsOccurrenceName = std::any_of(
2694 scope->begin(), scope->end(),
2695 [&]( const auto& aOccurrenceNet )
2696 {
2697 return lowerName( canonicalGlobalNetName( aOccurrenceNet.second ) ) == sourceName;
2698 } );
2699 bool selectedIsOccurrenceName = std::any_of(
2700 scope->begin(), scope->end(),
2701 [&]( const auto& aOccurrenceNet )
2702 {
2703 return lowerName( canonicalGlobalNetName( aOccurrenceNet.second ) ) == lowerName( *selected );
2704 } );
2705
2706 if( sourceIsOccurrenceName && selectedIsOccurrenceName && lowerName( *selected ) != sourceName )
2707 continue;
2708
2709 bool uniqueLength = std::none_of( effectiveNames.begin(), effectiveNames.end(),
2710 [&]( const std::string& aName )
2711 {
2712 return aName != *selected && aName.size() == selected->size();
2713 } );
2714
2715 if( uniqueLength )
2716 {
2717 interfaceAliasesByScope[scope][sourceName] = *selected;
2718
2719 auto connectorScope = connectorAliasCandidates.find( scope );
2720
2721 if( connectorScope != connectorAliasCandidates.end() )
2722 {
2723 auto connectorNames = connectorScope->second.find( sourceName );
2724
2725 if( connectorNames != connectorScope->second.end() && connectorNames->second.count( *selected ) )
2726 connectorAliasesByScope[scope].insert( sourceName );
2727 }
2728 }
2729 }
2730 }
2731
2732 for( const auto& [scope, graph] : interfaceNameGraphs )
2733 {
2734 std::set<std::string> unseen;
2735
2736 for( const auto& [name, neighbors] : graph )
2737 unseen.insert( name );
2738
2739 while( !unseen.empty() )
2740 {
2741 std::set<std::string> component;
2742 std::vector<std::string> pending = { *unseen.begin() };
2743 unseen.erase( pending.front() );
2744
2745 while( !pending.empty() )
2746 {
2747 std::string name = std::move( pending.back() );
2748 pending.pop_back();
2749 component.insert( name );
2750
2751 for( const std::string& neighbor : graph.at( name ) )
2752 {
2753 if( unseen.erase( neighbor ) )
2754 pending.push_back( neighbor );
2755 }
2756 }
2757
2758 std::set<std::string> authoritativeNames;
2759
2760 for( const auto& [occurrenceId, occurrenceName] : *scope )
2761 {
2762 std::string key = lowerName( canonicalGlobalNetName( occurrenceName ) );
2763
2764 if( component.count( key ) )
2765 authoritativeNames.insert( canonicalGlobalNetName( occurrenceName ) );
2766 }
2767
2768 if( authoritativeNames.size() != 1 )
2769 continue;
2770
2771 for( const std::string& sourceName : component )
2772 {
2773 if( connectorAliasesByScope[scope].count( sourceName ) )
2774 continue;
2775
2776 interfaceAliasesByScope[scope][sourceName] = *authoritativeNames.begin();
2777 connectorAliasesByScope[scope].erase( sourceName );
2778 }
2779 }
2780 }
2781
2782 std::function<void( const ORCAD_OCC_SCOPE& )> propagateInterfaceAliases =
2783 [&]( const ORCAD_OCC_SCOPE& aParentScope )
2784 {
2785 const auto& parentAliases = interfaceAliasesByScope[&aParentScope.netNames];
2786
2787 for( const ORCAD_OCC_BLOCK& block : aParentScope.blocks )
2788 {
2789 auto& childAliases = interfaceAliasesByScope[&block.scope.netNames];
2790 auto [drawnBlock, parentPage] = findBlock( block.targetDbId );
2791 if( drawnBlock && parentPage )
2792 {
2793 for( const ORCAD_BLOCK_PIN& pin : drawnBlock->pins )
2794 {
2795 if( pin.name.empty() )
2796 continue;
2797
2798 std::set<std::string> sourceNames = { lowerName( canonicalGlobalNetName( pin.name ) ) };
2799 std::set<uint32_t> wireObjectIds;
2800
2801 for( const ORCAD_WIRE& wire : parentPage->wires )
2802 {
2803 if( wire.isBus || !rawPointOnSegment( pin.x, pin.y, wire ) )
2804 continue;
2805
2806 wireObjectIds.insert( wire.dbId );
2807 auto pageName = parentPage->netmap.find( wire.id );
2808
2809 if( pageName != parentPage->netmap.end() && !pageName->second.empty() )
2810 sourceNames.insert( lowerName( canonicalGlobalNetName( pageName->second ) ) );
2811
2812 auto aliases = parentPage->netAliases.find( wire.id );
2813
2814 if( aliases != parentPage->netAliases.end() )
2815 {
2816 for( const std::string& alias : aliases->second )
2817 sourceNames.insert( lowerName( canonicalGlobalNetName( alias ) ) );
2818 }
2819 }
2820
2821 std::set<const std::string*> targets;
2822
2823 for( const auto& [occurrenceId, occurrenceName] : aParentScope.netNames )
2824 {
2825 std::string occurrenceKey = lowerName( canonicalGlobalNetName( occurrenceName ) );
2826 bool matches = sourceNames.count( occurrenceKey );
2827
2828 if( std::optional<uint32_t> objectId = occurrenceNetObjectId( occurrenceName ) )
2829 matches = matches || wireObjectIds.count( *objectId );
2830
2831 if( matches )
2832 targets.insert( &occurrenceName );
2833 }
2834
2835 std::string targetName;
2836
2837 if( targets.size() == 1 )
2838 targetName = canonicalGlobalNetName( **targets.begin() );
2839 else
2840 {
2841 for( const std::string& sourceName : sourceNames )
2842 {
2843 auto inherited = parentAliases.find( sourceName );
2844
2845 if( inherited != parentAliases.end() )
2846 targetName = inherited->second;
2847 }
2848 }
2849
2850 if( !targetName.empty() )
2851 childAliases[lowerName( canonicalGlobalNetName( pin.name ) )] = std::move( targetName );
2852
2853 }
2854 }
2855
2856 for( auto& [sourceName, targetName] : childAliases )
2857 {
2858 auto inherited = parentAliases.find( lowerName( canonicalGlobalNetName( targetName ) ) );
2859
2860 if( inherited != parentAliases.end() )
2861 targetName = inherited->second;
2862 }
2863
2864 for( const auto& [occurrenceId, occurrenceName] : block.scope.netNames )
2865 {
2866 std::string sourceName = lowerName( canonicalGlobalNetName( occurrenceName ) );
2867 auto inherited = parentAliases.find( sourceName );
2868
2869 if( inherited != parentAliases.end() )
2870 childAliases[sourceName] = inherited->second;
2871 }
2872
2873 propagateInterfaceAliases( block.scope );
2874 }
2875 };
2876 propagateInterfaceAliases( m_design.occurrenceRoot );
2877
2878 std::map<std::string, POWER_ALIAS_EVIDENCE> powerAliasCandidates;
2879
2880 for( const PAGE_JOB& job : jobs )
2881 {
2882 m_currentOccRefs = job.refs;
2883 m_currentOccUnitRefs = job.unitRefs;
2884 m_currentOccProps = job.props;
2885 m_currentOccNetNames = job.netNames;
2886 selectOccurrenceAliases( m_currentOccNetNames );
2887 m_currentFlatNetSuffix = job.flatNetSuffix;
2888 m_scopeNamedFlatNets = job.scopeNamedFlatNets;
2889 m_scopeGeneratedFlatNets = job.scopeGeneratedFlatNets;
2890 m_currentUnconnectedInterfaceNetNames = job.unconnectedInterfaceNetNames;
2891 m_currentInterfaceNetAliases = interfaceAliasesByScope[job.netNames];
2892 m_currentConnectorInterfaceNetAliases = connectorAliasesByScope[job.netNames];
2893 buildNetLookup( *job.page );
2894
2895 for( const ORCAD_GRAPHIC_INST& global : job.page->globals )
2896 {
2897 std::string sourceName = trimmed( global.logicalName );
2898 std::string electricalName = powerNet( *job.page, global );
2899 recordPowerAlias( powerAliasCandidates, sourceName, electricalName );
2900 }
2901 }
2902
2903 acceptPowerAliases( powerAliasCandidates );
2904
2905 m_currentOccRefs = nullptr;
2906 m_currentOccUnitRefs = nullptr;
2907 m_currentOccProps = nullptr;
2908 m_currentOccNetNames = nullptr;
2909 m_currentFlatNetSuffix.clear();
2910 m_scopeNamedFlatNets = false;
2911 m_scopeGeneratedFlatNets = false;
2912 m_currentUnconnectedInterfaceNetNames.clear();
2913 m_currentInterfaceNetAliases.clear();
2914 m_currentConnectorInterfaceNetAliases.clear();
2915 m_currentOccurrenceNetAliases.clear();
2916
2917 if( jobs.size() == 1 )
2918 {
2919 PAGE_JOB& job = jobs[0];
2920
2921 pollProgress( m_progressReporter, job.page->name );
2922 m_currentOccRefs = job.refs;
2923 m_currentOccUnitRefs = job.unitRefs;
2924 m_currentOccProps = job.props;
2925 m_currentOccNetNames = job.netNames;
2926 selectOccurrenceAliases( m_currentOccNetNames );
2927 m_currentFlatNetSuffix = job.flatNetSuffix;
2928 m_scopeNamedFlatNets = job.scopeNamedFlatNets;
2929 m_scopeGeneratedFlatNets = job.scopeGeneratedFlatNets;
2930 m_currentUnconnectedInterfaceNetNames = job.unconnectedInterfaceNetNames;
2931 m_currentInterfaceNetAliases = interfaceAliasesByScope[job.netNames];
2932 m_currentConnectorInterfaceNetAliases = connectorAliasesByScope[job.netNames];
2933 applyPageSettings( *job.page, rootScreen );
2934 convertPage( *job.page, rootScreen, rootPath );
2935 m_currentOccRefs = nullptr;
2936 m_currentOccUnitRefs = nullptr;
2937 m_currentOccProps = nullptr;
2938 m_currentOccNetNames = nullptr;
2939 m_currentFlatNetSuffix.clear();
2940 m_scopeNamedFlatNets = false;
2941 m_scopeGeneratedFlatNets = false;
2942 m_currentUnconnectedInterfaceNetNames.clear();
2943 m_currentInterfaceNetAliases.clear();
2944 m_currentConnectorInterfaceNetAliases.clear();
2945 }
2946 else
2947 {
2948 // Each page = flat top-level sheet (no stitching root); order root pages by
2949 // leading "N - " prefix, also stripped for title.
2950 struct SHEET_JOB
2951 {
2952 PAGE_JOB job;
2953 wxString name;
2954 int order;
2955 };
2956
2957 std::vector<SHEET_JOB> sheetJobs;
2958
2959 for( size_t i = 0; i < jobs.size(); ++i )
2960 {
2961 wxString name = FromOrcadString( jobs[i].page->name );
2962 int order = i < m_design.pages.size() ? OrcadPageOrder( name ) : -1;
2963
2964 if( order < 0 && i < m_design.pages.size() )
2965 {
2966 for( const ORCAD_GRAPHIC_INST& titleBlock : jobs[i].page->titleBlocks )
2967 {
2968 auto pageNumber = titleBlock.props.find( "Page Number" );
2969 long parsed = 0;
2970
2971 if( pageNumber != titleBlock.props.end()
2972 && wxString::FromUTF8( pageNumber->second ).ToLong( &parsed ) && parsed > 0 )
2973 {
2974 order = static_cast<int>( parsed );
2975 break;
2976 }
2977 }
2978 }
2979
2980 if( order < 0 && i < m_design.pages.size() && jobs[i].page->sourcePageNumber != 0 )
2981 order = static_cast<int>( jobs[i].page->sourcePageNumber );
2982
2983 if( name.IsEmpty() )
2984 name = wxString::Format( wxS( "PAGE%zu" ), i + 1 );
2985
2986 sheetJobs.push_back( { jobs[i], name, order } );
2987 }
2988
2989 std::stable_sort( sheetJobs.begin(), sheetJobs.end(),
2990 []( const SHEET_JOB& a, const SHEET_JOB& b )
2991 {
2992 int ka = a.order >= 0 ? a.order : std::numeric_limits<int>::max();
2993 int kb = b.order >= 0 ? b.order : std::numeric_limits<int>::max();
2994 return ka < kb;
2995 } );
2996
2997 if( jobs.size() == m_design.pages.size() )
2998 {
2999 for( size_t i = 0; i < sheetJobs.size(); ++i )
3000 {
3001 sheetJobs[i].job.page->sourcePageNumber = i + 1;
3002 sheetJobs[i].job.page->sourcePageCount = sheetJobs.size();
3003 }
3004 }
3005
3006 std::vector<SCH_SHEET*> topSheets;
3007 std::vector<SCH_SCREEN*> topScreens;
3008
3009 for( size_t i = 0; i < sheetJobs.size(); ++i )
3010 {
3011 SHEET_JOB& sj = sheetJobs[i];
3012
3013 SCH_SHEET* sheet;
3014 SCH_SCREEN* screen;
3015
3016 if( i == 0 )
3017 {
3018 // Reuse sheet the loader created for first page
3019 sheet = aRootSheet;
3020 screen = rootScreen;
3021 }
3022 else
3023 {
3024 screen = new SCH_SCREEN( m_schematic );
3025 const_cast<KIID&>( screen->GetUuid() ) = deterministicUuid( "screen", m_screenOrdinal++ );
3026 sheet = new SCH_SHEET( m_schematic );
3027 sheet->SetScreen( screen );
3028 sheet->SyncUuidToScreen();
3029 }
3030
3031 wxString base = sj.name;
3032
3033 for( int suffix = 2; !m_usedSheetNames.insert( sj.name.Lower() ).second; ++suffix )
3034 sj.name = wxString::Format( wxS( "%s (%d)" ), base, suffix );
3035
3036 wxString fileName = MakePageFileName( static_cast<int>( i + 1 ), sj.job.page->name );
3037
3038 sheet->GetField( FIELD_T::SHEET_NAME )->SetText( sj.name );
3039 sheet->GetField( FIELD_T::SHEET_FILENAME )->SetText( fileName );
3040 screen->SetFileName( m_schematic->Project().GetProjectPath() + fileName );
3041
3042 topSheets.push_back( sheet );
3043 topScreens.push_back( screen );
3044 }
3045
3046 m_schematic->SetTopLevelSheets( topSheets );
3047
3048 for( size_t i = 0; i < sheetJobs.size(); ++i )
3049 {
3050 SHEET_JOB& sj = sheetJobs[i];
3051 SCH_SHEET_PATH pagePath;
3052
3053 for( const SCH_SHEET_PATH& candidate : m_schematic->Hierarchy() )
3054 {
3055 if( candidate.Last() == topSheets[i] )
3056 {
3057 pagePath = candidate;
3058 break;
3059 }
3060 }
3061
3062 if( pagePath.empty() )
3063 pagePath.push_back( topSheets[i] );
3064
3065 pagePath.SetPageNumber( wxString::Format( wxS( "%zu" ), i + 1 ) );
3066 pollProgress( m_progressReporter, sj.job.page->name );
3067 m_currentOccRefs = sj.job.refs;
3068 m_currentOccUnitRefs = sj.job.unitRefs;
3069 m_currentOccProps = sj.job.props;
3070 m_currentOccNetNames = sj.job.netNames;
3071 selectOccurrenceAliases( m_currentOccNetNames );
3072 m_currentFlatNetSuffix = sj.job.flatNetSuffix;
3073 m_scopeNamedFlatNets = sj.job.scopeNamedFlatNets;
3074 m_scopeGeneratedFlatNets = sj.job.scopeGeneratedFlatNets;
3075 m_currentUnconnectedInterfaceNetNames = sj.job.unconnectedInterfaceNetNames;
3076 m_currentInterfaceNetAliases = interfaceAliasesByScope[sj.job.netNames];
3077 m_currentConnectorInterfaceNetAliases = connectorAliasesByScope[sj.job.netNames];
3078 applyPageSettings( *sj.job.page, topScreens[i] );
3079 convertPage( *sj.job.page, topScreens[i], pagePath );
3080 m_currentOccRefs = nullptr;
3081 m_currentOccUnitRefs = nullptr;
3082 m_currentOccProps = nullptr;
3083 m_currentOccNetNames = nullptr;
3084 m_currentFlatNetSuffix.clear();
3085 m_scopeNamedFlatNets = false;
3086 m_scopeGeneratedFlatNets = false;
3087 m_currentUnconnectedInterfaceNetNames.clear();
3088 m_currentInterfaceNetAliases.clear();
3089 m_currentConnectorInterfaceNetAliases.clear();
3090 }
3091 }
3092
3093 finishConversion();
3094 return aRootSheet;
3095}
3096
3097
3099{
3100 if( m_design.unreferencedFolderPages.empty() )
3101 return;
3102
3103 std::vector<SCH_SHEET*> topSheets = m_schematic->GetTopLevelSheets();
3104
3105 for( SCH_SHEET* sheet : topSheets )
3106 m_usedSheetNames.insert( sheet->GetName().Lower() );
3107
3108 std::set<wxString> usedFileNames;
3109
3110 for( const SCH_SHEET_PATH& path : m_schematic->BuildSheetListSortedByPageNumbers() )
3111 {
3112 if( path.LastScreen() )
3113 usedFileNames.insert( wxFileName( path.LastScreen()->GetFileName() ).GetFullName().Lower() );
3114 }
3115
3116 size_t fileIndex = topSheets.size() + 1;
3117
3118 struct PAGE_SHEET
3119 {
3120 ORCAD_RAW_PAGE* page;
3121 SCH_SHEET* sheet;
3122 SCH_SCREEN* screen;
3123 size_t pageNumber;
3124 };
3125
3126 std::vector<PAGE_SHEET> pageSheets;
3127
3128 for( auto& [folder, pages] : m_design.unreferencedFolderPages )
3129 {
3130 for( ORCAD_RAW_PAGE& page : pages )
3131 {
3132 SCH_SCREEN* screen = new SCH_SCREEN( m_schematic );
3133 const_cast<KIID&>( screen->GetUuid() ) = deterministicUuid( "screen", m_screenOrdinal++ );
3134 SCH_SHEET* sheet = new SCH_SHEET( m_schematic );
3135 sheet->SetScreen( screen );
3136 const_cast<KIID&>( sheet->m_Uuid ) = screen->GetUuid();
3137 sheet->SetExcludedFromBoard( true );
3138
3139 wxString name = FromOrcadString( page.name );
3140 wxString base = name;
3141
3142 for( int suffix = 2; !m_usedSheetNames.insert( name.Lower() ).second; ++suffix )
3143 name = wxString::Format( wxS( "%s (%d)" ), base, suffix );
3144
3145 size_t pageNumber = topSheets.size() + 1;
3146 wxString fileName;
3147
3148 do
3149 {
3150 fileName = MakePageFileName( static_cast<int>( fileIndex++ ), page.name );
3151 } while( !usedFileNames.insert( fileName.Lower() ).second );
3152
3154 sheet->GetField( FIELD_T::SHEET_FILENAME )->SetText( fileName );
3155 screen->SetFileName( m_schematic->Project().GetProjectPath() + fileName );
3156 topSheets.push_back( sheet );
3157 pageSheets.push_back( { &page, sheet, screen, pageNumber } );
3158 }
3159 }
3160
3161 m_schematic->SetTopLevelSheets( topSheets );
3162 m_currentOccRefs = nullptr;
3163 m_currentOccUnitRefs = nullptr;
3164 m_currentOccProps = nullptr;
3165 m_currentOccNetNames = nullptr;
3166 m_currentFlatNetSuffix.clear();
3167 m_scopeNamedFlatNets = false;
3173
3174 for( PAGE_SHEET& pageSheet : pageSheets )
3175 {
3177
3178 for( const SCH_SHEET_PATH& candidate : m_schematic->Hierarchy() )
3179 {
3180 if( candidate.Last() == pageSheet.sheet )
3181 {
3182 path = candidate;
3183 break;
3184 }
3185 }
3186
3187 if( path.empty() )
3188 path.push_back( pageSheet.sheet );
3189
3190 path.SetPageNumber( wxString::Format( wxS( "%zu" ), pageSheet.pageNumber ) );
3191 pollProgress( m_progressReporter, pageSheet.page->name );
3192 applyPageSettings( *pageSheet.page, pageSheet.screen );
3193 convertPage( *pageSheet.page, pageSheet.screen, path );
3194
3195 // Top-level sheet attributes are not serialized in schematic files.
3196 for( SCH_ITEM* item : pageSheet.screen->Items().OfType( SCH_SYMBOL_T ) )
3197 static_cast<SCH_SYMBOL*>( item )->SetExcludedFromBoard( true );
3198 }
3199}
3200
3201
3202static std::optional<VECTOR2I> safeConnectivityLabelPosition(
3203 SCH_SCREEN* aScreen, const VECTOR2I& aPosition,
3204 const std::optional<std::vector<SEG>>& aSourceWires );
3205
3206
3208 const std::vector<SEG>& aSourceWires )
3209{
3210 INTERFACE_LABEL_SOURCE source{ aScreen, aLabel, {} };
3211
3212 for( SCH_ITEM* item : aScreen->Items().OfType( SCH_LINE_T ) )
3213 {
3214 SCH_LINE* line = static_cast<SCH_LINE*>( item );
3215
3216 if( line->GetLayer() != LAYER_WIRE && line->GetLayer() != LAYER_BUS )
3217 continue;
3218
3219 const SEG emitted = line->GetSeg();
3220
3221 if( emitted.A != emitted.B
3222 && std::any_of( aSourceWires.begin(), aSourceWires.end(),
3223 [&]( const SEG& eligible )
3224 {
3225 return eligible.Contains( emitted.A ) && eligible.Contains( emitted.B );
3226 } ) )
3227 {
3228 source.wires.push_back( line );
3229 }
3230 }
3231
3232 if( !aSourceWires.empty() && source.wires.empty() )
3233 THROW_IO_ERROR( _( "OrCAD interface has no surviving source wire for label placement." ) );
3234
3235 m_interfaceLabelSources.push_back( std::move( source ) );
3236}
3237
3238
3239void ORCAD_CONVERTER::appendNetIntent( SCH_SCREEN* aScreen, SCH_LABEL* aLabel, bool aExplicitName, uint32_t aNetId )
3240{
3241 // Capture's occurrence names describe identity. They do not grant global connectivity.
3242 appendPageItem( aScreen, aLabel );
3243 m_netLabelIntents.push_back( { aScreen, aLabel, aExplicitName } );
3244
3245 if( aNetId )
3246 m_labelSourceNets[aLabel] = { aScreen, aNetId };
3247}
3248
3249
3251{
3252 using MEMBER = std::pair<SCH_SHEET_PATH, SCH_ITEM*>;
3253 auto itemKey = []( const SCH_ITEM* item )
3254 {
3255 if( item->Type() == SCH_PIN_T )
3256 {
3257 const SCH_PIN* pin = static_cast<const SCH_PIN*>( item );
3258 return wxS( "pin:" ) + pin->GetParentSymbol()->m_Uuid.AsString() + wxS( ":" ) + pin->GetNumber()
3259 + wxString::Format( wxS( ":%d:%d" ), pin->GetPosition().x, pin->GetPosition().y );
3260 }
3261
3262 return item->m_Uuid.AsString();
3263 };
3264
3265 struct SOURCE_PARTITION
3266 {
3267 std::vector<MEMBER> members;
3268 std::set<wxString> explicitNames;
3269 wxString sourceName;
3270 };
3271
3272 SCH_SHEET_LIST sheets = m_schematic->BuildSheetListSortedByPageNumbers();
3273 CONNECTION_GRAPH* graph = m_schematic->ConnectionGraph();
3274 graph->Recalculate( sheets, true );
3275 std::map<SCH_ITEM*, bool> intents;
3276
3277 for( const NET_LABEL_INTENT& intent : m_netLabelIntents )
3278 {
3279 CONNECTION_SUBGRAPH* subgraph = graph->GetSubgraphForItem( intent.label );
3280 bool bus = subgraph && subgraph->GetDriverConnection() && subgraph->GetDriverConnection()->IsBus();
3281
3282 if( !bus && !SCH_CONNECTION::IsBusLabel( intent.label->GetText() ) )
3283 intents.emplace( intent.label, intent.explicitName );
3284 }
3285
3286 // A scalar bus member needs its local name to enter the bus, even with no physical split.
3287 for( const auto& [key, subgraphs] : graph->GetNetMap() )
3288 {
3289 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
3290 {
3291 for( const auto& [member, parents] : subgraph->GetBusParents() )
3292 {
3293 wxString name = member->Name( true );
3294 SCH_LABEL_BASE* selected = nullptr;
3295 bool nativeDriver = false;
3296
3297 for( SCH_ITEM* item : subgraph->GetItems() )
3298 {
3299 auto* label = dynamic_cast<SCH_LABEL_BASE*>( item );
3300
3301 if( !label || label->GetText() != name )
3302 continue;
3303
3304 if( !intents.count( item ) )
3305 nativeDriver = true;
3306 else if( !selected || label->m_Uuid < selected->m_Uuid )
3307 selected = label;
3308 }
3309
3310 if( !nativeDriver && selected )
3311 intents.erase( selected );
3312 }
3313 }
3314 }
3315
3316 std::vector<SOURCE_PARTITION> partitions;
3317
3318 for( const auto& [key, subgraphs] : graph->GetNetMap() )
3319 {
3320 if( std::any_of( subgraphs.begin(), subgraphs.end(),
3321 []( const CONNECTION_SUBGRAPH* subgraph )
3322 {
3323 return subgraph->GetDriverConnection() && subgraph->GetDriverConnection()->IsBus();
3324 } ) )
3325 continue;
3326
3327 SOURCE_PARTITION partition;
3328 partition.sourceName = key.Name;
3329
3330 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
3331 {
3332 for( SCH_ITEM* item : subgraph->GetItems() )
3333 {
3334 auto intent = intents.find( item );
3335
3336 if( intent != intents.end() )
3337 {
3338 if( intent->second )
3339 partition.explicitNames.insert( static_cast<SCH_LABEL_BASE*>( item )->GetText() );
3340 }
3341 else if( item->Type() == SCH_PIN_T || item->Type() == SCH_SHEET_PIN_T
3342 || ( item->Type() == SCH_LINE_T && item->GetLayer() == LAYER_WIRE )
3343 || item->Type() == SCH_LABEL_T || item->Type() == SCH_GLOBAL_LABEL_T
3344 || item->Type() == SCH_HIER_LABEL_T )
3345 {
3346 partition.members.emplace_back( subgraph->GetSheet(), item );
3347 }
3348 }
3349 }
3350
3351 if( !partition.members.empty() )
3352 {
3353 // itemKey formats a UUID and a position, so key each member once instead of
3354 // rebuilding both strings on every comparison.
3355 std::vector<std::pair<wxString, MEMBER>> keyed;
3356 keyed.reserve( partition.members.size() );
3357
3358 for( MEMBER& member : partition.members )
3359 keyed.emplace_back( itemKey( member.second ), std::move( member ) );
3360
3361 std::sort( keyed.begin(), keyed.end(),
3362 []( const auto& left, const auto& right )
3363 {
3364 return left.second.first == right.second.first
3365 ? left.first < right.first
3366 : left.second.first < right.second.first;
3367 } );
3368
3369 for( size_t i = 0; i < keyed.size(); ++i )
3370 partition.members[i] = std::move( keyed[i].second );
3371
3372 partitions.push_back( std::move( partition ) );
3373 }
3374 }
3375
3376 for( const NET_LABEL_INTENT& intent : m_netLabelIntents )
3377 {
3378 if( intents.count( intent.label ) )
3379 intent.screen->Remove( intent.label );
3380 }
3381
3382 // Reset while removed items are still alive: the previous graph owns references to them.
3383 graph->Recalculate( sheets, true );
3384
3385 for( const auto& [item, explicitName] : intents )
3386 delete item;
3387
3388 m_netLabelIntents.clear();
3389
3390 auto component = [&]( const MEMBER& member ) -> std::pair<int, CONNECTION_SUBGRAPH*>
3391 {
3392 SCH_CONNECTION* connection = member.second->Connection( &member.first );
3393
3394 if( connection && connection->IsNet() && connection->NetCode() > 0 )
3395 return { connection->NetCode(), nullptr };
3396
3397 // Unannotated pins have physical subgraphs even when GetNetMap omits them.
3398 return { 0, graph->GetSubgraphForItem( member.second ) };
3399 };
3400
3401 using COMPONENT = std::pair<int, CONNECTION_SUBGRAPH*>;
3402 std::map<COMPONENT, size_t> owners;
3403
3404 for( size_t index = 0; index < partitions.size(); ++index )
3405 {
3406 for( const MEMBER& member : partitions[index].members )
3407 {
3408 COMPONENT key = component( member );
3409
3410 if( key.first == 0 && !key.second )
3411 continue;
3412
3413 auto [owner, inserted] = owners.emplace( key, index );
3414
3415 if( !inserted && owner->second != index )
3416 THROW_IO_ERROR( _( "OrCAD native connectivity joins distinct source nets." ) );
3417 }
3418 }
3419
3420 std::map<SCH_SHEET_PATH, std::map<wxString, std::set<size_t>>> reservedNames;
3421 std::map<wxString, std::set<size_t>> globalNames;
3422
3423 for( size_t index = 0; index < partitions.size(); ++index )
3424 {
3425 std::set<SCH_SHEET_PATH> memberSheets;
3426
3427 for( const MEMBER& member : partitions[index].members )
3428 {
3429 auto& names = reservedNames[member.first];
3430 SCH_CONNECTION* connection = member.second->Connection( &member.first );
3431 memberSheets.insert( member.first );
3432
3433 if( connection && connection->IsNet() )
3434 names[connection->Name( true )].insert( index );
3435
3437
3438 if( member.second->Type() != SCH_SHEET_PIN_T && priority > CONNECTION_SUBGRAPH::PRIORITY::PIN )
3439 {
3440 CONNECTION_SUBGRAPH* subgraph = graph->GetSubgraphForItem( member.second );
3441
3442 if( subgraph )
3443 {
3444 const wxString& driverName = subgraph->GetNameForDriver( member.second );
3445 names[driverName].insert( index );
3446
3448 globalNames[driverName].insert( index );
3449 }
3450 }
3451 }
3452
3453 // The explicit names belong to the partition, not to any one member of it.
3454 for( const SCH_SHEET_PATH& memberSheet : memberSheets )
3455 {
3456 auto& names = reservedNames[memberSheet];
3457
3458 for( const wxString& name : partitions[index].explicitNames )
3459 names[name].insert( index );
3460 }
3461 }
3462
3463 for( size_t index = 0; index < partitions.size(); ++index )
3464 {
3465 SOURCE_PARTITION& partition = partitions[index];
3466 std::map<SCH_SHEET_PATH, std::map<COMPONENT, std::vector<MEMBER>>> bySheet;
3467 bool hasExplicitDriver = false;
3468 bool needsDriver = false;
3469 wxString name;
3470
3471 for( const MEMBER& member : partition.members )
3472 {
3473 COMPONENT key = component( member );
3474
3475 if( key.first == 0 && !key.second )
3476 continue;
3477
3478 bySheet[member.first][key].push_back( member );
3479
3480 if( member.second->Type() == SCH_PIN_T )
3481 {
3482 CONNECTION_SUBGRAPH* subgraph = graph->GetSubgraphForItem( member.second );
3483 needsDriver |= subgraph && !subgraph->GetDriver();
3484 }
3485
3486 auto priority = CONNECTION_SUBGRAPH::GetDriverPriority( member.second );
3487
3488 if( priority > CONNECTION_SUBGRAPH::PRIORITY::PIN )
3489 {
3490 hasExplicitDriver = true;
3491 CONNECTION_SUBGRAPH* subgraph = graph->GetSubgraphForItem( member.second );
3492
3493 if( subgraph && name.IsEmpty() && member.second->Type() != SCH_SHEET_PIN_T )
3494 name = subgraph->GetNameForDriver( member.second );
3495 }
3496 }
3497
3498 bool needsBridge = std::any_of( bySheet.begin(), bySheet.end(),
3499 []( const auto& sheet ) { return sheet.second.size() > 1; } );
3500 bool needsName = needsDriver || ( !hasExplicitDriver && !partition.explicitNames.empty() );
3501
3502 if( !needsBridge && !needsName )
3503 continue;
3504
3505 if( name.IsEmpty() && !partition.explicitNames.empty() )
3506 name = *partition.explicitNames.begin();
3507
3508 const bool generatedName = name.IsEmpty();
3509
3510 if( generatedName )
3511 {
3512 for( const MEMBER& member : partition.members )
3513 {
3514 if( member.second->Type() == SCH_PIN_T )
3515 {
3516 wxString candidate = static_cast<SCH_PIN*>( member.second )->GetDefaultNetName( member.first );
3517
3518 if( !candidate.IsEmpty() && ( name.IsEmpty() || candidate < name ) )
3519 name = candidate;
3520 }
3521 }
3522
3523 if( name.IsEmpty() )
3524 name = wxS( "Net-(" ) + partition.members.front().second->m_Uuid.AsString() + wxS( ")" );
3525
3526 const wxString base = name;
3527 auto conflicts = [&]()
3528 {
3529 auto global = globalNames.find( name );
3530
3531 if( global != globalNames.end()
3532 && std::any_of( global->second.begin(), global->second.end(),
3533 [&]( size_t owner ) { return owner != index; } ) )
3534 return true;
3535
3536 for( const auto& [sheet, components] : bySheet )
3537 {
3538 const auto& names = reservedNames[sheet];
3539 auto reserved = names.find( name );
3540
3541 if( reserved != names.end()
3542 && std::any_of( reserved->second.begin(), reserved->second.end(),
3543 [&]( size_t owner ) { return owner != index; } ) )
3544 return true;
3545 }
3546
3547 return false;
3548 };
3549
3550 for( size_t suffix = 2; conflicts(); ++suffix )
3551 name = wxString::Format( wxS( "%s_%zu" ), base, suffix );
3552 }
3553
3554 for( const auto& [sheet, components] : bySheet )
3555 {
3556 if( components.size() < 2 && !needsName )
3557 continue;
3558
3559 for( const auto& [key, members] : components )
3560 {
3561 bool alreadyNamed = std::any_of( members.begin(), members.end(),
3562 [&]( const MEMBER& member )
3563 {
3564 if( member.second->Type() == SCH_SHEET_PIN_T
3565 || CONNECTION_SUBGRAPH::GetDriverPriority( member.second )
3566 <= CONNECTION_SUBGRAPH::PRIORITY::PIN )
3567 return false;
3568
3569 CONNECTION_SUBGRAPH* subgraph = graph->GetSubgraphForItem( member.second );
3570 return subgraph && subgraph->GetNameForDriver( member.second ) == name;
3571 } );
3572
3573 if( alreadyNamed )
3574 continue;
3575
3576 std::vector<SEG> wires;
3577 VECTOR2I preferred = members.front().second->GetPosition();
3578
3579 for( const MEMBER& member : members )
3580 {
3581 if( member.second->Type() == SCH_LINE_T )
3582 wires.push_back( static_cast<SCH_LINE*>( member.second )->GetSeg() );
3583 }
3584
3585 std::optional<VECTOR2I> anchor = safeConnectivityLabelPosition( sheet.LastScreen(), preferred, wires );
3586
3587 if( !anchor )
3588 THROW_IO_ERROR( wxString::Format( _( "Cannot place OrCAD net repair '%s' without a wire intersection." ), name ) );
3589
3590 SCH_LABEL* label = new SCH_LABEL( *anchor, name );
3591 const_cast<KIID&>( label->m_Uuid ) = deterministicUuid(
3592 "net-repair:" + sheet.PathAsString().ToStdString() + ":"
3593 + itemKey( members.front().second ).ToStdString() + ":" + name.ToStdString(), 0 );
3594 sheet.LastScreen()->Append( label );
3595 reservedNames[sheet][name].insert( index );
3596 needsName = false;
3597 }
3598 }
3599 }
3600
3601 graph->Recalculate( sheets, true );
3602 owners.clear();
3603
3604 for( size_t index = 0; index < partitions.size(); ++index )
3605 {
3606 std::set<COMPONENT> connected;
3607
3608 for( const MEMBER& member : partitions[index].members )
3609 {
3610 COMPONENT key = component( member );
3611
3612 if( key.first == 0 && !key.second )
3613 continue;
3614
3615 connected.insert( key );
3616 auto [owner, inserted] = owners.emplace( key, index );
3617
3618 if( !inserted && owner->second != index )
3619 {
3620 SCH_CONNECTION* connection = member.second->Connection( &member.first );
3621 THROW_IO_ERROR( wxString::Format(
3622 _( "OrCAD net repair joins source nets '%s' and '%s' at '%s' on sheet '%s' (net '%s')." ),
3623 partitions[owner->second].sourceName, partitions[index].sourceName,
3624 itemKey( member.second ), member.first.Last()->GetName(),
3625 connection ? connection->Name() : wxString() ) );
3626 }
3627 }
3628
3629 if( connected.size() > 1 )
3630 {
3631 wxString detail;
3632 std::set<COMPONENT> described;
3633
3634 for( const MEMBER& member : partitions[index].members )
3635 {
3636 if( described.insert( component( member ) ).second )
3637 {
3638 SCH_CONNECTION* connection = member.second->Connection( &member.first );
3639 detail += wxS( " " ) + member.first.Last()->GetName() + wxS( ":" )
3640 + ( connection ? connection->Name() : wxString() );
3641 }
3642 }
3643
3644 THROW_IO_ERROR( wxString::Format( _( "OrCAD source net '%s' remains disconnected after local repair:%s" ),
3645 partitions[index].sourceName, detail ) );
3646 }
3647 }
3648}
3649
3650
3651std::optional<uint32_t> ORCAD_CONVERTER::occurrenceNetIdFor( const std::string* aName ) const
3652{
3653 if( !m_currentOccNetNames || !aName )
3654 return std::nullopt;
3655
3656 // The occurrence table owns the strings, so identity is the only reliable match.
3657 auto entry = std::find_if( m_currentOccNetNames->begin(), m_currentOccNetNames->end(),
3658 [&]( const auto& aEntry )
3659 {
3660 return &aEntry.second == aName;
3661 } );
3662
3663 if( entry == m_currentOccNetNames->end() )
3664 return std::nullopt;
3665
3666 return entry->first;
3667}
3668
3669
3671{
3672 IMPORT_NET_MAP map;
3673 SCH_SHEET_LIST sheets = m_schematic->BuildSheetListSortedByPageNumbers();
3674 using TERMINAL = std::pair<SCH_SHEET_PATH, SCH_PIN*>;
3675 std::map<int, std::vector<TERMINAL>> terminals;
3676 std::map<int, wxString> netNames;
3677
3678 for( const auto& [key, subgraphs] : m_schematic->ConnectionGraph()->GetNetMap() )
3679 {
3680 for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
3681 {
3682 for( SCH_ITEM* item : subgraph->GetItems() )
3683 {
3684 if( item->Type() == SCH_PIN_T )
3685 {
3686 SCH_PIN* pin = static_cast<SCH_PIN*>( item );
3687 SCH_CONNECTION* connection = pin->Connection( &subgraph->GetSheet() );
3688
3689 if( connection && connection->IsNet() )
3690 {
3691 const int code = connection->NetCode();
3692 terminals[code].emplace_back( subgraph->GetSheet(), pin );
3693
3694 if( auto [entry, inserted] = netNames.try_emplace( code ); inserted )
3695 entry->second = connection->Name();
3696 }
3697 }
3698 }
3699 }
3700 }
3701
3702 std::map<SCH_PIN*, uint32_t> sourcePinIds;
3703
3704 struct EXTRA_RECORD
3705 {
3706 uint32_t id;
3707 std::string name;
3708 SCH_PIN* pin;
3709 bool noConnect;
3710 };
3711
3712 std::map<SCH_SCREEN*, std::vector<EXTRA_RECORD>> extraRecords;
3713
3714 for( const SCH_SHEET_PATH& sheet : sheets )
3715 {
3716 SCH_SCREEN* screen = sheet.LastScreen();
3717 auto page = m_sourcePages.find( screen );
3718
3719 if( page == m_sourcePages.end() )
3720 continue;
3721
3722 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
3723 {
3724 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
3725 auto source = m_sourceInstances.find( symbol );
3726
3727 if( source == m_sourceInstances.end() )
3728 continue;
3729
3730 const ORCAD_PLACED_INSTANCE& instance = *source->second;
3731 std::set<SCH_PIN*> assigned;
3732
3733 for( size_t index = 0; index < instance.pins.size(); ++index )
3734 {
3735 const ORCAD_PIN_INST& sourcePin = instance.pins[index];
3736 const SOURCE_PIN_IDENTITY& identity = m_sourcePinIdentities.at( symbol ).at( index );
3737
3738 if( identity.ignored )
3739 continue;
3740
3741 std::vector<SCH_PIN*> candidates;
3742
3743 for( SCH_PIN* candidate : symbol->GetPins( &sheet ) )
3744 {
3745 if( candidate->GetNumber() == identity.number && candidate->GetPosition() == identity.position
3746 && !assigned.count( candidate )
3747 && ( !identity.libraryPin || ( candidate->GetLibPin()
3748 && candidate->GetLibPin()->m_Uuid == *identity.libraryPin ) ) )
3749 {
3750 candidates.push_back( candidate );
3751 }
3752 }
3753
3754 if( candidates.size() != 1 )
3755 {
3756 warn( wxString::Format( _( "Cannot uniquely map OrCAD source pin %zu of '%s'." ),
3757 index + 1, symbol->GetRef( &sheet, false ) ) );
3758 continue;
3759 }
3760
3761 SCH_PIN* pin = candidates.front();
3762 assigned.insert( pin );
3763 sourcePinIds[pin] = index + 1;
3764 std::set<uint32_t> netIds;
3765
3766 if( sourcePin.wordB && page->second->netmap.count( sourcePin.wordB ) )
3767 netIds.insert( sourcePin.wordB );
3768
3769 if( netIds.empty() && sourcePin.wordA )
3770 {
3771 for( const ORCAD_WIRE& wire : page->second->wires )
3772 {
3773 if( !wire.isBus && wire.dbId == sourcePin.wordA )
3774 netIds.insert( wire.id );
3775 }
3776 }
3777
3778 if( netIds.empty() && !sourcePin.IsNoConnect() )
3779 {
3780 for( const ORCAD_WIRE& wire : page->second->wires )
3781 {
3782 if( !wire.isBus && onSegment( sourcePin.x, sourcePin.y, wire ) )
3783 netIds.insert( wire.id );
3784 }
3785
3786 if( netIds.size() > 1 )
3787 netIds.clear();
3788 }
3789
3790 auto wireless = m_wirelessNetNames.find( { screen, &instance, index } );
3791
3792 if( wireless != m_wirelessNetNames.end() )
3793 extraRecords[screen].push_back( { wireless->second.first, wireless->second.second, pin, false } );
3794
3795 if( sourcePin.IsNoConnect() )
3796 extraRecords[screen].push_back( { 0, std::string(), pin, true } );
3797
3798 for( uint32_t netId : netIds )
3799 m_sourceNetItems[{ screen, netId }].push_back( pin );
3800 }
3801 }
3802 }
3803
3804 for( const SCH_SHEET_PATH& sheet : sheets )
3805 {
3806 SCH_SCREEN* screen = sheet.LastScreen();
3807 auto page = m_sourcePages.find( screen );
3808
3809 if( page == m_sourcePages.end() )
3810 continue;
3811
3812 std::map<uint32_t, std::vector<const SCH_CONNECTION*>> busMembers;
3813 std::map<uint32_t, std::vector<KIID>> busMemberItems;
3814 auto busAliases = m_hierBusNamesByScreen.find( screen->GetUuid().AsStdString() );
3815
3816 for( const ORCAD_NET_GROUP& group : page->second->netGroups )
3817 {
3818 auto groupItems = m_sourceNetItems.find( { screen, group.id } );
3819
3820 if( groupItems == m_sourceNetItems.end() )
3821 continue;
3822
3823 for( SCH_ITEM* item : groupItems->second )
3824 {
3825 SCH_CONNECTION* connection = item->Connection( &sheet );
3826
3827 if( !connection || !connection->IsBus() )
3828 continue;
3829
3830 const auto members = connection->AllMembers();
3831
3832 for( uint32_t memberId : group.members )
3833 {
3834 auto sourceNames = m_sourceNetNames.find( { screen, memberId } );
3835
3836 if( sourceNames == m_sourceNetNames.end() )
3837 continue;
3838
3839 std::set<wxString> memberNames;
3840
3841 for( const std::string& sourceName : sourceNames->second )
3842 {
3843 memberNames.insert( FromOrcadString( kicadElectricalNetName( sourceName ) ) );
3844
3845 if( busAliases != m_hierBusNamesByScreen.end() )
3846 {
3847 memberNames.insert( FromOrcadString( kicadElectricalNetName(
3848 scopedHierBusMember( sourceName, busAliases->second ) ) ) );
3849 }
3850 }
3851
3852 // Source membership and the imported bus item bound this lookup to one native bus.
3853 for( const std::shared_ptr<SCH_CONNECTION>& member : members )
3854 {
3855 if( member->IsNet() && memberNames.count( member->Name( true ) ) )
3856 {
3857 busMembers[memberId].push_back( member.get() );
3858 busMemberItems[memberId].push_back( item->m_Uuid );
3859 }
3860 }
3861 }
3862 }
3863 }
3864
3865 const std::vector<wxString>& occurrence = m_sourceOccurrences[screen];
3866
3867 auto appendRecord = [&]( uint32_t id, const std::set<std::string>& names,
3868 const std::vector<SCH_ITEM*>& items, bool noConnect )
3869 {
3871 entry.view = FromOrcadString( page->second->name );
3872 entry.sourceNetId = id;
3873 entry.occurrence = occurrence;
3874
3875
3876 std::set<int> codes;
3877 std::set<wxString> finalNames;
3878 std::set<int> busCodes;
3879 std::set<wxString> finalBusNames;
3880 auto addNet = [&]( const SCH_CONNECTION* connection )
3881 {
3882 if( connection && connection->IsNet() && connection->NetCode() > 0 )
3883 {
3884 codes.insert( connection->NetCode() );
3885 auto name = netNames.find( connection->NetCode() );
3886 finalNames.insert( name != netNames.end() ? name->second : connection->Name() );
3887 }
3888 };
3889
3890 for( SCH_ITEM* item : items )
3891 {
3892 entry.itemUuids.push_back( item->Type() == SCH_PIN_T
3893 ? static_cast<SCH_PIN*>( item )->GetParentSymbol()->m_Uuid
3894 : item->m_Uuid );
3895 SCH_CONNECTION* connection = item->Connection( &sheet );
3896
3897 if( connection && connection->IsBus() )
3898 {
3899 busCodes.insert( connection->BusCode() );
3900 finalBusNames.insert( connection->Name() );
3901
3902 for( const std::shared_ptr<SCH_CONNECTION>& member : connection->AllMembers() )
3903 addNet( member.get() );
3904 }
3905 else
3906 {
3907 addNet( connection );
3908 }
3909 }
3910
3911 // A bus can retain its local member identity after the attached scalar net inherits
3912 // a parent name. Use membership only when no physical scalar connection is available.
3913 if( codes.empty() )
3914 {
3915 for( const SCH_CONNECTION* member : busMembers[id] )
3916 addNet( member );
3917 }
3918
3919 entry.itemUuids.insert( entry.itemUuids.end(), busMemberItems[id].begin(), busMemberItems[id].end() );
3920
3921 std::set<std::tuple<KIID, int, wxString, unsigned>> seen;
3922
3923 std::vector<TERMINAL> sourceTerminals;
3924
3925 for( int code : codes )
3926 sourceTerminals.insert( sourceTerminals.end(), terminals[code].begin(), terminals[code].end() );
3927
3928 for( SCH_ITEM* item : items )
3929 {
3930 if( item->Type() == SCH_PIN_T )
3931 sourceTerminals.emplace_back( sheet, static_cast<SCH_PIN*>( item ) );
3932 }
3933
3934 for( const auto& [pinSheet, pin] : sourceTerminals )
3935 {
3936 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( pin->GetParentSymbol() );
3937 unsigned duplicateIndex = 0;
3938 auto pinIdentity = [&]( SCH_PIN* candidate )
3939 {
3940 const SCH_PIN* definition = candidate->GetLibPin() ? candidate->GetLibPin() : candidate;
3941 VECTOR2I position = definition->GetPosition();
3942 return std::tuple( definition->GetUnit(), definition->GetBodyStyle(), position.x, position.y,
3943 definition->GetOrientation(), definition->GetName(), definition->GetType(),
3944 sourcePinIds[candidate] );
3945 };
3946
3947 for( SCH_PIN* peer : symbol->GetPins( &pinSheet ) )
3948 {
3949 if( peer->GetNumber() == pin->GetNumber() && pinIdentity( peer ) < pinIdentity( pin ) )
3950 ++duplicateIndex;
3951 }
3952
3953 int unit = symbol->GetUnitSelection( &pinSheet );
3954
3955 if( !seen.emplace( symbol->m_Uuid, unit, pin->GetNumber(), duplicateIndex ).second )
3956 continue;
3957
3958 entry.terminals.push_back( { symbol->m_Uuid, unit, sourcePinIds[pin], pin->GetNumber(),
3959 duplicateIndex } );
3960 }
3961
3962 std::sort( entry.terminals.begin(), entry.terminals.end(),
3964 {
3965 return std::tie( left.symbolUuid, left.unit, left.pinNumber, left.duplicateIndex )
3966 < std::tie( right.symbolUuid, right.unit, right.pinNumber, right.duplicateIndex );
3967 } );
3968 std::sort( entry.itemUuids.begin(), entry.itemUuids.end() );
3970
3971 if( noConnect )
3973 else if( !busCodes.empty() )
3974 entry.status = busCodes.size() == 1 ? IMPORT_NET_STATUS::BUS : IMPORT_NET_STATUS::SPLIT;
3975 else if( codes.empty() )
3977 else
3978 entry.status = codes.size() == 1 ? IMPORT_NET_STATUS::RESOLVED : IMPORT_NET_STATUS::SPLIT;
3979
3980 if( busCodes.size() == 1 && finalBusNames.size() == 1 )
3981 entry.nameAtImport = *finalBusNames.begin();
3982 else if( busCodes.empty() && finalNames.size() == 1 )
3983 entry.nameAtImport = *finalNames.begin();
3984
3985 for( const std::string& name : names )
3986 {
3988 entry.generatedName.clear();
3989
3990 if( auto generated = m_sourceGeneratedNetNames.find( { screen, id } );
3991 generated != m_sourceGeneratedNetNames.end()
3992 && kicadOccurrenceNetName( name ) == generated->second )
3993 {
3994 entry.generatedName = FromOrcadString( generated->second );
3995 }
3996
3997 map.entries.push_back( entry );
3998 }
3999 };
4000
4001 // Keyed on (screen, net), so this screen's nets are contiguous; seeking beats a full scan
4002 // once a design has many sheets.
4003 static const std::vector<SCH_ITEM*> noItems;
4004
4005 for( auto it = m_sourceNetNames.lower_bound( { screen, 0 } );
4006 it != m_sourceNetNames.end() && it->first.first == screen; ++it )
4007 {
4008 auto items = m_sourceNetItems.find( it->first );
4009
4010 appendRecord( it->first.second, it->second,
4011 items != m_sourceNetItems.end() ? items->second : noItems, false );
4012 }
4013
4014 for( const EXTRA_RECORD& record : extraRecords[screen] )
4015 appendRecord( record.id, { record.name }, { record.pin }, record.noConnect );
4016 }
4017
4018 std::sort( map.entries.begin(), map.entries.end(),
4020 {
4021 auto leftKey = std::tie( left.view, left.occurrence, left.sourceNetId, left.originalName,
4022 left.nameAtImport, left.status, left.itemUuids );
4023 auto rightKey = std::tie( right.view, right.occurrence, right.sourceNetId, right.originalName,
4024 right.nameAtImport, right.status, right.itemUuids );
4025
4026 if( leftKey != rightKey )
4027 return leftKey < rightKey;
4028
4029 return std::lexicographical_compare( left.terminals.begin(), left.terminals.end(),
4030 right.terminals.begin(), right.terminals.end(),
4031 []( const IMPORT_NET_TERMINAL& a, const IMPORT_NET_TERMINAL& b )
4032 {
4033 return std::tie( a.symbolUuid, a.unit, a.pinNumber, a.duplicateIndex,
4034 a.sourcePinId )
4035 < std::tie( b.symbolUuid, b.unit, b.pinNumber, b.duplicateIndex,
4036 b.sourcePinId );
4037 } );
4038 } );
4039
4040 m_schematic->SetImportNetMap( std::move( map ) );
4041}
4042
4043
4045{
4046 // finalizeNativePowerPackages consumes m_placedPackageUnits, so it must run again for the
4047 // units convertUnreferencedPages places after the first call.
4052}
4053
4054
4056{
4057 for( const auto& [label, sourceKey] : m_labelSourceNets )
4058 {
4059 std::vector<SEG> wires;
4060 auto sourceItems = m_sourceNetItems.find( sourceKey );
4061
4062 if( sourceItems != m_sourceNetItems.end() )
4063 {
4064 for( SCH_ITEM* item : sourceItems->second )
4065 {
4066 if( item->Type() == SCH_LINE_T )
4067 wires.push_back( static_cast<SCH_LINE*>( item )->GetSeg() );
4068 }
4069 }
4070
4071 auto position = safeConnectivityLabelPosition( sourceKey.first, label->GetPosition(), wires );
4072
4073 if( !position )
4074 THROW_IO_ERROR( wxString::Format( _( "Cannot place OrCAD label '%s' without a wire intersection." ),
4075 label->GetText() ) );
4076
4077 sourceKey.first->Remove( label );
4078 label->SetPosition( *position );
4079 sourceKey.first->Append( label );
4080 }
4081
4082 m_labelSourceNets.clear();
4083
4084 for( const INTERFACE_LABEL_SOURCE& source : m_interfaceLabelSources )
4085 {
4086 std::vector<SEG> wires;
4087
4088 for( const SCH_LINE* wire : source.wires )
4089 wires.push_back( wire->GetSeg() );
4090
4091 auto position = safeConnectivityLabelPosition( source.screen, source.label->GetPosition(), wires );
4092
4093 if( !position )
4094 THROW_IO_ERROR( wxString::Format( _( "Cannot place OrCAD interface '%s' clear of wires and pins." ),
4095 source.label->GetText() ) );
4096
4097 source.screen->Remove( source.label );
4098 source.label->SetPosition( *position );
4099 source.screen->Append( source.label );
4100 }
4101
4103
4104 std::map<SCH_LINE*, std::vector<std::pair<SCH_SCREEN*, uint32_t>>> wireSources;
4105
4106 for( const auto& [source, items] : m_sourceNetItems )
4107 {
4108 for( SCH_ITEM* item : items )
4109 {
4110 if( item->Type() == SCH_LINE_T )
4111 wireSources[static_cast<SCH_LINE*>( item )].push_back( source );
4112 }
4113 }
4114
4115 // Pins touching wire interiors must connect before repairing nets or recording their names.
4116 m_schematic->FixupJunctionsAfterImport(
4117 [&]( SCH_LINE* original, SCH_LINE* segment )
4118 {
4119 const VECTOR2I start = segment->GetStartPoint();
4120 const VECTOR2I end = segment->GetEndPoint();
4121 const_cast<KIID&>( segment->m_Uuid ) = KIID::FromName(
4122 "orcad-import:split:" + original->m_Uuid.AsStdString() + ":"
4123 + std::to_string( start.x ) + ":" + std::to_string( start.y ) + ":"
4124 + std::to_string( end.x ) + ":" + std::to_string( end.y ) );
4125 std::vector<std::pair<SCH_SCREEN*, uint32_t>> sources = wireSources[original];
4126
4127 for( const auto& source : sources )
4128 m_sourceNetItems[source].push_back( segment );
4129
4130 wireSources[segment] = std::move( sources );
4131 } );
4132
4135}
4136
4137
4139 bool aContainerPage, bool aSharedFolderPage )
4140{
4141 m_pageItemScreen = aScreen;
4142 m_pageItems.clear();
4143 m_sourcePages[aScreen] = &aPage;
4144 std::vector<wxString> occurrencePath = { FromOrcadString( m_design.library.schematicName ) };
4145 std::vector<std::vector<wxString>> exactOccurrences;
4146 std::vector<std::vector<wxString>> equivalentOccurrences;
4147 std::function<void( const ORCAD_OCC_SCOPE& )> findOccurrences = [&]( const ORCAD_OCC_SCOPE& scope )
4148 {
4149 if( m_currentOccRefs == &scope.partRefs || m_currentOccNetNames == &scope.netNames )
4150 exactOccurrences.push_back( occurrencePath );
4151 else if( m_currentOccRefs && m_currentOccNetNames && *m_currentOccRefs == scope.partRefs
4152 && *m_currentOccNetNames == scope.netNames )
4153 equivalentOccurrences.push_back( occurrencePath );
4154
4155 for( const ORCAD_OCC_BLOCK& block : scope.blocks )
4156 {
4157 occurrencePath.push_back( wxString::Format( wxS( "%u" ), block.targetDbId ) );
4158 occurrencePath.push_back( FromOrcadString( block.childFolder ) );
4159 findOccurrences( block.scope );
4160 occurrencePath.pop_back();
4161 occurrencePath.pop_back();
4162 }
4163 };
4164 findOccurrences( m_design.occurrenceRoot );
4165
4166 if( exactOccurrences.size() == 1 )
4167 occurrencePath = exactOccurrences.front();
4168 else if( equivalentOccurrences.size() == 1 )
4169 occurrencePath = equivalentOccurrences.front();
4171 {
4172 // Keep ambiguous source occurrences distinct without assigning another occurrence's identity.
4173 for( size_t index = 0; index < aSheetPath.size(); ++index )
4174 occurrencePath.push_back( aSheetPath.at( index )->m_Uuid.AsString() );
4175 }
4176
4177 m_sourceOccurrences[aScreen] = std::move( occurrencePath );
4178
4179 for( const auto& [id, name] : aPage.netmap )
4180 m_sourceNetNames[{ aScreen, id }].insert( name );
4181
4182 for( const auto& [id, aliases] : aPage.netAliases )
4183 m_sourceNetNames[{ aScreen, id }].insert( aliases.begin(), aliases.end() );
4184
4185 for( const ORCAD_WIRE& wire : aPage.wires )
4186 {
4187 for( const ORCAD_ALIAS& alias : wire.aliases )
4188 m_sourceNetNames[{ aScreen, wire.id }].insert( alias.name );
4189 }
4190
4191 placePageFrame( aPage, aScreen );
4192 applyTitleBlock( aPage, aScreen );
4193 buildNetLookup( aPage );
4194
4195 placeJunctions( aPage, aScreen );
4196 placeBusEntries( aPage, aScreen );
4197 bool hierarchical = aContainerPage || !aSheetPath.Last()->IsTopLevelSheet();
4198 bool sharedFolderPage = aSharedFolderPage
4199 || std::any_of(
4200 m_design.childFolderPages.begin(), m_design.childFolderPages.end(),
4201 [&]( const auto& aFolder )
4202 {
4203 return aFolder.second.size() > 1
4204 && std::any_of( aFolder.second.begin(), aFolder.second.end(),
4205 [&]( const ORCAD_RAW_PAGE& aCandidate )
4206 {
4207 return &aCandidate == &aPage;
4208 } );
4209 } );
4210 wxString syntheticPageSuffix = aSheetPath.Last()->GetName().Mid( 5 );
4211 syntheticPageSuffix.Trim( true ).Trim( false );
4212 bool syntheticPage = aSheetPath.Last()->GetName().StartsWith( wxS( "Sheet" ) )
4213 && !syntheticPageSuffix.empty()
4214 && std::all_of( syntheticPageSuffix.begin(), syntheticPageSuffix.end(),
4215 []( wxUniChar c )
4216 {
4217 return wxIsdigit( c );
4218 } );
4219 sharedFolderPage |= syntheticPage;
4220 bool semanticNestedHierarchy = aSheetPath.size() > 2 && !sharedFolderPage
4221 && !aSheetPath.Last()->GetPins().empty();
4222 placeWires( aPage, aScreen, hierarchical, semanticNestedHierarchy, sharedFolderPage );
4223 placeOffpageConnectors( aPage, aScreen, aSheetPath, hierarchical );
4224 placePorts( aPage, aScreen, !aSheetPath.Last()->IsTopLevelSheet() );
4225 placeGraphics( aPage, aScreen );
4226
4227 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
4228 placeInstance( aPage, instance, aScreen, aSheetPath );
4229
4230 for( const ORCAD_GRAPHIC_INST& global : aPage.globals )
4231 placePowerSymbol( aPage, global, powerNet( aPage, global ), aScreen, aSheetPath );
4232
4233 assignPageItemUuids( m_pageOrdinal++ );
4234 m_pageItemScreen = nullptr;
4235 m_pageItems.clear();
4236}
4237
4238
4240{
4241 if( !aPage.borderPrinted && !aPage.gridRefPrinted )
4242 return;
4243
4244 int widthDbu = KiROUND( aPage.isMetric ? aPage.width / 254.0 : aPage.width / 10.0 );
4245 int heightDbu = KiROUND( aPage.isMetric ? aPage.height / 254.0 : aPage.height / 10.0 );
4246
4247 if( widthDbu <= 0 || heightDbu <= 0 )
4248 return;
4249
4250 ORCAD_PRIMITIVE border;
4252 border.lineWidth = 0;
4253 border.lineStyle = 0;
4254 const KIGFX::COLOR4D color( 0.0, 0.0, 0.0, 1.0 );
4255
4256 auto addLine = [&]( std::initializer_list<ORCAD_POINT> aPoints )
4257 {
4258 std::vector<VECTOR2I> points;
4259
4260 for( const ORCAD_POINT& point : aPoints )
4261 points.push_back( OrcadDbuToIu( point.x, point.y ) );
4262
4263 appendPageItem( aScreen, makeSheetPoly( points, border, color ) );
4264 };
4265
4266 addLine( { { 0, 0 }, { widthDbu, 0 }, { widthDbu, heightDbu }, { 0, heightDbu }, { 0, 0 } } );
4267
4268 if( !aPage.gridRefPrinted )
4269 return;
4270
4271 int horizontalBand = KiROUND( aPage.isMetric ? aPage.horizontalWidth / 254.0
4272 : aPage.horizontalWidth / 10.0 );
4273 int verticalBand = KiROUND( aPage.isMetric ? aPage.verticalWidth / 254.0 : aPage.verticalWidth / 10.0 );
4274 horizontalBand = std::clamp( horizontalBand, 0, heightDbu / 2 );
4275 verticalBand = std::clamp( verticalBand, 0, widthDbu / 2 );
4276
4277 if( horizontalBand > 0 && verticalBand > 0 )
4278 {
4279 addLine( { { verticalBand, horizontalBand }, { widthDbu - verticalBand, horizontalBand },
4280 { widthDbu - verticalBand, heightDbu - horizontalBand },
4281 { verticalBand, heightDbu - horizontalBand }, { verticalBand, horizontalBand } } );
4282 }
4283
4284 auto addLabel = [&]( const wxString& aContent, int aX, int aY )
4285 {
4286 SCH_TEXT* label = new SCH_TEXT( OrcadDbuToIu( aX, aY ), aContent, LAYER_NOTES );
4287 int size = OrcadDbuToIu( 5, 5 ).x;
4288 label->SetTextSize( VECTOR2I( size, size ) );
4289 label->SetFont( KIFONT::FONT::GetFont( wxS( "Arial" ), false, false ) );
4292 label->SetTextColor( color );
4293 appendPageItem( aScreen, label );
4294 };
4295
4296 for( int i = 0; i < aPage.horizontalCount; ++i )
4297 {
4298 int left = KiROUND( static_cast<double>( widthDbu ) * i / aPage.horizontalCount );
4299 int right = KiROUND( static_cast<double>( widthDbu ) * ( i + 1 ) / aPage.horizontalCount );
4300 int value = aPage.horizontalAscending ? i : aPage.horizontalCount - i - 1;
4301 wxString label = aPage.horizontalChar ? wxString( static_cast<wxUniChar>( 'A' + value ) )
4302 : wxString::Format( wxS( "%d" ), value + 1 );
4303
4304 if( i > 0 )
4305 {
4306 addLine( { { left, 0 }, { left, horizontalBand } } );
4307 addLine( { { left, heightDbu - horizontalBand }, { left, heightDbu } } );
4308 }
4309
4310 addLabel( label, ( left + right ) / 2, horizontalBand / 2 );
4311 addLabel( label, ( left + right ) / 2, heightDbu - horizontalBand / 2 );
4312 }
4313
4314 for( int i = 0; i < aPage.verticalCount; ++i )
4315 {
4316 int top = KiROUND( static_cast<double>( heightDbu ) * i / aPage.verticalCount );
4317 int bottom = KiROUND( static_cast<double>( heightDbu ) * ( i + 1 ) / aPage.verticalCount );
4318 int value = aPage.verticalAscending ? i : aPage.verticalCount - i - 1;
4319 wxString label = aPage.verticalChar ? wxString( static_cast<wxUniChar>( 'A' + value ) )
4320 : wxString::Format( wxS( "%d" ), value + 1 );
4321
4322 if( i > 0 )
4323 {
4324 addLine( { { 0, top }, { verticalBand, top } } );
4325 addLine( { { widthDbu - verticalBand, top }, { widthDbu, top } } );
4326 }
4327
4328 addLabel( label, verticalBand / 2, ( top + bottom ) / 2 );
4329 addLabel( label, widthDbu - verticalBand / 2, ( top + bottom ) / 2 );
4330 }
4331}
4332
4333
4334wxString ORCAD_CONVERTER::MakePageFileName( int aPageIndex, const std::string& aPageName )
4335{
4336 wxString fileName =
4337 wxString::Format( wxS( "P%02d_" ), aPageIndex ) + SanitizeFileName( aPageName ) + wxS( ".kicad_sch" );
4338
4339 ReplaceIllegalFileNameChars( fileName, '_' );
4340
4341 return fileName;
4342}
4343
4344
4345wxString ORCAD_CONVERTER::SanitizeFileName( const std::string& aName )
4346{
4347 const wxString illegal( wxS( "<>:\"/\\|?*" ) );
4348 wxString in = FromOrcadString( aName );
4349 wxString out;
4350
4351 for( wxUniChar c : in )
4352 {
4353 if( c.GetValue() < 0x20 || illegal.Find( c ) != wxNOT_FOUND )
4354 out += '_';
4355 else
4356 out += c;
4357 }
4358
4359 while( !out.IsEmpty() && ( out.Last() == ' ' || out.Last() == '.' ) )
4360 out.RemoveLast();
4361
4362 while( !out.IsEmpty() && ( out.GetChar( 0 ) == ' ' || out.GetChar( 0 ) == '.' ) )
4363 out.Remove( 0, 1 );
4364
4365 if( out.IsEmpty() )
4366 out = wxS( "unnamed" );
4367
4368 return out;
4369}
4370
4371
4373{
4374 if( aPage.width > 0 && aPage.height > 0 )
4375 {
4376 double widthMils = aPage.isMetric ? aPage.width / 25.4 : aPage.width;
4377 double heightMils = aPage.isMetric ? aPage.height / 25.4 : aPage.height;
4378
4379 PAGE_INFO::SetCustomWidthMils( widthMils );
4380 PAGE_INFO::SetCustomHeightMils( heightMils );
4381
4382 PAGE_INFO pageInfo;
4383 pageInfo.SetType( PAGE_SIZE_TYPE::User );
4384 aScreen->SetPageSettings( pageInfo );
4385 return;
4386 }
4387
4388 BOX2I extent = pageExtentDbu( aPage );
4389
4390 // Nominal paper from stored page size; mils, or micrometres when metric.
4391 double k = aPage.isMetric ? 0.001 : 0.0254;
4392 double nominalWmm = aPage.width * k;
4393 double nominalHmm = aPage.height * k;
4394
4395 // The clearance shift below pushes a full-page drawing past its own paper, so a
4396 // named size only survives while the content stays at source coordinates
4397 PAGE_INFO named;
4398
4399 if( !aPage.pageSize.empty() && named.SetType( FromOrcadString( aPage.pageSize ), nominalHmm > nominalWmm )
4400 && named.GetType() != PAGE_SIZE_TYPE::User && extent.GetLeft() >= 0 && extent.GetTop() >= 0
4401 && extent.GetRight() * DBU_TO_MM <= named.GetWidthMM()
4402 && extent.GetBottom() * DBU_TO_MM <= named.GetHeightMM() )
4403 {
4404 aScreen->SetPageSettings( named );
4405 return;
4406 }
4407
4408 // Shift content clear of frame, round up to 10-DBU grid to keep points on grid.
4409 int dx = std::max( 0, MARGIN_L_DBU - extent.GetLeft() );
4410 int dy = std::max( 0, MARGIN_T_DBU - extent.GetTop() );
4411
4412 dx = ( dx + 9 ) / 10 * 10;
4413 dy = ( dy + 9 ) / 10 * 10;
4414
4415 int maxX = extent.GetRight();
4416 int maxY = extent.GetBottom();
4417
4418 if( dx || dy )
4419 {
4420 offsetPage( aPage, dx, dy );
4421 maxX += dx;
4422 maxY += dy;
4423 }
4424
4425 // Needed paper = shifted content extent plus right/bottom margins.
4426 double neededWmm = ( maxX + MARGIN_R_DBU ) * DBU_TO_MM;
4427 double neededHmm = ( maxY + MARGIN_B_DBU ) * DBU_TO_MM;
4428
4429 int paperWmm = static_cast<int>( std::ceil( std::max( nominalWmm, neededWmm ) ) );
4430 int paperHmm = static_cast<int>( std::ceil( std::max( nominalHmm, neededHmm ) ) );
4431
4432 PAGE_INFO pageInfo;
4433 PAGE_INFO::SetCustomWidthMils( paperWmm * 1000.0 / 25.4 );
4434 PAGE_INFO::SetCustomHeightMils( paperHmm * 1000.0 / 25.4 );
4435 pageInfo.SetType( PAGE_SIZE_TYPE::User );
4436
4437 aScreen->SetPageSettings( pageInfo );
4438}
4439
4440
4442{
4443 bool any = false;
4444 int minX = 0;
4445 int minY = 0;
4446 int maxX = 0;
4447 int maxY = 0;
4448
4449 auto add = [&]( int aX, int aY )
4450 {
4451 if( !any )
4452 {
4453 minX = maxX = aX;
4454 minY = maxY = aY;
4455 any = true;
4456 }
4457 else
4458 {
4459 minX = std::min( minX, aX );
4460 minY = std::min( minY, aY );
4461 maxX = std::max( maxX, aX );
4462 maxY = std::max( maxY, aY );
4463 }
4464 };
4465
4466 for( const ORCAD_WIRE& wire : aPage.wires )
4467 {
4468 add( wire.x1, wire.y1 );
4469 add( wire.x2, wire.y2 );
4470 }
4471
4472 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
4473 {
4474 add( instance.bbox.x1, instance.bbox.y1 );
4475 add( instance.bbox.x2, instance.bbox.y2 );
4476
4477 for( const ORCAD_PIN_INST& pin : instance.pins )
4478 add( pin.x, pin.y );
4479 }
4480
4481 for( const std::vector<ORCAD_GRAPHIC_INST>* list :
4482 { &aPage.globals, &aPage.offpage, &aPage.ports, &aPage.ercObjects } )
4483 {
4484 for( const ORCAD_GRAPHIC_INST& inst : *list )
4485 {
4486 add( inst.x, inst.y );
4487 add( inst.bbox.x1, inst.bbox.y1 );
4488 add( inst.bbox.x2, inst.bbox.y2 );
4489 }
4490 }
4491
4492 // Free graphics outer bbox = anchor-relative junk; only nested primitives carry real coords.
4493 for( const ORCAD_GRAPHIC_INST& gfx : aPage.graphics )
4494 {
4495 if( !gfx.nested )
4496 continue;
4497
4498 for( const ORCAD_PRIMITIVE& prim : gfx.nested->primitives )
4499 {
4500 if( !prim.points.empty() )
4501 {
4502 for( const ORCAD_POINT& pt : prim.points )
4503 add( pt.x, pt.y );
4504 }
4505 else
4506 {
4507 add( prim.x1, prim.y1 );
4508 add( prim.x2, prim.y2 );
4509 }
4510 }
4511 }
4512
4513 for( const ORCAD_BUS_ENTRY& entry : aPage.busEntries )
4514 {
4515 add( entry.x1, entry.y1 );
4516 add( entry.x2, entry.y2 );
4517 }
4518
4519 for( const ORCAD_DRAWN_INSTANCE& block : aPage.blocks )
4520 {
4521 add( block.x1, block.y1 );
4522 add( block.x1 + block.w, block.y1 + block.h );
4523
4524 for( const ORCAD_BLOCK_PIN& pin : block.pins )
4525 add( pin.x, pin.y );
4526 }
4527
4528 if( !any )
4529 return BOX2I( VECTOR2I( 0, 0 ), VECTOR2I( 3800, 2700 ) );
4530
4531 return BOX2I( VECTOR2I( minX, minY ), VECTOR2I( maxX - minX, maxY - minY ) );
4532}
4533
4534
4535void ORCAD_CONVERTER::offsetPage( ORCAD_RAW_PAGE& aPage, int aDx, int aDy )
4536{
4537 for( ORCAD_WIRE& wire : aPage.wires )
4538 {
4539 wire.x1 += aDx;
4540 wire.y1 += aDy;
4541 wire.x2 += aDx;
4542 wire.y2 += aDy;
4543
4544 for( ORCAD_ALIAS& alias : wire.aliases )
4545 {
4546 alias.x += aDx;
4547 alias.y += aDy;
4548 }
4549 }
4550
4551 for( ORCAD_PLACED_INSTANCE& instance : aPage.instances )
4552 {
4553 instance.x += aDx;
4554 instance.y += aDy;
4555 instance.bbox.x1 += aDx;
4556 instance.bbox.y1 += aDy;
4557 instance.bbox.x2 += aDx;
4558 instance.bbox.y2 += aDy;
4559
4560 for( ORCAD_PIN_INST& pin : instance.pins )
4561 {
4562 pin.x += aDx;
4563 pin.y += aDy;
4564 }
4565 }
4566
4567 auto shiftGraphic = [&]( ORCAD_GRAPHIC_INST& aInst )
4568 {
4569 aInst.x += aDx;
4570 aInst.y += aDy;
4571 aInst.bbox.x1 += aDx;
4572 aInst.bbox.y1 += aDy;
4573 aInst.bbox.x2 += aDx;
4574 aInst.bbox.y2 += aDy;
4575
4576 if( !aInst.nested )
4577 return;
4578
4579 for( ORCAD_PRIMITIVE& prim : aInst.nested->primitives )
4580 {
4581 prim.x1 += aDx;
4582 prim.y1 += aDy;
4583 prim.x2 += aDx;
4584 prim.y2 += aDy;
4585
4586 for( ORCAD_POINT& pt : prim.points )
4587 {
4588 pt.x += aDx;
4589 pt.y += aDy;
4590 }
4591
4592 if( prim.start )
4593 {
4594 prim.start->x += aDx;
4595 prim.start->y += aDy;
4596 }
4597
4598 if( prim.end )
4599 {
4600 prim.end->x += aDx;
4601 prim.end->y += aDy;
4602 }
4603 }
4604 };
4605
4606 for( std::vector<ORCAD_GRAPHIC_INST>* list :
4607 { &aPage.globals, &aPage.offpage, &aPage.ports, &aPage.ercObjects, &aPage.graphics } )
4608 {
4609 for( ORCAD_GRAPHIC_INST& inst : *list )
4610 shiftGraphic( inst );
4611 }
4612
4613 for( ORCAD_BUS_ENTRY& entry : aPage.busEntries )
4614 {
4615 entry.x1 += aDx;
4616 entry.y1 += aDy;
4617 entry.x2 += aDx;
4618 entry.y2 += aDy;
4619 }
4620
4621 for( ORCAD_DRAWN_INSTANCE& block : aPage.blocks )
4622 {
4623 block.x1 += aDx;
4624 block.y1 += aDy;
4625
4626 for( ORCAD_BLOCK_PIN& pin : block.pins )
4627 {
4628 pin.x += aDx;
4629 pin.y += aDy;
4630 }
4631 }
4632}
4633
4634
4636{
4637 for( const ORCAD_GRAPHIC_INST& tbInst : aPage.titleBlocks )
4638 {
4639 auto symbolIt = m_design.symbols.find( tbInst.name );
4640 const ORCAD_SYMBOL_DEF* symbolDef = symbolIt != m_design.symbols.end() ? &symbolIt->second : nullptr;
4641
4642 if( symbolDef )
4643 {
4644 auto normalizedPath = []( std::string aPath )
4645 {
4646 std::transform( aPath.begin(), aPath.end(), aPath.begin(),
4647 []( unsigned char aChar )
4648 {
4649 return aChar == '\\' ? '/' : static_cast<char>( std::tolower( aChar ) );
4650 } );
4651 return aPath;
4652 };
4653
4654 bool sourceMatched = false;
4655
4656 if( auto source = tbInst.props.find( "Source Library" );
4657 source != tbInst.props.end() && !source->second.empty() )
4658 {
4659 std::string sourceKey = normalizedPath( source->second );
4660
4661 if( normalizedPath( symbolDef->sourceLib ) == sourceKey )
4662 {
4663 sourceMatched = true;
4664 }
4665 else
4666 {
4667 for( const ORCAD_SYMBOL_DEF& variant : symbolIt->second.variants )
4668 {
4669 if( normalizedPath( variant.sourceLib ) == sourceKey )
4670 {
4671 symbolDef = &variant;
4672 sourceMatched = true;
4673 break;
4674 }
4675 }
4676 }
4677 }
4678
4679 bool dimensionsEncoded = tbInst.bbox.x2 < tbInst.bbox.x1 || tbInst.bbox.y2 < tbInst.bbox.y1;
4680 int placedWidth = std::abs( dimensionsEncoded ? tbInst.bbox.x2 : tbInst.bbox.x2 - tbInst.bbox.x1 );
4681 int placedHeight = std::abs( dimensionsEncoded ? tbInst.bbox.y2 : tbInst.bbox.y2 - tbInst.bbox.y1 );
4682
4683 auto matchesPlacedBounds = [&]( const ORCAD_SYMBOL_DEF& aDefinition )
4684 {
4685 if( !aDefinition.bbox )
4686 return false;
4687
4688 int width = std::abs( aDefinition.bbox->x2 - aDefinition.bbox->x1 );
4689 int height = std::abs( aDefinition.bbox->y2 - aDefinition.bbox->y1 );
4690
4691 if( tbInst.rotation & 1 )
4692 std::swap( width, height );
4693
4694 return width == placedWidth && height == placedHeight;
4695 };
4696
4697 if( !sourceMatched && !matchesPlacedBounds( *symbolDef ) )
4698 {
4699 for( const ORCAD_SYMBOL_DEF& variant : symbolIt->second.variants )
4700 {
4701 if( matchesPlacedBounds( variant ) )
4702 {
4703 symbolDef = &variant;
4704 break;
4705 }
4706 }
4707 }
4708 }
4709
4710 auto calendarDate = [&]( uint32_t aTimestamp )
4711 {
4712 return tbInst.name == "TITLEBLK/Rudy" ? orcadShortCalendarDate( aTimestamp )
4713 : orcadCalendarDate( aTimestamp );
4714 };
4715
4716 auto sourceValue = [&]( const std::string& aKey ) -> std::string
4717 {
4718 if( aKey == "Page Size" )
4719 return aPage.pageSize;
4720
4721 if( aKey == "Page Number" && aPage.sourcePageNumber != 0 )
4722 return std::to_string( aPage.sourcePageNumber );
4723
4724 if( aKey == "Page Count" && aPage.sourcePageCount != 0 )
4725 return std::to_string( aPage.sourcePageCount );
4726
4727 if( aKey == "SIZE" || aKey == "Page Number" || aKey == "Page Count" )
4728 {
4729 if( auto value = tbInst.props.find( aKey ); value != tbInst.props.end() && !value->second.empty() )
4730 {
4731 return value->second;
4732 }
4733
4734 if( auto value = aPage.props.find( aKey );
4735 value != aPage.props.end() && !value->second.empty() )
4736 {
4737 return value->second;
4738 }
4739
4740 if( symbolDef )
4741 {
4742 if( auto value = symbolDef->props.find( aKey );
4743 value != symbolDef->props.end() && !value->second.empty() )
4744 {
4745 return value->second;
4746 }
4747 }
4748 }
4749
4750 if( aKey == "SIZE" )
4751 return aPage.pageSize == "Custom" ? "N/A" : aPage.pageSize;
4752
4753 if( ( aKey == "Page Modify Date" || aKey == "Schematic Modify Date" )
4754 && aPage.modifyTimestamp != 0 )
4755 return calendarDate( aPage.modifyTimestamp );
4756
4757 if( aKey == "Design Modify Date" && m_design.library.modifyTimestamp != 0 )
4758 return calendarDate( m_design.library.modifyTimestamp );
4759
4760 if( ( aKey == "Page Create Date" || aKey == "Schematic Create Date" )
4761 && aPage.createTimestamp != 0 )
4762 return calendarDate( aPage.createTimestamp );
4763
4764 if( aKey == "Design Create Date" && m_design.library.createTimestamp != 0 )
4765 return calendarDate( m_design.library.createTimestamp );
4766
4767 if( auto value = tbInst.props.find( aKey ); value != tbInst.props.end() && !value->second.empty() )
4768 return value->second;
4769
4770 if( auto value = aPage.props.find( aKey ); value != aPage.props.end() && !value->second.empty() )
4771 return value->second;
4772
4773 if( symbolDef )
4774 {
4775 if( auto value = symbolDef->props.find( aKey );
4776 value != symbolDef->props.end() && !value->second.empty() )
4777 {
4778 return value->second;
4779 }
4780 }
4781
4782 if( aKey == "Page Modify Date" )
4783 return calendarDate( aPage.modifyTimestamp );
4784
4785 return {};
4786 };
4787
4788 if( symbolDef )
4789 {
4790 placeDefinitionVectors( *symbolDef, tbInst.bbox.x1, tbInst.bbox.y1,
4791 OrcadOrientOf( tbInst.rotation, tbInst.mirror ), aScreen, 35.0 / 32.0,
4792 6.0 / 5.0, true );
4793 placeDefinitionImages( *symbolDef, tbInst.bbox.x1, tbInst.bbox.y1,
4794 OrcadOrientOf( tbInst.rotation, tbInst.mirror ), aScreen );
4795 }
4796
4797 for( const ORCAD_DISPLAY_PROP& dp : tbInst.displayProps )
4798 {
4799 std::string value = sourceValue( dp.name );
4800
4801 if( !OrcadDisplayPropVisible( dp ) )
4802 continue;
4803
4804 if( value.empty() || value == "?" )
4805 value = "<" + dp.name + ">";
4806
4807 wxString content;
4808
4809 if( OrcadDisplayPropShowsValue( dp ) )
4810 content = FromOrcadString( value );
4811
4812 if( OrcadDisplayPropShowsName( dp ) )
4813 {
4814 content = FromOrcadString( dp.name );
4815
4816 if( OrcadDisplayPropShowsValue( dp ) )
4817 content += wxS( ": " ) + FromOrcadString( value );
4818 }
4819
4820 int fontId = displayFontId( dp );
4821 bool templateFont = displayUsesTemplateFont( dp );
4822 int size = textSizeIU( fontId, templateFont );
4823 int baseline = textBaselineOffset( size, fontId, templateFont );
4824 bool vertical = ( dp.rotation & 1 ) != 0;
4825 VECTOR2I position = OrcadDbuToIu( tbInst.bbox.x1 + dp.x, tbInst.bbox.y1 + dp.y )
4826 + ( vertical ? VECTOR2I( baseline, 0 ) : VECTOR2I( 0, baseline ) );
4827 SCH_TEXT* text = new SCH_TEXT( position, content, LAYER_NOTES );
4828 text->SetTextAngle( vertical ? ANGLE_VERTICAL : ANGLE_HORIZONTAL );
4829 text->SetTextSize( textSize( fontId, templateFont ) );
4830 applyFont( text, fontId, templateFont );
4831 applyMultilineSpacing( text, fontId, templateFont );
4832 text->SetTextColor( KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 ) );
4833 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
4834 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
4835 appendPageItem( aScreen, text );
4836 }
4837
4838 if( tbInst.props.empty() )
4839 continue;
4840
4841 auto get = [&]( const char* aKey ) -> wxString
4842 {
4843 std::string value = sourceValue( aKey );
4844 return value == "?" ? wxString() : FromOrcadString( value );
4845 };
4846
4847 TITLE_BLOCK titleBlock;
4848
4849 titleBlock.SetTitle( get( "Title" ) );
4850
4851 wxString date = get( "Page Modify Date" );
4852
4853 if( date.IsEmpty() )
4854 date = get( "Doc Date" );
4855
4856 titleBlock.SetDate( date );
4857 titleBlock.SetRevision( get( "RevCode" ) );
4858 titleBlock.SetCompany( get( "OrgName" ) );
4859
4860 int slot = 0;
4861
4862 for( const char* stock : { "Doc", "OrgAddr1", "OrgAddr2" } )
4863 {
4864 if( wxString text = get( stock ); !text.IsEmpty() )
4865 titleBlock.SetComment( slot++, text );
4866 }
4867
4868 // Custom title blocks name their own fields and KiCad has no slot for them, so
4869 // spill into the free comments. Page number and count KiCad resolves itself
4870 static const std::set<std::string> mapped = { "Title", "RevCode", "OrgName", "Doc",
4871 "OrgAddr1", "OrgAddr2", "Doc Date", "Page Count",
4872 "Page Number", "Page Modify Date" };
4873
4874 for( const auto& [propName, propValue] : tbInst.props )
4875 {
4876 if( slot >= COMMENT_COUNT || propValue.empty() || mapped.count( propName ) )
4877 continue;
4878
4879 titleBlock.SetComment( slot++, wxString::Format( wxS( "%s: %s" ), FromOrcadString( propName ),
4880 FromOrcadString( propValue ) ) );
4881 }
4882
4883 aScreen->SetTitleBlock( titleBlock );
4884 }
4885}
4886
4887
4888void ORCAD_CONVERTER::placeDefinitionVectors( const ORCAD_SYMBOL_DEF& aDefinition, int aBaseX, int aBaseY, int aOrient,
4889 SCH_SCREEN* aScreen, double aTextScaleX, double aTextScaleY,
4890 bool aUseGenericTextBaseline, const std::string& aTextFaceOverride )
4891{
4892 ORCAD_BBOX bbox = aDefinition.bbox.value_or( ORCAD_BBOX() );
4893 int width = bbox.x2 - bbox.x1;
4894 int height = bbox.y2 - bbox.y1;
4895
4896 auto transform = [&]( int aX, int aY )
4897 {
4898 return OrcadTransformPoint( aOrient, width, height, aBaseX, aBaseY, aX, aY );
4899 };
4900
4901 ORCAD_GRAPHIC_INST graphic;
4902 graphic.rotation = aOrient & 3;
4903 graphic.color = aDefinition.color;
4904 graphic.textScaleX = aTextScaleX;
4905 graphic.textScaleY = aTextScaleY;
4906 graphic.useGenericTextBaseline = aUseGenericTextBaseline;
4907 graphic.useSymbolLineWidths = true;
4908 graphic.textFaceOverride = aTextFaceOverride;
4909 graphic.nested = std::make_unique<ORCAD_SYMBOL_DEF>();
4910
4911 std::function<void( const std::vector<ORCAD_PRIMITIVE>&, int, int )> appendVectors =
4912 [&]( const std::vector<ORCAD_PRIMITIVE>& aPrimitives, int aOffsetX, int aOffsetY )
4913 {
4914 for( const ORCAD_PRIMITIVE& source : aPrimitives )
4915 {
4916 if( source.kind == ORCAD_PRIM_KIND::GROUP_PRIM )
4917 {
4918 appendVectors( source.children, aOffsetX + source.x1, aOffsetY + source.y1 );
4919 continue;
4920 }
4921
4922 if( source.kind == ORCAD_PRIM_KIND::IMAGE )
4923 continue;
4924
4925 ORCAD_PRIMITIVE primitive = source;
4926
4927 auto transformBox = [&]
4928 {
4929 std::array<VECTOR2I, 4> corners = { transform( aOffsetX + source.x1, aOffsetY + source.y1 ),
4930 transform( aOffsetX + source.x2, aOffsetY + source.y1 ),
4931 transform( aOffsetX + source.x2, aOffsetY + source.y2 ),
4932 transform( aOffsetX + source.x1, aOffsetY + source.y2 ) };
4933
4934 primitive.x1 = primitive.x2 = corners[0].x;
4935 primitive.y1 = primitive.y2 = corners[0].y;
4936
4937 for( const VECTOR2I& corner : corners )
4938 {
4939 primitive.x1 = std::min( primitive.x1, corner.x );
4940 primitive.y1 = std::min( primitive.y1, corner.y );
4941 primitive.x2 = std::max( primitive.x2, corner.x );
4942 primitive.y2 = std::max( primitive.y2, corner.y );
4943 }
4944 };
4945
4946 if( source.kind == ORCAD_PRIM_KIND::LINE )
4947 {
4948 VECTOR2I p1 = transform( aOffsetX + source.x1, aOffsetY + source.y1 );
4949 VECTOR2I p2 = transform( aOffsetX + source.x2, aOffsetY + source.y2 );
4950 primitive.x1 = p1.x;
4951 primitive.y1 = p1.y;
4952 primitive.x2 = p2.x;
4953 primitive.y2 = p2.y;
4954 }
4955 else if( source.kind == ORCAD_PRIM_KIND::TEXT )
4956 {
4957 VECTOR2I anchor = transform( aOffsetX + source.x1, aOffsetY + source.y1 );
4958 primitive.textBoundsStart.reset();
4959 primitive.x1 = primitive.x2 = anchor.x;
4960 primitive.y1 = primitive.y2 = anchor.y;
4961 }
4962 else
4963 {
4964 transformBox();
4965 }
4966
4967 for( ORCAD_POINT& point : primitive.points )
4968 {
4969 VECTOR2I transformed = transform( aOffsetX + point.x, aOffsetY + point.y );
4970 point.x = transformed.x;
4971 point.y = transformed.y;
4972 }
4973
4974 if( primitive.start )
4975 {
4976 VECTOR2I transformed = transform( aOffsetX + primitive.start->x, aOffsetY + primitive.start->y );
4977 primitive.start = ORCAD_POINT{ transformed.x, transformed.y };
4978 }
4979
4980 if( primitive.end )
4981 {
4982 VECTOR2I transformed = transform( aOffsetX + primitive.end->x, aOffsetY + primitive.end->y );
4983 primitive.end = ORCAD_POINT{ transformed.x, transformed.y };
4984 }
4985
4986 graphic.nested->primitives.push_back( std::move( primitive ) );
4987 }
4988 };
4989
4990 appendVectors( aDefinition.primitives, 0, 0 );
4991
4992 ORCAD_RAW_PAGE page;
4993 page.graphics.push_back( std::move( graphic ) );
4994 placeGraphics( page, aScreen );
4995}
4996
4997
4998void ORCAD_CONVERTER::placeDefinitionImages( const ORCAD_SYMBOL_DEF& aDefinition, int aBaseX, int aBaseY, int aOrient,
4999 SCH_SCREEN* aScreen )
5000{
5001 ORCAD_BBOX bbox = aDefinition.bbox.value_or( ORCAD_BBOX() );
5002 int width = bbox.x2 - bbox.x1;
5003 int height = bbox.y2 - bbox.y1;
5004
5005 std::function<void( const std::vector<ORCAD_PRIMITIVE>&, int, int )> placeImages =
5006 [&]( const std::vector<ORCAD_PRIMITIVE>& aPrimitives, int aOffsetX, int aOffsetY )
5007 {
5008 for( const ORCAD_PRIMITIVE& primitive : aPrimitives )
5009 {
5010 if( primitive.kind == ORCAD_PRIM_KIND::GROUP_PRIM )
5011 {
5012 placeImages( primitive.children, aOffsetX + primitive.x1, aOffsetY + primitive.y1 );
5013 continue;
5014 }
5015
5016 if( primitive.kind != ORCAD_PRIM_KIND::IMAGE )
5017 continue;
5018
5019 VECTOR2I center = OrcadTransformPoint( aOrient, width, height, aBaseX, aBaseY,
5020 aOffsetX + ( primitive.x1 + primitive.x2 ) / 2,
5021 aOffsetY + ( primitive.y1 + primitive.y2 ) / 2 );
5022 int imageWidth = std::abs( primitive.x2 - primitive.x1 );
5023 int imageHeight = std::abs( primitive.y2 - primitive.y1 );
5024 ORCAD_PRIMITIVE image = primitive;
5025 image.x1 = center.x - imageWidth / 2;
5026 image.y1 = center.y - imageHeight / 2;
5027 image.x2 = image.x1 + imageWidth;
5028 image.y2 = image.y1 + imageHeight;
5029 placeBitmap( image, aScreen, aOrient );
5030 }
5031 };
5032
5033 placeImages( aDefinition.primitives, 0, 0 );
5034}
5035
5036
5038{
5039 m_wireEndpoints.clear();
5040
5041 for( const ORCAD_WIRE& wire : aPage.wires )
5042 {
5043 m_wireEndpoints[{ wire.x1, wire.y1 }].push_back( &wire );
5044 m_wireEndpoints[{ wire.x2, wire.y2 }].push_back( &wire );
5045 }
5046}
5047
5048
5049std::string ORCAD_CONVERTER::netAt( const ORCAD_RAW_PAGE& aPage, int aX, int aY ) const
5050{
5051 auto wireName = [&aPage]( const ORCAD_WIRE& aWire )
5052 {
5053 for( const ORCAD_ALIAS& alias : aWire.aliases )
5054 {
5055 if( !trimmed( alias.name ).empty() )
5056 return alias.name;
5057 }
5058
5059 auto aliases = aPage.netAliases.find( aWire.id );
5060
5061 if( aliases != aPage.netAliases.end() )
5062 {
5063 std::set<std::string> names;
5064
5065 for( const std::string& alias : aliases->second )
5066 {
5067 if( !trimmed( alias ).empty() )
5068 names.insert( alias );
5069 }
5070
5071 if( names.size() == 1 )
5072 return *names.begin();
5073
5074 return std::string();
5075 }
5076
5077 auto net = aPage.netmap.find( aWire.id );
5078 return net != aPage.netmap.end() ? net->second : std::string();
5079 };
5080
5081 auto endIt = m_wireEndpoints.find( { aX, aY } );
5082
5083 const std::vector<const ORCAD_WIRE*>* endWires = endIt != m_wireEndpoints.end() ? &endIt->second : nullptr;
5084
5085 if( endWires )
5086 {
5087 for( const ORCAD_WIRE* wire : *endWires )
5088 {
5089 std::string name = wireName( *wire );
5090
5091 if( !name.empty() )
5092 return name;
5093 }
5094 }
5095
5096 // Also try wires passing through point.
5097 for( const ORCAD_WIRE& wire : aPage.wires )
5098 {
5099 if( onSegment( aX, aY, wire ) )
5100 {
5101 std::string name = wireName( wire );
5102
5103 if( !name.empty() )
5104 return name;
5105 }
5106 }
5107
5108 return std::string();
5109}
5110
5111
5112std::string ORCAD_CONVERTER::powerNet( const ORCAD_RAW_PAGE& aPage, const ORCAD_GRAPHIC_INST& aInst ) const
5113{
5114 VECTOR2I pin = powerPinPos( aPage, aInst );
5115 std::string net = netAt( aPage, pin.x, pin.y );
5116 auto nameIt = aInst.props.find( "Name" );
5117 std::string propertyName = nameIt != aInst.props.end() ? trimmed( nameIt->second ) : std::string();
5118 std::string logicalName = trimmed( aInst.logicalName );
5119
5120 bool authoritativeOccurrenceNet = false;
5121 std::map<uint32_t, std::set<const std::string*>> occurrenceNamesByPageNetId;
5122 auto occurrenceElectricalName = [&]( const std::string* aName )
5123 {
5124 return aName ? *aName : std::string();
5125 };
5126
5128 {
5129 std::set<const std::string*> generatedNamesAtPin;
5130
5131 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
5132 {
5133 for( size_t pinIndex = 0; pinIndex < instance.pins.size(); ++pinIndex )
5134 {
5135 const ORCAD_PIN_INST& placedPin = instance.pins[pinIndex];
5136
5137 if( !placedPin.wordA && !placedPin.wordB )
5138 continue;
5139
5140 VECTOR2I electricalPosition = placedPinElectricalPosition( instance, pinIndex );
5141 bool touchesWire = std::any_of( aPage.wires.begin(), aPage.wires.end(),
5142 [&]( const ORCAD_WIRE& aWire )
5143 {
5144 return onSegment( placedPin.x, placedPin.y, aWire );
5145 } );
5146
5147 if( electricalPosition != pin
5148 || ( electricalPosition == VECTOR2I( placedPin.x, placedPin.y ) && touchesWire ) )
5149 {
5150 continue;
5151 }
5152
5153 std::string generatedName = generatedPinNetName( instance.dbId, pinIndex );
5154
5155 for( const auto& [occurrenceId, occurrenceName] : *m_currentOccNetNames )
5156 {
5157 if( occurrenceName == generatedName )
5158 generatedNamesAtPin.insert( &occurrenceName );
5159 }
5160 }
5161 }
5162
5163 if( generatedNamesAtPin.size() == 1 )
5164 return occurrenceElectricalName( *generatedNamesAtPin.begin() );
5165
5166 if( net.empty() && !logicalName.empty() )
5167 {
5168 std::set<const std::string*> matchingNames;
5169
5170 for( const auto& [occurrenceId, occurrenceName] : *m_currentOccNetNames )
5171 {
5172 if( wxString::FromUTF8( kicadOccurrenceNetName( occurrenceName ) )
5173 .CmpNoCase( wxString::FromUTF8( logicalName ) )
5174 == 0 )
5175 {
5176 matchingNames.insert( &occurrenceName );
5177 }
5178 }
5179
5180 if( matchingNames.size() == 1 )
5181 {
5182 if( std::optional<uint32_t> occurrenceId = occurrenceNetIdFor( *matchingNames.begin() ) )
5183 return occurrenceElectricalNetName( *occurrenceId, **matchingNames.begin() );
5184 }
5185 }
5186
5187 for( const auto& [occurrenceId, occurrenceName] : *m_currentOccNetNames )
5188 {
5189 if( std::optional<uint32_t> objectId = occurrenceNetObjectId( occurrenceName ) )
5190 {
5191 auto sourceWire = std::find_if( aPage.wires.begin(), aPage.wires.end(),
5192 [&]( const ORCAD_WIRE& aWire )
5193 {
5194 return aWire.dbId == *objectId;
5195 } );
5196
5197 if( sourceWire != aPage.wires.end() )
5198 {
5199 occurrenceNamesByPageNetId[sourceWire->id].insert( &occurrenceName );
5200 continue;
5201 }
5202
5203 auto sourceInstance = std::find_if( aPage.instances.begin(), aPage.instances.end(),
5204 [&]( const ORCAD_PLACED_INSTANCE& aInstance )
5205 {
5206 return aInstance.dbId == *objectId;
5207 } );
5208
5209 if( sourceInstance == aPage.instances.end() )
5210 continue;
5211
5212 std::string occurrenceKey = occurrenceName;
5213 std::transform( occurrenceKey.begin(), occurrenceKey.end(), occurrenceKey.begin(),
5214 []( unsigned char c )
5215 {
5216 return static_cast<char>( std::tolower( c ) );
5217 } );
5218
5219 auto matchesOccurrenceName = [&]( uint32_t aNetId )
5220 {
5221 std::set<std::string> localNames;
5222 auto pageName = aPage.netmap.find( aNetId );
5223
5224 if( pageName != aPage.netmap.end() )
5225 localNames.insert( pageName->second );
5226
5227 auto aliases = aPage.netAliases.find( aNetId );
5228
5229 if( aliases != aPage.netAliases.end() )
5230 localNames.insert( aliases->second.begin(), aliases->second.end() );
5231
5232 return std::any_of( localNames.begin(), localNames.end(),
5233 [&]( std::string aName )
5234 {
5235 std::transform( aName.begin(), aName.end(), aName.begin(),
5236 []( unsigned char c )
5237 {
5238 return static_cast<char>( std::tolower( c ) );
5239 } );
5240 return occurrenceKey == aName
5241 || ( occurrenceKey.size() > aName.size()
5242 && occurrenceKey[aName.size()] == '_'
5243 && occurrenceKey.compare( 0, aName.size(), aName ) == 0 );
5244 } );
5245 };
5246
5247 for( const ORCAD_PIN_INST& sourcePin : sourceInstance->pins )
5248 {
5249 if( sourcePin.wordB && matchesOccurrenceName( sourcePin.wordB ) )
5250 occurrenceNamesByPageNetId[sourcePin.wordB].insert( &occurrenceName );
5251
5252 for( const ORCAD_WIRE& wire : aPage.wires )
5253 {
5254 if( matchesOccurrenceName( wire.id )
5255 && ( wire.id == sourcePin.wordB || wire.dbId == sourcePin.wordA
5256 || rawPointOnSegment( sourcePin.x, sourcePin.y, wire ) ) )
5257 {
5258 occurrenceNamesByPageNetId[wire.id].insert( &occurrenceName );
5259 }
5260 }
5261 }
5262 }
5263 }
5264
5265 std::set<const std::string*> objectNets;
5266 std::set<const std::string*> occurrenceNets;
5267
5268 for( const ORCAD_WIRE& wire : aPage.wires )
5269 {
5270 bool endpoint = ( pin.x == wire.x1 && pin.y == wire.y1 ) || ( pin.x == wire.x2 && pin.y == wire.y2 );
5271
5272 if( !endpoint && !onSegment( pin.x, pin.y, wire ) )
5273 continue;
5274
5275 auto objectNames = occurrenceNamesByPageNetId.find( wire.id );
5276
5277 if( objectNames != occurrenceNamesByPageNetId.end() && objectNames->second.size() == 1 )
5278 objectNets.insert( *objectNames->second.begin() );
5279
5280 auto pageNet = aPage.netmap.find( wire.id );
5281
5282 if( pageNet == aPage.netmap.end() || pageNet->second.empty() )
5283 continue;
5284
5285 for( const auto& [occurrenceId, occurrenceName] : *m_currentOccNetNames )
5286 {
5287 if( wxString::FromUTF8( pageNet->second ).CmpNoCase( wxString::FromUTF8( occurrenceName ) ) == 0 )
5288 occurrenceNets.insert( &occurrenceName );
5289 }
5290 }
5291
5292 if( objectNets.size() == 1 )
5293 {
5294 net = occurrenceElectricalName( *objectNets.begin() );
5295 authoritativeOccurrenceNet = true;
5296 }
5297 else if( occurrenceNets.size() == 1 )
5298 {
5299 net = occurrenceElectricalName( *occurrenceNets.begin() );
5300 authoritativeOccurrenceNet = true;
5301 }
5302 }
5303
5304 if( m_currentOccNetNames && !authoritativeOccurrenceNet )
5305 {
5306 std::set<const std::string*> directPinNets;
5307
5308 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
5309 {
5310 for( const ORCAD_PIN_INST& componentPin : instance.pins )
5311 {
5312 if( componentPin.x != pin.x || componentPin.y != pin.y || componentPin.IsNoConnect() )
5313 continue;
5314
5315 for( const ORCAD_WIRE& wire : aPage.wires )
5316 {
5317 if( wire.dbId != componentPin.wordA && wire.id != componentPin.wordB )
5318 continue;
5319
5320 auto names = occurrenceNamesByPageNetId.find( wire.id );
5321
5322 if( names != occurrenceNamesByPageNetId.end() && names->second.size() == 1 )
5323 directPinNets.insert( *names->second.begin() );
5324 }
5325 }
5326 }
5327
5328 if( directPinNets.size() == 1 )
5329 {
5330 net = occurrenceElectricalName( *directPinNets.begin() );
5331 authoritativeOccurrenceNet = true;
5332 }
5333 }
5334
5335 if( m_currentOccNetNames && !authoritativeOccurrenceNet )
5336 {
5337 std::string sourceName = trimmed( aInst.logicalName );
5338 std::set<uint32_t> matchingNetIds;
5339
5340 for( const auto& [netId, pageName] : aPage.netmap )
5341 {
5342 bool matches = wxString::FromUTF8( pageName ).CmpNoCase( wxString::FromUTF8( sourceName ) ) == 0;
5343 auto aliases = aPage.netAliases.find( netId );
5344
5345 if( aliases != aPage.netAliases.end() )
5346 {
5347 matches = matches
5348 || std::any_of( aliases->second.begin(), aliases->second.end(),
5349 [&]( const std::string& aAlias )
5350 {
5351 return wxString::FromUTF8( aAlias ).CmpNoCase(
5352 wxString::FromUTF8( sourceName ) )
5353 == 0;
5354 } );
5355 }
5356
5357 if( matches )
5358 matchingNetIds.insert( netId );
5359 }
5360
5361 if( matchingNetIds.size() == 1 )
5362 {
5363 auto names = occurrenceNamesByPageNetId.find( *matchingNetIds.begin() );
5364
5365 if( names != occurrenceNamesByPageNetId.end() && names->second.size() == 1 )
5366 {
5367 net = occurrenceElectricalName( *names->second.begin() );
5368 authoritativeOccurrenceNet = true;
5369 }
5370 }
5371 }
5372
5373 if( net.empty() )
5374 {
5375 std::set<std::string> pinNetNames;
5376
5377 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
5378 {
5379 for( const ORCAD_PIN_INST& componentPin : instance.pins )
5380 {
5381 if( componentPin.x != pin.x || componentPin.y != pin.y || componentPin.IsNoConnect() )
5382 continue;
5383
5384 for( uint32_t netId : { componentPin.wordB, componentPin.wordA } )
5385 {
5386 std::set<std::string> sourceNames;
5387 auto netName = aPage.netmap.find( netId );
5388
5389 if( netName != aPage.netmap.end() && !netName->second.empty() )
5390 sourceNames.insert( netName->second );
5391
5392 auto aliases = aPage.netAliases.find( netId );
5393
5394 if( aliases != aPage.netAliases.end() )
5395 sourceNames.insert( aliases->second.begin(), aliases->second.end() );
5396
5397 std::set<std::string> occurrenceNames;
5398
5399 if( m_currentOccNetNames )
5400 {
5401 for( const std::string& sourceName : sourceNames )
5402 {
5403 for( const auto& [occurrenceId, occurrenceName] : *m_currentOccNetNames )
5404 {
5405 if( wxString::FromUTF8( sourceName ).CmpNoCase( wxString::FromUTF8( occurrenceName ) )
5406 == 0 )
5407 {
5408 occurrenceNames.insert( occurrenceName );
5409 }
5410 }
5411 }
5412 }
5413
5414 if( occurrenceNames.size() == 1 )
5415 pinNetNames.insert( *occurrenceNames.begin() );
5416 else if( sourceNames.size() == 1 )
5417 pinNetNames.insert( *sourceNames.begin() );
5418 }
5419 }
5420 }
5421
5422 if( pinNetNames.size() == 1 )
5423 net = *pinNetNames.begin();
5424 }
5425
5426 auto namesEqual = []( const std::string& aLeft, const std::string& aRight )
5427 {
5428 return wxString::FromUTF8( aLeft ).CmpNoCase( wxString::FromUTF8( aRight ) ) == 0;
5429 };
5430
5431 std::string physicalName = !propertyName.empty() ? propertyName : logicalName;
5432
5433 if( !physicalName.empty() )
5434 {
5435 for( const ORCAD_WIRE& wire : aPage.wires )
5436 {
5437 bool endpoint = ( pin.x == wire.x1 && pin.y == wire.y1 ) || ( pin.x == wire.x2 && pin.y == wire.y2 );
5438
5439 if( !endpoint && !onSegment( pin.x, pin.y, wire ) )
5440 continue;
5441
5442 std::vector<std::string> names;
5443 auto pageName = aPage.netmap.find( wire.id );
5444
5445 if( pageName != aPage.netmap.end() )
5446 names.push_back( pageName->second );
5447
5448 auto aliases = aPage.netAliases.find( wire.id );
5449
5450 if( aliases != aPage.netAliases.end() )
5451 names.insert( names.end(), aliases->second.begin(), aliases->second.end() );
5452
5453 std::set<std::string> distinctNames;
5454
5455 for( std::string name : names )
5456 {
5457 std::transform( name.begin(), name.end(), name.begin(),
5458 []( unsigned char aChar ) { return static_cast<char>( std::tolower( aChar ) ); } );
5459 distinctNames.insert( std::move( name ) );
5460 }
5461
5462 bool hasPhysicalName = std::any_of( names.begin(), names.end(),
5463 [&]( const std::string& aName )
5464 { return namesEqual( aName, physicalName ); } );
5465
5466 if( ( !m_currentOccNetNames || m_currentOccNetNames->empty() ) && distinctNames.size() > 1
5467 && hasPhysicalName )
5468 return canonicalGlobalNetName( physicalName );
5469 }
5470 }
5471
5472 if( m_currentOccNetNames && m_currentOccNetNames->empty() && !net.empty() && !isPowerNetName( net ) )
5473 {
5474 std::string sourcePowerName = !logicalName.empty() ? logicalName : propertyName;
5475
5476 if( isPowerNetName( sourcePowerName ) )
5477 net = std::move( sourcePowerName );
5478 }
5479
5480 if( m_currentOccNetNames && !authoritativeOccurrenceNet )
5481 {
5482 std::set<const std::string*> occurrenceAliases;
5483
5484 for( const ORCAD_WIRE& wire : aPage.wires )
5485 {
5486 bool endpoint = ( pin.x == wire.x1 && pin.y == wire.y1 ) || ( pin.x == wire.x2 && pin.y == wire.y2 );
5487
5488 if( !endpoint && !onSegment( pin.x, pin.y, wire ) )
5489 continue;
5490
5491 auto aliases = aPage.netAliases.find( wire.id );
5492
5493 if( aliases == aPage.netAliases.end() )
5494 continue;
5495
5496 for( const std::string& alias : aliases->second )
5497 {
5498 for( const auto& [occurrenceId, occurrenceName] : *m_currentOccNetNames )
5499 {
5500 if( wxString::FromUTF8( alias ).CmpNoCase( wxString::FromUTF8( occurrenceName ) ) == 0 )
5501 occurrenceAliases.insert( &occurrenceName );
5502 }
5503 }
5504 }
5505
5506 if( occurrenceAliases.size() == 1 )
5507 net = occurrenceElectricalName( *occurrenceAliases.begin() );
5508 }
5509
5510 bool implicitGeneratedName = net.size() > 1 && net[0] == 'N'
5511 && std::all_of( net.begin() + 1, net.end(),
5512 []( unsigned char c )
5513 {
5514 return std::isdigit( c );
5515 } );
5516
5517 if( net.empty() || ( implicitGeneratedName && !authoritativeOccurrenceNet ) )
5518 {
5519 if( !logicalName.empty() )
5520 net = logicalName;
5521 else if( !propertyName.empty() )
5522 net = propertyName;
5523 }
5524
5525 if( net.empty() )
5526 net = aInst.name;
5527
5528 return effectiveInterfaceNetName( net );
5529}
5530
5531
5533{
5534 auto symIt = m_design.symbols.find( aInst.name );
5535
5536 if( symIt == m_design.symbols.end() || symIt->second.pins.empty() )
5537 return VECTOR2I( aInst.x, aInst.y );
5538
5539 const ORCAD_SYMBOL_DEF& sym = symIt->second;
5540 int baseX = std::min( aInst.bbox.x1, aInst.bbox.x2 );
5541 int baseY = std::min( aInst.bbox.y1, aInst.bbox.y2 );
5542
5543 ORCAD_BBOX symBox = sym.bbox.value_or( ORCAD_BBOX() );
5544 int width = symBox.x2 - symBox.x1;
5545 int height = symBox.y2 - symBox.y1;
5546 int bboxWidth = std::abs( aInst.bbox.x2 - aInst.bbox.x1 );
5547 int bboxHeight = std::abs( aInst.bbox.y2 - aInst.bbox.y1 );
5548 bool storedExtent = std::abs( aInst.bbox.x2 ) == width && std::abs( aInst.bbox.y2 ) == height;
5549 bool legacyExtent = width > 0 && height > 0 && storedExtent && ( bboxWidth > 4 * width || bboxHeight > 4 * height );
5550
5551 if( legacyExtent )
5552 {
5553 baseX = std::max( aInst.bbox.x1, aInst.bbox.x2 ) - std::abs( aInst.x );
5554 baseY = std::max( aInst.bbox.y1, aInst.bbox.y2 ) - std::abs( aInst.y );
5555 }
5556
5557 int orient = OrcadOrientOf( aInst.rotation, aInst.mirror );
5558 const ORCAD_SYMBOL_PIN& pin = sym.pins[0];
5559
5560 return OrcadTransformPoint( orient, width, height, baseX, baseY, pin.hotptX, pin.hotptY );
5561}
5562
5563
5565{
5566 VECTOR2I candidate = graphicPinPos( aInst );
5567 std::string logicalName = trimmed( aInst.logicalName );
5568 constexpr int MAX_PIN_SNAP_DISTANCE = 20;
5569
5570 std::transform( logicalName.begin(), logicalName.end(), logicalName.begin(),
5571 []( unsigned char c )
5572 {
5573 return static_cast<char>( std::tolower( c ) );
5574 } );
5575
5576 if( logicalName.empty() )
5577 return candidate;
5578
5579 size_t matchingConnectors = 0;
5580 auto countMatchingConnectors = [&]( const std::vector<ORCAD_GRAPHIC_INST>& aConnectors )
5581 {
5582 matchingConnectors += std::count_if( aConnectors.begin(), aConnectors.end(),
5583 [&]( const ORCAD_GRAPHIC_INST& aConnector )
5584 {
5585 std::string name = trimmed( aConnector.logicalName );
5586 std::transform( name.begin(), name.end(), name.begin(),
5587 []( unsigned char c )
5588 {
5589 return static_cast<char>( std::tolower( c ) );
5590 } );
5591 return name == logicalName;
5592 } );
5593 };
5594
5595 countMatchingConnectors( aPage.globals );
5596 countMatchingConnectors( aPage.offpage );
5597 countMatchingConnectors( aPage.ports );
5598
5599 int64_t bestDistanceSquared = matchingConnectors > 1 ? MAX_PIN_SNAP_DISTANCE * MAX_PIN_SNAP_DISTANCE + 1
5600 : std::numeric_limits<int64_t>::max();
5601 VECTOR2I best = candidate;
5602
5603 for( const ORCAD_WIRE& wire : aPage.wires )
5604 {
5605 auto netIt = aPage.netmap.find( wire.id );
5606
5607 if( netIt == aPage.netmap.end() )
5608 continue;
5609
5610 std::string wireName = trimmed( netIt->second );
5611 std::transform( wireName.begin(), wireName.end(), wireName.begin(),
5612 []( unsigned char c )
5613 {
5614 return static_cast<char>( std::tolower( c ) );
5615 } );
5616
5617 if( wireName != logicalName )
5618 continue;
5619
5620 for( const VECTOR2I& endpoint : { VECTOR2I( wire.x1, wire.y1 ), VECTOR2I( wire.x2, wire.y2 ) } )
5621 {
5622 int64_t dx = endpoint.x - candidate.x;
5623 int64_t dy = endpoint.y - candidate.y;
5624 int64_t distanceSquared = dx * dx + dy * dy;
5625
5626 if( distanceSquared < bestDistanceSquared )
5627 {
5628 bestDistanceSquared = distanceSquared;
5629 best = endpoint;
5630 }
5631 }
5632 }
5633
5634 return best;
5635}
5636
5637
5639{
5640 VECTOR2I sourceAnchor( aInst.x, aInst.y );
5641 constexpr int MAX_PIN_SNAP_DISTANCE = 20;
5642 int64_t bestDistanceSquared = MAX_PIN_SNAP_DISTANCE * MAX_PIN_SNAP_DISTANCE + 1;
5643 VECTOR2I best = sourceAnchor;
5644 std::string powerName = trimmed( aInst.logicalName );
5645
5646 if( powerName.empty() )
5647 {
5648 auto property = aInst.props.find( "Name" );
5649
5650 if( property != aInst.props.end() )
5651 powerName = trimmed( property->second );
5652 }
5653
5654 auto namesEqual = []( const std::string& aLeft, const std::string& aRight )
5655 {
5656 return wxString::FromUTF8( aLeft ).CmpNoCase( wxString::FromUTF8( aRight ) ) == 0;
5657 };
5658
5659 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
5660 {
5661 for( const ORCAD_PIN_INST& pin : instance.pins )
5662 {
5663 if( pin.IsNoConnect() || pin.wordA != std::numeric_limits<uint32_t>::max() || !pin.wordB )
5664 continue;
5665
5666 std::vector<std::string> pinNetNames;
5667 auto pageName = aPage.netmap.find( pin.wordB );
5668
5669 if( pageName != aPage.netmap.end() && !trimmed( pageName->second ).empty() )
5670 pinNetNames.push_back( trimmed( pageName->second ) );
5671
5672 auto aliases = aPage.netAliases.find( pin.wordB );
5673
5674 if( aliases != aPage.netAliases.end() )
5675 pinNetNames.insert( pinNetNames.end(), aliases->second.begin(), aliases->second.end() );
5676
5677 if( !powerName.empty() && !pinNetNames.empty()
5678 && std::none_of( pinNetNames.begin(), pinNetNames.end(),
5679 [&]( const std::string& aName )
5680 {
5681 return namesEqual( aName, powerName );
5682 } ) )
5683 {
5684 continue;
5685 }
5686
5687 int64_t dx = pin.x - sourceAnchor.x;
5688 int64_t dy = pin.y - sourceAnchor.y;
5689 int64_t distanceSquared = dx * dx + dy * dy;
5690
5691 if( distanceSquared < bestDistanceSquared )
5692 {
5693 bestDistanceSquared = distanceSquared;
5694 best = VECTOR2I( pin.x, pin.y );
5695 }
5696 }
5697 }
5698
5699 if( bestDistanceSquared <= MAX_PIN_SNAP_DISTANCE * MAX_PIN_SNAP_DISTANCE )
5700 return best;
5701
5702 return namedGraphicPinPos( aPage, aInst );
5703}
5704
5705
5706std::vector<int> ORCAD_CONVERTER::placedStackedPinOffsets( const ORCAD_PLACED_INSTANCE& aInstance ) const
5707{
5708 std::vector<int> offsets( aInstance.pins.size() );
5709
5710 auto shareNet = []( const ORCAD_PIN_INST& aLeft, const ORCAD_PIN_INST& aRight )
5711 {
5712 if( ( !aLeft.wordA && !aLeft.wordB ) || ( !aRight.wordA && !aRight.wordB ) )
5713 return true;
5714
5715 return ( aLeft.wordA && aLeft.wordA == aRight.wordA ) || ( aLeft.wordB && aLeft.wordB == aRight.wordB );
5716 };
5717
5718 for( size_t i = 0; i < aInstance.pins.size(); ++i )
5719 {
5720 const ORCAD_PIN_INST& pin = aInstance.pins[i];
5721 int nextOffset = 0;
5722
5723 for( size_t j = 0; j < i; ++j )
5724 {
5725 const ORCAD_PIN_INST& peer = aInstance.pins[j];
5726
5727 if( peer.x != pin.x || peer.y != pin.y )
5728 continue;
5729
5730 if( shareNet( pin, peer ) )
5731 {
5732 offsets[i] = offsets[j];
5733 nextOffset = -1;
5734 break;
5735 }
5736
5737 nextOffset = std::max( nextOffset, offsets[j] + 1 );
5738 }
5739
5740 if( nextOffset >= 0 )
5741 offsets[i] = nextOffset;
5742 }
5743
5744 return offsets;
5745}
5746
5747
5749{
5750 const ORCAD_PIN_INST& pin = aInstance.pins[aPinIndex];
5751 std::vector<int> offsets = placedStackedPinOffsets( aInstance );
5752 int orient = OrcadOrientOf( aInstance.rotation, aInstance.mirror );
5753 const ORCAD_ORIENT_ENTRY& transform = ORCAD_ORIENT_TABLE[orient];
5754
5755 return VECTOR2I( pin.x + transform.a * offsets[aPinIndex], pin.y + transform.c * offsets[aPinIndex] );
5756}
5757
5758
5759std::vector<ORCAD_CONVERTER::OFFPAGE_NET> ORCAD_CONVERTER::offpageNets( const ORCAD_RAW_PAGE& aPage ) const
5760{
5761 std::vector<OFFPAGE_NET> out;
5762 std::map<uint32_t, std::set<const std::string*>> occurrenceNamesByPageNetId;
5763
5765 {
5766 for( const auto& [occurrenceId, occurrenceName] : *m_currentOccNetNames )
5767 {
5768 if( std::optional<uint32_t> objectId = occurrenceNetObjectId( occurrenceName ) )
5769 {
5770 auto sourceWire = std::find_if( aPage.wires.begin(), aPage.wires.end(),
5771 [&]( const ORCAD_WIRE& aWire )
5772 {
5773 return aWire.dbId == *objectId;
5774 } );
5775
5776 if( sourceWire != aPage.wires.end() )
5777 {
5778 occurrenceNamesByPageNetId[sourceWire->id].insert( &occurrenceName );
5779 continue;
5780 }
5781
5782 auto sourceInstance = std::find_if( aPage.instances.begin(), aPage.instances.end(),
5783 [&]( const ORCAD_PLACED_INSTANCE& aInstance )
5784 {
5785 return aInstance.dbId == *objectId;
5786 } );
5787
5788 if( sourceInstance == aPage.instances.end() )
5789 continue;
5790
5791 std::string occurrenceKey = occurrenceName;
5792 std::transform( occurrenceKey.begin(), occurrenceKey.end(), occurrenceKey.begin(),
5793 []( unsigned char c )
5794 {
5795 return static_cast<char>( std::tolower( c ) );
5796 } );
5797
5798 auto matchesOccurrenceName = [&]( uint32_t aNetId )
5799 {
5800 std::set<std::string> localNames;
5801 auto pageName = aPage.netmap.find( aNetId );
5802
5803 if( pageName != aPage.netmap.end() )
5804 localNames.insert( pageName->second );
5805
5806 auto aliases = aPage.netAliases.find( aNetId );
5807
5808 if( aliases != aPage.netAliases.end() )
5809 localNames.insert( aliases->second.begin(), aliases->second.end() );
5810
5811 return std::any_of( localNames.begin(), localNames.end(),
5812 [&]( std::string aName )
5813 {
5814 std::transform( aName.begin(), aName.end(), aName.begin(),
5815 []( unsigned char c )
5816 {
5817 return static_cast<char>( std::tolower( c ) );
5818 } );
5819 return occurrenceKey == aName
5820 || ( occurrenceKey.size() > aName.size()
5821 && occurrenceKey[aName.size()] == '_'
5822 && occurrenceKey.compare( 0, aName.size(), aName ) == 0 );
5823 } );
5824 };
5825
5826 for( const ORCAD_PIN_INST& pin : sourceInstance->pins )
5827 {
5828 if( pin.wordB && matchesOccurrenceName( pin.wordB ) )
5829 occurrenceNamesByPageNetId[pin.wordB].insert( &occurrenceName );
5830
5831 for( const ORCAD_WIRE& wire : aPage.wires )
5832 {
5833 if( matchesOccurrenceName( wire.id )
5834 && ( wire.id == pin.wordB || wire.dbId == pin.wordA
5835 || rawPointOnSegment( pin.x, pin.y, wire ) ) )
5836 {
5837 occurrenceNamesByPageNetId[wire.id].insert( &occurrenceName );
5838 }
5839 }
5840 }
5841 }
5842 }
5843 }
5844
5845 for( size_t i = 0; i < aPage.offpage.size(); ++i )
5846 {
5847 const ORCAD_GRAPHIC_INST& conn = aPage.offpage[i];
5848 VECTOR2I pin = namedGraphicPinPos( aPage, conn );
5849
5850 OFFPAGE_NET entry;
5851 entry.index = static_cast<int>( i );
5852
5853 std::set<const std::string*> occurrenceNets;
5854 std::set<uint32_t> attachedNetIds;
5855
5856 for( const ORCAD_WIRE& wire : aPage.wires )
5857 {
5858 if( wire.isBus || !rawPointOnSegment( pin.x, pin.y, wire ) )
5859 continue;
5860
5861 attachedNetIds.insert( wire.id );
5862 auto occurrenceNames = occurrenceNamesByPageNetId.find( wire.id );
5863
5864 if( occurrenceNames != occurrenceNamesByPageNetId.end() && occurrenceNames->second.size() == 1 )
5865 occurrenceNets.insert( *occurrenceNames->second.begin() );
5866 }
5867
5868 std::set<uint32_t> matchingNetIds;
5869
5870 for( const auto& [netId, pageName] : aPage.netmap )
5871 {
5872 bool matches = wxString::FromUTF8( pageName ).CmpNoCase( wxString::FromUTF8( conn.logicalName ) ) == 0;
5873 auto aliases = aPage.netAliases.find( netId );
5874
5875 if( aliases != aPage.netAliases.end() )
5876 {
5877 matches = matches
5878 || std::any_of( aliases->second.begin(), aliases->second.end(),
5879 [&]( const std::string& aAlias )
5880 {
5881 return wxString::FromUTF8( aAlias ).CmpNoCase(
5882 wxString::FromUTF8( conn.logicalName ) )
5883 == 0;
5884 } );
5885 }
5886
5887 if( matches )
5888 matchingNetIds.insert( netId );
5889 }
5890
5891 if( occurrenceNets.empty() )
5892 {
5893 if( matchingNetIds.size() == 1 )
5894 {
5895 auto names = occurrenceNamesByPageNetId.find( *matchingNetIds.begin() );
5896
5897 if( names != occurrenceNamesByPageNetId.end() && names->second.size() == 1 )
5898 occurrenceNets.insert( *names->second.begin() );
5899 }
5900 }
5901
5902 if( occurrenceNets.size() == 1 )
5903 {
5904 const std::string& occurrenceName = **occurrenceNets.begin();
5905 std::optional<uint32_t> objectId = occurrenceNetObjectId( occurrenceName );
5906 bool instanceName = objectId
5907 && std::any_of( aPage.instances.begin(), aPage.instances.end(),
5908 [&]( const ORCAD_PLACED_INSTANCE& instance )
5909 {
5910 return instance.dbId == *objectId;
5911 } )
5912 && std::none_of( aPage.wires.begin(), aPage.wires.end(),
5913 [&]( const ORCAD_WIRE& wire )
5914 {
5915 return wire.dbId == *objectId;
5916 } );
5917 bool declaredNet = attachedNetIds.size() == 1 && matchingNetIds.count( *attachedNetIds.begin() );
5918 bool declaredOccurrence = m_currentOccNetNames
5919 && std::any_of( m_currentOccNetNames->begin(), m_currentOccNetNames->end(),
5920 [&]( const auto& occurrence )
5921 {
5922 return FromOrcadString( occurrence.second ).CmpNoCase(
5923 FromOrcadString( conn.logicalName ) ) == 0;
5924 } );
5925
5926 // Capture can retain an obsolete instance-derived occurrence after off-page connectors
5927 // join its net. A wire-object occurrence override still identifies the connection directly.
5928 entry.net = canonicalGlobalNetName( instanceName && declaredNet && declaredOccurrence
5929 ? conn.logicalName : occurrenceName );
5930 }
5931 else
5932 {
5933 entry.net = matchingNetIds.size() > 1 ? canonicalGlobalNetName( conn.logicalName )
5934 : effectiveInterfaceNetName( conn.logicalName );
5935 }
5936
5937 entry.x = pin.x;
5938 entry.y = pin.y;
5939
5940 out.push_back( entry );
5941 }
5942
5943 return out;
5944}
5945
5946
5947std::vector<VECTOR2I> ORCAD_CONVERTER::computeJunctions( const ORCAD_RAW_PAGE& aPage ) const
5948{
5949 std::set<std::pair<int, int>> pinPts;
5950
5951 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
5952 {
5953 for( const ORCAD_PIN_INST& pin : instance.pins )
5954 pinPts.insert( { pin.x, pin.y } );
5955 }
5956
5957 for( const ORCAD_DRAWN_INSTANCE& block : aPage.blocks )
5958 {
5959 for( const ORCAD_BLOCK_PIN& pin : block.pins )
5960 pinPts.insert( { pin.x, pin.y } );
5961 }
5962
5963 std::map<std::pair<int, int>, int> ends;
5964
5965 for( const ORCAD_WIRE& wire : aPage.wires )
5966 {
5967 ends[{ wire.x1, wire.y1 }]++;
5968 ends[{ wire.x2, wire.y2 }]++;
5969 }
5970
5971 std::set<std::pair<int, int>> candidates = pinPts;
5972 std::set<std::pair<int, int>> interiorCrossings;
5973
5974 for( const std::pair<const std::pair<int, int>, int>& end : ends )
5975 candidates.insert( end.first );
5976
5977 for( size_t firstIndex = 0; firstIndex < aPage.wires.size(); ++firstIndex )
5978 {
5979 const ORCAD_WIRE& first = aPage.wires[firstIndex];
5980
5981 if( first.isBus )
5982 continue;
5983
5984 for( size_t secondIndex = firstIndex + 1; secondIndex < aPage.wires.size(); ++secondIndex )
5985 {
5986 const ORCAD_WIRE& second = aPage.wires[secondIndex];
5987
5988 if( second.isBus || first.id != second.id )
5989 continue;
5990
5991 std::optional<VECTOR2I> intersection = rawWireIntersection( first, second );
5992
5993 if( intersection && onSegment( intersection->x, intersection->y, first )
5994 && onSegment( intersection->x, intersection->y, second ) )
5995 {
5996 interiorCrossings.insert( { intersection->x, intersection->y } );
5997 candidates.insert( { intersection->x, intersection->y } );
5998 }
5999 }
6000 }
6001
6002 std::vector<VECTOR2I> out;
6003
6004 for( const std::pair<int, int>& pt : candidates )
6005 {
6006 auto endIt = ends.find( pt );
6007 int endCount = endIt != ends.end() ? endIt->second : 0;
6008 int through = 0;
6009 std::set<uint32_t> netIds;
6010
6011 for( const ORCAD_WIRE& wire : aPage.wires )
6012 {
6013 if( onSegment( pt.first, pt.second, wire ) )
6014 {
6015 through++;
6016 netIds.insert( wire.id );
6017 }
6018 }
6019
6020 int pinCount = pinPts.count( pt ) ? 1 : 0;
6021 int score = endCount + 2 * through + pinCount;
6022
6023 // Junction needed when 3+ contributions meet and at least one terminates there.
6024 bool interiorCrossing = interiorCrossings.count( pt ) != 0;
6025
6026 if( netIds.size() <= 1 && score >= 3 && ( endCount + pinCount >= 1 || interiorCrossing )
6027 && endCount + through >= 2 )
6028 {
6029 out.emplace_back( pt.first, pt.second );
6030 }
6031 }
6032
6033 return out;
6034}
6035
6036
6038{
6039 for( const VECTOR2I& pt : computeJunctions( aPage ) )
6040 appendPageItem( aScreen, new SCH_JUNCTION( OrcadDbuToIu( pt.x, pt.y ) ) );
6041}
6042
6043
6044static std::optional<std::vector<SEG>> eligibleSourceConnectivityWires(
6045 const ORCAD_RAW_PAGE& aPage, const VECTOR2I& aPosition, const std::set<std::string>& aNames,
6046 bool aBus = false, uint32_t* aNetId = nullptr )
6047{
6048 std::map<uint32_t, std::vector<SEG>> incidentNets;
6049 std::set<uint32_t> matchingNets;
6050
6051 for( const ORCAD_WIRE& wire : aPage.wires )
6052 {
6053 if( wire.isBus != aBus || !rawPointOnSegment( aPosition.x, aPosition.y, wire ) )
6054 continue;
6055
6056 incidentNets[wire.id].emplace_back( OrcadDbuToIu( wire.x1, wire.y1 ),
6057 OrcadDbuToIu( wire.x2, wire.y2 ) );
6058 auto matchesName = [&]( const std::string& aName )
6059 {
6060 return std::any_of( aNames.begin(), aNames.end(),
6061 [&]( const std::string& aCandidate )
6062 {
6063 return !aCandidate.empty()
6064 && FromOrcadString( aName ).CmpNoCase(
6065 FromOrcadString( aCandidate ) ) == 0;
6066 } );
6067 };
6068 auto name = aPage.netmap.find( wire.id );
6069 auto aliases = aPage.netAliases.find( wire.id );
6070
6071 if( ( name != aPage.netmap.end() && matchesName( name->second ) )
6072 || ( aliases != aPage.netAliases.end()
6073 && std::any_of( aliases->second.begin(), aliases->second.end(), matchesName ) ) )
6074 matchingNets.insert( wire.id );
6075 }
6076
6077 if( matchingNets.size() == 1 || ( matchingNets.empty() && incidentNets.size() == 1 ) )
6078 {
6079 uint32_t netId = matchingNets.empty() ? incidentNets.begin()->first : *matchingNets.begin();
6080
6081 if( aNetId )
6082 *aNetId = netId;
6083
6084 return incidentNets.at( netId );
6085 }
6086
6087 if( incidentNets.empty() )
6088 return std::vector<SEG>();
6089
6090 return std::nullopt;
6091}
6092
6093
6094void ORCAD_CONVERTER::placeWires( const ORCAD_RAW_PAGE& aPage, SCH_SCREEN* aScreen, bool aHierarchical,
6095 bool aNestedHierarchy, bool aSharedFolderPage )
6096{
6097 bool sharedFlatFolder = aSharedFolderPage && !m_scopeNamedFlatNets && !m_scopeGeneratedFlatNets;
6098 std::map<uint32_t, const std::string*> generatedNamesByObjectId;
6099 std::map<uint32_t, const std::string*> occurrenceNamesByNetId;
6100 std::set<uint32_t> localizedOwnedOccurrenceNetIds;
6101 std::map<uint32_t, std::string> connectorNamesByObjectId;
6102 std::set<uint32_t> globalConnectorObjectIds;
6103 std::map<uint32_t, std::string> netNamesByObjectId;
6104 std::map<uint32_t, const std::string*> netNamesByWireObjectId;
6105 std::map<uint32_t, std::set<std::string>> blockPinNamesByNetId;
6106 std::map<std::pair<int, int>, std::set<std::string>> connectorNamesByPosition;
6107 std::set<std::pair<int, int>> globalConnectorPositions;
6108 std::vector<size_t> wireParents( aPage.wires.size() );
6109 std::map<size_t, std::set<std::string>> powerAliasesByWireGroup;
6110 std::map<size_t, std::set<std::string>> powerGlobalNamesByWireGroup;
6111 std::map<size_t, std::set<std::string>> resolvedPowerNamesByWireGroup;
6112 std::map<uint32_t, std::set<std::string>> resolvedPowerNamesByNetId;
6113 std::map<uint32_t, std::string> flattenedNamesByNetId;
6114 std::set<uint32_t> renamedNetIds;
6115 std::set<std::string> occurrenceNames;
6116 std::map<const std::string*, std::string> occurrenceElectricalNames;
6117 const std::map<std::string, std::string>* hierBusNames = nullptr;
6118 std::iota( wireParents.begin(), wireParents.end(), 0 );
6119
6121 {
6122 for( const auto& [occurrenceId, name] : *m_currentOccNetNames )
6123 occurrenceElectricalNames[&name] = occurrenceElectricalNetName( occurrenceId, name );
6124 }
6125
6126 auto occurrenceElectricalName = [&]( const std::string* aName ) -> const std::string&
6127 {
6128 return occurrenceElectricalNames.at( aName );
6129 };
6130
6131 auto wireRoot = [&]( size_t aIndex )
6132 {
6133 while( wireParents[aIndex] != aIndex )
6134 {
6135 wireParents[aIndex] = wireParents[wireParents[aIndex]];
6136 aIndex = wireParents[aIndex];
6137 }
6138
6139 return aIndex;
6140 };
6141
6142 auto joinWires = [&]( size_t aLeft, size_t aRight )
6143 {
6144 size_t left = wireRoot( aLeft );
6145 size_t right = wireRoot( aRight );
6146
6147 if( left != right )
6148 wireParents[right] = left;
6149 };
6150
6151 auto pointOnWire = []( const VECTOR2I& aPoint, const ORCAD_WIRE& aWire )
6152 {
6153 return ( aPoint.x == aWire.x1 && aPoint.y == aWire.y1 ) || ( aPoint.x == aWire.x2 && aPoint.y == aWire.y2 )
6154 || onSegment( aPoint.x, aPoint.y, aWire );
6155 };
6156
6157 std::map<uint32_t, std::vector<size_t>> powerWireIndicesByNetId;
6158
6159 for( size_t i = 0; i < aPage.wires.size(); ++i )
6160 {
6161 if( !aPage.wires[i].isBus )
6162 powerWireIndicesByNetId[aPage.wires[i].id].push_back( i );
6163 }
6164
6165 std::vector<VECTOR2I> junctions = computeJunctions( aPage );
6166
6167 for( const auto& [netId, indices] : powerWireIndicesByNetId )
6168 {
6169 for( size_t left = 0; left < indices.size(); ++left )
6170 {
6171 const ORCAD_WIRE& leftWire = aPage.wires[indices[left]];
6172
6173 for( size_t right = left + 1; right < indices.size(); ++right )
6174 {
6175 const ORCAD_WIRE& rightWire = aPage.wires[indices[right]];
6176 bool connected = pointOnWire( VECTOR2I( leftWire.x1, leftWire.y1 ), rightWire )
6177 || pointOnWire( VECTOR2I( leftWire.x2, leftWire.y2 ), rightWire )
6178 || pointOnWire( VECTOR2I( rightWire.x1, rightWire.y1 ), leftWire )
6179 || pointOnWire( VECTOR2I( rightWire.x2, rightWire.y2 ), leftWire );
6180
6181 if( !connected )
6182 {
6183 connected = std::any_of( junctions.begin(), junctions.end(),
6184 [&]( const VECTOR2I& aJunction )
6185 {
6186 return pointOnWire( aJunction, leftWire )
6187 && pointOnWire( aJunction, rightWire );
6188 } );
6189 }
6190
6191 if( connected )
6192 joinWires( indices[left], indices[right] );
6193 }
6194 }
6195 }
6196
6197 for( const ORCAD_NET_GROUP& net : aPage.netGroups )
6198 {
6199 if( !net.name.empty() )
6200 netNamesByObjectId[net.id] = canonicalGlobalNetName( net.name );
6201 }
6202
6203 for( const ORCAD_GRAPHIC_INST& global : aPage.globals )
6204 {
6205 std::string name = powerNet( aPage, global );
6206 connectorNamesByObjectId[global.dbId] = name;
6207 globalConnectorObjectIds.insert( global.dbId );
6208 VECTOR2I position = powerPinPos( aPage, global );
6209 connectorNamesByPosition[{ position.x, position.y }].insert( name );
6210 globalConnectorPositions.insert( { position.x, position.y } );
6211
6212 std::set<std::string> sourceNames;
6213 std::string logicalName = effectiveInterfaceNetName( trimmed( global.logicalName ) );
6214
6215 if( !logicalName.empty() )
6216 sourceNames.insert( logicalName );
6217
6218 auto property = global.props.find( "Name" );
6219
6220 if( property != global.props.end() )
6221 {
6222 std::string propertyName = effectiveInterfaceNetName( trimmed( property->second ) );
6223
6224 if( !propertyName.empty() )
6225 sourceNames.insert( propertyName );
6226 }
6227
6228 VECTOR2I pin = powerPinPos( aPage, global );
6229
6230 for( size_t wireIndex = 0; wireIndex < aPage.wires.size(); ++wireIndex )
6231 {
6232 const ORCAD_WIRE& wire = aPage.wires[wireIndex];
6233 bool endpoint = ( pin.x == wire.x1 && pin.y == wire.y1 ) || ( pin.x == wire.x2 && pin.y == wire.y2 );
6234
6235 if( !wire.isBus && ( endpoint || onSegment( pin.x, pin.y, wire ) ) )
6236 {
6237 size_t root = wireRoot( wireIndex );
6238 powerAliasesByWireGroup[root].insert( sourceNames.begin(), sourceNames.end() );
6239 powerGlobalNamesByWireGroup[root].insert( sourceNames.begin(), sourceNames.end() );
6240 resolvedPowerNamesByWireGroup[root].insert( name );
6241 resolvedPowerNamesByNetId[wire.id].insert( name );
6242 }
6243 }
6244 }
6245
6246 for( const auto& [root, globalNames] : powerGlobalNamesByWireGroup )
6247 {
6248 if( globalNames.size() < 2 )
6249 continue;
6250
6251 for( size_t wireIndex = 0; wireIndex < aPage.wires.size(); ++wireIndex )
6252 {
6253 if( aPage.wires[wireIndex].isBus || wireRoot( wireIndex ) != root )
6254 continue;
6255
6256 for( const ORCAD_ALIAS& alias : aPage.wires[wireIndex].aliases )
6257 {
6258 std::string name = effectiveInterfaceNetName( alias.name );
6259
6260 if( !name.empty() )
6261 powerAliasesByWireGroup[root].insert( std::move( name ) );
6262 }
6263 }
6264 }
6265
6266 for( const ORCAD_GRAPHIC_INST& offpage : aPage.offpage )
6267 {
6268 std::string name =
6269 effectiveInterfaceNetName( offpage.logicalName.empty() ? offpage.name : offpage.logicalName );
6270 connectorNamesByObjectId[offpage.dbId] = name;
6271 if( !aHierarchical )
6272 globalConnectorObjectIds.insert( offpage.dbId );
6273 VECTOR2I position = namedGraphicPinPos( aPage, offpage );
6274 connectorNamesByPosition[{ position.x, position.y }].insert( std::move( name ) );
6275 if( !aHierarchical )
6276 globalConnectorPositions.insert( { position.x, position.y } );
6277 }
6278
6279 for( const ORCAD_GRAPHIC_INST& port : aPage.ports )
6280 {
6281 std::string name = canonicalGlobalNetName( port.logicalName.empty() ? port.name : port.logicalName );
6282 connectorNamesByObjectId[port.dbId] = name;
6283 if( !aHierarchical )
6284 globalConnectorObjectIds.insert( port.dbId );
6285 VECTOR2I position = namedGraphicPinPos( aPage, port );
6286 connectorNamesByPosition[{ position.x, position.y }].insert( std::move( name ) );
6287 if( !aHierarchical )
6288 globalConnectorPositions.insert( { position.x, position.y } );
6289 }
6290
6291 for( const ORCAD_DRAWN_INSTANCE& block : aPage.blocks )
6292 {
6293 for( const ORCAD_BLOCK_PIN& pin : block.pins )
6294 {
6295 std::string name = canonicalGlobalNetName( pin.name );
6296
6297 if( name.empty() )
6298 continue;
6299
6300 connectorNamesByPosition[{ pin.x, pin.y }].insert( name );
6301
6302 for( const ORCAD_WIRE& wire : aPage.wires )
6303 {
6304 if( !wire.isBus && pointOnWire( VECTOR2I( pin.x, pin.y ), wire ) )
6305 blockPinNamesByNetId[wire.id].insert( name );
6306 }
6307 }
6308 }
6309
6310 auto isImplicitGeneratedName = []( const std::string& aName )
6311 {
6312 if( aName.size() < 2 || aName[0] != 'N' || !std::isdigit( static_cast<unsigned char>( aName[1] ) ) )
6313 return false;
6314
6315 return std::all_of( aName.begin() + 1, aName.end(),
6316 []( unsigned char c )
6317 {
6318 return std::isdigit( c );
6319 } );
6320 };
6321
6322 auto busNames = m_hierBusNamesByScreen.find( aScreen->GetUuid().AsStdString() );
6323
6324 if( busNames != m_hierBusNamesByScreen.end() )
6325 hierBusNames = &busNames->second;
6326
6328 {
6329 std::map<uint32_t, uint32_t> netIdByWireObjectId;
6330 std::map<uint32_t, std::set<const std::string*>> objectNamesByNetId;
6331
6332 for( const ORCAD_WIRE& wire : aPage.wires )
6333 netIdByWireObjectId[wire.dbId] = wire.id;
6334
6335 auto lower = []( const std::string& aName )
6336 {
6337 std::string result = aName;
6338 std::transform( result.begin(), result.end(), result.begin(),
6339 []( unsigned char c )
6340 {
6341 return static_cast<char>( std::tolower( c ) );
6342 } );
6343 return result;
6344 };
6345 for( const auto& [netId, localName] : aPage.netmap )
6346 {
6347 std::set<std::string> localNames = { localName };
6348 std::set<const std::string*> candidates;
6349 std::set<const std::string*> ownedCandidates;
6350 bool localizedOwnedCandidate = false;
6351
6352 auto aliases = aPage.netAliases.find( netId );
6353
6354 if( aliases != aPage.netAliases.end() )
6355 localNames.insert( aliases->second.begin(), aliases->second.end() );
6356
6357 bool directOffpage = std::any_of(
6358 aPage.offpage.begin(), aPage.offpage.end(),
6359 [&]( const ORCAD_GRAPHIC_INST& aConnector )
6360 {
6361 std::string connectorName = aConnector.logicalName.empty() ? aConnector.name
6362 : aConnector.logicalName;
6363 std::string connectorKey = lower( connectorName );
6364 return std::any_of( localNames.begin(), localNames.end(),
6365 [&]( const std::string& aName )
6366 {
6367 return lower( aName ) == connectorKey;
6368 } );
6369 } );
6370
6371 for( const std::string& candidateLocalName : localNames )
6372 {
6373 bool implicitGenerated = isImplicitGeneratedName( candidateLocalName );
6374 std::string localKey = lower( candidateLocalName );
6375
6376 for( const auto& [occurrenceId, occurrenceName] : *m_currentOccNetNames )
6377 {
6378 std::string occurrenceKey = lower( occurrenceName );
6379 bool occurrenceOwnedByNet = false;
6380
6381 if( std::optional<uint32_t> objectId = occurrenceNetObjectId( occurrenceName ) )
6382 {
6383 occurrenceOwnedByNet = std::any_of(
6384 aPage.instances.begin(), aPage.instances.end(),
6385 [&]( const ORCAD_PLACED_INSTANCE& aInstance )
6386 {
6387 if( aInstance.dbId != *objectId )
6388 return false;
6389
6390 return std::any_of(
6391 aInstance.pins.begin(), aInstance.pins.end(),
6392 [&]( const ORCAD_PIN_INST& aPin )
6393 {
6394 if( aPin.wordB == netId )
6395 return true;
6396
6397 return std::any_of(
6398 aPage.wires.begin(), aPage.wires.end(),
6399 [&]( const ORCAD_WIRE& aWire )
6400 {
6401 return aWire.id == netId
6402 && ( aPin.wordA == aWire.dbId
6403 || rawPointOnSegment( aPin.x, aPin.y,
6404 aWire ) );
6405 } );
6406 } );
6407 } );
6408 }
6409
6410 if( occurrenceKey == localKey
6411 || ( !implicitGenerated
6412 && ( !isOffpageNetName( candidateLocalName ) || occurrenceOwnedByNet )
6413 && !isPowerNetName( candidateLocalName ) && occurrenceKey.size() > localKey.size()
6414 && occurrenceKey[localKey.size()] == '_'
6415 && occurrenceKey.compare( 0, localKey.size(), localKey ) == 0 ) )
6416 {
6417 candidates.insert( &occurrenceName );
6418
6419 if( occurrenceOwnedByNet && !directOffpage )
6420 {
6421 ownedCandidates.insert( &occurrenceName );
6422 localizedOwnedCandidate |= !sharedFlatFolder && isOffpageNetName( candidateLocalName );
6423 }
6424 }
6425 }
6426 }
6427
6428 if( ownedCandidates.size() == 1 )
6429 {
6430 occurrenceNamesByNetId[netId] = *ownedCandidates.begin();
6431 if( localizedOwnedCandidate )
6432 localizedOwnedOccurrenceNetIds.insert( netId );
6433 }
6434 else if( candidates.size() == 1 )
6435 {
6436 occurrenceNamesByNetId[netId] = *candidates.begin();
6437
6438 const std::string* selected = *candidates.begin();
6439 std::string selectedKey = lower( kicadOccurrenceNetName( *selected ) );
6440 bool localOffpage = std::any_of( localNames.begin(), localNames.end(),
6441 [&]( const std::string& aName )
6442 {
6443 return isOffpageNetName( aName )
6444 && lower( aName ) == selectedKey;
6445 } );
6446 bool competingSuffixedOccurrence = std::any_of(
6448 [&]( const auto& aOccurrence )
6449 {
6450 std::string key = lower( kicadOccurrenceNetName( aOccurrence.second ) );
6451 return &aOccurrence.second != selected && key.size() > selectedKey.size()
6452 && key[selectedKey.size()] == '_'
6453 && key.compare( 0, selectedKey.size(), selectedKey ) == 0
6454 && occurrenceNetObjectId( aOccurrence.second ).has_value();
6455 } );
6456
6457 if( localOffpage && !directOffpage && competingSuffixedOccurrence )
6458 {
6459 std::optional<uint32_t> componentId;
6460
6461 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
6462 {
6463 bool connected = std::any_of(
6464 instance.pins.begin(), instance.pins.end(),
6465 [&]( const ORCAD_PIN_INST& aPin )
6466 {
6467 if( aPin.wordB == netId )
6468 return true;
6469
6470 return std::any_of( aPage.wires.begin(), aPage.wires.end(),
6471 [&]( const ORCAD_WIRE& aWire )
6472 {
6473 return aWire.id == netId
6474 && ( aPin.wordA == aWire.dbId
6475 || rawPointOnSegment( aPin.x, aPin.y,
6476 aWire ) );
6477 } );
6478 } );
6479
6480 if( connected && ( !componentId || instance.dbId < *componentId ) )
6481 componentId = instance.dbId;
6482 }
6483
6484 if( componentId )
6485 {
6486 occurrenceElectricalNames[selected] = kicadOccurrenceNetName( *selected ) + "_"
6487 + std::to_string( *componentId );
6488
6489 if( !sharedFlatFolder )
6490 localizedOwnedOccurrenceNetIds.insert( netId );
6491 }
6492 }
6493 }
6494 }
6495
6496 for( const auto& [occurrenceId, name] : *m_currentOccNetNames )
6497 {
6498 occurrenceNames.insert( name );
6499
6500 if( std::optional<uint32_t> objectId = occurrenceNetObjectId( name ) )
6501 {
6502 auto netId = netIdByWireObjectId.find( *objectId );
6503
6504 if( netId != netIdByWireObjectId.end() )
6505 objectNamesByNetId[netId->second].insert( &name );
6506 }
6507
6508 if( name.size() < 2 || name[0] != 'N' )
6509 continue;
6510
6511 uint64_t objectId = 0;
6512
6513 auto it = name.begin() + 1;
6514
6515 for( ; it != name.end() && std::isdigit( static_cast<unsigned char>( *it ) ); ++it )
6516 {
6517 objectId = objectId * 10 + static_cast<unsigned>( *it - '0' );
6518 }
6519
6520 if( objectId && objectId <= std::numeric_limits<uint32_t>::max() && ( it == name.end() || *it == '_' ) )
6521 {
6522 generatedNamesByObjectId[static_cast<uint32_t>( objectId )] = &name;
6523 }
6524 }
6525
6526 for( const auto& [netId, names] : objectNamesByNetId )
6527 {
6528 if( names.size() == 1 )
6529 occurrenceNamesByNetId[netId] = *names.begin();
6530 }
6531
6532 for( const auto& [netId, occurrenceName] : occurrenceNamesByNetId )
6533 {
6534 std::string electricalName = occurrenceElectricalNames.at( occurrenceName );
6535 bool collides = std::any_of( aPage.netmap.begin(), aPage.netmap.end(),
6536 [&]( const auto& aPageNet )
6537 {
6538 return aPageNet.first != netId
6539 && wxString::FromUTF8(
6540 kicadElectricalNetName( aPageNet.second ) )
6541 .CmpNoCase( wxString::FromUTF8( electricalName ) )
6542 == 0;
6543 } );
6544
6545 if( !collides )
6546 continue;
6547
6548 std::optional<uint32_t> occurrenceId = occurrenceNetIdFor( occurrenceName );
6549
6550 if( !occurrenceId )
6551 continue;
6552
6553 std::string suffix = std::to_string( *occurrenceId );
6554
6555 if( suffix.size() < 6 )
6556 suffix.insert( 0, 6 - suffix.size(), '0' );
6557
6558 occurrenceElectricalNames[occurrenceName] += "_" + suffix;
6559 }
6560 }
6561
6562 std::map<uint32_t, const std::string*> netNamesById;
6563 std::map<uint32_t, std::vector<size_t>> wiresByNetId;
6564 std::set<uint32_t> ambiguousNetIds;
6565
6566 for( const auto& [netId, name] : aPage.netmap )
6567 {
6568 auto aliases = aPage.netAliases.find( netId );
6569 std::set<std::string> distinctNames;
6570
6571 if( aliases != aPage.netAliases.end() )
6572 {
6573 for( const std::string& alias : aliases->second )
6574 {
6575 if( !alias.empty() )
6576 distinctNames.insert( alias );
6577 }
6578 }
6579
6580 if( distinctNames.size() > 1 )
6581 {
6582 ambiguousNetIds.insert( netId );
6583
6584 if( isPowerNetName( name ) )
6585 netNamesById[netId] = &name;
6586
6587 continue;
6588 }
6589
6590 bool explicitAlias =
6591 aliases != aPage.netAliases.end()
6592 && std::find( aliases->second.begin(), aliases->second.end(), name ) != aliases->second.end();
6593
6594 if( !name.empty() && ( !isImplicitGeneratedName( name ) || explicitAlias ) )
6595 netNamesById[netId] = &name;
6596 }
6597
6598 for( const auto& [netId, name] : occurrenceNamesByNetId )
6599 {
6600 netNamesById[netId] = name;
6601 m_sourceNetNames[{ aScreen, netId }].insert( *name );
6602 }
6603
6604 std::set<uint32_t> explicitlyAliasedNets;
6605
6606 for( const ORCAD_WIRE& wire : aPage.wires )
6607 {
6608 if( !wire.aliases.empty() )
6609 explicitlyAliasedNets.insert( wire.id );
6610 }
6611
6612 // Generated nets can exist only in the occurrence table, with no serialized page-net name.
6613 std::map<uint32_t, std::string> generatedSourceNames = aPage.netmap;
6614
6615 for( const auto& [netId, name] : occurrenceNamesByNetId )
6616 generatedSourceNames.try_emplace( netId, kicadOccurrenceNetName( *name ) );
6617
6618 for( const auto& [netId, sourceName] : generatedSourceNames )
6619 {
6620 bool occurrenceOnly = !aPage.netmap.count( netId );
6621 std::string generatedBase = occurrenceOnly ? sourceName.substr( 0, sourceName.find( '_' ) ) : sourceName;
6622
6623 if( !isImplicitGeneratedName( generatedBase ) )
6624 continue;
6625
6626 auto aliases = aPage.netAliases.find( netId );
6627
6628 // The serialized name table includes the generated primary name as well as secondary names.
6629 if( explicitlyAliasedNets.count( netId )
6630 || ( aliases != aPage.netAliases.end()
6631 && std::any_of( aliases->second.begin(), aliases->second.end(),
6632 [&]( const std::string& alias )
6633 {
6634 return !alias.empty() && alias != sourceName;
6635 } ) ) )
6636 continue;
6637
6638 auto occurrence = occurrenceNamesByNetId.find( netId );
6639 std::string name = occurrence != occurrenceNamesByNetId.end()
6640 ? kicadOccurrenceNetName( *occurrence->second ) : sourceName;
6641
6642 if( m_scopeGeneratedFlatNets && isImplicitGeneratedName( name ) )
6643 {
6644 // Cadence qualifies a reused sheet's generated net with its occurrence id, and that is
6645 // the spelling the paired board carries, so prefer it over our own flat suffix.
6646 std::optional<uint32_t> occurrenceId;
6647
6648 if( occurrence != occurrenceNamesByNetId.end() )
6649 occurrenceId = occurrenceNetIdFor( occurrence->second );
6650
6651 if( occurrenceId )
6652 {
6653 name += "_" + std::to_string( *occurrenceId );
6654 }
6655 else
6656 {
6657 if( m_currentFlatNetSuffix.empty() )
6658 continue;
6659
6660 std::string suffix = m_currentFlatNetSuffix;
6661 std::transform( suffix.begin(), suffix.end(), suffix.begin(),
6662 []( unsigned char c )
6663 {
6664 return static_cast<char>( std::toupper( c ) );
6665 } );
6666 name += "_" + suffix;
6667 }
6668 }
6669
6670 if( ( isImplicitGeneratedName( name ) || name.starts_with( generatedBase + "_" ) )
6671 && !isPowerNetName( name ) && !isOffpageNetName( name ) )
6672 {
6673 m_sourceNetNames[{ aScreen, netId }].insert( name );
6674 m_sourceGeneratedNetNames[{ aScreen, netId }] = std::move( name );
6675 }
6676 }
6677
6678 for( const auto& [netId, occurrenceName] : occurrenceNamesByNetId )
6679 {
6680 std::string occurrenceKey = kicadOccurrenceNetName( *occurrenceName );
6681 std::transform( occurrenceKey.begin(), occurrenceKey.end(), occurrenceKey.begin(),
6682 []( unsigned char c )
6683 {
6684 return static_cast<char>( std::tolower( c ) );
6685 } );
6686 auto occurrenceDepth = m_occurrenceNetNameMinDepth.find( occurrenceKey );
6687
6688 if( occurrenceDepth == m_occurrenceNetNameMinDepth.end() )
6689 continue;
6690
6691 std::map<size_t, std::set<std::string>> candidatesByDepth;
6692 std::set<std::string> sourceNames;
6693 auto pageName = aPage.netmap.find( netId );
6694
6695 if( pageName != aPage.netmap.end() && !pageName->second.empty() )
6696 sourceNames.insert( canonicalGlobalNetName( pageName->second ) );
6697
6698 auto aliases = aPage.netAliases.find( netId );
6699
6700 if( aliases != aPage.netAliases.end() )
6701 {
6702 for( const std::string& alias : aliases->second )
6703 sourceNames.insert( canonicalGlobalNetName( alias ) );
6704 }
6705
6706 for( const std::string& sourceName : sourceNames )
6707 {
6708 std::string sourceKey = sourceName;
6709 std::transform( sourceKey.begin(), sourceKey.end(), sourceKey.begin(),
6710 []( unsigned char c )
6711 {
6712 return static_cast<char>( std::tolower( c ) );
6713 } );
6714 auto sourceDepth = m_occurrenceNetNameMinDepth.find( sourceKey );
6715
6716 if( sourceDepth != m_occurrenceNetNameMinDepth.end() && sourceDepth->second < occurrenceDepth->second )
6717 candidatesByDepth[sourceDepth->second].insert( sourceName );
6718 }
6719
6720 if( !candidatesByDepth.empty() && candidatesByDepth.begin()->second.size() == 1 )
6721 {
6722 std::string& flattened = flattenedNamesByNetId[netId];
6723 flattened = *candidatesByDepth.begin()->second.begin();
6724 netNamesById[netId] = &flattened;
6725 renamedNetIds.insert( netId );
6726 }
6727 }
6728
6729 std::map<std::string, std::set<uint32_t>> blockNetIdsByName;
6730
6731 for( const auto& [netId, name] : aPage.netmap )
6732 {
6733 if( !name.empty() && !isImplicitGeneratedName( name ) )
6734 blockNetIdsByName[canonicalGlobalNetName( name )].insert( netId );
6735 }
6736
6737 for( const auto& [netId, names] : blockPinNamesByNetId )
6738 {
6739 if( !names.empty() )
6740 blockNetIdsByName[*names.begin()].insert( netId );
6741 }
6742
6743 std::map<uint32_t, std::string> blockElectricalNames;
6744 std::set<uint32_t> blockElectricalNetIds;
6745
6746 for( const auto& [netId, names] : blockPinNamesByNetId )
6747 {
6748 auto currentName = netNamesById.find( netId );
6749
6750 if( !names.empty() && ( currentName == netNamesById.end() || isImplicitGeneratedName( *currentName->second ) ) )
6751 {
6752 std::string name = *names.begin();
6753
6754 if( blockNetIdsByName.at( name ).size() > 1 )
6755 {
6756 std::string suffix = std::to_string( netId );
6757
6758 if( suffix.size() < 6 )
6759 suffix.insert( 0, 6 - suffix.size(), '0' );
6760
6761 name += "_" + suffix;
6762 }
6763
6764 auto inserted = blockElectricalNames.emplace( netId, std::move( name ) ).first;
6765 netNamesById[netId] = &inserted->second;
6766 blockElectricalNetIds.insert( netId );
6767 }
6768 }
6769
6770 if( !m_currentUnconnectedInterfaceNetNames.empty() )
6771 {
6772 for( const ORCAD_GRAPHIC_INST& port : aPage.ports )
6773 {
6774 std::string name = canonicalGlobalNetName( port.logicalName.empty() ? port.name : port.logicalName );
6775 std::string key = name;
6776 std::transform( key.begin(), key.end(), key.begin(),
6777 []( unsigned char c )
6778 {
6779 return static_cast<char>( std::tolower( c ) );
6780 } );
6781
6782 auto targetName = m_currentUnconnectedInterfaceNetNames.find( key );
6783
6784 if( targetName == m_currentUnconnectedInterfaceNetNames.end() )
6785 continue;
6786
6787 VECTOR2I pin = namedGraphicPinPos( aPage, port );
6788
6789 for( const ORCAD_WIRE& wire : aPage.wires )
6790 {
6791 if( wire.isBus || !pointOnWire( pin, wire ) )
6792 continue;
6793
6794 std::string& flattened = flattenedNamesByNetId[wire.id];
6795 flattened = targetName->second;
6796 netNamesById[wire.id] = &flattened;
6797 renamedNetIds.insert( wire.id );
6798 }
6799 }
6800 }
6801
6802 for( const ORCAD_WIRE& wire : aPage.wires )
6803 {
6804 auto name = netNamesById.find( wire.id );
6805
6806 if( name != netNamesById.end() )
6807 netNamesByWireObjectId[wire.dbId] = name->second;
6808 }
6809
6810 std::set<std::string> interfaceNames;
6811 std::set<uint32_t> interfaceNetIds;
6812 std::map<uint32_t, std::set<std::string>> globalInterfaceNamesByNetId;
6813
6814 auto addInterfacePoint = [&]( const VECTOR2I& aPin, const std::string* aGlobalName,
6815 std::optional<uint32_t> aNetId )
6816 {
6817 for( const ORCAD_WIRE& wire : aPage.wires )
6818 {
6819 bool endpoint = ( aPin.x == wire.x1 && aPin.y == wire.y1 ) || ( aPin.x == wire.x2 && aPin.y == wire.y2 );
6820
6821 if( !wire.isBus && ( !aNetId || wire.id == *aNetId )
6822 && ( endpoint || onSegment( aPin.x, aPin.y, wire ) ) )
6823 {
6824 interfaceNetIds.insert( wire.id );
6825
6826 if( aGlobalName && !aGlobalName->empty() )
6827 globalInterfaceNamesByNetId[wire.id].insert( *aGlobalName );
6828 }
6829 }
6830 };
6831
6832 auto addInterfaceName = [&]( const ORCAD_GRAPHIC_INST& aInstance, bool aGlobal, bool aPower )
6833 {
6834 std::string name = aInstance.logicalName.empty() ? aInstance.name : aInstance.logicalName;
6835
6836 if( aPower )
6837 name = powerNet( aPage, aInstance );
6838 else if( aGlobal )
6839 name = effectiveInterfaceNetName( name );
6840
6841 if( !name.empty() )
6842 interfaceNames.insert( name );
6843
6844 VECTOR2I pin = aPower ? powerPinPos( aPage, aInstance ) : namedGraphicPinPos( aPage, aInstance );
6845 std::optional<uint32_t> sourceNetId;
6846
6847 if( !aPower )
6848 {
6849 uint32_t netId = 0;
6851 auto sourceWires = eligibleSourceConnectivityWires(
6852 aPage, pin, { aInstance.logicalName, name }, bus, &netId );
6853
6854 if( !sourceWires )
6855 {
6856 THROW_IO_ERROR( wxString::Format( _( "Page '%s': cannot determine the source net for interface '%s' "
6857 "at (%d, %d)." ),
6858 FromOrcadString( aPage.name ), FromOrcadString( name ), pin.x, pin.y ) );
6859 }
6860
6861 if( sourceWires->empty() || bus )
6862 return;
6863
6864 sourceNetId = netId;
6865 }
6866
6867 addInterfacePoint( pin, aGlobal ? &name : nullptr, sourceNetId );
6868 };
6869
6870 for( const ORCAD_GRAPHIC_INST& port : aPage.ports )
6871 addInterfaceName( port, false, false );
6872
6873 for( const ORCAD_GRAPHIC_INST& connector : aPage.offpage )
6874 addInterfaceName( connector, !aHierarchical, false );
6875
6876 for( const ORCAD_GRAPHIC_INST& global : aPage.globals )
6877 addInterfaceName( global, true, true );
6878
6879 for( const auto& [netId, name] : occurrenceNamesByNetId )
6880 {
6881 auto selectedName = netNamesById.find( netId );
6882
6883 if( selectedName == netNamesById.end() || selectedName->second != name )
6884 continue;
6885
6886 std::string sourceName = kicadOccurrenceNetName( *name );
6887
6888 std::transform( sourceName.begin(), sourceName.end(), sourceName.begin(),
6889 []( unsigned char c )
6890 {
6891 return static_cast<char>( std::tolower( c ) );
6892 } );
6893
6894 if( !sharedFlatFolder && ( m_scopeNamedFlatNets || m_scopeGeneratedFlatNets || aNestedHierarchy )
6895 && !m_currentInterfaceNetAliases.count( sourceName )
6896 && m_occurrenceNetNameScopeCounts[sourceName] > 1 )
6897 {
6898 continue;
6899 }
6900
6901 if( !aNestedHierarchy
6902 && ( localizedOwnedOccurrenceNetIds.count( netId )
6903 || !m_currentConnectorInterfaceNetAliases.count( sourceName ) ) )
6904 {
6905 globalInterfaceNamesByNetId[netId].clear();
6906 globalInterfaceNamesByNetId[netId].insert( occurrenceElectricalName( name ) );
6907 }
6908
6909 }
6910
6911 for( const ORCAD_DRAWN_INSTANCE& block : aPage.blocks )
6912 {
6913 for( const ORCAD_BLOCK_PIN& pin : block.pins )
6914 addInterfacePoint( VECTOR2I( pin.x, pin.y ), nullptr, std::nullopt );
6915 }
6916
6917 if( m_scopeNamedFlatNets && !m_currentFlatNetSuffix.empty() )
6918 {
6919 for( const auto& [netId, name] : aPage.netmap )
6920 {
6921 if( name.empty() || interfaceNetIds.count( netId ) || interfaceNames.count( name )
6922 || isOffpageNetName( name ) )
6923 continue;
6924
6925 auto selected = netNamesById.find( netId );
6926
6927 if( selected == netNamesById.end() || *selected->second != name )
6928 continue;
6929
6930 std::string& flattened = flattenedNamesByNetId[netId];
6931 flattened = name + "_" + m_currentFlatNetSuffix;
6932 netNamesById[netId] = &flattened;
6933 renamedNetIds.insert( netId );
6934 }
6935 }
6936
6937 for( size_t i = 0; i < aPage.wires.size(); ++i )
6938 {
6939 const ORCAD_WIRE& wire = aPage.wires[i];
6940
6941 if( wire.isBus )
6942 continue;
6943
6944 wiresByNetId[wire.id].push_back( i );
6945
6946 auto generatedName = generatedNamesByObjectId.find( wire.dbId );
6947
6948 if( generatedName != generatedNamesByObjectId.end() )
6949 {
6950 if( m_scopeGeneratedFlatNets && !m_currentFlatNetSuffix.empty() && !interfaceNetIds.count( wire.id ) )
6951 {
6952 std::string& flattened = flattenedNamesByNetId[wire.id];
6953 flattened = *generatedName->second + "_" + m_currentFlatNetSuffix;
6954 netNamesById[wire.id] = &flattened;
6955 renamedNetIds.insert( wire.id );
6956 }
6957 }
6958 }
6959
6960 std::set<VECTOR2I> electricalContacts;
6961
6962 for( const ORCAD_WIRE& wire : aPage.wires )
6963 {
6964 electricalContacts.emplace( wire.x1, wire.y1 );
6965 electricalContacts.emplace( wire.x2, wire.y2 );
6966 }
6967
6968 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
6969 {
6970 for( size_t index = 0; index < instance.pins.size(); ++index )
6971 electricalContacts.insert( placedPinElectricalPosition( instance, index ) );
6972 }
6973
6974 for( const auto* connectors : { &aPage.globals, &aPage.ports, &aPage.offpage } )
6975 {
6976 for( const ORCAD_GRAPHIC_INST& connector : *connectors )
6977 electricalContacts.emplace( connector.x, connector.y );
6978 }
6979
6980 for( const ORCAD_DRAWN_INSTANCE& block : aPage.blocks )
6981 {
6982 for( const ORCAD_BLOCK_PIN& pin : block.pins )
6983 electricalContacts.emplace( pin.x, pin.y );
6984 }
6985
6986 std::map<int, std::vector<size_t>> horizontalWires;
6987 std::map<int, std::vector<size_t>> verticalWires;
6988 std::map<size_t, std::vector<VECTOR2I>> cutsByWire;
6989
6990 for( size_t i = 0; i < aPage.wires.size(); ++i )
6991 {
6992 const ORCAD_WIRE& wire = aPage.wires[i];
6993
6994 if( wire.isBus )
6995 continue;
6996
6997 if( wire.y1 == wire.y2 )
6998 horizontalWires[wire.y1].push_back( i );
6999 else if( wire.x1 == wire.x2 )
7000 verticalWires[wire.x1].push_back( i );
7001 }
7002
7003 for( const auto& [y, horizontalIndices] : horizontalWires )
7004 {
7005 for( size_t horizontalIndex : horizontalIndices )
7006 {
7007 const ORCAD_WIRE& horizontal = aPage.wires[horizontalIndex];
7008 int minX = std::min( horizontal.x1, horizontal.x2 );
7009 int maxX = std::max( horizontal.x1, horizontal.x2 );
7010
7011 for( auto verticalIt = verticalWires.lower_bound( minX );
7012 verticalIt != verticalWires.end() && verticalIt->first <= maxX; ++verticalIt )
7013 {
7014 for( size_t verticalIndex : verticalIt->second )
7015 {
7016 const ORCAD_WIRE& vertical = aPage.wires[verticalIndex];
7017
7018 if( vertical.id == horizontal.id || !rawPointOnSegment( vertical.x1, y, horizontal )
7019 || !rawPointOnSegment( vertical.x1, y, vertical ) )
7020 {
7021 continue;
7022 }
7023
7024 if( !electricalContacts.count( VECTOR2I( vertical.x1, y ) ) )
7025 continue;
7026
7027 // Only electrical contacts need isolation; a plain X has no KiCad junction.
7028 size_t cutIndex = netNamesById.count( vertical.id ) ? verticalIndex : horizontalIndex;
7029
7030 if( netNamesById.count( aPage.wires[cutIndex].id ) )
7031 cutsByWire[cutIndex].emplace_back( vertical.x1, y );
7032 }
7033 }
7034 }
7035 }
7036
7037 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
7038 {
7039 for( const ORCAD_PIN_INST& pin : instance.pins )
7040 {
7041 if( pin.wordA || pin.wordB )
7042 continue;
7043
7044 for( size_t wireIndex = 0; wireIndex < aPage.wires.size(); ++wireIndex )
7045 {
7046 const ORCAD_WIRE& wire = aPage.wires[wireIndex];
7047
7048 if( !wire.isBus && netNamesById.count( wire.id ) && onSegment( pin.x, pin.y, wire ) )
7049 cutsByWire[wireIndex].emplace_back( pin.x, pin.y );
7050 }
7051 }
7052 }
7053
7054 std::set<size_t> labelWireIndices;
7055
7056 for( const auto& [netId, wireIndices] : wiresByNetId )
7057 {
7058 if( !netNamesById.count( netId ) && globalInterfaceNamesByNetId[netId].size() != 1 )
7059 continue;
7060
7061 std::set<size_t> unseen( wireIndices.begin(), wireIndices.end() );
7062
7063 while( !unseen.empty() )
7064 {
7065 size_t representative = *unseen.begin();
7066 std::vector<size_t> pending = { representative };
7067 unseen.erase( representative );
7068
7069 while( !pending.empty() )
7070 {
7071 const ORCAD_WIRE& current = aPage.wires[pending.back()];
7072 pending.pop_back();
7073
7074 for( auto it = unseen.begin(); it != unseen.end(); )
7075 {
7076 const ORCAD_WIRE& candidate = aPage.wires[*it];
7077 bool touches = onSegment( current.x1, current.y1, candidate )
7078 || onSegment( current.x2, current.y2, candidate )
7079 || onSegment( candidate.x1, candidate.y1, current )
7080 || onSegment( candidate.x2, candidate.y2, current );
7081
7082 if( touches )
7083 {
7084 pending.push_back( *it );
7085 it = unseen.erase( it );
7086 }
7087 else
7088 {
7089 ++it;
7090 }
7091 }
7092 }
7093
7094 labelWireIndices.insert( representative );
7095 }
7096 }
7097
7098 for( size_t wireIndex = 0; wireIndex < aPage.wires.size(); ++wireIndex )
7099 {
7100 const ORCAD_WIRE& wire = aPage.wires[wireIndex];
7101 auto appendSegment = [&]( const VECTOR2I& aStart, const VECTOR2I& aEnd, int aLayer )
7102 {
7103 if( aStart == aEnd )
7104 return;
7105
7106 SCH_LINE* line = new SCH_LINE( OrcadDbuToIu( aStart.x, aStart.y ), aLayer );
7107 line->SetEndPoint( OrcadDbuToIu( aEnd.x, aEnd.y ) );
7108 line->SetLineWidth( OrcadLineWidthIu( wire.lineWidth ) );
7109 line->SetLineStyle( OrcadLineStyle( wire.lineStyle ) );
7110 line->SetLineColor( OrcadColor( wire.color ) );
7111 appendPageItem( aScreen, line );
7112
7113 if( aLayer == LAYER_WIRE || aLayer == LAYER_BUS )
7114 m_sourceNetItems[{ aScreen, wire.id }].push_back( line );
7115 };
7116
7117 std::vector<VECTOR2I> cuts = cutsByWire[wireIndex];
7118
7119 int64_t dx = static_cast<int64_t>( wire.x2 ) - wire.x1;
7120 int64_t dy = static_cast<int64_t>( wire.y2 ) - wire.y1;
7121
7122 std::sort( cuts.begin(), cuts.end(),
7123 [&]( const VECTOR2I& a, const VECTOR2I& b )
7124 {
7125 int64_t ta = ( static_cast<int64_t>( a.x ) - wire.x1 ) * dx
7126 + ( static_cast<int64_t>( a.y ) - wire.y1 ) * dy;
7127 int64_t tb = ( static_cast<int64_t>( b.x ) - wire.x1 ) * dx
7128 + ( static_cast<int64_t>( b.y ) - wire.y1 ) * dy;
7129 return ta < tb;
7130 } );
7131 cuts.erase( std::unique( cuts.begin(), cuts.end() ), cuts.end() );
7132
7133 VECTOR2I step( dx == 0 ? 0 : ( dx > 0 ? 1 : -1 ), dy == 0 ? 0 : ( dy > 0 ? 1 : -1 ) );
7134 VECTOR2I cursor( wire.x1, wire.y1 );
7135
7136 auto netName = netNamesById.find( wire.id );
7137
7138 auto segmentMidpoint = []( const VECTOR2I& aStart, const VECTOR2I& aEnd )
7139 {
7140 return VECTOR2I( aStart.x + ( aEnd.x - aStart.x ) / 2, aStart.y + ( aEnd.y - aStart.y ) / 2 );
7141 };
7142 auto wireIntentPosition = [&]( const VECTOR2I& aStart, const VECTOR2I& aEnd )
7143 {
7144 VECTOR2I midpoint = segmentMidpoint( aStart, aEnd );
7145 VECTOR2I direction( aEnd.x == aStart.x ? 0 : ( aEnd.x > aStart.x ? 1 : -1 ),
7146 aEnd.y == aStart.y ? 0 : ( aEnd.y > aStart.y ? 1 : -1 ) );
7147 std::vector<VECTOR2I> candidates = { midpoint, aStart + direction, aEnd - direction };
7148
7149 for( int numerator : { 1, 3, 2, 6 } )
7150 {
7151 constexpr int denominator = 8;
7152 candidates.emplace_back( aStart.x + ( aEnd.x - aStart.x ) * numerator / denominator,
7153 aStart.y + ( aEnd.y - aStart.y ) * numerator / denominator );
7154 }
7155
7156 for( const VECTOR2I& candidate : candidates )
7157 {
7158 if( candidate == aStart || candidate == aEnd || !onSegment( candidate.x, candidate.y, wire ) )
7159 continue;
7160
7161 bool crossesAnotherNet =
7162 std::any_of( aPage.wires.begin(), aPage.wires.end(),
7163 [&]( const ORCAD_WIRE& aOther )
7164 {
7165 return &aOther != &wire
7166 && ( aOther.isBus != wire.isBus || aOther.id != wire.id )
7167 && onSegment( candidate.x, candidate.y, aOther );
7168 } );
7169
7170 if( !crossesAnotherNet )
7171 return candidate;
7172 }
7173
7174 return midpoint;
7175 };
7176 auto appendWireNetIntents = [&]( const VECTOR2I& aPosition )
7177 {
7178 std::set<std::string> names;
7179 size_t wireGroup = wireRoot( wireIndex );
7180 bool ambiguousPhysicalNet = ambiguousNetIds.count( wire.id );
7181 auto resolvedPower = resolvedPowerNamesByWireGroup.find( wireGroup );
7182
7183 if( ambiguousPhysicalNet && resolvedPower != resolvedPowerNamesByWireGroup.end() )
7184 names.insert( resolvedPower->second.begin(), resolvedPower->second.end() );
7185 else if( ambiguousPhysicalNet )
7186 {
7187 auto netPower = resolvedPowerNamesByNetId.find( wire.id );
7188
7189 if( netPower != resolvedPowerNamesByNetId.end() && netPower->second.size() == 1 )
7190 names.insert( *netPower->second.begin() );
7191 }
7192
7193 auto aliases = aPage.netAliases.find( wire.id );
7194
7195 if( aliases != aPage.netAliases.end() && !ambiguousPhysicalNet && !occurrenceNamesByNetId.count( wire.id ) )
7196 {
7197 for( const std::string& alias : aliases->second )
7198 names.insert( alias );
7199 }
7200
7201 auto powerAliases = powerAliasesByWireGroup.find( wireGroup );
7202
7203 bool joinedPowerAliases = !ambiguousPhysicalNet && powerAliases != powerAliasesByWireGroup.end()
7204 && powerAliases->second.size() > 1
7205 && powerGlobalNamesByWireGroup[wireGroup].size() > 1;
7206
7207 if( joinedPowerAliases )
7208 names.insert( powerAliases->second.begin(), powerAliases->second.end() );
7209
7210 if( ambiguousPhysicalNet && occurrenceNamesByNetId.count( wire.id ) )
7211 {
7212 names.insert( *occurrenceNamesByNetId.at( wire.id ) );
7213 }
7214 else if( !ambiguousPhysicalNet && netName != netNamesById.end() )
7215 {
7216 std::string key = *netName->second;
7217 std::transform( key.begin(), key.end(), key.begin(),
7218 []( unsigned char c )
7219 {
7220 return static_cast<char>( std::tolower( c ) );
7221 } );
7222
7223 if( !m_currentConnectorInterfaceNetAliases.count( key ) )
7224 names.insert( *netName->second );
7225 }
7226
7227 if( !ambiguousPhysicalNet )
7228 names.insert( globalInterfaceNamesByNetId[wire.id].begin(), globalInterfaceNamesByNetId[wire.id].end() );
7229 else if( globalInterfaceNamesByNetId[wire.id].size() == 1 )
7230 names.insert( *globalInterfaceNamesByNetId[wire.id].begin() );
7231
7232 if( hierBusNames )
7233 {
7234 std::vector<std::string> sourceNames( names.begin(), names.end() );
7235
7236 for( const std::string& name : sourceNames )
7237 names.insert( scopedHierBusMember( name, *hierBusNames ) );
7238 }
7239
7240 for( const std::string& name : names )
7241 {
7242 if( name.empty() )
7243 continue;
7244
7245 bool occurrenceName = occurrenceNamesByNetId.count( wire.id )
7246 && name == *occurrenceNamesByNetId.at( wire.id );
7247 wxString electricalName = FromOrcadString(
7248 occurrenceName ? occurrenceElectricalName( occurrenceNamesByNetId.at( wire.id ) )
7249 : kicadElectricalNetName( name ) );
7250 SCH_LABEL* label = new SCH_LABEL( OrcadDbuToIu( aPosition.x, aPosition.y ), electricalName );
7251
7252 auto sourceName = aPage.netmap.find( wire.id );
7253 bool generated = isImplicitGeneratedName( name )
7254 || ( sourceName != aPage.netmap.end() && isImplicitGeneratedName( sourceName->second )
7255 && name.starts_with( sourceName->second + "_" ) );
7256 bool explicitName = ( !generated && sourceName != aPage.netmap.end()
7257 && canonicalGlobalNetName( sourceName->second ) == name )
7258 || std::any_of( wire.aliases.begin(), wire.aliases.end(),
7259 [&]( const ORCAD_ALIAS& alias )
7260 {
7261 return alias.name == name;
7262 } );
7263 appendNetIntent( aScreen, label, explicitName, wire.id );
7264 }
7265 };
7266
7267 for( const VECTOR2I& cut : cuts )
7268 {
7269 VECTOR2I before = cut - step;
7270 VECTOR2I after = cut + step;
7271
7272 if( !onSegment( before.x, before.y, wire ) )
7273 before = cut;
7274
7275 if( !onSegment( after.x, after.y, wire ) )
7276 after = cut;
7277
7278 appendSegment( cursor, before, wire.isBus ? LAYER_BUS : LAYER_WIRE );
7279
7280 if( cursor != before )
7281 appendWireNetIntents( wireIntentPosition( cursor, before ) );
7282
7283 appendSegment( before, after, LAYER_NOTES );
7284 cursor = after;
7285 }
7286
7287 VECTOR2I end( wire.x2, wire.y2 );
7288 appendSegment( cursor, end, wire.isBus ? LAYER_BUS : LAYER_WIRE );
7289
7290 if( !cuts.empty() && cursor != end )
7291 appendWireNetIntents( wireIntentPosition( cursor, end ) );
7292
7293 for( const ORCAD_ALIAS& alias : wire.aliases )
7294 {
7295 wxString text = FromOrcadString( kicadBusName( trimmed( alias.name ) ) );
7296 std::string electricalName = kicadElectricalNetName( trimmed( alias.name ) );
7297 std::string mappedName = electricalName;
7298
7299 if( hierBusNames )
7300 {
7301 mappedName = wire.isBus ? scopedHierBusRange( electricalName, *hierBusNames )
7302 : scopedHierBusMember( electricalName, *hierBusNames );
7303 }
7304
7305 bool mappedBusAlias = mappedName != electricalName;
7306 wxString electricalText = FromOrcadString( mappedName );
7307
7308 if( text.IsEmpty() )
7309 continue;
7310
7311 VECTOR2I anchor = snapToWire( alias.x, alias.y, wire );
7312 // Quadrants 2/3 fold to 0/90 so text reads upright.
7313 int quadrant = alias.rotation & 3;
7314 int fontId = wireAliasFontId( alias );
7315 bool electrical = !( ambiguousNetIds.count( wire.id ) && netName != netNamesById.end()
7316 && isPowerNetName( *netName->second )
7317 && canonicalGlobalNetName( alias.name ) != *netName->second );
7318
7319 if( !mappedBusAlias && occurrenceNamesByNetId.count( wire.id ) && netName != netNamesById.end()
7320 && canonicalGlobalNetName( alias.name ) != canonicalGlobalNetName( *netName->second ) )
7321 {
7322 electrical = false;
7323 }
7324
7325 VECTOR2I electricalAnchor = anchor;
7326 bool interiorCrossing = anchor != VECTOR2I( wire.x1, wire.y1 ) && anchor != VECTOR2I( wire.x2, wire.y2 )
7327 && std::any_of( aPage.wires.begin(), aPage.wires.end(),
7328 [&]( const ORCAD_WIRE& aOther )
7329 {
7330 return !aOther.isBus && aOther.id != wire.id
7331 && anchor != VECTOR2I( aOther.x1, aOther.y1 )
7332 && anchor != VECTOR2I( aOther.x2, aOther.y2 )
7333 && onSegment( anchor.x, anchor.y, aOther );
7334 } );
7335
7336 if( interiorCrossing && std::find( cuts.begin(), cuts.end(), anchor ) != cuts.end() )
7337 {
7338 VECTOR2I candidate = anchor - step;
7339
7340 if( candidate == anchor || !onSegment( candidate.x, candidate.y, wire ) )
7341 candidate = anchor + step;
7342
7343 electricalAnchor = candidate;
7344 }
7345
7346 if( electrical && ( ( electricalText != text && !mappedBusAlias ) || electricalAnchor != anchor ) )
7347 {
7348 SCH_LABEL* label =
7349 new SCH_LABEL( OrcadDbuToIu( electricalAnchor.x, electricalAnchor.y ), electricalText );
7350 appendNetIntent( aScreen, label, true, wire.id );
7351 electrical = false;
7352 }
7353
7354 if( electrical )
7355 {
7356 SCH_LABEL* label = new SCH_LABEL( OrcadDbuToIu( anchor.x, anchor.y ), electricalText );
7357 label->SetSpinStyle( ( quadrant == 1 || quadrant == 3 ) ? SPIN_STYLE::UP : SPIN_STYLE::RIGHT );
7358 label->SetTextSize( textSize( fontId ) );
7359 applyFont( label, fontId );
7360 label->SetTextColor( OrcadColor( alias.color ) );
7361 appendPageItem( aScreen, label );
7362 m_labelSourceNets[label] = { aScreen, wire.id };
7363 }
7364 else
7365 {
7366 SCH_TEXT* note = new SCH_TEXT( OrcadDbuToIu( anchor.x, anchor.y ), text, LAYER_NOTES );
7367 note->SetTextAngle( ( quadrant == 1 || quadrant == 3 ) ? ANGLE_VERTICAL : ANGLE_HORIZONTAL );
7368 note->SetTextSize( textSize( fontId ) );
7371 applyFont( note, fontId );
7372 note->SetTextColor( OrcadColor( alias.color ) );
7373 appendPageItem( aScreen, note );
7374 }
7375 }
7376
7377 size_t wireGroup = wireRoot( wireIndex );
7378 bool hasPowerAliases = powerAliasesByWireGroup.count( wireGroup )
7379 && powerAliasesByWireGroup.at( wireGroup ).size() > 1
7380 && powerGlobalNamesByWireGroup[wireGroup].size() > 1;
7381
7382 bool authoritativeInterface = globalInterfaceNamesByNetId[wire.id].size() == 1;
7383
7384 if( cuts.empty()
7385 && ( hasPowerAliases
7386 || ( labelWireIndices.count( wireIndex )
7387 && ( netName != netNamesById.end() || authoritativeInterface ) ) ) )
7388 {
7389 appendWireNetIntents( wireIntentPosition( VECTOR2I( wire.x1, wire.y1 ), end ) );
7390 }
7391
7392 }
7393
7394 auto pinTouchesWire = [&aPage]( const ORCAD_PIN_INST& aPin )
7395 {
7396 return std::any_of( aPage.wires.begin(), aPage.wires.end(),
7397 [&aPin]( const ORCAD_WIRE& aWire )
7398 {
7399 return ( aPin.x == aWire.x1 && aPin.y == aWire.y1 )
7400 || ( aPin.x == aWire.x2 && aPin.y == aWire.y2 )
7401 || onSegment( aPin.x, aPin.y, aWire );
7402 } );
7403 };
7404
7405 m_currentImplicitPowerPins.clear();
7406 std::map<std::pair<int, int>, std::string> generatedWirelessNets;
7407
7408 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
7409 {
7410 for( size_t pinIndex = 0; pinIndex < instance.pins.size(); ++pinIndex )
7411 {
7412 const ORCAD_PIN_INST& pin = instance.pins[pinIndex];
7413 VECTOR2I electricalPosition = placedPinElectricalPosition( instance, pinIndex );
7414
7415 if( ( pin.wordA || pin.wordB )
7416 && ( electricalPosition != VECTOR2I( pin.x, pin.y ) || !pinTouchesWire( pin ) ) )
7417 {
7418 std::string generatedName = generatedPinNetName( instance.dbId, pinIndex );
7419
7420 if( occurrenceNames.count( generatedName ) )
7421 generatedWirelessNets[{ electricalPosition.x, electricalPosition.y }] = std::move( generatedName );
7422 }
7423 }
7424 }
7425
7426 for( const ORCAD_PLACED_INSTANCE& instance : aPage.instances )
7427 {
7428 const ORCAD_SYMBOL_DEF* definition = pickVariant( instance ).first;
7429
7430 for( size_t pinIndex = 0; pinIndex < instance.pins.size(); ++pinIndex )
7431 {
7432 const ORCAD_PIN_INST& pin = instance.pins[pinIndex];
7433 VECTOR2I electricalPosition = placedPinElectricalPosition( instance, pinIndex );
7434
7435 if( !pin.wordA && !pin.wordB )
7436 continue;
7437
7438 std::string generatedName = generatedPinNetName( instance.dbId, pinIndex );
7439 std::string name;
7440 bool globalName = false;
7441
7442 if( electricalPosition == VECTOR2I( pin.x, pin.y ) && pinTouchesWire( pin ) )
7443 continue;
7444
7445 if( occurrenceNames.count( generatedName ) )
7446 name = std::move( generatedName );
7447 else if( generatedWirelessNets.count( { electricalPosition.x, electricalPosition.y } ) )
7448 name = generatedWirelessNets.at( { electricalPosition.x, electricalPosition.y } );
7449 else if( pin.wordA && netNamesByWireObjectId.count( pin.wordA ) )
7450 name = *netNamesByWireObjectId.at( pin.wordA );
7451 else if( pin.wordA && netNamesByObjectId.count( pin.wordA ) )
7452 name = netNamesByObjectId.at( pin.wordA );
7453 else if( pin.wordA && connectorNamesByObjectId.count( pin.wordA ) )
7454 {
7455 name = connectorNamesByObjectId.at( pin.wordA );
7456 globalName = globalConnectorObjectIds.count( pin.wordA );
7457 }
7458 else if( pin.wordB )
7459 {
7460 auto occurrenceName = occurrenceNamesByNetId.find( pin.wordB );
7461 auto pageName = aPage.netmap.find( pin.wordB );
7462
7463 if( occurrenceName != occurrenceNamesByNetId.end() )
7464 {
7465 name = occurrenceElectricalName( occurrenceName->second );
7466 std::string sourceName = kicadOccurrenceNetName( *occurrenceName->second );
7467 std::string key = sourceName;
7468 std::transform( key.begin(), key.end(), key.begin(),
7469 []( unsigned char c )
7470 {
7471 return static_cast<char>( std::tolower( c ) );
7472 } );
7473 globalName = !localizedOwnedOccurrenceNetIds.count( pin.wordB )
7474 && ( sharedFlatFolder
7475 || !( m_scopeNamedFlatNets || m_scopeGeneratedFlatNets || aNestedHierarchy )
7476 || m_currentInterfaceNetAliases.count( key )
7477 || m_occurrenceNetNameScopeCounts[key] == 1 );
7478
7479 }
7480 else if( !ambiguousNetIds.count( pin.wordB ) && pageName != aPage.netmap.end() )
7481 name = pageName->second;
7482 }
7483
7484 if( name.empty() && connectorNamesByPosition[{ pin.x, pin.y }].size() == 1 )
7485 {
7486 name = *connectorNamesByPosition.at( { pin.x, pin.y } ).begin();
7487 globalName = globalConnectorPositions.count( { pin.x, pin.y } );
7488 }
7489
7490 bool stackedPeer = std::any_of( instance.pins.begin(), instance.pins.end(),
7491 [&]( const ORCAD_PIN_INST& aPeer )
7492 {
7493 return &aPeer != &pin && aPeer.x == pin.x && aPeer.y == pin.y;
7494 } );
7495 bool sameNetPeer = std::any_of( instance.pins.begin(), instance.pins.end(),
7496 [&]( const ORCAD_PIN_INST& aPeer )
7497 {
7498 return &aPeer != &pin && aPeer.x == pin.x && aPeer.y == pin.y
7499 && ( ( pin.wordA && pin.wordA == aPeer.wordA )
7500 || ( pin.wordB && pin.wordB == aPeer.wordB ) );
7501 } );
7502
7503 if( name.empty() && ( !stackedPeer || sameNetPeer ) && electricalPosition != VECTOR2I( pin.x, pin.y )
7504 && pinTouchesWire( pin ) )
7505 {
7506 name = netAt( aPage, pin.x, pin.y );
7507 }
7508
7509 if( name.empty() && sameNetPeer && electricalPosition != VECTOR2I( pin.x, pin.y ) )
7510 {
7511 auto sourceNet = generatedWirelessNets.find( { pin.x, pin.y } );
7512
7513 if( sourceNet != generatedWirelessNets.end() )
7514 name = sourceNet->second;
7515 }
7516
7517 if( name.empty() && !generatedWirelessNets.count( { electricalPosition.x, electricalPosition.y } )
7518 && definition && pin.pinIndex > 0 && static_cast<size_t>( pin.pinIndex ) <= definition->pins.size() )
7519 {
7520 std::string sourceName = trimmed( definition->pins[pin.pinIndex - 1].name );
7521
7522 if( occurrenceNames.count( sourceName ) )
7523 name = std::move( sourceName );
7524 }
7525
7526 if( name.empty() )
7527 continue;
7528
7529 std::string scopedName = name;
7530
7531 if( hierBusNames )
7532 scopedName = scopedHierBusMember( name, *hierBusNames );
7533
7534 bool globalNet = scopedName == name
7535 && ( globalName || name.rfind( "$$$", 0 ) == 0
7536 || ( !aHierarchical && isOffpageNetName( name ) ) || isPowerNetName( name ) );
7537
7538 uint32_t sourceNetId = pin.wordB;
7539
7540 if( m_currentOccNetNames )
7541 {
7542 for( const auto& [id, occurrenceName] : *m_currentOccNetNames )
7543 {
7544 if( kicadOccurrenceNetName( occurrenceName ) == name )
7545 {
7546 sourceNetId = id;
7547 break;
7548 }
7549 }
7550 }
7551
7552 m_wirelessNetNames[{ aScreen, &instance, pinIndex }] = { sourceNetId, name };
7553
7554 if( isImplicitGeneratedName( name )
7555 && generatedWirelessNets.count( { electricalPosition.x, electricalPosition.y } ) )
7556 {
7557 m_sourceGeneratedNetNames[{ aScreen, sourceNetId }] = name;
7558 }
7559
7560 // A source net pointer also records implicit power connectivity; it is not always an override.
7561 if( globalNet && !pin.IsNoConnect() && !stackedPeer
7562 && electricalPosition == VECTOR2I( pin.x, pin.y )
7563 && hasImplicitPowerPinName( instance, pinIndex, scopedName ) )
7564 {
7565 m_currentImplicitPowerPins.insert( &pin );
7566 continue;
7567 }
7568
7569 SCH_LABEL* label = new SCH_LABEL( OrcadDbuToIu( electricalPosition.x, electricalPosition.y ),
7570 FromOrcadString( name ) );
7571
7572 appendNetIntent( aScreen, label, !isImplicitGeneratedName( name ) );
7573
7574 if( scopedName != name )
7575 {
7576 SCH_LABEL* scopedLabel = new SCH_LABEL( OrcadDbuToIu( electricalPosition.x, electricalPosition.y ),
7577 FromOrcadString( scopedName ) );
7578 appendNetIntent( aScreen, scopedLabel, !isImplicitGeneratedName( name ) );
7579 }
7580 }
7581 }
7582}
7583
7584
7586 const std::string& aChildFolder )
7587{
7588 SCH_FIELD* sheetName = aSheet->GetField( FIELD_T::SHEET_NAME );
7589 SCH_FIELD* sheetFile = aSheet->GetField( FIELD_T::SHEET_FILENAME );
7590 sheetName->SetVisible( false );
7591 sheetFile->SetVisible( false );
7592
7593 const ORCAD_DISPLAY_PROP* referenceDisplay = nullptr;
7594 const ORCAD_DISPLAY_PROP* valueDisplay = nullptr;
7595 auto namesEqual = []( const std::string& aLeft, std::string_view aRight )
7596 {
7597 return aLeft.size() == aRight.size()
7598 && std::equal( aLeft.begin(), aLeft.end(), aRight.begin(),
7599 []( unsigned char a, unsigned char b )
7600 {
7601 return std::tolower( a ) == std::tolower( b );
7602 } );
7603 };
7604
7605 for( const ORCAD_DISPLAY_PROP& display : aBlock.displayProps )
7606 {
7607 if( namesEqual( display.name, "Part Reference" ) )
7608 referenceDisplay = &display;
7609 else if( namesEqual( display.name, "Reference" ) && !referenceDisplay )
7610 referenceDisplay = &display;
7611 else if( namesEqual( display.name, "Value" ) )
7612 valueDisplay = &display;
7613 }
7614
7615 auto applyDisplay = [&]( SCH_FIELD* aField, const ORCAD_DISPLAY_PROP& aDisplay )
7616 {
7617 int fontId = displayFontId( aDisplay );
7618 bool templateFont = displayUsesTemplateFont( aDisplay );
7619 int size = textSizeIU( fontId, templateFont );
7620 int baseline = textBaselineOffset( size, fontId, templateFont );
7621 bool vertical = ( aDisplay.rotation & 1 ) != 0;
7622 VECTOR2I position = OrcadDbuToIu( aBlock.x1 + aDisplay.x, aBlock.y1 + aDisplay.y )
7623 + ( vertical ? VECTOR2I( baseline, 0 ) : VECTOR2I( 0, baseline ) );
7624
7625 aField->SetPosition( position );
7626 aField->SetTextAngle( vertical ? ANGLE_VERTICAL : ANGLE_HORIZONTAL );
7627 aField->SetTextSize( textSize( fontId, templateFont ) );
7628 applyFont( aField, fontId, templateFont );
7629 aField->SetTextColor( OrcadColor( aDisplay.color ) );
7632 aField->SetVisible( OrcadDisplayPropVisible( aDisplay ) );
7633 aField->SetNameShown( OrcadDisplayPropShowsName( aDisplay ) );
7634 };
7635
7636 if( referenceDisplay )
7637 applyDisplay( sheetName, *referenceDisplay );
7638
7639 if( valueDisplay )
7640 {
7641 auto value = std::find_if( aBlock.props.begin(), aBlock.props.end(),
7642 [&]( const auto& aProperty )
7643 {
7644 return namesEqual( aProperty.first, "Value" );
7645 } );
7646 std::string valueText;
7647
7648 if( value != aBlock.props.end() )
7649 valueText = value->second;
7650
7651 if( valueText.empty() )
7652 valueText = aBlock.childName;
7653
7654 if( valueText.empty() )
7655 valueText = aChildFolder;
7656
7657 if( !valueText.empty() )
7658 {
7659 SCH_FIELD implementation( aSheet, FIELD_T::USER, wxS( "Implementation" ) );
7660 implementation.SetText( FromOrcadString( valueText ) );
7661 applyDisplay( &implementation, *valueDisplay );
7662 aSheet->AddField( implementation );
7663 }
7664 }
7665}
7666
7667
7669{
7670 std::vector<VECTOR2I> points;
7671 aPin->CreateGraphicShape( nullptr, points, aPin->GetPosition() );
7672
7673 if( points.empty() )
7674 return;
7675
7676 KIGFX::COLOR4D color = OrcadColor( 26 );
7678
7679 for( const VECTOR2I& point : points )
7680 fill->AddPoint( point );
7681
7682 fill->SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID, color ) );
7684 fill->SetFillColor( color );
7685 appendPageItem( aScreen, fill );
7686}
7687
7688
7690{
7691 for( const ORCAD_BUS_ENTRY& busEntry : aPage.busEntries )
7692 {
7693 SCH_BUS_WIRE_ENTRY* entry = new SCH_BUS_WIRE_ENTRY( OrcadDbuToIu( busEntry.x1, busEntry.y1 ) );
7694 entry->SetSize( VECTOR2I( ( busEntry.x2 - busEntry.x1 ) * ORCAD_IU_PER_DBU,
7695 ( busEntry.y2 - busEntry.y1 ) * ORCAD_IU_PER_DBU ) );
7696 entry->SetStroke( STROKE_PARAMS( 0, LINE_STYLE::DEFAULT, OrcadColor( busEntry.color ) ) );
7697 appendPageItem( aScreen, entry );
7698 }
7699}
7700
7701
7703 const ORCAD_DISPLAY_PROP& aDisplay,
7704 const std::string& aText, SCH_SCREEN* aScreen )
7705{
7706 int fontId = displayFontId( aDisplay );
7707 int size = textSizeIU( fontId );
7708 int baseline = textBaselineOffset( size, fontId ) + KiROUND( size * 7.0 / 21.0 );
7709 int textRotation = aDisplay.rotation & 3;
7710 bool vertical = ( textRotation & 1 ) != 0;
7711 int baseX = std::min( aGraphic.bbox.x1, aGraphic.bbox.x2 );
7712 int baseY = std::min( aGraphic.bbox.y1, aGraphic.bbox.y2 );
7713 VECTOR2I textPosition = OrcadDbuToIu( baseX + aDisplay.x, baseY + aDisplay.y )
7714 + ( vertical ? VECTOR2I( baseline, 0 ) : VECTOR2I( 0, baseline ) );
7715 SCH_TEXT* text = new SCH_TEXT( textPosition, FromOrcadString( aText ), LAYER_NOTES );
7716
7717 switch( textRotation )
7718 {
7719 case 1: text->SetTextAngle( ANGLE_270 ); break;
7720 case 2: text->SetTextAngle( ANGLE_180 ); break;
7721 case 3: text->SetTextAngle( ANGLE_90 ); break;
7722 default: text->SetTextAngle( ANGLE_0 ); break;
7723 }
7724
7725 text->SetTextSize( textSize( fontId ) );
7726 applyFont( text, fontId );
7727 applyMultilineSpacing( text, fontId );
7728 KIGFX::COLOR4D color = OrcadColor( aGraphic.color );
7729
7730 if( color == KIGFX::COLOR4D::UNSPECIFIED )
7731 color = OrcadColor( 8 );
7732
7733 text->SetTextColor( color );
7734 text->SetHorizJustify( textRotation == 1 || textRotation == 2 ? GR_TEXT_H_ALIGN_RIGHT
7736 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
7737 text->SetVisible( OrcadDisplayPropVisible( aDisplay ) );
7738 appendPageItem( aScreen, text );
7739}
7740
7741
7742
7743
7744static std::optional<VECTOR2I> safeConnectivityLabelPosition( SCH_SCREEN* aScreen, const VECTOR2I& aPosition,
7745 const std::optional<std::vector<SEG>>& aSourceWires )
7746{
7747 if( !aSourceWires )
7748 return std::nullopt;
7749
7750 std::vector<SEG> wires;
7751 std::set<VECTOR2I> junctions;
7752 std::set<VECTOR2I> busEntries;
7753 std::set<VECTOR2I> pins;
7754
7755 for( SCH_ITEM* item : aScreen->Items() )
7756 {
7757 if( item->Type() == SCH_LINE_T )
7758 {
7759 SCH_LINE* line = static_cast<SCH_LINE*>( item );
7760
7761 if( line->GetLayer() == LAYER_WIRE || line->GetLayer() == LAYER_BUS )
7762 wires.push_back( line->GetSeg() );
7763 }
7764 else if( item->Type() == SCH_SYMBOL_T )
7765 {
7766 for( SCH_PIN* pin : static_cast<SCH_SYMBOL*>( item )->GetPins() )
7767 pins.insert( pin->GetPosition() );
7768 }
7769 else if( item->Type() == SCH_SHEET_T )
7770 {
7771 for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( item )->GetPins() )
7772 pins.insert( pin->GetPosition() );
7773 }
7774 else if( item->Type() == SCH_JUNCTION_T )
7775 {
7776 junctions.insert( item->GetPosition() );
7777 }
7778 else if( item->Type() == SCH_BUS_WIRE_ENTRY_T || item->Type() == SCH_BUS_BUS_ENTRY_T )
7779 {
7780 for( const VECTOR2I& point : item->GetConnectionPoints() )
7781 busEntries.insert( point );
7782 }
7783 }
7784
7785 auto safe = [&]( const VECTOR2I& aCandidate )
7786 {
7787 if( busEntries.count( aCandidate ) )
7788 return false;
7789
7790 int contacts = std::count_if( wires.begin(), wires.end(),
7791 [&]( const SEG& wire ) { return wire.Contains( aCandidate ); } );
7792
7793 if( aSourceWires->empty() )
7794 {
7795 // Coincident pins can require a junction dot without any crossing wires.
7796 return aCandidate == aPosition && contacts == 0
7797 && ( !junctions.count( aCandidate ) || pins.count( aCandidate ) );
7798 }
7799
7800 return !junctions.count( aCandidate ) && !pins.count( aCandidate ) && contacts <= 1;
7801 };
7802
7803 auto supported = [&]( const SEG& aWire, const VECTOR2I& aCandidate )
7804 {
7805 return aWire.Contains( aCandidate )
7806 && std::any_of( aSourceWires->begin(), aSourceWires->end(),
7807 [&]( const SEG& aSource )
7808 {
7809 return aSource.A != aSource.B && aSource.Contains( aCandidate )
7810 && ( aSource.B - aSource.A ).Cross( aWire.B - aWire.A ) == 0;
7811 } );
7812 };
7813
7814 if( safe( aPosition )
7815 && ( aSourceWires->empty()
7816 || std::any_of( wires.begin(), wires.end(),
7817 [&]( const SEG& aWire ) { return supported( aWire, aPosition ); } ) ) )
7818 return aPosition;
7819
7820 std::set<VECTOR2I> candidates;
7821
7822 for( const SEG& wire : wires )
7823 {
7824 // A crossing cut can remove the intended wire at the original anchor while leaving another net there.
7825 if( !std::any_of( aSourceWires->begin(), aSourceWires->end(),
7826 [&]( const SEG& aSource )
7827 {
7828 return aSource.A != aSource.B
7829 && ( aSource.B - aSource.A ).Cross( wire.B - wire.A ) == 0
7830 && ( aSource.Contains( wire.A ) || aSource.Contains( wire.B )
7831 || wire.Contains( aSource.A ) || wire.Contains( aSource.B ) );
7832 } ) )
7833 continue;
7834
7835 VECTOR2I delta = wire.B - wire.A;
7836 int steps = std::gcd( std::abs( delta.x ), std::abs( delta.y ) );
7837
7838 if( !steps )
7839 continue;
7840
7841 VECTOR2I step = delta / steps;
7842 std::set<int> offsets = { 0, steps, steps / 2 };
7843 auto addOffset = [&]( long double aOffset )
7844 {
7845 if( aOffset < -2 || aOffset > steps + 2.0L )
7846 return;
7847
7848 int offset = static_cast<int>( std::floor( aOffset ) );
7849
7850 for( int adjacent = -1; adjacent <= 2; ++adjacent )
7851 {
7852 if( offset + adjacent >= 0 && offset + adjacent <= steps )
7853 offsets.insert( offset + adjacent );
7854 }
7855 };
7856 auto projection = [&]( const VECTOR2I& aPoint ) -> long double
7857 {
7858 return step.x ? ( (long double) aPoint.x - wire.A.x ) / step.x
7859 : ( (long double) aPoint.y - wire.A.y ) / step.y;
7860 };
7861
7862 addOffset( projection( aPosition ) );
7863 long double inset = schIUScale.MilsToIU( 50 ) / std::hypot( step.x, step.y );
7864 addOffset( projection( aPosition ) - inset );
7865 addOffset( projection( aPosition ) + inset );
7866
7867 // Segment ends bound overlaps; crossings and junctions bound the remaining clear intervals.
7868 for( const SEG& other : wires )
7869 {
7870 addOffset( projection( other.A ) );
7871 addOffset( projection( other.B ) );
7872 VECTOR2I direction = other.B - other.A;
7873 long double denominator = (long double) step.x * direction.y - (long double) step.y * direction.x;
7874
7875 if( denominator != 0 )
7876 {
7877 long double x = (long double) other.A.x - wire.A.x;
7878 long double y = (long double) other.A.y - wire.A.y;
7879 addOffset( ( x * direction.y - y * direction.x ) / denominator );
7880 }
7881 }
7882
7883 for( const SEG& source : *aSourceWires )
7884 {
7885 addOffset( projection( source.A ) );
7886 addOffset( projection( source.B ) );
7887 }
7888
7889 for( const VECTOR2I& junction : junctions )
7890 addOffset( projection( junction ) );
7891
7892 for( const VECTOR2I& entry : busEntries )
7893 addOffset( projection( entry ) );
7894
7895 for( const VECTOR2I& pin : pins )
7896 addOffset( projection( pin ) );
7897
7898 for( int offset : offsets )
7899 {
7900 VECTOR2I candidate = wire.A + step * offset;
7901
7902 if( safe( candidate ) && supported( wire, candidate ) )
7903 candidates.insert( candidate );
7904 }
7905 }
7906
7907 if( candidates.empty() )
7908 return std::nullopt;
7909
7910 return *std::min_element( candidates.begin(), candidates.end(),
7911 [&]( const VECTOR2I& aLeft, const VECTOR2I& aRight )
7912 {
7913 int64_t left = ( aLeft - aPosition ).SquaredEuclideanNorm();
7914 int64_t right = ( aRight - aPosition ).SquaredEuclideanNorm();
7915 int64_t clearance = schIUScale.MilsToIU( 50 );
7916 bool leftClear = left >= clearance * clearance;
7917 bool rightClear = right >= clearance * clearance;
7918
7919 return leftClear != rightClear ? leftClear : left < right;
7920 } );
7921}
7922
7923
7925 const SCH_SHEET_PATH& aSheetPath, bool aHierarchical )
7926{
7927 for( const OFFPAGE_NET& offpage : offpageNets( aPage ) )
7928 {
7929 std::string net = offpage.net;
7930 auto busNames = m_hierBusNamesByScreen.find( aScreen->GetUuid().AsStdString() );
7931
7932 if( busNames != m_hierBusNamesByScreen.end() )
7933 {
7934 auto renamed = busNames->second.find( canonicalGlobalNetName( net ) );
7935
7936 if( renamed != busNames->second.end() )
7937 net = renamed->second;
7938 }
7939
7940 if( net.empty() )
7941 {
7942 note( wxString::Format( _( "Page '%s': the off-page connector at (%d, %d) is "
7943 "unconnected in the source design; skipped." ),
7944 FromOrcadString( aPage.name ), offpage.x, offpage.y ) );
7945 continue;
7946 }
7947
7948 const ORCAD_GRAPHIC_INST& connector = aPage.offpage[offpage.index];
7949 bool bus = SCH_CONNECTION::IsBusLabel( FromOrcadString( net ) );
7950 auto sourceWires = eligibleSourceConnectivityWires(
7951 aPage, VECTOR2I( offpage.x, offpage.y ), { connector.logicalName, offpage.net }, bus );
7952
7953 if( !bus && sourceWires && sourceWires->empty() )
7954 sourceWires = eligibleSourceConnectivityWires(
7955 aPage, VECTOR2I( offpage.x, offpage.y ), { connector.logicalName, offpage.net }, true );
7956
7957
7958 VECTOR2I originalPosition = OrcadDbuToIu( offpage.x, offpage.y );
7959 std::optional<VECTOR2I> position = safeConnectivityLabelPosition( aScreen, originalPosition, sourceWires );
7960
7961 if( !position )
7962 {
7963 THROW_IO_ERROR( wxString::Format( _( "Page '%s': cannot place off-page connector '%s' at (%d, %d) "
7964 "without a wire intersection." ),
7965 FromOrcadString( aPage.name ), FromOrcadString( net ),
7966 offpage.x, offpage.y ) );
7967 }
7968
7969 SCH_GLOBALLABEL* label = new SCH_GLOBALLABEL( *position, FromOrcadString( net ) );
7971 label->SetTextColor( OrcadColor( aPage.offpage[offpage.index].color ) );
7972
7973 // Point label away from attached wire; vertical wire gives up/down, horizontal left/right.
7975 auto endIt = m_wireEndpoints.find( { offpage.x, offpage.y } );
7976
7977 if( endIt != m_wireEndpoints.end() && !endIt->second.empty() )
7978 {
7979 const ORCAD_WIRE* wire = endIt->second.front();
7980 int64_t dx = (int64_t) wire->x1 + wire->x2 - 2LL * offpage.x;
7981 int64_t dy = (int64_t) wire->y1 + wire->y2 - 2LL * offpage.y;
7982
7983 if( std::abs( dx ) >= std::abs( dy ) )
7984 spin = dx > 0 ? SPIN_STYLE::LEFT : SPIN_STYLE::RIGHT;
7985 else
7986 spin = dy > 0 ? SPIN_STYLE::UP : SPIN_STYLE::BOTTOM;
7987 }
7988
7989 label->SetSpinStyle( spin );
7990
7991 if( SCH_FIELD* refs = label->GetField( FIELD_T::INTERSHEET_REFS ) )
7992 refs->SetVisible( false );
7993
7994 auto displayedName = std::find_if( connector.displayProps.begin(), connector.displayProps.end(),
7995 []( const ORCAD_DISPLAY_PROP& aProp )
7996 {
7997 return aProp.name == "Name";
7998 } );
7999 auto displayedIref = std::find_if( connector.displayProps.begin(), connector.displayProps.end(),
8000 []( const ORCAD_DISPLAY_PROP& aProp )
8001 {
8002 return aProp.name == "IREF";
8003 } );
8004 auto storedIref = connector.props.find( "IREF" );
8005
8006 if( displayedName != connector.displayProps.end() )
8007 {
8008 label->SetTextSize( textSize( OrcadDisplayFontId( *displayedName ) ) );
8009 applyFont( label, OrcadDisplayFontId( *displayedName ) );
8010 }
8011
8012 if( storedIref != connector.props.end() && displayedIref != connector.displayProps.end()
8013 && OrcadDisplayPropVisible( *displayedIref ) )
8014 {
8015 placeGraphicDisplayText( connector, *displayedIref, storedIref->second, aScreen );
8016 }
8017
8018 appendPageItem( aScreen, label );
8019 rememberInterfaceLabelSource( aScreen, label, *sourceWires );
8020
8021 // Some source off-page connectors also bind a drawn block's parent sheet pin.
8022 const auto& parentPins = aSheetPath.Last()->GetPins();
8023 auto parentPin = std::find_if( parentPins.begin(), parentPins.end(),
8024 [&]( const SCH_SHEET_PIN* aPin )
8025 {
8026 return aPin->GetText().CmpNoCase( label->GetText() ) == 0;
8027 } );
8028
8029 if( aHierarchical && parentPin != parentPins.end() )
8030 {
8031 SCH_HIERLABEL* parentLabel = new SCH_HIERLABEL( *position, ( *parentPin )->GetText() );
8032 parentLabel->SetShape( ( *parentPin )->GetShape() );
8033 parentLabel->SetSpinStyle( spin.RotateCCW().RotateCCW() );
8034 parentLabel->SetTextColor( label->GetTextColor() );
8035 parentLabel->SetTextSize( label->GetTextSize() );
8036
8037 if( displayedName != connector.displayProps.end() )
8038 applyFont( parentLabel, OrcadDisplayFontId( *displayedName ) );
8039
8040 appendPageItem( aScreen, parentLabel );
8041 rememberInterfaceLabelSource( aScreen, parentLabel, *sourceWires );
8042 }
8043 }
8044}
8045
8046
8047void ORCAD_CONVERTER::placePorts( const ORCAD_RAW_PAGE& aPage, SCH_SCREEN* aScreen, bool aHierarchical )
8048{
8049 for( const ORCAD_GRAPHIC_INST& port : aPage.ports )
8050 {
8051 std::string net = effectiveInterfaceNetName( port.logicalName.empty() ? port.name : port.logicalName );
8052 auto busNames = m_hierBusNamesByScreen.find( aScreen->GetUuid().AsStdString() );
8053 VECTOR2I pos = namedGraphicPinPos( aPage, port );
8054 std::map<size_t, std::set<std::string>> occurrenceNamesByDepth;
8055
8056 for( const ORCAD_WIRE& wire : aPage.wires )
8057 {
8058 if( wire.isBus || !rawPointOnSegment( pos.x, pos.y, wire ) )
8059 continue;
8060
8061 std::set<std::string> sourceNames;
8062 auto pageName = aPage.netmap.find( wire.id );
8063
8064 if( pageName != aPage.netmap.end() && !pageName->second.empty() )
8065 sourceNames.insert( canonicalGlobalNetName( pageName->second ) );
8066
8067 auto aliases = aPage.netAliases.find( wire.id );
8068
8069 if( aliases != aPage.netAliases.end() )
8070 {
8071 for( const std::string& alias : aliases->second )
8072 sourceNames.insert( canonicalGlobalNetName( alias ) );
8073 }
8074
8075 for( const std::string& sourceName : sourceNames )
8076 {
8077 std::string key = sourceName;
8078 std::transform( key.begin(), key.end(), key.begin(),
8079 []( unsigned char c )
8080 {
8081 return static_cast<char>( std::tolower( c ) );
8082 } );
8083 auto depth = m_occurrenceNetNameMinDepth.find( key );
8084
8085 if( depth != m_occurrenceNetNameMinDepth.end() )
8086 occurrenceNamesByDepth[depth->second].insert( sourceName );
8087 }
8088 }
8089
8090 if( !occurrenceNamesByDepth.empty() && occurrenceNamesByDepth.begin()->second.size() == 1 )
8091 net = *occurrenceNamesByDepth.begin()->second.begin();
8092
8093 if( busNames != m_hierBusNamesByScreen.end() )
8094 {
8095 auto renamed = busNames->second.find( net );
8096
8097 if( renamed != busNames->second.end() )
8098 net = renamed->second;
8099 }
8100
8101 if( net.empty() )
8102 continue;
8103
8104 bool bus = SCH_CONNECTION::IsBusLabel( FromOrcadString( net ) );
8105 auto sourceWires = eligibleSourceConnectivityWires(
8106 aPage, pos, { port.logicalName, net }, bus );
8107
8108 if( !bus && sourceWires && sourceWires->empty() )
8109 sourceWires = eligibleSourceConnectivityWires( aPage, pos, { port.logicalName, net }, true );
8110
8111 std::optional<VECTOR2I> position = safeConnectivityLabelPosition(
8112 aScreen, OrcadDbuToIu( pos.x, pos.y ), sourceWires );
8113
8114 if( !position )
8115 {
8116 THROW_IO_ERROR( wxString::Format( _( "Page '%s': cannot place port '%s' at (%d, %d) "
8117 "without a wire intersection." ),
8118 FromOrcadString( aPage.name ), FromOrcadString( net ), pos.x, pos.y ) );
8119 }
8120
8121 // Symbol name encodes arrow direction, e.g. "PORTLEFT-L".
8122 wxString symbolName = FromOrcadString( port.name ).Upper();
8124
8125 if( symbolName.Contains( wxS( "LEFT" ) ) )
8127 else if( symbolName.Contains( wxS( "RIGHT" ) ) )
8129
8130 SCH_LABEL_BASE* label;
8131
8132 if( aHierarchical )
8133 label = new SCH_HIERLABEL( *position, FromOrcadString( net ) );
8134 else
8135 label = new SCH_GLOBALLABEL( *position, FromOrcadString( net ) );
8136
8137 label->SetShape( shape );
8139
8140 auto end = m_wireEndpoints.find( { pos.x, pos.y } );
8141
8142 if( end != m_wireEndpoints.end() && !end->second.empty() )
8143 {
8144 const ORCAD_WIRE& wire = *end->second.front();
8145 int64_t dx = static_cast<int64_t>( wire.x1 ) + wire.x2 - 2LL * pos.x;
8146 int64_t dy = static_cast<int64_t>( wire.y1 ) + wire.y2 - 2LL * pos.y;
8147
8148 if( std::abs( dx ) >= std::abs( dy ) )
8149 label->SetSpinStyle( dx > 0 ? SPIN_STYLE::LEFT : SPIN_STYLE::RIGHT );
8150 else
8151 label->SetSpinStyle( dy > 0 ? SPIN_STYLE::UP : SPIN_STYLE::BOTTOM );
8152 }
8153
8154 label->SetTextColor( OrcadColor( port.color ) );
8155
8156 if( SCH_GLOBALLABEL* global = dynamic_cast<SCH_GLOBALLABEL*>( label ) )
8157 {
8158 if( SCH_FIELD* refs = global->GetField( FIELD_T::INTERSHEET_REFS ) )
8159 refs->SetVisible( false );
8160 }
8161
8162 auto displayedName = std::find_if( port.displayProps.begin(), port.displayProps.end(),
8163 []( const ORCAD_DISPLAY_PROP& aProp )
8164 {
8165 return aProp.name == "Name";
8166 } );
8167
8168 if( displayedName != port.displayProps.end() )
8169 {
8170 int fontId = OrcadDisplayFontId( *displayedName );
8171 label->SetTextSize( textSize( fontId ) );
8172 applyFont( label, fontId );
8173 }
8174
8175 auto storedIref = port.props.find( "IREF" );
8176 auto displayedIref = std::find_if( port.displayProps.begin(), port.displayProps.end(),
8177 []( const ORCAD_DISPLAY_PROP& aProp )
8178 {
8179 return aProp.name == "IREF";
8180 } );
8181
8182 if( storedIref != port.props.end() && displayedIref != port.displayProps.end()
8183 && OrcadDisplayPropVisible( *displayedIref ) )
8184 {
8185 placeGraphicDisplayText( port, *displayedIref, storedIref->second, aScreen );
8186 }
8187
8188 appendPageItem( aScreen, label );
8189 rememberInterfaceLabelSource( aScreen, label, *sourceWires );
8190 }
8191}
8192
8193
8195{
8196 for( const ORCAD_GRAPHIC_INST& gfx : aPage.graphics )
8197 {
8198 if( !gfx.nested )
8199 continue;
8200
8201 KIGFX::COLOR4D graphicColor = OrcadColor( gfx.color );
8202
8203 if( graphicColor == KIGFX::COLOR4D::UNSPECIFIED
8204 || graphicColor == KIGFX::COLOR4D( 1.0, 1.0, 1.0, 1.0 ) )
8205 {
8206 graphicColor = KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 );
8207 }
8208
8209 std::function<void( const ORCAD_PRIMITIVE&, int, int )> placePrimitive =
8210 [&]( const ORCAD_PRIMITIVE& aSource, int aOffsetX, int aOffsetY )
8211 {
8212 if( aSource.kind == ORCAD_PRIM_KIND::GROUP_PRIM )
8213 {
8214 for( const ORCAD_PRIMITIVE& child : aSource.children )
8215 placePrimitive( child, aOffsetX + aSource.x1, aOffsetY + aSource.y1 );
8216
8217 return;
8218 }
8219
8220 ORCAD_PRIMITIVE prim = aSource;
8221 prim.x1 += aOffsetX;
8222 prim.y1 += aOffsetY;
8223 prim.x2 += aOffsetX;
8224 prim.y2 += aOffsetY;
8225
8226 for( ORCAD_POINT& point : prim.points )
8227 {
8228 point.x += aOffsetX;
8229 point.y += aOffsetY;
8230 }
8231
8232 if( prim.start )
8233 {
8234 prim.start->x += aOffsetX;
8235 prim.start->y += aOffsetY;
8236 }
8237
8238 if( prim.end )
8239 {
8240 prim.end->x += aOffsetX;
8241 prim.end->y += aOffsetY;
8242 }
8243
8244 switch( prim.kind )
8245 {
8246 case ORCAD_PRIM_KIND::GROUP_PRIM: break;
8247
8249 {
8251 {
8252 prim.x1 += 10;
8253 prim.y1 += 10;
8254 }
8255
8256 bool usedEmbeddedEmf = false;
8257 placeBitmap( prim, aScreen, 0, &usedEmbeddedEmf );
8258
8260 {
8261 ORCAD_PRIMITIVE frame = prim;
8262 KIGFX::COLOR4D frameColor = OrcadColor( gfx.color );
8263
8264 if( frameColor == KIGFX::COLOR4D::UNSPECIFIED
8265 || frameColor == KIGFX::COLOR4D( 1.0, 1.0, 1.0, 1.0 ) )
8266 {
8267 frameColor = usedEmbeddedEmf ? OrcadColor( 8 )
8268 : KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 );
8269 }
8270
8272 frame.lineStyle = 0;
8273 frame.lineWidth = 0;
8274 frame.fillStyle = 1;
8275 appendPageItem( aScreen,
8276 makeSheetPoly( { OrcadDbuToIu( prim.x1, prim.y1 ),
8277 OrcadDbuToIu( prim.x2, prim.y1 ),
8278 OrcadDbuToIu( prim.x2, prim.y2 ),
8279 OrcadDbuToIu( prim.x1, prim.y2 ),
8280 OrcadDbuToIu( prim.x1, prim.y1 ) },
8281 frame, frameColor, true, false ) );
8282 }
8283
8284 break;
8285 }
8286
8288 {
8289 wxString content = FromOrcadString( prim.text );
8290 std::string sourceFace;
8291 const ORCAD_FONT* sourceFont = nullptr;
8292
8293 if( prim.fontIdx > 0 && prim.fontIdx <= static_cast<int>( m_design.library.fonts.size() ) )
8294 {
8295 sourceFont = &m_design.library.fonts[prim.fontIdx - 1];
8296 sourceFace = sourceFont->face;
8297 std::transform( sourceFace.begin(), sourceFace.end(), sourceFace.begin(),
8298 []( unsigned char aChar ) { return std::tolower( aChar ); } );
8299 }
8300
8301 if( sourceFace == "greekc" )
8302 content.Replace( wxS( "m" ), wxString::FromUTF8( "µ" ) );
8303 else if( sourceFace == "commercialpi bt" )
8304 content.Replace( wxS( "b" ), wxString::FromUTF8( "®" ) );
8305
8306 if( content.IsEmpty() )
8307 break;
8308
8309 VECTOR2I scaledTextSize = textSize( prim.fontIdx, false );
8310
8311 if( !gfx.textFaceOverride.empty() && prim.fontIdx > 0
8312 && prim.fontIdx <= static_cast<int>( m_design.library.fonts.size() ) )
8313 {
8314 const ORCAD_FONT& sourceFontDef = m_design.library.fonts[prim.fontIdx - 1];
8315
8316 if( sourceFace == "arial narrow" )
8317 {
8318 double widthScale = sourceFontDef.width == 0 ? 35.0 / 32.0 : 8.0 / 9.0;
8319 scaledTextSize.x = KiROUND( scaledTextSize.x * widthScale );
8320 }
8321 }
8322
8323 scaledTextSize.x = KiROUND( scaledTextSize.x * gfx.textScaleX );
8324 scaledTextSize.y = KiROUND( scaledTextSize.y * gfx.textScaleY );
8325
8326 int size = scaledTextSize.y;
8327 int baseline = gfx.useGenericTextBaseline ? OrcadTextBaselineOffset( size )
8328 : textBaselineOffset( size, prim.fontIdx, false );
8329 bool vertical = ( gfx.rotation & 3 ) == 1;
8330 bool hasBox = prim.x2 > prim.x1 && prim.y2 > prim.y1 && !content.Contains( '\n' );
8331 bool hasSourceAnchor = prim.textBoundsStart.has_value();
8332 bool wrappedToSourceBounds = false;
8333 EDA_TEXT* text;
8334 SCH_ITEM* item;
8335 SCH_TEXT* multilineText = nullptr;
8336
8337 int anchorX = hasSourceAnchor || vertical || !hasBox ? prim.x1 : ( prim.x1 + prim.x2 ) / 2;
8338 int anchorY = hasSourceAnchor || !vertical || !hasBox ? prim.y1 : ( prim.y1 + prim.y2 ) / 2;
8339 VECTOR2I position = OrcadDbuToIu( anchorX, anchorY )
8340 + ( vertical ? VECTOR2I( baseline, 0 ) : VECTOR2I( 0, baseline ) );
8341 SCH_TEXT* sheetText = new SCH_TEXT( position, content );
8342 sheetText->SetMultilineAllowed( true );
8343 multilineText = sheetText;
8344 text = sheetText;
8345 item = sheetText;
8346
8347 text->SetHorizJustify( hasBox && !hasSourceAnchor ? GR_TEXT_H_ALIGN_CENTER
8349 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
8350
8351 text->SetTextSize( scaledTextSize );
8352 applyFont( text, prim.fontIdx, false );
8353
8354 if( sourceFace == "greekc" || sourceFace == "commercialpi bt" )
8355 text->SetFont( KIFONT::FONT::GetFont( wxS( "Arial" ), text->IsBold(), text->IsItalic() ) );
8356
8357 if( !gfx.textFaceOverride.empty() )
8358 {
8359 text->SetFont( KIFONT::FONT::GetFont( FromOrcadString( gfx.textFaceOverride ), text->IsBold(),
8360 text->IsItalic() ) );
8361 }
8362
8363 auto sourceCellWidthDbu = [&]()
8364 {
8365 if( !sourceFont )
8366 return 0;
8367
8368 int sourceCellWidth = std::abs( sourceFont->width );
8369
8370 if( sourceFace == "arial narrow" && sourceFont->bold )
8371 {
8372 sourceCellWidth = std::max( sourceCellWidth,
8373 ( std::abs( sourceFont->height ) + 1 ) / 2 );
8374 }
8375
8376 return sourceCellWidth;
8377 };
8378
8379 if( hasBox && hasSourceAnchor && !vertical && !text->GetText().Contains( '\n' ) )
8380 {
8381 int sourceWidthDbu = prim.x2 - prim.x1;
8382 int sourceHeightDbu = prim.y2 - prim.y1;
8383 int sourceCellWidth = sourceCellWidthDbu();
8384
8385 if( sourceWidthDbu > 0 && sourceCellWidth > 0
8386 && sourceHeightDbu <= std::abs( sourceFont->height )
8387 && static_cast<int>( text->GetText().length() ) * sourceCellWidth > sourceWidthDbu )
8388 {
8389 wxString original = text->GetText();
8390 wxString wrapped;
8391 size_t maxCharacters = sourceWidthDbu / sourceCellWidth;
8392 size_t bestWidth = std::numeric_limits<size_t>::max();
8393
8394 for( size_t pos = original.find( ' ' ); pos != wxString::npos;
8395 pos = original.find( ' ', pos + 1 ) )
8396 {
8397 wxString candidate = original.Left( pos ) + wxS( "\n" ) + original.Mid( pos + 1 );
8398 size_t candidateWidth = std::max( pos, original.length() - pos - 1 );
8399
8400 if( candidateWidth <= maxCharacters && candidateWidth < bestWidth )
8401 {
8402 wrapped = candidate;
8403 bestWidth = candidateWidth;
8404 }
8405 }
8406
8407 if( !wrapped.IsEmpty() )
8408 {
8409 text->SetText( wrapped );
8410 wrappedToSourceBounds = true;
8411 }
8412 else
8413 {
8414 text->SetText( original );
8415 }
8416 }
8417 }
8418
8419 if( sourceFace == "elephant" )
8420 {
8421 VECTOR2I plotOffset = sheetText->GetSchematicTextOffset( nullptr )
8422 + sheetText->GetOffsetToMatchSCH_FIELD( nullptr );
8423 sheetText->SetPosition( sheetText->GetPosition() - plotOffset );
8424 }
8425
8426 if( hasBox && hasSourceAnchor )
8427 {
8428 int sourceLengthDbu = vertical ? prim.y2 - prim.y1 : prim.x2 - prim.x1;
8429
8430 if( !wrappedToSourceBounds && !vertical && text->GetText().Contains( '\n' )
8431 && sourceCellWidthDbu() > 0 )
8432 {
8433 size_t maxCharacters = 0;
8434 wxStringTokenizer lines( text->GetText(), wxS( "\n" ), wxTOKEN_RET_EMPTY_ALL );
8435
8436 while( lines.HasMoreTokens() )
8437 maxCharacters = std::max( maxCharacters, lines.GetNextToken().length() );
8438
8439 sourceLengthDbu = std::min( sourceLengthDbu,
8440 static_cast<int>( maxCharacters ) * sourceCellWidthDbu() );
8441 }
8442
8443 int sourceLength = OrcadDbuToIu( sourceLengthDbu, 0 ).x;
8444 int renderedLength = text->GetTextBox( nullptr ).GetWidth();
8445
8446 if( sourceLength > 0 && renderedLength > 0 )
8447 {
8448 scaledTextSize.x = KiROUND( static_cast<double>( scaledTextSize.x ) * sourceLength
8449 / renderedLength );
8450 text->SetTextSize( scaledTextSize );
8451 }
8452 }
8453
8454 if( multilineText )
8455 applyMultilineSpacing( multilineText, prim.fontIdx, false );
8456
8457 text->SetTextColor( graphicColor );
8458
8459 // Quadrants 2/3 fold to horizontal so text reads upright.
8460 if( vertical )
8461 text->SetTextAngle( ANGLE_VERTICAL );
8462
8463 appendPageItem( aScreen, item );
8464 break;
8465 }
8466
8468 appendPageItem( aScreen,
8469 makeSheetPoly( { OrcadDbuToIu( prim.x1, prim.y1 ),
8470 OrcadDbuToIu( prim.x2, prim.y2 ) },
8471 prim, graphicColor, false, gfx.useSymbolLineWidths ) );
8472 break;
8473
8475 appendPageItem( aScreen,
8476 makeSheetPoly( { OrcadDbuToIu( prim.x1, prim.y1 ),
8477 OrcadDbuToIu( prim.x2, prim.y1 ),
8478 OrcadDbuToIu( prim.x2, prim.y2 ),
8479 OrcadDbuToIu( prim.x1, prim.y2 ),
8480 OrcadDbuToIu( prim.x1, prim.y1 ) },
8481 prim, graphicColor, true, gfx.useSymbolLineWidths ) );
8482 break;
8483
8486 {
8487 if( prim.points.size() < 2 )
8488 break;
8489
8490 std::vector<VECTOR2I> pts;
8491
8492 for( const ORCAD_POINT& pt : prim.points )
8493 pts.push_back( OrcadDbuToIu( pt.x, pt.y ) );
8494
8495 if( prim.kind == ORCAD_PRIM_KIND::POLYGON )
8496 pts.push_back( pts.front() );
8497
8498 appendPageItem( aScreen,
8499 makeSheetPoly( pts, prim, graphicColor,
8501 gfx.useSymbolLineWidths ) );
8502 break;
8503 }
8504
8506 {
8507 if( prim.points.size() < 4 || ( prim.points.size() - 1 ) % 3 != 0 )
8508 {
8509 if( prim.points.size() >= 2 )
8510 {
8511 std::vector<VECTOR2I> pts;
8512
8513 for( const ORCAD_POINT& pt : prim.points )
8514 pts.push_back( OrcadDbuToIu( pt.x, pt.y ) );
8515
8516 appendPageItem( aScreen,
8517 makeSheetPoly( pts, prim, graphicColor, false,
8518 gfx.useSymbolLineWidths ) );
8519 }
8520
8521 break;
8522 }
8523
8524 for( size_t i = 0; i + 3 < prim.points.size(); i += 3 )
8525 {
8527 shape->SetPosition( OrcadDbuToIu( prim.points[i].x, prim.points[i].y ) );
8528 shape->SetBezierC1( OrcadDbuToIu( prim.points[i + 1].x, prim.points[i + 1].y ) );
8529 shape->SetBezierC2( OrcadDbuToIu( prim.points[i + 2].x, prim.points[i + 2].y ) );
8530 shape->SetEnd( OrcadDbuToIu( prim.points[i + 3].x, prim.points[i + 3].y ) );
8531
8532 int lineWidth = gfx.useSymbolLineWidths ? OrcadLineWidthIu( prim.lineWidth )
8534 shape->SetStroke( STROKE_PARAMS( lineWidth,
8535 OrcadLineStyle( prim.lineStyle ), graphicColor ) );
8536 shape->SetFillMode( FILL_T::NO_FILL );
8537 appendPageItem( aScreen, shape );
8538 }
8539
8540 break;
8541 }
8542
8545 {
8546 if( prim.kind == ORCAD_PRIM_KIND::ARC && prim.start == prim.end )
8547 break;
8548
8549 double cx = ( prim.x1 + prim.x2 ) / 2.0;
8550 double cy = ( prim.y1 + prim.y2 ) / 2.0;
8551 double rx = std::abs( prim.x2 - prim.x1 ) / 2.0;
8552 double ry = std::abs( prim.y2 - prim.y1 ) / 2.0;
8553
8554 if( rx == 0.0 )
8555 rx = 0.01;
8556
8557 if( ry == 0.0 )
8558 ry = 0.01;
8559
8560 double a0 = 0.0;
8561 double a1 = 2.0 * M_PI;
8562
8563 if( prim.kind == ORCAD_PRIM_KIND::ARC && prim.start && prim.end )
8564 {
8565 a0 = std::atan2( ( prim.start->y - cy ) / ry, ( prim.start->x - cx ) / rx );
8566 a1 = std::atan2( ( prim.end->y - cy ) / ry, ( prim.end->x - cx ) / rx );
8567
8568 // Arcs run CCW in screen (Y-down) coords.
8569 if( a1 >= a0 )
8570 a1 -= 2.0 * M_PI;
8571 }
8572
8573 int steps = std::max( 8, static_cast<int>( std::abs( a1 - a0 ) / ( M_PI / 16.0 ) ) );
8574
8575 std::vector<VECTOR2I> pts;
8576
8577 for( int k = 0; k <= steps; ++k )
8578 {
8579 double a = a0 + ( a1 - a0 ) * k / steps;
8580 pts.push_back( dbuPointToIu( cx + rx * std::cos( a ), cy + ry * std::sin( a ) ) );
8581 }
8582
8583 appendPageItem( aScreen,
8584 makeSheetPoly( pts, prim, graphicColor,
8586 gfx.useSymbolLineWidths ) );
8587 break;
8588 }
8589 }
8590 };
8591
8592 for( const ORCAD_PRIMITIVE& sourcePrimitive : gfx.nested->primitives )
8593 placePrimitive( sourcePrimitive, 0, 0 );
8594 }
8595}
8596
8597
8598void ORCAD_CONVERTER::placeBitmap( const ORCAD_PRIMITIVE& aPrim, SCH_SCREEN* aScreen, int aOrient,
8599 bool* aUsedEmbeddedEmf )
8600{
8601 if( aUsedEmbeddedEmf )
8602 *aUsedEmbeddedEmf = false;
8603
8604 int widthDbu = std::abs( aPrim.x2 - aPrim.x1 );
8605 int heightDbu = std::abs( aPrim.y2 - aPrim.y1 );
8606
8607 if( widthDbu < 2 || heightDbu < 2 || aPrim.data.empty() )
8608 return;
8609
8610 wxMemoryBuffer bmpData;
8611
8612 VECTOR2I center = dbuPointToIu( ( aPrim.x1 + aPrim.x2 ) / 2.0, ( aPrim.y1 + aPrim.y2 ) / 2.0 );
8613
8614 std::unique_ptr<SCH_BITMAP> bitmap = std::make_unique<SCH_BITMAP>( center );
8615 REFERENCE_IMAGE& refImage = bitmap->GetReferenceImage();
8616
8617 bool readOk = false;
8618
8619 std::vector<uint8_t> ciImage = OleExtractCiImage( aPrim.data );
8620
8621 if( !ciImage.empty() )
8622 {
8623 bmpData.AppendData( ciImage.data(), ciImage.size() );
8624 wxLogNull noLog;
8625 readOk = refImage.ReadImageFile( bmpData );
8626 }
8627
8628 if( !readOk )
8629 bmpData.Clear();
8630
8631 if( !readOk && OleMakeBmpFromDib( aPrim.data, bmpData ) )
8632 {
8633 wxLogNull noLog;
8634 readOk = refImage.ReadImageFile( bmpData );
8635 }
8636 if( !readOk )
8637 {
8638 bmpData.Clear();
8640
8641 if( preview.type == OLE_IMAGE_TYPE::BMP )
8642 {
8643 bmpData.AppendData( preview.data.data(), preview.data.size() );
8644 wxLogNull noLog;
8645 readOk = refImage.ReadImageFile( bmpData );
8646 }
8647 else if( preview.type == OLE_IMAGE_TYPE::DIB && OleMakeBmpFromDib( preview.data, bmpData ) )
8648 {
8649 wxLogNull noLog;
8650 readOk = refImage.ReadImageFile( bmpData );
8651 }
8652 else if( preview.type == OLE_IMAGE_TYPE::WMF )
8653 {
8654 wxImage image;
8655 int maxWidth = static_cast<int>( std::clamp<int64_t>( static_cast<int64_t>( widthDbu ) * 4, 1, 4096 ) );
8656 int maxHeight = static_cast<int>( std::clamp<int64_t>( static_cast<int64_t>( heightDbu ) * 4, 1, 4096 ) );
8657
8658 if( OleRenderMetafilePreview( preview.data, maxWidth, maxHeight, image,
8659 static_cast<double>( widthDbu ) / heightDbu, aUsedEmbeddedEmf ) )
8660 readOk = refImage.SetImage( image );
8661 }
8662 }
8663
8664 if( !readOk )
8665 {
8666 warn( wxString::Format( _( "An embedded picture could not be decoded and was skipped "
8667 "(%s)." ),
8668 OleDescribeImagePayload( aPrim.data ) ) );
8669 return;
8670 }
8671
8672 if( const wxImage* sourceImage = refImage.GetImage().GetImageData() )
8673 {
8674 VECTOR2I stretchedSize = OrcadStretchedImageSize( sourceImage->GetWidth(), sourceImage->GetHeight(),
8675 widthDbu, heightDbu );
8676
8677 if( stretchedSize.x != sourceImage->GetWidth() || stretchedSize.y != sourceImage->GetHeight() )
8678 {
8679 wxImage stretched = sourceImage->Scale( stretchedSize.x, stretchedSize.y, wxIMAGE_QUALITY_HIGH );
8680
8681 if( stretched.IsOk() )
8682 refImage.SetImage( stretched );
8683 }
8684 }
8685
8686 VECTOR2I nativeSize = refImage.GetSize();
8687
8688 if( nativeSize.x > 0 && nativeSize.y > 0 )
8689 {
8690 double scaleX = static_cast<double>( widthDbu * ORCAD_IU_PER_DBU ) / nativeSize.x;
8691 double scaleY = static_cast<double>( heightDbu * ORCAD_IU_PER_DBU ) / nativeSize.y;
8692
8693 refImage.SetImageScale( std::min( scaleX, scaleY ) );
8694 }
8695
8696 const ORCAD_ORIENT_ENTRY& orientation = ORCAD_ORIENT_TABLE[aOrient & 7];
8697
8698 if( orientation.mirror == 'x' )
8699 bitmap->MirrorVertically( center.y );
8700 else if( orientation.mirror == 'y' )
8701 bitmap->MirrorHorizontally( center.x );
8702
8703 for( int angle = 0; angle < orientation.angle; angle += 90 )
8704 bitmap->Rotate( center, true );
8705
8706 appendPageItem( aScreen, bitmap.release() );
8707}
8708
8709
8710VECTOR2I ORCAD_CONVERTER::snapToWire( int aX, int aY, const ORCAD_WIRE& aWire )
8711{
8712 if( aWire.x1 == aWire.x2 ) // vertical
8713 {
8714 return VECTOR2I( aWire.x1, std::clamp( aY, std::min( aWire.y1, aWire.y2 ), std::max( aWire.y1, aWire.y2 ) ) );
8715 }
8716
8717 if( aWire.y1 == aWire.y2 ) // horizontal
8718 {
8719 return VECTOR2I( std::clamp( aX, std::min( aWire.x1, aWire.x2 ), std::max( aWire.x1, aWire.x2 ) ), aWire.y1 );
8720 }
8721
8722 // diagonal, project onto segment
8723 double dx = aWire.x2 - aWire.x1;
8724 double dy = aWire.y2 - aWire.y1;
8725 double t = ( ( aX - aWire.x1 ) * dx + ( aY - aWire.y1 ) * dy ) / ( dx * dx + dy * dy );
8726
8727 t = std::clamp( t, 0.0, 1.0 );
8728
8729 return VECTOR2I( KiROUND( aWire.x1 + t * dx ), KiROUND( aWire.y1 + t * dy ) );
8730}
8731
8732
8733bool ORCAD_CONVERTER::onSegment( int aX, int aY, const ORCAD_WIRE& aWire )
8734{
8735 if( ( aX == aWire.x1 && aY == aWire.y1 ) || ( aX == aWire.x2 && aY == aWire.y2 ) )
8736 return false;
8737
8738 if( aWire.x1 == aWire.x2 && aWire.x1 == aX )
8739 return std::min( aWire.y1, aWire.y2 ) < aY && aY < std::max( aWire.y1, aWire.y2 );
8740
8741 if( aWire.y1 == aWire.y2 && aWire.y1 == aY )
8742 return std::min( aWire.x1, aWire.x2 ) < aX && aX < std::max( aWire.x1, aWire.x2 );
8743
8744 return false;
8745}
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
wxImage * GetImageData()
Definition bitmap_base.h:64
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
Store all of the related component information found in a netlist.
Calculate the connectivity of a schematic and generate netlists.
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.
CONNECTION_SUBGRAPH * GetSubgraphForItem(SCH_ITEM *aItem) const
A subgraph is a set of items that are electrically connected on a single sheet.
const std::set< SCH_ITEM * > & GetItems() const
Provide a read-only reference to the items in the subgraph.
const SCH_ITEM * GetDriver() const
const wxString & GetNameForDriver(SCH_ITEM *aItem) const
Return the candidate net name for a driver.
const SCH_SHEET_PATH & GetSheet() const
const SCH_CONNECTION * GetDriverConnection() const
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
virtual void SetBezierC2(const VECTOR2I &aPt)
Definition eda_shape.h:367
virtual void SetBezierC1(const VECTOR2I &aPt)
Definition eda_shape.h:364
void SetFillColor(const COLOR4D &aColor)
Definition eda_shape.h:160
void SetFillMode(FILL_T aFill)
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
void SetTextColor(const COLOR4D &aColor)
Definition eda_text.h:309
COLOR4D GetTextColor() const
Definition eda_text.h:310
virtual void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:495
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:373
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:342
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition eda_text.cpp:263
void SetMultilineAllowed(bool aAllow)
Definition eda_text.cpp:357
void SetFont(KIFONT::FONT *aFont)
Definition eda_text.cpp:458
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:365
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
static FONT * GetFont(const wxString &aFontName=wxEmptyString, bool aBold=false, bool aItalic=false, const std::vector< wxString > *aEmbeddedFiles=nullptr, bool aForDrawingSheet=false)
Definition font.cpp:143
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
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
wxString AsString() const
Definition kiid.cpp:264
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
void placePageFrame(const ORCAD_RAW_PAGE &aPage, SCH_SCREEN *aScreen)
void placeJunctions(const ORCAD_RAW_PAGE &aPage, SCH_SCREEN *aScreen)
Append computed junctions as SCH_JUNCTION items.
bool displayUsesTemplateFont(const ORCAD_DISPLAY_PROP &aProp) const
std::map< std::string, std::string > m_currentInterfaceNetAliases
std::vector< VECTOR2I > computeJunctions(const ORCAD_RAW_PAGE &aPage) const
Use both geometry and net IDs; a crossing alone does not create a junction.
int displayFontId(const ORCAD_DISPLAY_PROP &aProp) const
std::vector< OFFPAGE_NET > offpageNets(const ORCAD_RAW_PAGE &aPage) const
Resolve every off-page connector on the page to (index, net, pin position).
const std::map< uint32_t, std::string > * m_currentOccUnitRefs
std::string netAt(const ORCAD_RAW_PAGE &aPage, int aX, int aY) const
Prefer endpoint nets, then intersecting wires, then endpoint aliases.
static constexpr int MARGIN_B_DBU
PROGRESS_REPORTER * m_progressReporter
SCH_SHEET * Convert(SCH_SHEET *aRootSheet)
aRootSheet must have a screen and be registered on the schematic.
void assignPageItemUuids(size_t aPageOrdinal)
void buildNetLookup(const ORCAD_RAW_PAGE &aPage)
Rebuild m_wireEndpoints for a page (both endpoints of every wire).
std::map< SCH_LABEL_BASE *, std::pair< SCH_SCREEN *, uint32_t > > m_labelSourceNets
std::map< SCH_SYMBOL *, std::vector< SOURCE_PIN_IDENTITY > > m_sourcePinIdentities
std::vector< int > placedStackedPinOffsets(const ORCAD_PLACED_INSTANCE &aInstance) const
void rememberInterfaceLabelSource(SCH_SCREEN *aScreen, SCH_LABEL_BASE *aLabel, const std::vector< SEG > &aSourceWires)
static constexpr int MARGIN_R_DBU
std::map< SCH_SCREEN *, std::vector< wxString > > m_sourceOccurrences
std::map< SCH_SCREEN *, const ORCAD_RAW_PAGE * > m_sourcePages
std::set< std::string > m_currentConnectorInterfaceNetAliases
std::map< SCH_SYMBOL *, const ORCAD_PLACED_INSTANCE * > m_sourceInstances
void placeDefinitionVectors(const ORCAD_SYMBOL_DEF &aDefinition, int aBaseX, int aBaseY, int aOrient, SCH_SCREEN *aScreen, double aTextScaleX=1.0, double aTextScaleY=1.0, bool aUseGenericTextBaseline=false, const std::string &aTextFaceOverride={})
const std::map< uint32_t, std::string > * m_currentOccRefs
Per-page occurrence references distinguish repeated child schematics.
bool isOffpageNetName(const std::string &aName) const
void placeBitmap(const ORCAD_PRIMITIVE &aPrim, SCH_SCREEN *aScreen, int aOrient=0, bool *aUsedEmbeddedEmf=nullptr)
Skip undecodable images with a warning.
VECTOR2I graphicPinPos(const ORCAD_GRAPHIC_INST &aInst) const
Use the transformed cache pin position, or the instance anchor if no pin is available.
void applyPageSettings(ORCAD_RAW_PAGE &aPage, SCH_SCREEN *aScreen)
Shift content on the 10-DBU grid to preserve connectivity.
const std::map< uint32_t, std::map< std::string, std::string > > * m_currentOccProps
std::map< std::string, std::string > m_currentOccurrenceNetAliases
ORCAD_CONVERTER(ORCAD_DESIGN &aDesign, SCHEMATIC *aSchematic, REPORTER *aReporter, PROGRESS_REPORTER *aProgressReporter=nullptr)
aDesign is mutated and must outlive the converter.
static bool onSegment(int aX, int aY, const ORCAD_WIRE &aWire)
True when the point lies strictly INSIDE (not at an endpoint of) an H/V wire.
std::map< std::tuple< SCH_SCREEN *, const ORCAD_PLACED_INSTANCE *, size_t >, std::pair< uint32_t, std::string > > m_wirelessNetNames
~ORCAD_CONVERTER()
Out-of-line: LIB_ENTRY holds LIB_SYMBOL by unique_ptr.
void placeGraphicDisplayText(const ORCAD_GRAPHIC_INST &aGraphic, const ORCAD_DISPLAY_PROP &aDisplay, const std::string &aText, SCH_SCREEN *aScreen)
One displayed property attached to a placed page symbol, preserving source text geometry.
SCH_SHEET * m_rootSheet
VECTOR2I namedGraphicPinPos(const ORCAD_RAW_PAGE &aPage, const ORCAD_GRAPHIC_INST &aInst) const
Graphic-symbol connection point, corrected to a nearby endpoint of its named wire.
static wxString MakePageFileName(int aPageIndex, const std::string &aPageName)
static BOX2I pageExtentDbu(const ORCAD_RAW_PAGE &aPage)
Free graphics use nested primitive bounds; their outer boxes do not describe page coordinates.
void appendPageItem(SCH_SCREEN *aScreen, SCH_ITEM *aItem)
void note(const wxString &aMsg)
Fact about the source design (not a conversion problem): RPT_SEVERITY_INFO.
ORCAD_DESIGN & m_design
std::vector< NET_LABEL_INTENT > m_netLabelIntents
int textBaselineOffset(int aTextSize, int aFontIdx, bool aTemplateFont=true) const
int m_fontBaselineDbu
dominant text height; 0 = none
std::set< wxString > m_usedSheetNames
Lower-cased sheet names already emitted, to keep sibling sheet names unique.
std::vector< INTERFACE_LABEL_SOURCE > m_interfaceLabelSources
SCH_SCREEN * m_pageItemScreen
void prepareGlobalNetNames()
Capture net names ignore case; KiCad net names do not.
std::map< std::string, size_t > m_occurrenceNetNameScopeCounts
int m_powerCount
"#PWR%04d" counter
void convertPage(ORCAD_RAW_PAGE &aPage, SCH_SCREEN *aScreen, const SCH_SHEET_PATH &aSheetPath, bool aContainerPage=false, bool aSharedFolderPage=false)
VECTOR2I powerPinPos(const ORCAD_RAW_PAGE &aPage, const ORCAD_GRAPHIC_INST &aInst) const
Power-symbol connection point, corrected to a nearby endpoint of its named wire.
KIID deterministicUuid(const std::string &aRole, size_t aOrdinal) const
std::set< std::string > m_offpageNetNames
void applyTitleBlock(const ORCAD_RAW_PAGE &aPage, SCH_SCREEN *aScreen)
void finishConversion()
Run the shared finalization sequence that ends every conversion entry point.
std::map< std::pair< SCH_SCREEN *, uint32_t >, std::vector< SCH_ITEM * > > m_sourceNetItems
void placeDefinitionImages(const ORCAD_SYMBOL_DEF &aDefinition, int aBaseX, int aBaseY, int aOrient, SCH_SCREEN *aScreen)
std::string occurrenceElectricalNetName(uint32_t aOccurrenceId, const std::string &aName) const
std::map< std::pair< SCH_SCREEN *, uint32_t >, std::string > m_sourceGeneratedNetNames
void applyFont(EDA_TEXT *aText, int aFontIdx, bool aTemplateFont=true) const
std::map< std::pair< SCH_SCREEN *, uint32_t >, std::set< std::string > > m_sourceNetNames
static void offsetPage(ORCAD_RAW_PAGE &aPage, int aDx, int aDy)
aDx and aDy must be multiples of the 10-DBU grid.
VECTOR2I textSize(int aFontIdx, bool aTemplateFont=true) const
Preserve an explicit LOGFONT width while retaining natural font aspect when it is zero.
bool isPowerNetName(const std::string &aName) const
std::string m_currentFlatNetSuffix
static constexpr int MARGIN_T_DBU
void placeHierarchicalBlockFields(SCH_SHEET *aSheet, const ORCAD_DRAWN_INSTANCE &aBlock, const std::string &aChildFolder)
SCHEMATIC * m_schematic
void placeGraphics(const ORCAD_RAW_PAGE &aPage, SCH_SCREEN *aScreen)
const std::map< uint32_t, std::string > * m_currentOccNetNames
void placeHierarchicalBlockPinFill(SCH_SCREEN *aScreen, SCH_SHEET_PIN *aPin)
std::vector< SCH_ITEM * > m_pageItems
void placeWires(const ORCAD_RAW_PAGE &aPage, SCH_SCREEN *aScreen, bool aHierarchical, bool aNestedHierarchy, bool aSharedFolderPage)
int textSizeIU(int aFontIdx, bool aTemplateFont=true) const
std::map< std::string, std::map< std::string, std::string > > m_hierBusNamesByScreen
void warn(const wxString &aMsg)
– reporting [orcad_converter_sheet.cpp] ---------------------------------------—
std::map< std::string, std::string > m_globalNetAliases
std::map< std::string, std::string > m_currentUnconnectedInterfaceNetNames
std::string canonicalGlobalNetName(const std::string &aName) const
Return the design-wide spelling selected by prepareGlobalNetNames().
REPORTER * m_reporter
void applyMultilineSpacing(SCH_TEXT *aText, int aFontIdx, bool aTemplateFont=true) const
std::map< std::string, size_t > m_occurrenceNetNameMinDepth
void placePorts(const ORCAD_RAW_PAGE &aPage, SCH_SCREEN *aScreen, bool aHierarchical)
static VECTOR2I snapToWire(int aX, int aY, const ORCAD_WIRE &aWire)
Closest point (DBU) on a wire segment to (aX, aY); exact for H/V wires.
static constexpr int MARGIN_L_DBU
Page margins in DBU leave room for the KiCad frame and title block.
std::set< std::string > m_connectedBlockInterfaceNames
VECTOR2I placedPinElectricalPosition(const ORCAD_PLACED_INSTANCE &aInstance, size_t aPinIndex) const
std::string powerNet(const ORCAD_RAW_PAGE &aPage, const ORCAD_GRAPHIC_INST &aInst) const
std::map< std::pair< int, int >, std::vector< const ORCAD_WIRE * > > m_wireEndpoints
Per-page wire lookup: endpoint -> wires ending there.
void placeBusEntries(const ORCAD_RAW_PAGE &aPage, SCH_SCREEN *aScreen)
SCH_BUS_WIRE_ENTRY per bus entry (position x1,y1; size x2-x1, y2-y1).
std::optional< uint32_t > occurrenceNetIdFor(const std::string *aName) const
Return the occurrence net id owning aName, which the table identifies by address.
static wxString SanitizeFileName(const std::string &aName)
std::map< std::string, std::string > m_globalNetNames
void appendNetIntent(SCH_SCREEN *aScreen, SCH_LABEL *aLabel, bool aExplicitName, uint32_t aNetId=0)
std::set< std::string > m_powerNetNames
std::string effectiveInterfaceNetName(const std::string &aName) const
void placeOffpageConnectors(const ORCAD_RAW_PAGE &aPage, SCH_SCREEN *aScreen, const SCH_SHEET_PATH &aSheetPath, bool aHierarchical)
Keep computed intersheet references hidden; display the stored OrCAD IREF text.
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
bool SetType(PAGE_SIZE_TYPE aPageSize, bool aIsPortrait=false)
Set the name of the page type and also the sizes and margins commonly associated with that type name.
static void SetCustomWidthMils(double aWidthInMils)
Set the width of Custom page in mils for any custom page constructed or made via SetType() after maki...
double GetHeightMM() const
Definition page_info.h:144
double GetWidthMM() const
Definition page_info.h:139
static void SetCustomHeightMils(double aHeightInMils)
Set the height of Custom page in mils for any custom page constructed or made via SetType() after mak...
const PAGE_SIZE_TYPE & GetType() const
Definition page_info.h:98
A progress reporter interface for use in multi-threaded environments.
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void Report(const wxString &aMessage)=0
Display aMessage in the progress bar dialog.
A REFERENCE_IMAGE is a wrapper around a BITMAP_IMAGE that is displayed in an editor as a reference fo...
bool ReadImageFile(const wxString &aFullFilename)
Read and store an image file.
bool SetImage(const wxImage &aImage)
Set the image from an existing wxImage.
VECTOR2I GetSize() const
const BITMAP_BASE & GetImage() const
Get the underlying image.
void SetImageScale(double aScale)
Set the image "zoom" value.
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
Holds all the data relating to one schematic.
Definition schematic.h:148
void SetSize(const VECTOR2I &aSize)
virtual void SetStroke(const STROKE_PARAMS &aStroke) override
Class for a wire to bus entry.
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
bool IsNet() const
const std::vector< std::shared_ptr< SCH_CONNECTION > > AllMembers() const
int BusCode() const
wxString Name(bool aIgnoreSheet=false) const
bool IsBus() const
static bool IsBusLabel(const wxString &aLabel)
Test if aLabel has a bus notation.
int NetCode() const
void SetEffectiveHorizJustify(GR_TEXT_H_ALIGN_T)
void SetEffectiveVertJustify(GR_TEXT_V_ALIGN_T)
void SetPosition(const VECTOR2I &aPosition) override
void SetText(const wxString &aText) override
void SetNameShown(bool aShown=true)
Definition sch_field.h:229
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this label.
void SetSpinStyle(SPIN_STYLE aSpinStyle) override
void SetSpinStyle(SPIN_STYLE aSpinStyle) override
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
const SYMBOL * GetParentSymbol() const
Definition sch_item.cpp:287
int GetBodyStyle() const
Definition sch_item.h:247
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
void SetShape(LABEL_FLAG_SHAPE aShape)
Definition sch_label.h:179
virtual void SetSpinStyle(SPIN_STYLE aSpinStyle)
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
void SetLineColor(const COLOR4D &aColor)
Definition sch_line.cpp:371
void SetLineWidth(const int aSize)
Definition sch_line.cpp:437
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
void SetLineStyle(const LINE_STYLE aStyle)
Definition sch_line.cpp:408
void SetEndPoint(const VECTOR2I &aPosition)
Definition sch_line.h:146
SCH_PIN * GetLibPin() const
Definition sch_pin.h:107
const wxString & GetName() const
Definition sch_pin.cpp:503
PIN_ORIENTATION GetOrientation() const
Definition sch_pin.cpp:362
VECTOR2I GetPosition() const override
Definition sch_pin.cpp:354
ELECTRICAL_PINTYPE GetType() const
Definition sch_pin.cpp:411
void SetTitleBlock(const TITLE_BLOCK &aTitleBlock)
Definition sch_screen.h:167
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
void SetPageSettings(const PAGE_INFO &aPageSettings)
Definition sch_screen.h:141
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:118
const KIID & GetUuid() const
Definition sch_screen.h:540
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
void SetPosition(const VECTOR2I &aPos) override
Definition sch_shape.h:87
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition sch_shape.cpp:97
void AddPoint(const VECTOR2I &aPosition)
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...
bool empty() const
Forwarded method from std::vector.
SCH_SHEET * at(size_t aIndex) const
Forwarded method from std::vector.
void SetPageNumber(const wxString &aPageNumber)
Set the sheet instance user definable page number.
SCH_SHEET * Last() const
Return a pointer to the last SCH_SHEET of the list.
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
size_t size() const
Forwarded method from std::vector.
Define a sheet pin (label) used in sheets to create hierarchical schematics.
void CreateGraphicShape(const RENDER_SETTINGS *aSettings, std::vector< VECTOR2I > &aPoints, const VECTOR2I &aPos) const override
Calculate the graphic shape (a polygon) associated to the text.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
void AddPin(SCH_SHEET_PIN *aSheetPin)
Add aSheetPin to the sheet.
void SyncUuidToScreen()
Take the identity of the screen this sheet owns.
bool IsTopLevelSheet() const
Check if this sheet is a top-level sheet.
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this sheet.
wxString GetName() const
Definition sch_sheet.h:142
void SetExcludedFromBoard(bool aExclude, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
Set or clear exclude from board netlist flag.
Definition sch_sheet.h:469
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
SCH_FIELD * AddField(const SCH_FIELD &aField)
Add a @aField to the list of fields.
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition sch_sheet.h:241
Schematic symbol object.
Definition sch_symbol.h:75
std::vector< std::unique_ptr< SCH_PIN > > & GetRawPins()
Definition sch_symbol.h:692
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
int GetUnitSelection(const SCH_SHEET_PATH *aSheet) const
Return the instance-specific unit selection for the given sheet path.
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
VECTOR2I GetPosition() const override
Definition sch_text.h:143
VECTOR2I GetOffsetToMatchSCH_FIELD(SCH_RENDER_SETTINGS *aRenderSettings) const
Definition sch_text.cpp:497
void SetPosition(const VECTOR2I &aPosition) override
Definition sch_text.h:144
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
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
bool Contains(const SEG &aSeg) const
Definition seg.h:320
SPIN_STYLE RotateCCW()
Simple container to manage line stroke parameters.
Hold the information shown in the lower right corner of a plot, printout, or editing view.
Definition title_block.h:38
void SetRevision(const wxString &aRevision)
Definition title_block.h:78
void SetComment(int aIdx, const wxString &aComment)
Definition title_block.h:98
void SetTitle(const wxString &aTitle)
Definition title_block.h:55
void SetCompany(const wxString &aCompany)
Definition title_block.h:88
void SetDate(const wxString &aDate)
Set the date field, and defaults to the current time and date.
Definition title_block.h:68
A type-safe container of any type.
Definition ki_any.h:92
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:422
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:424
static constexpr EDA_ANGLE ANGLE_VERTICAL
Definition eda_angle.h:419
static constexpr EDA_ANGLE ANGLE_HORIZONTAL
Definition eda_angle.h:418
static constexpr EDA_ANGLE ANGLE_270
Definition eda_angle.h:427
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:426
@ FILLED_WITH_COLOR
Definition eda_fill.h:33
@ NO_FILL
Definition eda_fill.h:30
@ FILLED_SHAPE
Fill with object color.
Definition eda_fill.h:31
@ BUS
the source net is carried by a bus
@ NO_CONNECT
the source marks the net as deliberately unconnected
@ RESOLVED
exactly one physical net carries the source net
@ SPLIT
the source net reaches more than one physical net
@ UNCONNECTED
the source net reaches no physical net
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_CANCELLED()
@ LAYER_WIRE
Definition layer_ids.h:474
@ LAYER_NOTES
Definition layer_ids.h:489
@ LAYER_BUS
Definition layer_ids.h:475
void remove_duplicates(_Container &__c)
Deletes all duplicate values from __c.
Definition kicad_algo.h:157
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
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.
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.
bool OleMakeBmpFromDib(const std::vector< uint8_t > &aDib, wxMemoryBuffer &aOut)
wxString OleDescribeImagePayload(const std::vector< uint8_t > &aPayload)
Include leading bytes when the payload format is unknown.
constexpr ORCAD_ORIENT_ENTRY ORCAD_ORIENT_TABLE[8]
Indexed by orientation bits: angle, mirror, offset selectors, then matrix coefficients.
VECTOR2I OrcadDbuToIu(int aX, int aY)
bool OrcadDisplayPropShowsName(const ORCAD_DISPLAY_PROP &aProp)
FILL_T OrcadFillType(int aFillStyle, int aHatchStyle)
VECTOR2I OrcadTransformPoint(int aOrient, int aWidth, int aHeight, int aBaseX, int aBaseY, int aPx, int aPy)
Parts use the instance anchor as the base.
int OrcadLineWidthIu(int aWidth)
int OrcadTextBaselineOffset(int aTextSize)
bool OrcadDisplayPropShowsValue(const ORCAD_DISPLAY_PROP &aProp)
KIGFX::COLOR4D OrcadColor(int aColorIndex)
int OrcadOrientOf(int aRotation, bool aMirror)
Compose the 3-bit orientation code from the rotation bits and mirror bit.
int OrcadDisplayFontId(const ORCAD_DISPLAY_PROP &aProp)
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)
static std::set< std::string > busPrefixTokens(const std::string &aPrefix)
static bool parseVectorBusName(const std::string &aName, std::string &aPrefix, int &aFirst, int &aLast)
static std::string scopedHierBusRange(const std::string &aName, const std::map< std::string, std::string > &aBusNames)
static bool rawPointOnSegment(int aX, int aY, const ORCAD_WIRE &aWire)
static bool rawBusSegmentsTouch(const ORCAD_WIRE &aFirst, const ORCAD_WIRE &aSecond)
static std::optional< VECTOR2I > safeConnectivityLabelPosition(SCH_SCREEN *aScreen, const VECTOR2I &aPosition, const std::optional< std::vector< SEG > > &aSourceWires)
static uint32_t busNetAt(const ORCAD_RAW_PAGE &aPage, const ORCAD_BLOCK_PIN &aPin)
static std::string scopedHierBusName(const std::string &aName, uint32_t aOccurrenceId)
static std::string firstBusPrefixToken(const std::string &aPrefix)
int OrcadPageOrder(wxString &aName)
Return a numeric page prefix (or -1); strip only the "N - title" convention.
static std::optional< std::vector< SEG > > eligibleSourceConnectivityWires(const ORCAD_RAW_PAGE &aPage, const VECTOR2I &aPosition, const std::set< std::string > &aNames, bool aBus=false, uint32_t *aNetId=nullptr)
static std::string flatNetSuffix(const ORCAD_DRAWN_INSTANCE &aBlock)
KIGFX::COLOR4D OrcadColor(int aColorIndex)
static std::string scopedHierBusMember(const std::string &aName, const std::map< std::string, std::string > &aBusNames)
static std::optional< VECTOR2I > rawWireIntersection(const ORCAD_WIRE &aFirst, const ORCAD_WIRE &aSecond)
static LABEL_FLAG_SHAPE hierarchicalPinShape(ORCAD_PORT_TYPE aType)
static std::string connectedBusName(const ORCAD_RAW_PAGE &aPage, const ORCAD_BLOCK_PIN &aPin, const std::string &aFallback)
VECTOR2I OrcadStretchedImageSize(int aWidth, int aHeight, int aBoxWidth, int aBoxHeight)
static std::string normalizedPath(std::string aPath)
ORCAD_PORT_TYPE
Map unknown electrical type codes to PASSIVE.
@ ORCAD_ST_GRAPHIC_OLE_INST
wxString FromOrcadString(const std::string &aText)
Use an 8-bit fallback if Windows-1252 decoding fails.
CITER next(CITER it)
Definition ptree.cpp:120
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_INFO
LABEL_FLAG_SHAPE
Definition sch_label.h:97
@ L_BIDI
Definition sch_label.h:100
@ L_TRISTATE
Definition sch_label.h:101
@ L_UNSPECIFIED
Definition sch_label.h:102
@ 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 ReplaceIllegalFileNameChars(std::string &aName, int aReplaceChar)
Checks aName for illegal file name characters.
wxString generatedName
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
int rotation
0..3 quarter turns
Axis-aligned box in OrCAD DBU; corner order as stored (not normalized).
One interface pin of a hierarchical block, at its absolute page position.
ORCAD_PORT_TYPE portType
std::string name
int y1
int color
int x2
int y2
int x1
One resolved off-page connector: index into page.offpage, net, pin position.
Child-folder pages are instantiated for each occurrence, with that occurrence's references.
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
std::string childName
child folder, when embedded
std::map< std::string, std::string > props
Font indices are one-based; zero selects the default.
bool bold
lfWeight >= 600
int width
raw lfWidth; zero lets the font choose its natural aspect ratio
std::string face
int height
raw lfHeight (typically negative)
Free graphics use nested primitive coordinates.
std::unique_ptr< ORCAD_SYMBOL_DEF > nested
SthInPages0 body, else nullptr.
std::map< std::string, std::string > props
int rotation
0..3 quarter turns
std::string name
cache symbol name
std::string textFaceOverride
std::vector< ORCAD_DISPLAY_PROP > displayProps
std::string logicalName
ports: resolved net/port name
int typeId
ORCAD_ST value.
std::string name
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::map< uint32_t, std::string > partUnitRefs
dbId -> package unit reference
OrCAD rotates about the bounding box; KiCad rotates about the anchor.
char mirror
0 = none, 'x' or 'y' = KiCad mirror axis
int8_t c
int8_t a
int angle
KiCad placement angle in degrees (0/90/180/270, CCW)
Pin positions are absolute page connection points.
uint32_t wordA
two flag/id words after (x, y)
bool IsNoConnect() const
The placed box includes displayed text.
ORCAD_BBOX bbox
placed box, page DBU
int rotation
0..3 quarter turns
bool mirror
orientation bit 2
std::vector< ORCAD_PIN_INST > pins
successfully parsed T0x10 records
Integer point in OrCAD DBU.
Primitive byte lengths can include or exclude the eight-byte size envelope.
int fillStyle
0 solid, 1 none, 2 hatch pattern
std::string text
kind == TEXT
std::vector< ORCAD_PRIMITIVE > children
kind == GROUP, translated by (x1, y1)
std::vector< ORCAD_POINT > points
polygon/polyline/bezier vertices
std::optional< ORCAD_POINT > textBoundsStart
ORCAD_PRIM_KIND kind
std::vector< uint8_t > data
kind == IMAGE: raw embedded payload
int lineStyle
0 solid, 1 dash, 2 dot, 3 dash-dot, 4 dash-dot-dot, 5 default
std::optional< ORCAD_POINT > start
arc start point
std::optional< ORCAD_POINT > end
arc end point
int lineWidth
Capture width enum: 0 thin, 1 medium, 2 wide, 3 default.
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::map< std::string, std::string > props
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 createTimestamp
uint32_t modifyTimestamp
The bounding box occupies the final eight bytes before the next prefix stop.
std::vector< ORCAD_SYMBOL_PIN > pins
std::vector< ORCAD_PRIMITIVE > primitives
std::map< std::string, std::string > props
std::string sourceLib
std::optional< ORCAD_BBOX > bbox
symbol-space body box
std::vector< ORCAD_SYMBOL_DEF > variants
Variant zero is this entry.
Pin coordinates use symbol space with Y down.
The wire ID refers to the page net table.
uint32_t id
uint32_t dbId
bool isBus
true for structure type 21
std::vector< ORCAD_ALIAS > aliases
@ USER
The field ID hasn't been set yet; field is invalid.
@ INTERSHEET_REFS
Global label cross-reference page numbers.
std::string path
KIBIS top(path, &reporter)
KIBIS_PIN * pin
VECTOR2I center
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
int delta
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
#define M_PI
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_LABEL_T
Definition typeinfo.h:163
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_HIER_LABEL_T
Definition typeinfo.h:165
@ SCH_BUS_BUS_ENTRY_T
Definition typeinfo.h:158
@ SCH_SHEET_PIN_T
Definition typeinfo.h:170
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:157
@ 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