KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pads_sch_schematic_builder.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 (C) 2025 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <sch_line.h>
23#include <sch_junction.h>
24#include <sch_label.h>
25#include <sch_screen.h>
26#include <sch_sheet.h>
27#include <sch_sheet_path.h>
28#include <sch_sheet_pin.h>
29#include <sch_symbol.h>
30#include <schematic.h>
32#include <layer_ids.h>
33#include <template_fieldnames.h>
34#include <title_block.h>
36#include <io/pads/pads_common.h>
37
38#include <advanced_config.h>
39
40#include <algorithm>
41#include <cctype>
42#include <cmath>
43#include <map>
44#include <set>
45
46namespace PADS_SCH
47{
48
50 SCHEMATIC* aSchematic ) :
51 m_params( aParams ),
52 m_schematic( aSchematic ),
53 m_pageHeightIU( schIUScale.MilsToIU( aParams.sheet_size.height ) )
54{
55}
56
57
61
62
63int PADS_SCH_SCHEMATIC_BUILDER::toKiCadUnits( double aPadsValue ) const
64{
65 // PADS Logic ASCII schematics always store geometry in mils. The UNITS field selects only
66 // the design-rules unit and must not scale the schematic coordinates.
67 return schIUScale.MilsToIU( aPadsValue );
68}
69
70
71int PADS_SCH_SCHEMATIC_BUILDER::toKiCadY( double aPadsY ) const
72{
73 return m_pageHeightIU - toKiCadUnits( aPadsY );
74}
75
76
77wxString PADS_SCH_SCHEMATIC_BUILDER::convertNetName( const std::string& aName ) const
78{
80}
81
82
83int PADS_SCH_SCHEMATIC_BUILDER::CreateWires( const std::vector<SCH_SIGNAL>& aSignals,
84 SCH_SCREEN* aScreen )
85{
86 int wireCount = 0;
87
88 for( const auto& signal : aSignals )
89 {
90 for( const auto& wire : signal.wires )
91 {
92 SCH_LINE* schLine = CreateWire( wire );
93
94 if( schLine )
95 {
96 schLine->SetFlags( IS_NEW );
97 aScreen->Append( schLine );
98 wireCount++;
99 }
100 }
101 }
102
103 return wireCount;
104}
105
106
108{
109 VECTOR2I start( toKiCadUnits( aWire.start.x ), toKiCadY( aWire.start.y ) );
110 VECTOR2I end( toKiCadUnits( aWire.end.x ), toKiCadY( aWire.end.y ) );
111
112 SCH_LINE* line = new SCH_LINE( start, SCH_LAYER_ID::LAYER_WIRE );
113 line->SetEndPoint( end );
114
115 return line;
116}
117
118
119int PADS_SCH_SCHEMATIC_BUILDER::CreateJunctions( const std::vector<SCH_SIGNAL>& aSignals,
120 SCH_SCREEN* aScreen )
121{
122 std::vector<VECTOR2I> junctionPoints = findJunctionPoints( aSignals );
123
124 for( const auto& pt : junctionPoints )
125 {
126 SCH_JUNCTION* junction = new SCH_JUNCTION( pt );
127 junction->SetFlags( IS_NEW );
128 aScreen->Append( junction );
129 }
130
131 return static_cast<int>( junctionPoints.size() );
132}
133
134
136 const std::vector<SCH_SIGNAL>& aSignals )
137{
138 std::vector<VECTOR2I> junctions;
139
140 for( const auto& signal : aSignals )
141 {
142 // Count how many wire endpoints connect at each point
143 std::map<std::pair<int, int>, int> pointCount;
144
145 for( const auto& wire : signal.wires )
146 {
147 VECTOR2I start( toKiCadUnits( wire.start.x ), toKiCadY( wire.start.y ) );
148 VECTOR2I end( toKiCadUnits( wire.end.x ), toKiCadY( wire.end.y ) );
149
150 pointCount[{ start.x, start.y }]++;
151 pointCount[{ end.x, end.y }]++;
152 }
153
154 // Junction needed where 3+ wire endpoints meet
155 for( const auto& [coords, count] : pointCount )
156 {
157 if( count >= 3 )
158 {
159 junctions.emplace_back( coords.first, coords.second );
160 }
161 }
162 }
163
164 return junctions;
165}
166
167
169 const std::vector<SCH_SIGNAL>& aSignals, SCH_SCREEN* aScreen,
170 const std::set<std::string>& aSignalOpcIds, const std::set<std::string>& aSkipSignals,
171 const std::map<std::string, NETNAME_LABEL>& aNetNameLabels )
172{
173 int labelCount = 0;
174
175 for( const auto& signal : aSignals )
176 {
177 if( signal.name.empty() || signal.name[0] == '$' )
178 continue;
179
180 if( aSkipSignals.count( signal.name ) )
181 continue;
182
183 if( signal.wires.empty() )
184 continue;
185
186 // Collect label placements from OPC wire endpoints. Each OPC produces one label.
187 // The anchor ref (@@@O..) is retained so the authoritative *NETNAMES* orientation
188 // can be looked up; the wire direction is only a fallback when no entry exists.
189 struct PLACEMENT
190 {
191 VECTOR2I labelPos;
192 VECTOR2I adjPos;
193 std::string anchorRef;
194 };
195
196 std::vector<PLACEMENT> opcPlacements;
197
198 for( const auto& wire : signal.wires )
199 {
200 if( wire.vertices.size() < 2 )
201 continue;
202
203 // endpoint_a references first vertex, endpoint_b references last vertex
204 if( !wire.endpoint_a.empty() && wire.endpoint_a.substr( 0, 3 ) == "@@@"
205 && aSignalOpcIds.count( wire.endpoint_a ) )
206 {
207 VECTOR2I labelPos( toKiCadUnits( wire.vertices.front().x ),
208 toKiCadY( wire.vertices.front().y ) );
209 VECTOR2I adjPos( toKiCadUnits( wire.vertices[1].x ),
210 toKiCadY( wire.vertices[1].y ) );
211 opcPlacements.push_back( { labelPos, adjPos, wire.endpoint_a } );
212 }
213
214 if( !wire.endpoint_b.empty() && wire.endpoint_b.substr( 0, 3 ) == "@@@"
215 && aSignalOpcIds.count( wire.endpoint_b ) )
216 {
217 size_t last = wire.vertices.size() - 1;
218 VECTOR2I labelPos( toKiCadUnits( wire.vertices[last].x ),
219 toKiCadY( wire.vertices[last].y ) );
220 VECTOR2I adjPos( toKiCadUnits( wire.vertices[last - 1].x ),
221 toKiCadY( wire.vertices[last - 1].y ) );
222 opcPlacements.push_back( { labelPos, adjPos, wire.endpoint_b } );
223 }
224 }
225
226 for( const auto& placement : opcPlacements )
227 {
228 // Prefer the orientation authored in the PADS *NETNAMES* section. The wire
229 // direction is unreliable for off-page connectors whose stub wire is
230 // zero-length, so it is only used when no NETNAMES entry matches.
231 auto nnIt = aNetNameLabels.find( placement.anchorRef );
232
233 SPIN_STYLE orient = ( nnIt != aNetNameLabels.end() )
234 ? SpinFromNetNameLabel( nnIt->second )
235 : computeLabelOrientation( placement.labelPos,
236 placement.adjPos );
237
238 SCH_GLOBALLABEL* label = CreateNetLabel( signal, placement.labelPos, orient );
239
240 if( label )
241 {
242 label->SetFlags( IS_NEW );
243 aScreen->Append( label );
244 labelCount++;
245 }
246 }
247 }
248
249 return labelCount;
250}
251
252
254{
255 // PADS net labels are drawn at an offset from the anchor (connection) point. The text
256 // reads away from the connection, so the sign of the dominant offset axis maps directly
257 // to the KiCad spin style. The observed files do not encode the axis reliably in the
258 // rotation field, so the larger offset component selects the axis. PADS uses a Y-up
259 // coordinate system, so a positive Y offset places the text above the anchor.
260 int dx = aLabel.x_offset;
261 int dy = aLabel.y_offset;
262
263 if( std::abs( dx ) >= std::abs( dy ) )
264 return ( dx >= 0 ) ? SPIN_STYLE::RIGHT : SPIN_STYLE::LEFT;
265
266 return ( dy >= 0 ) ? SPIN_STYLE::UP : SPIN_STYLE::BOTTOM;
267}
268
269
271 const VECTOR2I& aLabelPos, const VECTOR2I& aAdjacentPos )
272{
273 // The wire goes from aLabelPos toward aAdjacentPos. The label text extends
274 // in the opposite direction so it doesn't overlap the wire.
275 int dx = aAdjacentPos.x - aLabelPos.x;
276 int dy = aAdjacentPos.y - aLabelPos.y;
277
278 if( std::abs( dx ) >= std::abs( dy ) )
279 {
280 return ( dx > 0 ) ? SPIN_STYLE::LEFT : SPIN_STYLE::RIGHT;
281 }
282 else
283 {
284 return ( dy > 0 ) ? SPIN_STYLE::UP : SPIN_STYLE::BOTTOM;
285 }
286}
287
288
290 const VECTOR2I& aPosition,
291 SPIN_STYLE aOrientation )
292{
293 wxString labelName = convertNetName( aSignal.name );
294
295 SCH_GLOBALLABEL* label = new SCH_GLOBALLABEL( aPosition, labelName );
297 label->SetSpinStyle( aOrientation );
298
299 int labelSize = schIUScale.MilsToIU( 50 );
300 label->SetTextSize( VECTOR2I( labelSize, labelSize ) );
301
302 return label;
303}
304
305
307{
308 if( aSignal.wires.empty() )
309 return VECTOR2I( 0, 0 );
310
311 // Count how many wire segments share each endpoint. An endpoint referenced
312 // only once is a dangling wire end -- the correct place for a global label.
313 // Only first and last vertices are true endpoints; interior ones are bends.
314 std::map<std::pair<int, int>, int> endpointCount;
315
316 for( const auto& wire : aSignal.wires )
317 {
318 if( wire.vertices.size() < 2 )
319 continue;
320
321 auto first = wire.vertices.front();
322 auto last = wire.vertices.back();
323
324 endpointCount[{ static_cast<int>( first.x * 1000 ),
325 static_cast<int>( first.y * 1000 ) }]++;
326 endpointCount[{ static_cast<int>( last.x * 1000 ),
327 static_cast<int>( last.y * 1000 ) }]++;
328 }
329
330 // Also count pin connection positions so we can avoid placing on a pin
331 std::set<std::pair<int, int>> pinEndpoints;
332
333 for( const auto& wire : aSignal.wires )
334 {
335 if( wire.vertices.size() < 2 )
336 continue;
337
338 // endpoint_a/endpoint_b hold pin references (e.g. "R1.1"); if non-empty,
339 // that endpoint connects to a component pin. OPC references (starting
340 // with "@@@") are off-page connectors, not physical pins.
341 if( !wire.endpoint_a.empty() && wire.endpoint_a.substr( 0, 3 ) != "@@@" )
342 {
343 auto pt = wire.vertices.front();
344 pinEndpoints.insert( { static_cast<int>( pt.x * 1000 ),
345 static_cast<int>( pt.y * 1000 ) } );
346 }
347
348 if( !wire.endpoint_b.empty() && wire.endpoint_b.substr( 0, 3 ) != "@@@" )
349 {
350 auto pt = wire.vertices.back();
351 pinEndpoints.insert( { static_cast<int>( pt.x * 1000 ),
352 static_cast<int>( pt.y * 1000 ) } );
353 }
354 }
355
356 // Prefer a dangling endpoint that is NOT at a pin
357 for( const auto& wire : aSignal.wires )
358 {
359 if( wire.vertices.size() < 2 )
360 continue;
361
362 for( const auto* vtx : { &wire.vertices.front(), &wire.vertices.back() } )
363 {
364 auto key = std::make_pair( static_cast<int>( vtx->x * 1000 ),
365 static_cast<int>( vtx->y * 1000 ) );
366
367 if( endpointCount[key] == 1 && pinEndpoints.count( key ) == 0 )
368 return VECTOR2I( toKiCadUnits( vtx->x ), toKiCadY( vtx->y ) );
369 }
370 }
371
372 // Fallback: any dangling endpoint (even if at a pin)
373 for( const auto& wire : aSignal.wires )
374 {
375 if( wire.vertices.size() < 2 )
376 continue;
377
378 for( const auto* vtx : { &wire.vertices.front(), &wire.vertices.back() } )
379 {
380 auto key = std::make_pair( static_cast<int>( vtx->x * 1000 ),
381 static_cast<int>( vtx->y * 1000 ) );
382
383 if( endpointCount[key] == 1 )
384 return VECTOR2I( toKiCadUnits( vtx->x ), toKiCadY( vtx->y ) );
385 }
386 }
387
388 // Last resort: first endpoint of the first wire that has vertices
389 for( const auto& wire : aSignal.wires )
390 {
391 if( !wire.vertices.empty() )
392 {
393 const auto& vtx = wire.vertices[0];
394 return VECTOR2I( toKiCadUnits( vtx.x ), toKiCadY( vtx.y ) );
395 }
396 }
397
398 return VECTOR2I( 0, 0 );
399}
400
401
402int PADS_SCH_SCHEMATIC_BUILDER::CreateBusWires( const std::vector<SCH_SIGNAL>& aSignals,
403 SCH_SCREEN* aScreen )
404{
405 int busCount = 0;
406
407 for( const auto& signal : aSignals )
408 {
409 if( !IsBusSignal( signal.name ) )
410 continue;
411
412 for( const auto& wire : signal.wires )
413 {
414 SCH_LINE* busLine = CreateBusWire( wire );
415
416 if( busLine )
417 {
418 busLine->SetFlags( IS_NEW );
419 aScreen->Append( busLine );
420 busCount++;
421 }
422 }
423 }
424
425 return busCount;
426}
427
428
430{
431 VECTOR2I start( toKiCadUnits( aWire.start.x ), toKiCadY( aWire.start.y ) );
432 VECTOR2I end( toKiCadUnits( aWire.end.x ), toKiCadY( aWire.end.y ) );
433
434 SCH_LINE* line = new SCH_LINE( start, SCH_LAYER_ID::LAYER_BUS );
435 line->SetEndPoint( end );
436
437 return line;
438}
439
440
441bool PADS_SCH_SCHEMATIC_BUILDER::IsBusSignal( const std::string& aName )
442{
443 if( aName.empty() )
444 return false;
445
446 // Check for bus naming patterns commonly used in PADS
447 // Pattern 1: NAME[n:m] or NAME[n..m] - range notation
448 size_t bracketPos = aName.find( '[' );
449
450 if( bracketPos != std::string::npos )
451 {
452 size_t closeBracket = aName.find( ']', bracketPos );
453
454 if( closeBracket != std::string::npos )
455 {
456 std::string range = aName.substr( bracketPos + 1, closeBracket - bracketPos - 1 );
457
458 if( range.find( ':' ) != std::string::npos ||
459 range.find( ".." ) != std::string::npos )
460 {
461 return true;
462 }
463 }
464 }
465
466 // Pattern 2: NAME<n:m> or NAME<n..m>
467 size_t anglePos = aName.find( '<' );
468
469 if( anglePos != std::string::npos )
470 {
471 size_t closeAngle = aName.find( '>', anglePos );
472
473 if( closeAngle != std::string::npos )
474 {
475 std::string range = aName.substr( anglePos + 1, closeAngle - anglePos - 1 );
476
477 if( range.find( ':' ) != std::string::npos ||
478 range.find( ".." ) != std::string::npos )
479 {
480 return true;
481 }
482 }
483 }
484
485 return false;
486}
487
488
490 const PART_PLACEMENT& aPlacement )
491{
492 if( !aSymbol || !m_schematic )
493 return;
494
495 // Set reference designator, stripping any gate suffix (e.g., "U17-A" → "U17")
496 if( !aPlacement.reference.empty() )
497 {
498 std::string ref = aPlacement.reference;
499 size_t sepPos = ref.rfind( '-' );
500
501 if( sepPos == std::string::npos )
502 sepPos = ref.rfind( '.' );
503
504 if( sepPos != std::string::npos && sepPos + 1 < ref.size()
505 && std::isalpha( static_cast<unsigned char>( ref[sepPos + 1] ) ) )
506 {
507 ref = ref.substr( 0, sepPos );
508 }
509
510 aSymbol->SetRef( &m_schematic->CurrentSheet(), wxString::FromUTF8( ref ) );
511 }
512
513 // Value field is always the PARTTYPE name. Parametric values like VALUE1
514 // flow through CreateCustomFields as user-defined fields.
515 if( !aPlacement.part_type.empty() )
516 {
517 aSymbol->SetValueFieldText( wxString::FromUTF8( aPlacement.part_type ) );
518 }
519
520 // Look for PCB DECAL attribute to set footprint
521 for( const auto& attr : aPlacement.attributes )
522 {
523 if( attr.name == "PCB DECAL" || attr.name == "PCB_DECAL" || attr.name == "FOOTPRINT" )
524 {
525 if( !attr.value.empty() )
526 {
527 aSymbol->SetFootprintFieldText( wxString::FromUTF8( attr.value ) );
528 }
529
530 break;
531 }
532 }
533
534 // Apply visibility and position settings
535 ApplyFieldSettings( aSymbol, aPlacement );
536}
537
538
540 const PART_PLACEMENT& aPlacement )
541{
542 if( !aSymbol )
543 return;
544
546
547 for( const auto& attr : aPlacement.attributes )
548 {
549 SCH_FIELD* field = nullptr;
550 bool isRefOrValue = false;
551
552 if( mapper.IsReferenceField( attr.name ) )
553 {
554 field = aSymbol->GetField( FIELD_T::REFERENCE );
555 isRefOrValue = true;
556 }
557 else if( mapper.IsValueField( attr.name ) )
558 {
559 field = aSymbol->GetField( FIELD_T::VALUE );
560 isRefOrValue = true;
561 }
562 else if( mapper.IsFootprintField( attr.name ) )
563 {
564 field = aSymbol->GetField( FIELD_T::FOOTPRINT );
565 }
566
567 if( field )
568 {
569 bool isRef = mapper.IsReferenceField( attr.name );
570
571 if( isRef )
572 field->SetVisible( true );
573 else
574 field->SetVisible( isRefOrValue ? attr.visible : false );
575
576 // PADS field positions are in CAEDECAL coordinates (pre-mirror).
577 // KiCad applies the symbol transform to field positions, so
578 // pre-compensate X for mirrored-Y symbols.
579 int fx = toKiCadUnits( attr.position.x );
580
581 if( aPlacement.mirror_flags & 1 )
582 fx = -fx;
583
584 VECTOR2I fieldPos( fx, -toKiCadUnits( attr.position.y ) );
585 field->SetPosition( aSymbol->GetPosition() + fieldPos );
586
587 if( attr.rotation != 0.0 )
588 {
589 field->SetTextAngleDegrees( attr.rotation );
590 }
591
592 if( attr.height > 0 )
593 {
594 int scaledH = static_cast<int>(
595 schIUScale.MilsToIU( attr.height )
596 * ADVANCED_CFG::GetCfg().m_PadsSchTextHeightScale );
597 int scaledW = static_cast<int>(
598 schIUScale.MilsToIU( attr.height )
599 * ADVANCED_CFG::GetCfg().m_PadsSchTextWidthScale );
600 field->SetTextSize( VECTOR2I( scaledW, scaledH ) );
601 }
602 else
603 {
604 int fieldTextSize = schIUScale.MilsToIU( 50 );
605 field->SetTextSize( VECTOR2I( fieldTextSize, fieldTextSize ) );
606 }
607
608 if( attr.width > 0 )
609 field->SetTextThickness( schIUScale.MilsToIU( attr.width ) );
610
611 // Map the PADS justification code so reference and value fields keep the
612 // alignment authored in PADS instead of forcing center.
615 PADS_COMMON::DecodeJustification( attr.justification, hJustify, vJustify );
616
617 // KiCad applies the symbol transform to field justification. Mirrored-Y
618 // symbols flip horizontal alignment, so pre-compensate to keep the rendered
619 // alignment matching PADS.
620 if( aPlacement.mirror_flags & 1 )
621 hJustify = GetFlippedAlignment( hJustify );
622
623 field->SetHorizJustify( hJustify );
624 field->SetVertJustify( vJustify );
625 }
626 }
627}
628
629
631 const PART_PLACEMENT& aPlacement )
632{
633 if( !aSymbol )
634 return 0;
635
636 int fieldsCreated = 0;
638
639 std::set<std::string> processedNames;
640
641 for( const auto& attr : aPlacement.attributes )
642 {
643 // Skip standard fields that are handled by ApplyPartAttributes
644 if( mapper.IsStandardField( attr.name ) )
645 continue;
646
647 // Skip empty attributes
648 if( attr.value.empty() )
649 continue;
650
651 processedNames.insert( attr.name );
652
653 // Get the mapped field name
654 std::string fieldName = mapper.GetKiCadFieldName( attr.name );
655
656 // Check if this field already exists on the symbol
657 SCH_FIELD* existingField = aSymbol->GetField( wxString::FromUTF8( fieldName ) );
658
659 if( existingField )
660 {
661 // Update existing field value and settings
662 existingField->SetText( wxString::FromUTF8( attr.value ) );
663 existingField->SetVisible( false );
664 }
665 else
666 {
667 // Create a new custom field using FIELD_T::USER for custom fields
668 SCH_FIELD newField( aSymbol, FIELD_T::USER, wxString::FromUTF8( fieldName ) );
669
670 newField.SetText( wxString::FromUTF8( attr.value ) );
671 newField.SetVisible( false );
672
673 // Apply position offset from attribute
674 VECTOR2I fieldPos( toKiCadUnits( attr.position.x ), -toKiCadUnits( attr.position.y ) );
675 newField.SetPosition( aSymbol->GetPosition() + fieldPos );
676
677 // Apply rotation if specified
678 if( attr.rotation != 0.0 )
679 {
680 newField.SetTextAngleDegrees( attr.rotation );
681 }
682
683 // Apply text size if specified
684 if( attr.size > 0.0 )
685 {
686 int textSize = toKiCadUnits( attr.size );
687 newField.SetTextSize( VECTOR2I( textSize, textSize ) );
688 }
689
690 aSymbol->GetFields().push_back( newField );
691 fieldsCreated++;
692 }
693 }
694
695 // Create fields from attr_overrides that weren't in the attributes vector
696 for( const auto& [name, value] : aPlacement.attr_overrides )
697 {
698 if( value.empty() || processedNames.count( name ) || mapper.IsStandardField( name ) )
699 continue;
700
701 std::string fieldName = mapper.GetKiCadFieldName( name );
702 SCH_FIELD* existingField = aSymbol->GetField( wxString::FromUTF8( fieldName ) );
703
704 if( existingField )
705 {
706 existingField->SetText( wxString::FromUTF8( value ) );
707 existingField->SetVisible( false );
708 }
709 else
710 {
711 SCH_FIELD newField( aSymbol, FIELD_T::USER, wxString::FromUTF8( fieldName ) );
712 newField.SetText( wxString::FromUTF8( value ) );
713 newField.SetVisible( false );
714 newField.SetPosition( aSymbol->GetPosition() );
715
716 aSymbol->GetFields().push_back( newField );
717 fieldsCreated++;
718 }
719 }
720
721 return fieldsCreated;
722}
723
724
726{
727 if( !aScreen )
728 return;
729
730 // Look up the first non-empty value from a list of candidate field names
731 auto findField = [this]( const std::initializer_list<const char*>& aCandidates ) -> std::string
732 {
733 for( const char* name : aCandidates )
734 {
735 auto it = m_params.fields.find( name );
736
737 if( it != m_params.fields.end() && !it->second.empty() )
738 return it->second;
739 }
740
741 return {};
742 };
743
744 TITLE_BLOCK tb;
745
746 std::string title = findField( { "Title", "TITLE1" } );
747
748 if( title.empty() )
749 title = m_params.job_name;
750
751 if( !title.empty() )
752 tb.SetTitle( wxString::FromUTF8( title ) );
753
754 std::string date = findField( { "DATE", "Release Date", "Drawn Date" } );
755
756 if( !date.empty() )
757 tb.SetDate( wxString::FromUTF8( date ) );
758
759 std::string revision = findField( { "Revision", "VER" } );
760
761 if( !revision.empty() )
762 tb.SetRevision( wxString::FromUTF8( revision ) );
763
764 std::string company = findField( { "Company Name" } );
765
766 if( !company.empty() )
767 tb.SetCompany( wxString::FromUTF8( company ) );
768
769 std::string drawingNumber = findField( { "DN", "Drawing Number" } );
770
771 if( !drawingNumber.empty() )
772 tb.SetComment( 0, wxString::FromUTF8( drawingNumber ) );
773
774 std::string designer = findField( { "DESIGNER" } );
775
776 if( !designer.empty() )
777 tb.SetComment( 1, wxString::FromUTF8( designer ) );
778
779 std::string drawnBy = findField( { "DRAWNBY", "Drawn By" } );
780
781 if( !drawnBy.empty() )
782 tb.SetComment( 2, wxString::FromUTF8( drawnBy ) );
783
784 std::string builtFor = findField( { "BUILTFOR" } );
785
786 if( !builtFor.empty() )
787 tb.SetComment( 3, wxString::FromUTF8( builtFor ) );
788
789 aScreen->SetTitleBlock( tb );
790}
791
792
794 SCH_SHEET* aParentSheet,
795 const wxString& aBaseFilename )
796{
797 if( !aParentSheet || !m_schematic )
798 return nullptr;
799
800 VECTOR2I pos = CalculateSheetPosition( aSheetNumber - 1, aTotalSheets );
802
803 SCH_SHEET* sheet = new SCH_SHEET( aParentSheet, pos, size );
804
805 // Create a screen for this sheet
806 SCH_SCREEN* screen = new SCH_SCREEN( m_schematic );
807 sheet->SetScreen( screen );
808
809 // Generate sheet filename based on base filename and sheet number
810 wxFileName fn( aBaseFilename );
811 wxString sheetFilename = wxString::Format( wxT( "%s_sheet%d.%s" ),
812 fn.GetName(),
813 aSheetNumber,
815
816 // Set the sheet filename field
817 sheet->GetField( FIELD_T::SHEET_FILENAME )->SetText( sheetFilename );
818
819 // Set sheet name
820 wxString sheetName = wxString::Format( wxT( "Sheet %d" ), aSheetNumber );
821 sheet->GetField( FIELD_T::SHEET_NAME )->SetText( sheetName );
822
823 // Set full path for the screen if project is available
824 if( m_schematic->IsValid() )
825 {
826 wxFileName screenFn( m_schematic->Project().GetProjectPath(), sheetFilename );
827 screen->SetFileName( screenFn.GetFullPath() );
828 }
829 else
830 {
831 screen->SetFileName( sheetFilename );
832 }
833
834 sheet->SetFlags( IS_NEW );
835
836 // Add the sheet to the parent's screen
837 SCH_SCREEN* parentScreen = aParentSheet->GetScreen();
838
839 if( parentScreen )
840 {
841 parentScreen->Append( sheet );
842 }
843
844 return sheet;
845}
846
847
849{
850 // Default sheet symbol size in mils (approximately 2" x 1.5")
851 return VECTOR2I( schIUScale.MilsToIU( 2000 ), schIUScale.MilsToIU( 1500 ) );
852}
853
854
855VECTOR2I PADS_SCH_SCHEMATIC_BUILDER::CalculateSheetPosition( int aSheetIndex, int aTotalSheets ) const
856{
857 // Arrange sheet symbols in a grid on the root sheet
858 // Start position offset from origin
859 const int startX = schIUScale.MilsToIU( 500 );
860 const int startY = schIUScale.MilsToIU( 500 );
861
862 // Spacing between sheet symbols
863 const int spacingX = schIUScale.MilsToIU( 2500 );
864 const int spacingY = schIUScale.MilsToIU( 2000 );
865
866 // Calculate grid columns based on total sheets (aim for roughly square layout)
867 int columns = static_cast<int>( std::ceil( std::sqrt( static_cast<double>( aTotalSheets ) ) ) );
868
869 if( columns < 1 )
870 columns = 1;
871
872 int row = aSheetIndex / columns;
873 int col = aSheetIndex % columns;
874
875 return VECTOR2I( startX + col * spacingX, startY + row * spacingY );
876}
877
878
880 const std::string& aSignalName,
881 int aPinIndex )
882{
883 if( !aSheet )
884 return nullptr;
885
886 wxString name = wxString::FromUTF8( aSignalName );
887
888 // Position pins along the left edge of the sheet
889 VECTOR2I sheetPos = aSheet->GetPosition();
890
891 int pinSpacing = schIUScale.MilsToIU( 200 );
892 int yOffset = schIUScale.MilsToIU( 100 ) + aPinIndex * pinSpacing;
893
894 VECTOR2I pinPos( sheetPos.x, sheetPos.y + yOffset );
895
896 SCH_SHEET_PIN* pin = new SCH_SHEET_PIN( aSheet, pinPos, name );
897 pin->SetSide( SHEET_SIDE::LEFT );
899
900 aSheet->AddPin( pin );
901
902 return pin;
903}
904
905
907 const VECTOR2I& aPosition,
908 SCH_SCREEN* aScreen )
909{
910 if( !aScreen )
911 return nullptr;
912
913 wxString name = wxString::FromUTF8( aSignalName );
914
915 SCH_HIERLABEL* label = new SCH_HIERLABEL( aPosition, name );
917 label->SetFlags( IS_NEW );
918
919 aScreen->Append( label );
920
921 return label;
922}
923
924
925
926bool PADS_SCH_SCHEMATIC_BUILDER::IsGlobalSignal( const std::string& aSignalName,
927 const std::set<int>& aSheetNumbers )
928{
929 if( aSignalName.empty() )
930 return false;
931
932 // Signals appearing on multiple sheets should be global
933 if( aSheetNumbers.size() > 1 )
934 return true;
935
936 // Common power net names are always global
937 std::string upperName = aSignalName;
938 std::transform( upperName.begin(), upperName.end(), upperName.begin(), ::toupper );
939
940 static const std::set<std::string> globalPatterns = {
941 "VCC", "VDD", "VEE", "VSS", "GND", "AGND", "DGND", "PGND",
942 "V+", "V-", "VBAT", "VBUS", "VIN", "VOUT",
943 "+5V", "+3V3", "+3.3V", "+12V", "-12V", "+24V",
944 "0V", "EARTH", "CHASSIS"
945 };
946
947 if( globalPatterns.count( upperName ) > 0 )
948 return true;
949
950 // Check for voltage patterns like +1V8, +2V5, etc.
951 if( ( upperName[0] == '+' || upperName[0] == '-' ) && upperName.length() >= 3 )
952 {
953 bool hasDigit = false;
954 bool hasV = false;
955
956 for( char c : upperName.substr( 1 ) )
957 {
958 if( std::isdigit( c ) )
959 hasDigit = true;
960
961 if( c == 'V' )
962 hasV = true;
963 }
964
965 if( hasDigit && hasV )
966 return true;
967 }
968
969 return false;
970}
971
972} // namespace PADS_SCH
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:152
virtual void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:532
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:412
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
virtual void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition eda_text.cpp:279
void SetTextAngleDegrees(double aOrientation)
Definition eda_text.h:171
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:404
Maps PADS attribute names to KiCad field names.
bool IsReferenceField(const std::string &aPadsAttr) const
Check if a PADS attribute maps to the Reference field.
std::string GetKiCadFieldName(const std::string &aPadsAttr) const
Get the KiCad field name for a PADS attribute.
bool IsFootprintField(const std::string &aPadsAttr) const
Check if a PADS attribute maps to the Footprint field.
bool IsStandardField(const std::string &aPadsAttr) const
Check if a PADS attribute maps to a standard KiCad field.
bool IsValueField(const std::string &aPadsAttr) const
Check if a PADS attribute maps to the Value field.
SCH_LINE * CreateWire(const WIRE_SEGMENT &aWire)
Create a single wire segment.
std::vector< VECTOR2I > findJunctionPoints(const std::vector< SCH_SIGNAL > &aSignals)
Find junction points where 3+ wire segments meet.
static SPIN_STYLE SpinFromNetNameLabel(const NETNAME_LABEL &aLabel)
Map a PADS NETNAMES label entry to a KiCad global-label spin style.
int toKiCadY(double aPadsY) const
Convert PADS Y coordinate to KiCad Y, accounting for Y-axis inversion and page offset.
SCH_SHEET_PIN * CreateSheetPin(SCH_SHEET *aSheet, const std::string &aSignalName, int aPinIndex)
Create hierarchical sheet pin on a sheet symbol.
int CreateJunctions(const std::vector< SCH_SIGNAL > &aSignals, SCH_SCREEN *aScreen)
Create junctions at wire intersection points.
void ApplyPartAttributes(SCH_SYMBOL *aSymbol, const PART_PLACEMENT &aPlacement)
Apply part attributes to a symbol instance.
PADS_SCH_SCHEMATIC_BUILDER(const PARAMETERS &aParams, SCHEMATIC *aSchematic)
void CreateTitleBlock(SCH_SCREEN *aScreen)
Create title block from parsed PADS parameters.
VECTOR2I CalculateSheetPosition(int aSheetIndex, int aTotalSheets) const
Calculate position for a sheet symbol on the parent sheet.
SPIN_STYLE computeLabelOrientation(const VECTOR2I &aLabelPos, const VECTOR2I &aAdjacentPos)
Compute label orientation from the wire direction at the label position.
int toKiCadUnits(double aPadsValue) const
Convert PADS coordinate to KiCad internal units.
int CreateCustomFields(SCH_SYMBOL *aSymbol, const PART_PLACEMENT &aPlacement)
Create custom fields from non-standard PADS attributes.
VECTOR2I chooseLabelPosition(const SCH_SIGNAL &aSignal)
Choose best position for net label on a signal's wires.
SCH_LINE * CreateBusWire(const WIRE_SEGMENT &aWire)
Create a single bus wire segment.
SCH_HIERLABEL * CreateHierLabel(const std::string &aSignalName, const VECTOR2I &aPosition, SCH_SCREEN *aScreen)
Create hierarchical label in a sub-schematic.
void ApplyFieldSettings(SCH_SYMBOL *aSymbol, const PART_PLACEMENT &aPlacement)
Apply field visibility and position from PADS attribute settings.
int CreateWires(const std::vector< SCH_SIGNAL > &aSignals, SCH_SCREEN *aScreen)
Create wire segments from signal definitions and add to screen.
SCH_GLOBALLABEL * CreateNetLabel(const SCH_SIGNAL &aSignal, const VECTOR2I &aPosition, SPIN_STYLE aOrientation=SPIN_STYLE::RIGHT)
Create a global net label for a signal.
static bool IsGlobalSignal(const std::string &aSignalName, const std::set< int > &aSheetNumbers)
Check if a signal name represents a global signal.
int CreateBusWires(const std::vector< SCH_SIGNAL > &aSignals, SCH_SCREEN *aScreen)
Create bus wires and entries for bus signals.
wxString convertNetName(const std::string &aName) const
Convert a PADS net name for use as a KiCad label.
SCH_SHEET * CreateHierarchicalSheet(int aSheetNumber, int aTotalSheets, SCH_SHEET *aParentSheet, const wxString &aBaseFilename)
Create hierarchical sheet for a sub-schematic page.
VECTOR2I GetDefaultSheetSize() const
Get standard sheet size for a given sheet number.
int CreateNetLabels(const std::vector< SCH_SIGNAL > &aSignals, SCH_SCREEN *aScreen, const std::set< std::string > &aSignalOpcIds, const std::set< std::string > &aSkipSignals={}, const std::map< std::string, NETNAME_LABEL > &aNetNameLabels={})
Create net labels for named signals.
static bool IsBusSignal(const std::string &aName)
Check if a signal name indicates a bus.
Holds all the data relating to one schematic.
Definition schematic.h:90
void SetPosition(const VECTOR2I &aPosition) override
void SetText(const wxString &aText) override
void SetSpinStyle(SPIN_STYLE aSpinStyle) override
void SetShape(LABEL_FLAG_SHAPE aShape)
Definition sch_label.h:179
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:38
void SetEndPoint(const VECTOR2I &aPosition)
Definition sch_line.h:145
void SetTitleBlock(const TITLE_BLOCK &aTitleBlock)
Definition sch_screen.h:164
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
Define a sheet pin (label) used in sheets to create hierarchical schematics.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:44
void AddPin(SCH_SHEET_PIN *aSheetPin)
Add aSheetPin to the sheet.
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this sheet.
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:139
VECTOR2I GetPosition() const override
Definition sch_sheet.h:490
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Schematic symbol object.
Definition sch_symbol.h:69
void SetRef(const SCH_SHEET_PATH *aSheet, const wxString &aReference)
Set the reference for the given sheet path for this symbol.
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly) const override
Populate a std::vector with SCH_FIELDs, sorted in ordinal order.
void SetFootprintFieldText(const wxString &aFootprint)
VECTOR2I GetPosition() const override
Definition sch_symbol.h:885
void SetValueFieldText(const wxString &aValue, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString)
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
Hold the information shown in the lower right corner of a plot, printout, or editing view.
Definition title_block.h:37
void SetRevision(const wxString &aRevision)
Definition title_block.h:77
void SetComment(int aIdx, const wxString &aComment)
Definition title_block.h:97
void SetTitle(const wxString &aTitle)
Definition title_block.h:54
void SetCompany(const wxString &aCompany)
Definition title_block.h:87
void SetDate(const wxString &aDate)
Set the date field, and defaults to the current time and date.
Definition title_block.h:67
#define IS_NEW
New item, just created.
static const std::string KiCadSchematicFileExtension
@ LAYER_WIRE
Definition layer_ids.h:450
@ LAYER_BUS
Definition layer_ids.h:451
void DecodeJustification(int aJustification, GR_TEXT_H_ALIGN_T &aHJustify, GR_TEXT_V_ALIGN_T &aVJustify)
Decode a PADS text justification code into KiCad horizontal and vertical alignment.
wxString ConvertInvertedNetName(const std::string &aNetName)
Convert a PADS net name to KiCad format, handling inverted signal notation.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
Common utilities and types for parsing PADS file formats.
@ L_BIDI
Definition sch_label.h:100
@ L_UNSPECIFIED
Definition sch_label.h:102
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
Net name label from NETNAMES section.
General schematic parameters from SCH and FIELDS sections.
Part instance from PART section.
std::map< std::string, std::string > attr_overrides
std::vector< PART_ATTRIBUTE > attributes
Signal (net) definition from CONNECTION and SIGNAL sections.
std::vector< WIRE_SEGMENT > wires
Wire segment connecting two endpoints through coordinate vertices.
@ USER
The field ID hasn't been set yet; field is invalid.
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
KIBIS_PIN * pin
VECTOR2I end
GR_TEXT_H_ALIGN_T
This is API surface mapped to common.types.HorizontalAlignment.
@ GR_TEXT_H_ALIGN_LEFT
GR_TEXT_V_ALIGN_T
This is API surface mapped to common.types.VertialAlignment.
@ GR_TEXT_V_ALIGN_BOTTOM
constexpr GR_TEXT_H_ALIGN_T GetFlippedAlignment(GR_TEXT_H_ALIGN_T aAlign)
Get the reverse alignment: left-right are swapped, others are unchanged.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
Definition of file extensions used in Kicad.