kicad-sch-api 0.3.5__py3-none-any.whl → 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kicad_sch_api/__init__.py +2 -2
- kicad_sch_api/cli/__init__.py +45 -0
- kicad_sch_api/cli/base.py +302 -0
- kicad_sch_api/cli/bom.py +164 -0
- kicad_sch_api/cli/erc.py +229 -0
- kicad_sch_api/cli/export_docs.py +289 -0
- kicad_sch_api/cli/netlist.py +94 -0
- kicad_sch_api/cli/types.py +43 -0
- kicad_sch_api/collections/__init__.py +2 -2
- kicad_sch_api/collections/base.py +5 -7
- kicad_sch_api/collections/components.py +24 -12
- kicad_sch_api/collections/junctions.py +31 -43
- kicad_sch_api/collections/labels.py +19 -27
- kicad_sch_api/collections/wires.py +17 -18
- kicad_sch_api/core/collections/__init__.py +5 -0
- kicad_sch_api/core/collections/base.py +248 -0
- kicad_sch_api/core/component_bounds.py +5 -0
- kicad_sch_api/core/components.py +67 -45
- kicad_sch_api/core/config.py +85 -3
- kicad_sch_api/core/factories/__init__.py +5 -0
- kicad_sch_api/core/factories/element_factory.py +276 -0
- kicad_sch_api/core/formatter.py +3 -1
- kicad_sch_api/core/junctions.py +26 -75
- kicad_sch_api/core/labels.py +29 -53
- kicad_sch_api/core/managers/__init__.py +26 -0
- kicad_sch_api/core/managers/file_io.py +244 -0
- kicad_sch_api/core/managers/format_sync.py +501 -0
- kicad_sch_api/core/managers/graphics.py +579 -0
- kicad_sch_api/core/managers/metadata.py +269 -0
- kicad_sch_api/core/managers/sheet.py +454 -0
- kicad_sch_api/core/managers/text_elements.py +536 -0
- kicad_sch_api/core/managers/validation.py +475 -0
- kicad_sch_api/core/managers/wire.py +352 -0
- kicad_sch_api/core/nets.py +38 -43
- kicad_sch_api/core/no_connects.py +33 -55
- kicad_sch_api/core/parser.py +75 -1731
- kicad_sch_api/core/schematic.py +951 -1192
- kicad_sch_api/core/texts.py +28 -55
- kicad_sch_api/core/types.py +60 -22
- kicad_sch_api/core/wires.py +27 -75
- kicad_sch_api/geometry/font_metrics.py +3 -1
- kicad_sch_api/geometry/symbol_bbox.py +40 -21
- kicad_sch_api/interfaces/__init__.py +1 -1
- kicad_sch_api/interfaces/parser.py +1 -1
- kicad_sch_api/interfaces/repository.py +1 -1
- kicad_sch_api/interfaces/resolver.py +1 -1
- kicad_sch_api/parsers/__init__.py +2 -2
- kicad_sch_api/parsers/base.py +7 -10
- kicad_sch_api/parsers/elements/__init__.py +22 -0
- kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
- kicad_sch_api/parsers/elements/label_parser.py +194 -0
- kicad_sch_api/parsers/elements/library_parser.py +165 -0
- kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
- kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
- kicad_sch_api/parsers/elements/symbol_parser.py +313 -0
- kicad_sch_api/parsers/elements/text_parser.py +250 -0
- kicad_sch_api/parsers/elements/wire_parser.py +242 -0
- kicad_sch_api/parsers/registry.py +4 -2
- kicad_sch_api/parsers/utils.py +80 -0
- kicad_sch_api/symbols/__init__.py +1 -1
- kicad_sch_api/symbols/cache.py +9 -12
- kicad_sch_api/symbols/resolver.py +20 -26
- kicad_sch_api/symbols/validators.py +188 -137
- kicad_sch_api/validation/__init__.py +25 -0
- kicad_sch_api/validation/erc.py +171 -0
- kicad_sch_api/validation/erc_models.py +203 -0
- kicad_sch_api/validation/pin_matrix.py +243 -0
- kicad_sch_api/validation/validators.py +391 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/METADATA +17 -9
- kicad_sch_api-0.4.1.dist-info/RECORD +87 -0
- kicad_sch_api/core/manhattan_routing.py +0 -430
- kicad_sch_api/core/simple_manhattan.py +0 -228
- kicad_sch_api/core/wire_routing.py +0 -380
- kicad_sch_api/parsers/label_parser.py +0 -254
- kicad_sch_api/parsers/symbol_parser.py +0 -227
- kicad_sch_api/parsers/wire_parser.py +0 -99
- kicad_sch_api-0.3.5.dist-info/RECORD +0 -58
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/top_level.txt +0 -0
|
@@ -120,7 +120,9 @@ class SymbolBoundingBoxCalculator:
|
|
|
120
120
|
if min_x == float("inf") or max_x == float("-inf"):
|
|
121
121
|
raise ValueError(f"No valid geometry found in symbol data")
|
|
122
122
|
|
|
123
|
-
logger.debug(
|
|
123
|
+
logger.debug(
|
|
124
|
+
f"After geometry processing: ({min_x:.2f}, {min_y:.2f}) to ({max_x:.2f}, {max_y:.2f})"
|
|
125
|
+
)
|
|
124
126
|
logger.debug(f" Width: {max_x - min_x:.2f}, Height: {max_y - min_y:.2f}")
|
|
125
127
|
|
|
126
128
|
# Add small margin for text that might extend beyond shapes
|
|
@@ -252,7 +254,9 @@ class SymbolBoundingBoxCalculator:
|
|
|
252
254
|
if min_x == float("inf") or max_x == float("-inf"):
|
|
253
255
|
raise ValueError(f"No valid geometry found in symbol data")
|
|
254
256
|
|
|
255
|
-
logger.debug(
|
|
257
|
+
logger.debug(
|
|
258
|
+
f"After geometry processing: ({min_x:.2f}, {min_y:.2f}) to ({max_x:.2f}, {max_y:.2f})"
|
|
259
|
+
)
|
|
256
260
|
logger.debug(f" Width: {max_x - min_x:.2f}, Height: {max_y - min_y:.2f}")
|
|
257
261
|
|
|
258
262
|
# Add small margin for visual spacing
|
|
@@ -265,12 +269,16 @@ class SymbolBoundingBoxCalculator:
|
|
|
265
269
|
# Add minimal space for component properties (Reference above, Value below)
|
|
266
270
|
# Use adaptive spacing based on component height for better visual hierarchy
|
|
267
271
|
component_height = max_y - min_y
|
|
268
|
-
property_spacing = max(
|
|
272
|
+
property_spacing = max(
|
|
273
|
+
3.0, component_height * 0.15
|
|
274
|
+
) # Adaptive: minimum 3mm or 15% of height
|
|
269
275
|
property_height = 1.27 # Reduced from 2.54mm
|
|
270
276
|
min_y -= property_spacing + property_height # Reference above
|
|
271
277
|
max_y += property_spacing + property_height # Value below
|
|
272
278
|
|
|
273
|
-
logger.debug(
|
|
279
|
+
logger.debug(
|
|
280
|
+
f"FINAL PLACEMENT BBOX: ({min_x:.2f}, {min_y:.2f}) to ({max_x:.2f}, {max_y:.2f})"
|
|
281
|
+
)
|
|
274
282
|
logger.debug(f" Width: {max_x - min_x:.2f}, Height: {max_y - min_y:.2f}")
|
|
275
283
|
logger.debug("=" * 50)
|
|
276
284
|
|
|
@@ -359,7 +367,10 @@ class SymbolBoundingBoxCalculator:
|
|
|
359
367
|
|
|
360
368
|
@classmethod
|
|
361
369
|
def get_symbol_dimensions(
|
|
362
|
-
cls,
|
|
370
|
+
cls,
|
|
371
|
+
symbol_data: Dict[str, Any],
|
|
372
|
+
include_properties: bool = True,
|
|
373
|
+
pin_net_map: Optional[Dict[str, str]] = None,
|
|
363
374
|
) -> Tuple[float, float]:
|
|
364
375
|
"""
|
|
365
376
|
Get the width and height of a symbol.
|
|
@@ -488,24 +499,28 @@ class SymbolBoundingBoxCalculator:
|
|
|
488
499
|
# If no net name match, use minimal fallback to avoid oversized bounding boxes
|
|
489
500
|
if pin_net_map and pin_number in pin_net_map:
|
|
490
501
|
label_text = pin_net_map[pin_number]
|
|
491
|
-
logger.debug(
|
|
502
|
+
logger.debug(
|
|
503
|
+
f" PIN {pin_number}: ✅ USING NET '{label_text}' (len={len(label_text)}), at=({x:.2f}, {y:.2f}), angle={angle}"
|
|
504
|
+
)
|
|
492
505
|
else:
|
|
493
506
|
# No net match - use minimal size (3 chars) instead of potentially long pin name
|
|
494
507
|
label_text = "XXX" # 3-character placeholder for unmatched pins
|
|
495
|
-
logger.debug(
|
|
508
|
+
logger.debug(
|
|
509
|
+
f" PIN {pin_number}: ⚠️ NO MATCH, using minimal fallback (pin name was '{pin_name}'), at=({x:.2f}, {y:.2f})"
|
|
510
|
+
)
|
|
496
511
|
|
|
497
512
|
if label_text and label_text != "~": # ~ means no name
|
|
498
513
|
# Calculate text dimensions
|
|
499
514
|
# For horizontal text: width = char_count * char_width
|
|
500
515
|
name_width = (
|
|
501
|
-
len(label_text)
|
|
502
|
-
* cls.DEFAULT_TEXT_HEIGHT
|
|
503
|
-
* cls.DEFAULT_PIN_TEXT_WIDTH_RATIO
|
|
516
|
+
len(label_text) * cls.DEFAULT_TEXT_HEIGHT * cls.DEFAULT_PIN_TEXT_WIDTH_RATIO
|
|
504
517
|
)
|
|
505
518
|
# For vertical text: height = char_count * char_height (characters stack vertically)
|
|
506
519
|
name_height = len(label_text) * cls.DEFAULT_TEXT_HEIGHT
|
|
507
520
|
|
|
508
|
-
logger.debug(
|
|
521
|
+
logger.debug(
|
|
522
|
+
f" label_width={name_width:.2f}, label_height={name_height:.2f} (len={len(label_text)})"
|
|
523
|
+
)
|
|
509
524
|
|
|
510
525
|
# Adjust bounds based on pin orientation
|
|
511
526
|
# Labels are placed at PIN ENDPOINT with offset, extending AWAY from the component
|
|
@@ -515,32 +530,36 @@ class SymbolBoundingBoxCalculator:
|
|
|
515
530
|
|
|
516
531
|
if angle == 0: # Pin points right - label extends LEFT from endpoint
|
|
517
532
|
label_x = end_x - offset - name_width
|
|
518
|
-
logger.debug(
|
|
533
|
+
logger.debug(
|
|
534
|
+
f" Angle 0 (Right pin): min_x {min_x:.2f} -> {label_x:.2f} (offset={offset:.3f})"
|
|
535
|
+
)
|
|
519
536
|
min_x = min(min_x, label_x)
|
|
520
537
|
elif angle == 180: # Pin points left - label extends RIGHT from endpoint
|
|
521
538
|
label_x = end_x + offset + name_width
|
|
522
|
-
logger.debug(
|
|
539
|
+
logger.debug(
|
|
540
|
+
f" Angle 180 (Left pin): max_x {max_x:.2f} -> {label_x:.2f} (offset={offset:.3f})"
|
|
541
|
+
)
|
|
523
542
|
max_x = max(max_x, label_x)
|
|
524
543
|
elif angle == 90: # Pin points up - label extends DOWN from endpoint
|
|
525
544
|
label_y = end_y - offset - name_height
|
|
526
|
-
logger.debug(
|
|
545
|
+
logger.debug(
|
|
546
|
+
f" Angle 90 (Up pin): min_y {min_y:.2f} -> {label_y:.2f} (offset={offset:.3f})"
|
|
547
|
+
)
|
|
527
548
|
min_y = min(min_y, label_y)
|
|
528
549
|
elif angle == 270: # Pin points down - label extends UP from endpoint
|
|
529
550
|
label_y = end_y + offset + name_height
|
|
530
|
-
logger.debug(
|
|
551
|
+
logger.debug(
|
|
552
|
+
f" Angle 270 (Down pin): max_y {max_y:.2f} -> {label_y:.2f} (offset={offset:.3f})"
|
|
553
|
+
)
|
|
531
554
|
max_y = max(max_y, label_y)
|
|
532
555
|
|
|
533
556
|
# Pin numbers are typically placed near the component body
|
|
534
557
|
if pin_number:
|
|
535
558
|
num_width = (
|
|
536
|
-
len(pin_number)
|
|
537
|
-
* cls.DEFAULT_PIN_NUMBER_SIZE
|
|
538
|
-
* cls.DEFAULT_PIN_TEXT_WIDTH_RATIO
|
|
559
|
+
len(pin_number) * cls.DEFAULT_PIN_NUMBER_SIZE * cls.DEFAULT_PIN_TEXT_WIDTH_RATIO
|
|
539
560
|
)
|
|
540
561
|
# Add some space for the pin number
|
|
541
|
-
margin =
|
|
542
|
-
cls.DEFAULT_PIN_NUMBER_SIZE * 1.5
|
|
543
|
-
) # Increase margin for better spacing
|
|
562
|
+
margin = cls.DEFAULT_PIN_NUMBER_SIZE * 1.5 # Increase margin for better spacing
|
|
544
563
|
min_x -= margin
|
|
545
564
|
min_y -= margin
|
|
546
565
|
max_x += margin
|
|
@@ -5,10 +5,10 @@ This package provides specialized parsers for different types of KiCAD
|
|
|
5
5
|
S-expression elements, organized by responsibility and testable in isolation.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .registry import ElementParserRegistry
|
|
9
8
|
from .base import BaseElementParser
|
|
9
|
+
from .registry import ElementParserRegistry
|
|
10
10
|
|
|
11
11
|
__all__ = [
|
|
12
12
|
"ElementParserRegistry",
|
|
13
13
|
"BaseElementParser",
|
|
14
|
-
]
|
|
14
|
+
]
|
kicad_sch_api/parsers/base.py
CHANGED
|
@@ -92,10 +92,7 @@ class BaseElementParser(IElementParser):
|
|
|
92
92
|
return None
|
|
93
93
|
|
|
94
94
|
try:
|
|
95
|
-
result = {
|
|
96
|
-
"x": float(element[1]),
|
|
97
|
-
"y": float(element[2])
|
|
98
|
-
}
|
|
95
|
+
result = {"x": float(element[1]), "y": float(element[2])}
|
|
99
96
|
|
|
100
97
|
# Optional angle parameter
|
|
101
98
|
if len(element) > 3:
|
|
@@ -105,7 +102,9 @@ class BaseElementParser(IElementParser):
|
|
|
105
102
|
except (ValueError, IndexError):
|
|
106
103
|
return None
|
|
107
104
|
|
|
108
|
-
def _extract_property_list(
|
|
105
|
+
def _extract_property_list(
|
|
106
|
+
self, elements: List[Any], property_name: str
|
|
107
|
+
) -> List[Dict[str, Any]]:
|
|
109
108
|
"""
|
|
110
109
|
Extract all instances of a property from a list of elements.
|
|
111
110
|
|
|
@@ -118,9 +117,7 @@ class BaseElementParser(IElementParser):
|
|
|
118
117
|
"""
|
|
119
118
|
properties = []
|
|
120
119
|
for element in elements:
|
|
121
|
-
if
|
|
122
|
-
len(element) > 0 and
|
|
123
|
-
element[0] == property_name):
|
|
120
|
+
if isinstance(element, list) and len(element) > 0 and element[0] == property_name:
|
|
124
121
|
prop = self._parse_property_element(element)
|
|
125
122
|
if prop:
|
|
126
123
|
properties.append(prop)
|
|
@@ -144,5 +141,5 @@ class BaseElementParser(IElementParser):
|
|
|
144
141
|
return {
|
|
145
142
|
"type": element[0],
|
|
146
143
|
"value": element[1] if len(element) > 1 else None,
|
|
147
|
-
"raw": element
|
|
148
|
-
}
|
|
144
|
+
"raw": element,
|
|
145
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Element-specific parsers for KiCAD schematic elements.
|
|
3
|
+
|
|
4
|
+
This module contains specialized parsers for different schematic element types,
|
|
5
|
+
extracted from the monolithic parser.py for better maintainability.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .graphics_parser import GraphicsParser
|
|
9
|
+
|
|
10
|
+
# Additional element parsers will be imported here as they are created
|
|
11
|
+
# from .wire_parser import WireParser
|
|
12
|
+
# from .label_parser import LabelParser
|
|
13
|
+
# from .text_parser import TextParser
|
|
14
|
+
# from .sheet_parser import SheetParser
|
|
15
|
+
# from .library_parser import LibraryParser
|
|
16
|
+
# from .symbol_parser import SymbolParser
|
|
17
|
+
# from .metadata_parser import MetadataParser
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"GraphicsParser",
|
|
21
|
+
# Will be populated as parsers are added
|
|
22
|
+
]
|