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