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 SCH_SIGNAL& signal : aSignals )
89 {
90 for( const WIRE_SEGMENT& 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 VECTOR2I& 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
135std::vector<VECTOR2I> PADS_SCH_SCHEMATIC_BUILDER::findJunctionPoints( const std::vector<SCH_SIGNAL>& aSignals )
136{
137 std::vector<VECTOR2I> junctions;
138
139 for( const SCH_SIGNAL& signal : aSignals )
140 {
141 // Count how many wire endpoints connect at each point
142 std::map<std::pair<int, int>, int> pointCount;
143
144 for( const WIRE_SEGMENT& wire : signal.wires )
145 {
146 VECTOR2I start( toKiCadUnits( wire.start.x ), toKiCadY( wire.start.y ) );
147 VECTOR2I end( toKiCadUnits( wire.end.x ), toKiCadY( wire.end.y ) );
148
149 pointCount[{ start.x, start.y }]++;
150 pointCount[{ end.x, end.y }]++;
151 }
152
153 // Junction needed where 3+ wire endpoints meet
154 for( const auto& [coords, count] : pointCount )
155 {
156 if( count >= 3 )
157 junctions.emplace_back( coords.first, coords.second );
158 }
159 }
160
161 return junctions;
162}
163
164
165int PADS_SCH_SCHEMATIC_BUILDER::CreateNetLabels( const std::vector<SCH_SIGNAL>& aSignals, SCH_SCREEN* aScreen,
166 const std::set<std::string>& aSignalOpcIds,
167 const std::set<std::string>& aSkipSignals,
168 const std::map<std::string, NETNAME_LABEL>& aNetNameLabels )
169{
170 int labelCount = 0;
171
172 for( const SCH_SIGNAL& signal : aSignals )
173 {
174 if( signal.name.empty() || signal.name[0] == '$' )
175 continue;
176
177 if( aSkipSignals.count( signal.name ) )
178 continue;
179
180 if( signal.wires.empty() )
181 continue;
182
183 // Collect label placements from OPC wire endpoints. Each OPC produces one label.
184 // The anchor ref (@@@O..) is retained so the authoritative *NETNAMES* orientation
185 // can be looked up; the wire direction is used only when no entry exists.
186 struct PLACEMENT
187 {
188 VECTOR2I labelPos;
189 VECTOR2I adjPos;
190 std::string anchorRef;
191 };
192
193 std::vector<PLACEMENT> opcPlacements;
194
195 for( const WIRE_SEGMENT& wire : signal.wires )
196 {
197 if( wire.vertices.size() < 2 )
198 continue;
199
200 // endpoint_a references first vertex, endpoint_b references last vertex
201 if( !wire.endpoint_a.empty() && wire.endpoint_a.substr( 0, 3 ) == "@@@"
202 && aSignalOpcIds.count( wire.endpoint_a ) )
203 {
204 VECTOR2I labelPos( toKiCadUnits( wire.vertices.front().x ),
205 toKiCadY( wire.vertices.front().y ) );
206 VECTOR2I adjPos( toKiCadUnits( wire.vertices[1].x ),
207 toKiCadY( wire.vertices[1].y ) );
208 opcPlacements.push_back( { labelPos, adjPos, wire.endpoint_a } );
209 }
210
211 if( !wire.endpoint_b.empty() && wire.endpoint_b.substr( 0, 3 ) == "@@@"
212 && aSignalOpcIds.count( wire.endpoint_b ) )
213 {
214 size_t last = wire.vertices.size() - 1;
215 VECTOR2I labelPos( toKiCadUnits( wire.vertices[last].x ),
216 toKiCadY( wire.vertices[last].y ) );
217 VECTOR2I adjPos( toKiCadUnits( wire.vertices[last - 1].x ),
218 toKiCadY( wire.vertices[last - 1].y ) );
219 opcPlacements.push_back( { labelPos, adjPos, wire.endpoint_b } );
220 }
221 }
222
223 for( const PLACEMENT& placement : opcPlacements )
224 {
225 // Prefer the orientation authored in the PADS *NETNAMES* section. The wire
226 // direction is unreliable for off-page connectors whose stub wire is
227 // zero-length, so it is only used when no NETNAMES entry matches.
228 auto nnIt = aNetNameLabels.find( placement.anchorRef );
229
230 SPIN_STYLE orient = ( nnIt != aNetNameLabels.end() ) ? SpinFromNetNameLabel( nnIt->second )
231 : computeLabelOrientation( placement.labelPos,
232 placement.adjPos );
233
234 SCH_GLOBALLABEL* label = CreateNetLabel( signal, placement.labelPos, orient );
235
236 if( label )
237 {
238 label->SetFlags( IS_NEW );
239 aScreen->Append( label );
240 labelCount++;
241 }
242 }
243 }
244
245 return labelCount;
246}
247
248
250{
251 // PADS net labels are drawn at an offset from the anchor (connection) point. The text
252 // reads away from the connection, so the sign of the dominant offset axis maps directly
253 // to the KiCad spin style. The observed files do not encode the axis reliably in the
254 // rotation field, so the larger offset component selects the axis. PADS uses a Y-up
255 // coordinate system, so a positive Y offset places the text above the anchor.
256 int dx = aLabel.x_offset;
257 int dy = aLabel.y_offset;
258
259 if( std::abs( dx ) >= std::abs( dy ) )
260 return ( dx >= 0 ) ? SPIN_STYLE::RIGHT : SPIN_STYLE::LEFT;
261
262 return ( dy >= 0 ) ? SPIN_STYLE::UP : SPIN_STYLE::BOTTOM;
263}
264
265
267 const VECTOR2I& aAdjacentPos )
268{
269 // The wire goes from aLabelPos toward aAdjacentPos. The label text extends
270 // in the opposite direction so it doesn't overlap the wire.
271 int dx = aAdjacentPos.x - aLabelPos.x;
272 int dy = aAdjacentPos.y - aLabelPos.y;
273
274 if( std::abs( dx ) >= std::abs( dy ) )
275 {
276 return ( dx > 0 ) ? SPIN_STYLE::LEFT : SPIN_STYLE::RIGHT;
277 }
278 else
279 {
280 return ( dy > 0 ) ? SPIN_STYLE::UP : SPIN_STYLE::BOTTOM;
281 }
282}
283
284
286 const VECTOR2I& aPosition,
287 SPIN_STYLE aOrientation )
288{
289 wxString labelName = convertNetName( aSignal.name );
290
291 SCH_GLOBALLABEL* label = new SCH_GLOBALLABEL( aPosition, labelName );
293 label->SetSpinStyle( aOrientation );
294
295 int labelSize = schIUScale.MilsToIU( 50 );
296 label->SetTextSize( VECTOR2I( labelSize, labelSize ) );
297
298 return label;
299}
300
301
303{
304 if( aSignal.wires.empty() )
305 return VECTOR2I( 0, 0 );
306
307 // Count how many wire segments share each endpoint. An endpoint referenced
308 // only once is a dangling wire end -- the correct place for a global label.
309 // Only first and last vertices are true endpoints; interior ones are bends.
310 std::map<std::pair<int, int>, int> endpointCount;
311
312 for( const WIRE_SEGMENT& wire : aSignal.wires )
313 {
314 if( wire.vertices.size() < 2 )
315 continue;
316
317 POINT first = wire.vertices.front();
318 POINT last = wire.vertices.back();
319
320 endpointCount[{ static_cast<int>( first.x * 1000 ), static_cast<int>( first.y * 1000 ) }]++;
321 endpointCount[{ static_cast<int>( last.x * 1000 ), static_cast<int>( last.y * 1000 ) }]++;
322 }
323
324 // Also count pin connection positions so we can avoid placing on a pin
325 std::set<std::pair<int, int>> pinEndpoints;
326
327 for( const WIRE_SEGMENT& wire : aSignal.wires )
328 {
329 if( wire.vertices.size() < 2 )
330 continue;
331
332 // endpoint_a/endpoint_b hold pin references (e.g. "R1.1"); if non-empty,
333 // that endpoint connects to a component pin. OPC references (starting
334 // with "@@@") are off-page connectors, not physical pins.
335 if( !wire.endpoint_a.empty() && wire.endpoint_a.substr( 0, 3 ) != "@@@" )
336 {
337 POINT pt = wire.vertices.front();
338 pinEndpoints.insert( { static_cast<int>( pt.x * 1000 ), static_cast<int>( pt.y * 1000 ) } );
339 }
340
341 if( !wire.endpoint_b.empty() && wire.endpoint_b.substr( 0, 3 ) != "@@@" )
342 {
343 POINT pt = wire.vertices.back();
344 pinEndpoints.insert( { static_cast<int>( pt.x * 1000 ), static_cast<int>( pt.y * 1000 ) } );
345 }
346 }
347
348 // Prefer a dangling endpoint that is NOT at a pin
349 for( const WIRE_SEGMENT& wire : aSignal.wires )
350 {
351 if( wire.vertices.size() < 2 )
352 continue;
353
354 for( const POINT* vtx : { &wire.vertices.front(), &wire.vertices.back() } )
355 {
356 auto key = std::make_pair( static_cast<int>( vtx->x * 1000 ), static_cast<int>( vtx->y * 1000 ) );
357
358 if( endpointCount[key] == 1 && pinEndpoints.count( key ) == 0 )
359 return VECTOR2I( toKiCadUnits( vtx->x ), toKiCadY( vtx->y ) );
360 }
361 }
362
363 // Fallback: any dangling endpoint (even if at a pin)
364 for( const WIRE_SEGMENT& wire : aSignal.wires )
365 {
366 if( wire.vertices.size() < 2 )
367 continue;
368
369 for( const POINT* vtx : { &wire.vertices.front(), &wire.vertices.back() } )
370 {
371 auto key = std::make_pair( static_cast<int>( vtx->x * 1000 ), static_cast<int>( vtx->y * 1000 ) );
372
373 if( endpointCount[key] == 1 )
374 return VECTOR2I( toKiCadUnits( vtx->x ), toKiCadY( vtx->y ) );
375 }
376 }
377
378 // Last resort: first endpoint of the first wire that has vertices
379 for( const WIRE_SEGMENT& wire : aSignal.wires )
380 {
381 if( !wire.vertices.empty() )
382 {
383 const POINT& vtx = wire.vertices[0];
384 return VECTOR2I( toKiCadUnits( vtx.x ), toKiCadY( vtx.y ) );
385 }
386 }
387
388 return VECTOR2I( 0, 0 );
389}
390
391
392int PADS_SCH_SCHEMATIC_BUILDER::CreateBusWires( const std::vector<SCH_SIGNAL>& aSignals,
393 SCH_SCREEN* aScreen )
394{
395 int busCount = 0;
396
397 for( const SCH_SIGNAL& signal : aSignals )
398 {
399 if( !IsBusSignal( signal.name ) )
400 continue;
401
402 for( const WIRE_SEGMENT& wire : signal.wires )
403 {
404 SCH_LINE* busLine = CreateBusWire( wire );
405
406 if( busLine )
407 {
408 busLine->SetFlags( IS_NEW );
409 aScreen->Append( busLine );
410 busCount++;
411 }
412 }
413 }
414
415 return busCount;
416}
417
418
420{
421 VECTOR2I start( toKiCadUnits( aWire.start.x ), toKiCadY( aWire.start.y ) );
422 VECTOR2I end( toKiCadUnits( aWire.end.x ), toKiCadY( aWire.end.y ) );
423
424 SCH_LINE* line = new SCH_LINE( start, SCH_LAYER_ID::LAYER_BUS );
425 line->SetEndPoint( end );
426
427 return line;
428}
429
430
431bool PADS_SCH_SCHEMATIC_BUILDER::IsBusSignal( const std::string& aName )
432{
433 if( aName.empty() )
434 return false;
435
436 // Check for bus naming patterns commonly used in PADS
437 // Pattern 1: NAME[n:m] or NAME[n..m] - range notation
438 size_t bracketPos = aName.find( '[' );
439
440 if( bracketPos != std::string::npos )
441 {
442 size_t closeBracket = aName.find( ']', bracketPos );
443
444 if( closeBracket != std::string::npos )
445 {
446 std::string range = aName.substr( bracketPos + 1, closeBracket - bracketPos - 1 );
447
448 if( range.find( ':' ) != std::string::npos || range.find( ".." ) != std::string::npos )
449 return true;
450 }
451 }
452
453 // Pattern 2: NAME<n:m> or NAME<n..m>
454 size_t anglePos = aName.find( '<' );
455
456 if( anglePos != std::string::npos )
457 {
458 size_t closeAngle = aName.find( '>', anglePos );
459
460 if( closeAngle != std::string::npos )
461 {
462 std::string range = aName.substr( anglePos + 1, closeAngle - anglePos - 1 );
463
464 if( range.find( ':' ) != std::string::npos || range.find( ".." ) != std::string::npos )
465 return true;
466 }
467 }
468
469 return false;
470}
471
472
474 const PART_PLACEMENT& aPlacement )
475{
476 if( !aSymbol || !m_schematic )
477 return;
478
479 // Set reference designator, stripping any gate suffix (e.g., "U17-A" → "U17")
480 if( !aPlacement.reference.empty() )
481 {
482 std::string ref = aPlacement.reference;
483 size_t sepPos = ref.rfind( '-' );
484
485 if( sepPos == std::string::npos )
486 sepPos = ref.rfind( '.' );
487
488 if( sepPos != std::string::npos
489 && sepPos + 1 < ref.size()
490 && std::isalpha( static_cast<unsigned char>( ref[sepPos + 1] ) ) )
491 {
492 ref = ref.substr( 0, sepPos );
493 }
494
495 aSymbol->SetRef( &m_schematic->CurrentSheet(), wxString::FromUTF8( ref ) );
496 }
497
498 // Value field is always the PARTTYPE name. Parametric values like VALUE1
499 // flow through CreateCustomFields as user-defined fields.
500 if( !aPlacement.part_type.empty() )
501 aSymbol->SetValueFieldText( wxString::FromUTF8( aPlacement.part_type ) );
502
503 // Look for PCB DECAL attribute to set footprint
504 for( const PART_ATTRIBUTE& attr : aPlacement.attributes )
505 {
506 if( attr.name == "PCB DECAL" || attr.name == "PCB_DECAL" || attr.name == "FOOTPRINT" )
507 {
508 if( !attr.value.empty() )
509 aSymbol->SetFootprintFieldText( wxString::FromUTF8( attr.value ) );
510
511 break;
512 }
513 }
514
515 // Apply visibility and position settings
516 ApplyFieldSettings( aSymbol, aPlacement );
517}
518
519
521 const PART_PLACEMENT& aPlacement )
522{
523 if( !aSymbol )
524 return;
525
527
528 for( const PART_ATTRIBUTE& attr : aPlacement.attributes )
529 {
530 SCH_FIELD* field = nullptr;
531 bool isRefOrValue = false;
532
533 if( mapper.IsReferenceField( attr.name ) )
534 {
535 field = aSymbol->GetField( FIELD_T::REFERENCE );
536 isRefOrValue = true;
537 }
538 else if( mapper.IsValueField( attr.name ) )
539 {
540 field = aSymbol->GetField( FIELD_T::VALUE );
541 isRefOrValue = true;
542 }
543 else if( mapper.IsFootprintField( attr.name ) )
544 {
545 field = aSymbol->GetField( FIELD_T::FOOTPRINT );
546 }
547
548 if( field )
549 {
550 bool isRef = mapper.IsReferenceField( attr.name );
551
552 if( isRef )
553 field->SetVisible( true );
554 else
555 field->SetVisible( isRefOrValue ? attr.visible : false );
556
557 // PADS field positions are in CAEDECAL coordinates (pre-mirror).
558 // KiCad applies the symbol transform to field positions, so
559 // pre-compensate X for mirrored-Y symbols.
560 int fx = toKiCadUnits( attr.position.x );
561
562 if( aPlacement.mirror_flags & 1 )
563 fx = -fx;
564
565 VECTOR2I fieldPos( fx, -toKiCadUnits( attr.position.y ) );
566 field->SetPosition( aSymbol->GetPosition() + fieldPos );
567
568 if( attr.rotation != 0.0 )
569 field->SetTextAngleDegrees( attr.rotation );
570
571 if( attr.height > 0 )
572 {
573 int scaledH = KiROUND( schIUScale.MilsToIU( attr.height )
574 * ADVANCED_CFG::GetCfg().m_PadsSchTextHeightScale );
575 int scaledW = KiROUND( schIUScale.MilsToIU( attr.height )
576 * ADVANCED_CFG::GetCfg().m_PadsSchTextWidthScale );
577 field->SetTextSize( VECTOR2I( scaledW, scaledH ) );
578 }
579 else
580 {
581 int fieldTextSize = schIUScale.MilsToIU( 50 );
582 field->SetTextSize( VECTOR2I( fieldTextSize, fieldTextSize ) );
583 }
584
585 if( attr.width > 0 )
586 field->SetTextThickness( schIUScale.MilsToIU( attr.width ) );
587
588 // Map the PADS justification code so reference and value fields keep the
589 // alignment authored in PADS instead of forcing center.
592 PADS_COMMON::DecodeJustification( attr.justification, hJustify, vJustify );
593
594 // KiCad applies the symbol transform to field justification. Mirrored-Y
595 // symbols flip horizontal alignment, so pre-compensate to keep the rendered
596 // alignment matching PADS.
597 if( aPlacement.mirror_flags & 1 )
598 hJustify = GetFlippedAlignment( hJustify );
599
600 field->SetHorizJustify( hJustify );
601 field->SetVertJustify( vJustify );
602 }
603 }
604}
605
606
608 const PART_PLACEMENT& aPlacement )
609{
610 if( !aSymbol )
611 return 0;
612
613 int fieldsCreated = 0;
615
616 std::set<std::string> processedNames;
617
618 for( const PART_ATTRIBUTE& attr : aPlacement.attributes )
619 {
620 // Skip standard fields that are handled by ApplyPartAttributes
621 if( mapper.IsStandardField( attr.name ) )
622 continue;
623
624 // Skip empty attributes
625 if( attr.value.empty() )
626 continue;
627
628 processedNames.insert( attr.name );
629
630 // Get the mapped field name
631 std::string fieldName = mapper.GetKiCadFieldName( attr.name );
632
633 // Check if this field already exists on the symbol
634 SCH_FIELD* existingField = aSymbol->GetField( wxString::FromUTF8( fieldName ) );
635
636 if( existingField )
637 {
638 // Update existing field value and settings
639 existingField->SetText( wxString::FromUTF8( attr.value ) );
640 existingField->SetVisible( false );
641 }
642 else
643 {
644 // Create a new custom field using FIELD_T::USER for custom fields
645 SCH_FIELD newField( aSymbol, FIELD_T::USER, wxString::FromUTF8( fieldName ) );
646
647 newField.SetText( wxString::FromUTF8( attr.value ) );
648 newField.SetVisible( false );
649
650 // Apply position offset from attribute
651 VECTOR2I fieldPos( toKiCadUnits( attr.position.x ), -toKiCadUnits( attr.position.y ) );
652 newField.SetPosition( aSymbol->GetPosition() + fieldPos );
653
654 // Apply rotation if specified
655 if( attr.rotation != 0.0 )
656 {
657 newField.SetTextAngleDegrees( attr.rotation );
658 }
659
660 // Apply text size if specified
661 if( attr.size > 0.0 )
662 {
663 int textSize = toKiCadUnits( attr.size );
664 newField.SetTextSize( VECTOR2I( textSize, textSize ) );
665 }
666
667 aSymbol->GetFields().push_back( newField );
668 fieldsCreated++;
669 }
670 }
671
672 // Create fields from attr_overrides that weren't in the attributes vector
673 for( const auto& [name, value] : aPlacement.attr_overrides )
674 {
675 if( value.empty() || processedNames.count( name ) || mapper.IsStandardField( name ) )
676 continue;
677
678 std::string fieldName = mapper.GetKiCadFieldName( name );
679 SCH_FIELD* existingField = aSymbol->GetField( wxString::FromUTF8( fieldName ) );
680
681 if( existingField )
682 {
683 existingField->SetText( wxString::FromUTF8( value ) );
684 existingField->SetVisible( false );
685 }
686 else
687 {
688 SCH_FIELD newField( aSymbol, FIELD_T::USER, wxString::FromUTF8( fieldName ) );
689 newField.SetText( wxString::FromUTF8( value ) );
690 newField.SetVisible( false );
691 newField.SetPosition( aSymbol->GetPosition() );
692
693 aSymbol->GetFields().push_back( newField );
694 fieldsCreated++;
695 }
696 }
697
698 return fieldsCreated;
699}
700
701
703{
704 if( !aScreen )
705 return;
706
707 // Look up the first non-empty value from a list of candidate field names
708 auto findField =
709 [this]( const std::initializer_list<const char*>& aCandidates ) -> std::string
710 {
711 for( const char* name : aCandidates )
712 {
713 auto it = m_params.fields.find( name );
714
715 if( it != m_params.fields.end() && !it->second.empty() )
716 return it->second;
717 }
718
719 return {};
720 };
721
722 TITLE_BLOCK tb;
723
724 std::string title = findField( { "Title", "TITLE1" } );
725
726 if( title.empty() )
727 title = m_params.job_name;
728
729 if( !title.empty() )
730 tb.SetTitle( wxString::FromUTF8( title ) );
731
732 std::string date = findField( { "DATE", "Release Date", "Drawn Date" } );
733
734 if( !date.empty() )
735 tb.SetDate( wxString::FromUTF8( date ) );
736
737 std::string revision = findField( { "Revision", "VER" } );
738
739 if( !revision.empty() )
740 tb.SetRevision( wxString::FromUTF8( revision ) );
741
742 std::string company = findField( { "Company Name" } );
743
744 if( !company.empty() )
745 tb.SetCompany( wxString::FromUTF8( company ) );
746
747 std::string drawingNumber = findField( { "DN", "Drawing Number" } );
748
749 if( !drawingNumber.empty() )
750 tb.SetComment( 0, wxString::FromUTF8( drawingNumber ) );
751
752 std::string designer = findField( { "DESIGNER" } );
753
754 if( !designer.empty() )
755 tb.SetComment( 1, wxString::FromUTF8( designer ) );
756
757 std::string drawnBy = findField( { "DRAWNBY", "Drawn By" } );
758
759 if( !drawnBy.empty() )
760 tb.SetComment( 2, wxString::FromUTF8( drawnBy ) );
761
762 std::string builtFor = findField( { "BUILTFOR" } );
763
764 if( !builtFor.empty() )
765 tb.SetComment( 3, wxString::FromUTF8( builtFor ) );
766
767 aScreen->SetTitleBlock( tb );
768}
769
770
772 SCH_SHEET* aParentSheet,
773 const wxString& aBaseFilename )
774{
775 if( !aParentSheet || !m_schematic )
776 return nullptr;
777
778 VECTOR2I pos = CalculateSheetPosition( aSheetNumber - 1, aTotalSheets );
780
781 SCH_SHEET* sheet = new SCH_SHEET( aParentSheet, pos, size );
782
783 // Create a screen for this sheet
784 SCH_SCREEN* screen = new SCH_SCREEN( m_schematic );
785 sheet->SetScreen( screen );
786
787 // Generate sheet filename based on base filename and sheet number
788 wxFileName fn( aBaseFilename );
789 wxString sheetFilename = wxString::Format( wxT( "%s_sheet%d.%s" ),
790 fn.GetName(),
791 aSheetNumber,
793
794 // Set the sheet filename field
795 sheet->GetField( FIELD_T::SHEET_FILENAME )->SetText( sheetFilename );
796
797 // Set sheet name
798 wxString sheetName = wxString::Format( wxT( "Sheet %d" ), aSheetNumber );
799 sheet->GetField( FIELD_T::SHEET_NAME )->SetText( sheetName );
800
801 // Set full path for the screen if project is available
802 if( m_schematic->IsValid() )
803 {
804 wxFileName screenFn( m_schematic->Project().GetProjectPath(), sheetFilename );
805 screen->SetFileName( screenFn.GetFullPath() );
806 }
807 else
808 {
809 screen->SetFileName( sheetFilename );
810 }
811
812 sheet->SetFlags( IS_NEW );
813
814 // Add the sheet to the parent's screen
815 SCH_SCREEN* parentScreen = aParentSheet->GetScreen();
816
817 if( parentScreen )
818 {
819 parentScreen->Append( sheet );
820 }
821
822 return sheet;
823}
824
825
827{
828 // Default sheet symbol size in mils (approximately 2" x 1.5")
829 return VECTOR2I( schIUScale.MilsToIU( 2000 ), schIUScale.MilsToIU( 1500 ) );
830}
831
832
833VECTOR2I PADS_SCH_SCHEMATIC_BUILDER::CalculateSheetPosition( int aSheetIndex, int aTotalSheets ) const
834{
835 // Arrange sheet symbols in a grid on the root sheet
836 // Start position offset from origin
837 const int startX = schIUScale.MilsToIU( 500 );
838 const int startY = schIUScale.MilsToIU( 500 );
839
840 // Spacing between sheet symbols
841 const int spacingX = schIUScale.MilsToIU( 2500 );
842 const int spacingY = schIUScale.MilsToIU( 2000 );
843
844 // Calculate grid columns based on total sheets (aim for roughly square layout)
845 int columns = static_cast<int>( std::ceil( std::sqrt( static_cast<double>( aTotalSheets ) ) ) );
846
847 if( columns < 1 )
848 columns = 1;
849
850 int row = aSheetIndex / columns;
851 int col = aSheetIndex % columns;
852
853 return VECTOR2I( startX + col * spacingX, startY + row * spacingY );
854}
855
856
858 const std::string& aSignalName,
859 int aPinIndex )
860{
861 if( !aSheet )
862 return nullptr;
863
864 wxString name = wxString::FromUTF8( aSignalName );
865
866 // Position pins along the left edge of the sheet
867 VECTOR2I sheetPos = aSheet->GetPosition();
868
869 int pinSpacing = schIUScale.MilsToIU( 200 );
870 int yOffset = schIUScale.MilsToIU( 100 ) + aPinIndex * pinSpacing;
871
872 VECTOR2I pinPos( sheetPos.x, sheetPos.y + yOffset );
873
874 SCH_SHEET_PIN* pin = new SCH_SHEET_PIN( aSheet, pinPos, name );
875 pin->SetSide( SHEET_SIDE::LEFT );
877
878 aSheet->AddPin( pin );
879
880 return pin;
881}
882
883
885 const VECTOR2I& aPosition,
886 SCH_SCREEN* aScreen )
887{
888 if( !aScreen )
889 return nullptr;
890
891 wxString name = wxString::FromUTF8( aSignalName );
892
893 SCH_HIERLABEL* label = new SCH_HIERLABEL( aPosition, name );
895 label->SetFlags( IS_NEW );
896
897 aScreen->Append( label );
898
899 return label;
900}
901
902
903
904bool PADS_SCH_SCHEMATIC_BUILDER::IsGlobalSignal( const std::string& aSignalName,
905 const std::set<int>& aSheetNumbers )
906{
907 if( aSignalName.empty() )
908 return false;
909
910 // Signals appearing on multiple sheets should be global
911 if( aSheetNumbers.size() > 1 )
912 return true;
913
914 // Common power net names are always global
915 std::string upperName = aSignalName;
916 std::transform( upperName.begin(), upperName.end(), upperName.begin(), ::toupper );
917
918 static const std::set<std::string> globalPatterns = {
919 "VCC", "VDD", "VEE", "VSS", "GND", "AGND", "DGND", "PGND",
920 "V+", "V-", "VBAT", "VBUS", "VIN", "VOUT",
921 "+5V", "+3V3", "+3.3V", "+12V", "-12V", "+24V",
922 "0V", "EARTH", "CHASSIS"
923 };
924
925 if( globalPatterns.count( upperName ) > 0 )
926 return true;
927
928 // Check for voltage patterns like +1V8, +2V5, etc.
929 if( ( upperName[0] == '+' || upperName[0] == '-' ) && upperName.length() >= 3 )
930 {
931 bool hasDigit = false;
932 bool hasV = false;
933
934 for( char c : upperName.substr( 1 ) )
935 {
936 if( std::isdigit( c ) )
937 hasDigit = true;
938
939 if( c == 'V' )
940 hasV = true;
941 }
942
943 if( hasDigit && hasV )
944 return true;
945 }
946
947 return false;
948}
949
950} // namespace PADS_SCH
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
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:158
virtual void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:495
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:373
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:342
virtual void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition eda_text.cpp:245
void SetTextAngleDegrees(double aOrientation)
Definition eda_text.h:181
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:365
Maps PADS attribute names to KiCad field names and identifies which attributes are standard KiCad fie...
bool IsReferenceField(const std::string &aPadsAttr) const
std::string GetKiCadFieldName(const std::string &aPadsAttr) const
Get the KiCad field name for a PADS attribute, or the original name unchanged when no mapping exists.
bool IsFootprintField(const std::string &aPadsAttr) const
bool IsStandardField(const std::string &aPadsAttr) const
Check if a PADS attribute maps to a standard KiCad field (Reference, Value, or Footprint).
bool IsValueField(const std::string &aPadsAttr) const
SCH_LINE * CreateWire(const WIRE_SEGMENT &aWire)
std::vector< VECTOR2I > findJunctionPoints(const std::vector< SCH_SIGNAL > &aSignals)
Find 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 a sheet pin that connects to a hierarchical label in the sub-schematic.
int CreateJunctions(const std::vector< SCH_SIGNAL > &aSignals, SCH_SCREEN *aScreen)
void ApplyPartAttributes(SCH_SYMBOL *aSymbol, const PART_PLACEMENT &aPlacement)
Set reference, value, footprint and other fields on a symbol from a part placement.
PADS_SCH_SCHEMATIC_BUILDER(const PARAMETERS &aParams, SCHEMATIC *aSchematic)
void CreateTitleBlock(SCH_SCREEN *aScreen)
Set the title block from PADS fields, checking custom names too because PADS designs often leave the ...
VECTOR2I CalculateSheetPosition(int aSheetIndex, int aTotalSheets) const
Position a sheet symbol in a roughly square grid on the parent.
static SPIN_STYLE computeLabelOrientation(const VECTOR2I &aLabelPos, const VECTOR2I &aAdjacentPos)
Orient a label opposite to the wire direction at its position so it clears the wire.
int CreateCustomFields(SCH_SYMBOL *aSymbol, const PART_PLACEMENT &aPlacement)
Create KiCad user fields for PADS attributes that have no standard-field mapping.
VECTOR2I chooseLabelPosition(const SCH_SIGNAL &aSignal)
SCH_LINE * CreateBusWire(const WIRE_SEGMENT &aWire)
SCH_HIERLABEL * CreateHierLabel(const std::string &aSignalName, const VECTOR2I &aPosition, SCH_SCREEN *aScreen)
Create a hierarchical label that connects to a sheet pin on the parent.
void ApplyFieldSettings(SCH_SYMBOL *aSymbol, const PART_PLACEMENT &aPlacement)
int CreateWires(const std::vector< SCH_SIGNAL > &aSignals, SCH_SCREEN *aScreen)
SCH_GLOBALLABEL * CreateNetLabel(const SCH_SIGNAL &aSignal, const VECTOR2I &aPosition, SPIN_STYLE aOrientation=SPIN_STYLE::RIGHT)
Create a net label.
static bool IsGlobalSignal(const std::string &aSignalName, const std::set< int > &aSheetNumbers)
A signal is global when it spans multiple sheets or is a common power net.
int CreateBusWires(const std::vector< SCH_SIGNAL > &aSignals, SCH_SCREEN *aScreen)
wxString convertNetName(const std::string &aName) const
Convert a PADS net name to a KiCad label, mapping a "/" prefix to a "~{}" overbar.
SCH_SHEET * CreateHierarchicalSheet(int aSheetNumber, int aTotalSheets, SCH_SHEET *aParentSheet, const wxString &aBaseFilename)
Create a sub-sheet positioned on its parent and backed by its own screen.
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)
Holds all the data relating to one schematic.
Definition schematic.h:148
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:39
void SetEndPoint(const VECTOR2I &aPosition)
Definition sch_line.h:146
void SetTitleBlock(const TITLE_BLOCK &aTitleBlock)
Definition sch_screen.h:167
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:48
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:145
VECTOR2I GetPosition() const override
Definition sch_sheet.h:504
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Schematic symbol object.
Definition sch_symbol.h:75
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:934
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: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
#define IS_NEW
New item, just created.
static const std::string KiCadSchematicFileExtension
@ LAYER_WIRE
Definition layer_ids.h:474
@ LAYER_BUS
Definition layer_ids.h:475
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 notation.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
@ 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.
std::map< std::string, std::string > attr_overrides
std::vector< PART_ATTRIBUTE > attributes
std::vector< WIRE_SEGMENT > wires
Wire segment connecting two endpoints through coordinate vertices.
std::vector< POINT > 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".
static const HTTP_LIB_PART::field_type * findField(const HTTP_LIB_PART &aPart, const std::string &aName)
Issue #23023.
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.