kicad-sch-api 0.3.2__tar.gz → 0.3.4__tar.gz
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.
Potentially problematic release.
This version of kicad-sch-api might be problematic. Click here for more details.
- {kicad_sch_api-0.3.2/kicad_sch_api.egg-info → kicad_sch_api-0.3.4}/PKG-INFO +1 -1
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/__init__.py +2 -2
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/formatter.py +31 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/parser.py +72 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/schematic.py +49 -7
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/types.py +14 -0
- kicad_sch_api-0.3.4/kicad_sch_api/geometry/__init__.py +26 -0
- kicad_sch_api-0.3.4/kicad_sch_api/geometry/font_metrics.py +20 -0
- kicad_sch_api-0.3.4/kicad_sch_api/geometry/symbol_bbox.py +595 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4/kicad_sch_api.egg-info}/PKG-INFO +1 -1
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api.egg-info/SOURCES.txt +5 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/pyproject.toml +1 -1
- kicad_sch_api-0.3.4/tests/test_geometry.py +566 -0
- kicad_sch_api-0.3.4/tests/test_image_support.py +119 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/dead-code-analysis.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/publish-pypi.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/review-implementation.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/run-tests.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/update-and-commit.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/update-memory-bank.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/test/run-reference-tests.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/CHANGELOG.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/LICENSE +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/MANIFEST.in +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/README.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/advanced_usage.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/basic_usage.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/mcp_basic_example.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/mcp_integration.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/pin_to_pin_wiring_demo.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/simple_circuit_with_pin_wiring.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/simple_two_resistor_routing.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/cli.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/__init__.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/component_bounds.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/components.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/config.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/geometry.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/ic_manager.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/junctions.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/manhattan_routing.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/pin_utils.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/simple_manhattan.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/wire_routing.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/wires.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/discovery/__init__.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/discovery/search_index.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/library/__init__.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/library/cache.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/py.typed +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/utils/__init__.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/utils/validation.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api.egg-info/dependency_links.txt +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api.egg-info/entry_points.txt +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api.egg-info/requires.txt +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api.egg-info/top_level.txt +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/setup.cfg +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/README.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/subcircuit1.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_bounding_box_rectangles.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_component_removal.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_element_removal.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_grid_snapping.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_kicad_validation.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_manhattan_routing.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_parse_reference_rectangles.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_pin_positioning.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_pin_to_pin_wiring.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_rectangle.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_rectangle_roundtrip.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_removal_against_references.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_wire_operations.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kicad-sch-api
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Professional KiCAD schematic manipulation library with exact format preservation
|
|
5
5
|
Author-email: Circuit-Synth <shane@circuit-synth.com>
|
|
6
6
|
Maintainer-email: Circuit-Synth <shane@circuit-synth.com>
|
|
@@ -42,7 +42,7 @@ Advanced Usage:
|
|
|
42
42
|
print(f"Found {len(issues)} validation issues")
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
|
-
__version__ = "0.3.
|
|
45
|
+
__version__ = "0.3.3"
|
|
46
46
|
__author__ = "Circuit-Synth"
|
|
47
47
|
__email__ = "info@circuit-synth.com"
|
|
48
48
|
|
|
@@ -55,7 +55,7 @@ from .library.cache import SymbolLibraryCache, get_symbol_cache
|
|
|
55
55
|
from .utils.validation import ValidationError, ValidationIssue
|
|
56
56
|
|
|
57
57
|
# Version info
|
|
58
|
-
VERSION_INFO = (0, 3,
|
|
58
|
+
VERSION_INFO = (0, 3, 3)
|
|
59
59
|
|
|
60
60
|
# Public API
|
|
61
61
|
__all__ = [
|
|
@@ -146,6 +146,9 @@ class ExactFormatter:
|
|
|
146
146
|
self.rules["embedded_fonts"] = FormatRule(inline=True)
|
|
147
147
|
self.rules["page"] = FormatRule(inline=True, quote_indices={1})
|
|
148
148
|
|
|
149
|
+
# Image element
|
|
150
|
+
self.rules["image"] = FormatRule(inline=False, custom_handler=self._format_image)
|
|
151
|
+
|
|
149
152
|
def format(self, data: Any) -> str:
|
|
150
153
|
"""
|
|
151
154
|
Format S-expression data with exact KiCAD formatting.
|
|
@@ -511,6 +514,34 @@ class ExactFormatter:
|
|
|
511
514
|
result += f"\n{indent})"
|
|
512
515
|
return result
|
|
513
516
|
|
|
517
|
+
def _format_image(self, lst: List[Any], indent_level: int) -> str:
|
|
518
|
+
"""Format image elements with base64 data split across lines."""
|
|
519
|
+
indent = "\t" * indent_level
|
|
520
|
+
next_indent = "\t" * (indent_level + 1)
|
|
521
|
+
|
|
522
|
+
result = f"({lst[0]}"
|
|
523
|
+
|
|
524
|
+
# Process each element
|
|
525
|
+
for element in lst[1:]:
|
|
526
|
+
if isinstance(element, list):
|
|
527
|
+
tag = str(element[0]) if element else ""
|
|
528
|
+
if tag == "data":
|
|
529
|
+
# Special handling for data element
|
|
530
|
+
# First chunk on same line as (data, rest on subsequent lines
|
|
531
|
+
if len(element) > 1:
|
|
532
|
+
result += f'\n{next_indent}({element[0]} "{element[1]}"'
|
|
533
|
+
for chunk in element[2:]:
|
|
534
|
+
result += f'\n{next_indent}\t"{chunk}"'
|
|
535
|
+
result += f"\n{next_indent})"
|
|
536
|
+
else:
|
|
537
|
+
result += f"\n{next_indent}({element[0]})"
|
|
538
|
+
else:
|
|
539
|
+
# Regular element formatting
|
|
540
|
+
result += f"\n{next_indent}{self._format_element(element, indent_level + 1)}"
|
|
541
|
+
|
|
542
|
+
result += f"\n{indent})"
|
|
543
|
+
return result
|
|
544
|
+
|
|
514
545
|
|
|
515
546
|
class CompactFormatter(ExactFormatter):
|
|
516
547
|
"""Compact formatter for minimal output size."""
|
|
@@ -197,6 +197,7 @@ class SExpressionParser:
|
|
|
197
197
|
"circles": [],
|
|
198
198
|
"beziers": [],
|
|
199
199
|
"rectangles": [],
|
|
200
|
+
"images": [],
|
|
200
201
|
"nets": [],
|
|
201
202
|
"lib_symbols": {},
|
|
202
203
|
"sheet_instances": [],
|
|
@@ -282,6 +283,10 @@ class SExpressionParser:
|
|
|
282
283
|
rectangle = self._parse_rectangle(item)
|
|
283
284
|
if rectangle:
|
|
284
285
|
schematic_data["rectangles"].append(rectangle)
|
|
286
|
+
elif element_type == "image":
|
|
287
|
+
image = self._parse_image(item)
|
|
288
|
+
if image:
|
|
289
|
+
schematic_data["images"].append(image)
|
|
285
290
|
elif element_type == "lib_symbols":
|
|
286
291
|
schematic_data["lib_symbols"] = self._parse_lib_symbols(item)
|
|
287
292
|
elif element_type == "sheet_instances":
|
|
@@ -357,6 +362,10 @@ class SExpressionParser:
|
|
|
357
362
|
for graphic in schematic_data.get("graphics", []):
|
|
358
363
|
sexp_data.append(self._graphic_to_sexp(graphic))
|
|
359
364
|
|
|
365
|
+
# Images
|
|
366
|
+
for image in schematic_data.get("images", []):
|
|
367
|
+
sexp_data.append(self._image_to_sexp(image))
|
|
368
|
+
|
|
360
369
|
# Circles
|
|
361
370
|
for circle in schematic_data.get("circles", []):
|
|
362
371
|
sexp_data.append(self._circle_to_sexp(circle))
|
|
@@ -1108,6 +1117,37 @@ class SExpressionParser:
|
|
|
1108
1117
|
|
|
1109
1118
|
return rectangle if rectangle else None
|
|
1110
1119
|
|
|
1120
|
+
def _parse_image(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
1121
|
+
"""Parse an image element."""
|
|
1122
|
+
# Format: (image (at x y) (uuid "...") (data "base64..."))
|
|
1123
|
+
image = {
|
|
1124
|
+
"position": {"x": 0, "y": 0},
|
|
1125
|
+
"data": "",
|
|
1126
|
+
"scale": 1.0,
|
|
1127
|
+
"uuid": None
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
for elem in item[1:]:
|
|
1131
|
+
if not isinstance(elem, list):
|
|
1132
|
+
continue
|
|
1133
|
+
|
|
1134
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
1135
|
+
|
|
1136
|
+
if elem_type == "at" and len(elem) >= 3:
|
|
1137
|
+
image["position"] = {"x": float(elem[1]), "y": float(elem[2])}
|
|
1138
|
+
elif elem_type == "scale" and len(elem) >= 2:
|
|
1139
|
+
image["scale"] = float(elem[1])
|
|
1140
|
+
elif elem_type == "data" and len(elem) >= 2:
|
|
1141
|
+
# The data can be spread across multiple string elements
|
|
1142
|
+
data_parts = []
|
|
1143
|
+
for data_elem in elem[1:]:
|
|
1144
|
+
data_parts.append(str(data_elem).strip('"'))
|
|
1145
|
+
image["data"] = "".join(data_parts)
|
|
1146
|
+
elif elem_type == "uuid" and len(elem) >= 2:
|
|
1147
|
+
image["uuid"] = str(elem[1]).strip('"')
|
|
1148
|
+
|
|
1149
|
+
return image if image.get("uuid") and image.get("data") else None
|
|
1150
|
+
|
|
1111
1151
|
def _parse_lib_symbols(self, item: List[Any]) -> Dict[str, Any]:
|
|
1112
1152
|
"""Parse lib_symbols section."""
|
|
1113
1153
|
# Implementation for lib_symbols parsing
|
|
@@ -1933,6 +1973,38 @@ class SExpressionParser:
|
|
|
1933
1973
|
|
|
1934
1974
|
return sexp
|
|
1935
1975
|
|
|
1976
|
+
def _image_to_sexp(self, image_data: Dict[str, Any]) -> List[Any]:
|
|
1977
|
+
"""Convert image element to S-expression."""
|
|
1978
|
+
sexp = [sexpdata.Symbol("image")]
|
|
1979
|
+
|
|
1980
|
+
# Add position
|
|
1981
|
+
position = image_data.get("position", {"x": 0, "y": 0})
|
|
1982
|
+
pos_x, pos_y = position["x"], position["y"]
|
|
1983
|
+
sexp.append([sexpdata.Symbol("at"), pos_x, pos_y])
|
|
1984
|
+
|
|
1985
|
+
# Add UUID
|
|
1986
|
+
if "uuid" in image_data:
|
|
1987
|
+
sexp.append([sexpdata.Symbol("uuid"), image_data["uuid"]])
|
|
1988
|
+
|
|
1989
|
+
# Add scale if not default
|
|
1990
|
+
scale = image_data.get("scale", 1.0)
|
|
1991
|
+
if scale != 1.0:
|
|
1992
|
+
sexp.append([sexpdata.Symbol("scale"), scale])
|
|
1993
|
+
|
|
1994
|
+
# Add image data
|
|
1995
|
+
# KiCad splits base64 data into multiple lines for readability
|
|
1996
|
+
# Each line is roughly 76 characters (standard base64 line length)
|
|
1997
|
+
data = image_data.get("data", "")
|
|
1998
|
+
if data:
|
|
1999
|
+
data_sexp = [sexpdata.Symbol("data")]
|
|
2000
|
+
# Split the data into 76-character chunks
|
|
2001
|
+
chunk_size = 76
|
|
2002
|
+
for i in range(0, len(data), chunk_size):
|
|
2003
|
+
data_sexp.append(data[i:i+chunk_size])
|
|
2004
|
+
sexp.append(data_sexp)
|
|
2005
|
+
|
|
2006
|
+
return sexp
|
|
2007
|
+
|
|
1936
2008
|
def _lib_symbols_to_sexp(self, lib_symbols: Dict[str, Any]) -> List[Any]:
|
|
1937
2009
|
"""Convert lib_symbols to S-expression."""
|
|
1938
2010
|
sexp = [sexpdata.Symbol("lib_symbols")]
|
|
@@ -1138,6 +1138,55 @@ class Schematic:
|
|
|
1138
1138
|
logger.debug(f"Added text box: '{text}' at {position} size {size}")
|
|
1139
1139
|
return text_box.uuid
|
|
1140
1140
|
|
|
1141
|
+
def add_image(
|
|
1142
|
+
self,
|
|
1143
|
+
position: Union[Point, Tuple[float, float]],
|
|
1144
|
+
data: str,
|
|
1145
|
+
scale: float = 1.0,
|
|
1146
|
+
uuid: Optional[str] = None,
|
|
1147
|
+
) -> str:
|
|
1148
|
+
"""
|
|
1149
|
+
Add an image element.
|
|
1150
|
+
|
|
1151
|
+
Args:
|
|
1152
|
+
position: Image position
|
|
1153
|
+
data: Base64-encoded image data
|
|
1154
|
+
scale: Image scale factor (default 1.0)
|
|
1155
|
+
uuid: Optional UUID (auto-generated if None)
|
|
1156
|
+
|
|
1157
|
+
Returns:
|
|
1158
|
+
UUID of created image element
|
|
1159
|
+
"""
|
|
1160
|
+
if isinstance(position, tuple):
|
|
1161
|
+
position = Point(position[0], position[1])
|
|
1162
|
+
|
|
1163
|
+
from .types import Image
|
|
1164
|
+
|
|
1165
|
+
import uuid as uuid_module
|
|
1166
|
+
|
|
1167
|
+
image = Image(
|
|
1168
|
+
uuid=uuid if uuid else str(uuid_module.uuid4()),
|
|
1169
|
+
position=position,
|
|
1170
|
+
data=data,
|
|
1171
|
+
scale=scale,
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
if "images" not in self._data:
|
|
1175
|
+
self._data["images"] = []
|
|
1176
|
+
|
|
1177
|
+
self._data["images"].append(
|
|
1178
|
+
{
|
|
1179
|
+
"uuid": image.uuid,
|
|
1180
|
+
"position": {"x": image.position.x, "y": image.position.y},
|
|
1181
|
+
"data": image.data,
|
|
1182
|
+
"scale": image.scale,
|
|
1183
|
+
}
|
|
1184
|
+
)
|
|
1185
|
+
self._modified = True
|
|
1186
|
+
|
|
1187
|
+
logger.debug(f"Added image at {position} with {len(data)} bytes of data")
|
|
1188
|
+
return image.uuid
|
|
1189
|
+
|
|
1141
1190
|
def add_rectangle(
|
|
1142
1191
|
self,
|
|
1143
1192
|
start: Union[Point, Tuple[float, float]],
|
|
@@ -1592,9 +1641,7 @@ class Schematic:
|
|
|
1592
1641
|
break
|
|
1593
1642
|
|
|
1594
1643
|
# Fix string/symbol conversion issues in pin definitions
|
|
1595
|
-
print(f"🔧 DEBUG: Before fix - checking for pin definitions...")
|
|
1596
1644
|
self._fix_symbol_strings_recursively(modified_data)
|
|
1597
|
-
print(f"🔧 DEBUG: After fix - symbol strings fixed")
|
|
1598
1645
|
|
|
1599
1646
|
return modified_data
|
|
1600
1647
|
|
|
@@ -1607,15 +1654,10 @@ class Schematic:
|
|
|
1607
1654
|
if isinstance(item, list):
|
|
1608
1655
|
# Check for pin definitions that need fixing
|
|
1609
1656
|
if len(item) >= 3 and item[0] == sexpdata.Symbol("pin"):
|
|
1610
|
-
print(
|
|
1611
|
-
f"🔧 DEBUG: Found pin definition: {item[:3]} - types: {[type(x) for x in item[:3]]}"
|
|
1612
|
-
)
|
|
1613
1657
|
# Fix pin type and shape - ensure they are symbols not strings
|
|
1614
1658
|
if isinstance(item[1], str):
|
|
1615
|
-
print(f"🔧 DEBUG: Converting pin type '{item[1]}' to symbol")
|
|
1616
1659
|
item[1] = sexpdata.Symbol(item[1]) # pin type: "passive" -> passive
|
|
1617
1660
|
if len(item) >= 3 and isinstance(item[2], str):
|
|
1618
|
-
print(f"🔧 DEBUG: Converting pin shape '{item[2]}' to symbol")
|
|
1619
1661
|
item[2] = sexpdata.Symbol(item[2]) # pin shape: "line" -> line
|
|
1620
1662
|
|
|
1621
1663
|
# Recursively process nested lists
|
|
@@ -372,6 +372,20 @@ class SchematicRectangle:
|
|
|
372
372
|
)
|
|
373
373
|
|
|
374
374
|
|
|
375
|
+
@dataclass
|
|
376
|
+
class Image:
|
|
377
|
+
"""Image element in schematic."""
|
|
378
|
+
|
|
379
|
+
uuid: str
|
|
380
|
+
position: Point
|
|
381
|
+
data: str # Base64-encoded image data
|
|
382
|
+
scale: float = 1.0
|
|
383
|
+
|
|
384
|
+
def __post_init__(self):
|
|
385
|
+
if not self.uuid:
|
|
386
|
+
self.uuid = str(uuid4())
|
|
387
|
+
|
|
388
|
+
|
|
375
389
|
@dataclass
|
|
376
390
|
class Net:
|
|
377
391
|
"""Electrical net connecting components."""
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geometry module for KiCad schematic symbol bounding box calculations.
|
|
3
|
+
|
|
4
|
+
This module provides accurate bounding box calculations for KiCad symbols,
|
|
5
|
+
including font metrics and symbol geometry analysis.
|
|
6
|
+
|
|
7
|
+
Migrated from circuit-synth to kicad-sch-api for better architectural separation.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .font_metrics import (
|
|
11
|
+
DEFAULT_PIN_LENGTH,
|
|
12
|
+
DEFAULT_PIN_NAME_OFFSET,
|
|
13
|
+
DEFAULT_PIN_NUMBER_SIZE,
|
|
14
|
+
DEFAULT_PIN_TEXT_WIDTH_RATIO,
|
|
15
|
+
DEFAULT_TEXT_HEIGHT,
|
|
16
|
+
)
|
|
17
|
+
from .symbol_bbox import SymbolBoundingBoxCalculator
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"SymbolBoundingBoxCalculator",
|
|
21
|
+
"DEFAULT_TEXT_HEIGHT",
|
|
22
|
+
"DEFAULT_PIN_LENGTH",
|
|
23
|
+
"DEFAULT_PIN_NAME_OFFSET",
|
|
24
|
+
"DEFAULT_PIN_NUMBER_SIZE",
|
|
25
|
+
"DEFAULT_PIN_TEXT_WIDTH_RATIO",
|
|
26
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Font metrics and text rendering constants for KiCad schematic text.
|
|
3
|
+
|
|
4
|
+
These constants are used for accurate text bounding box calculations
|
|
5
|
+
and symbol spacing in schematic layouts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# KiCad default text size in mm
|
|
9
|
+
# Increased to better match actual KiCad rendering
|
|
10
|
+
DEFAULT_TEXT_HEIGHT = 2.54 # 100 mils (doubled from 50 mils)
|
|
11
|
+
|
|
12
|
+
# Default pin dimensions
|
|
13
|
+
DEFAULT_PIN_LENGTH = 2.54 # 100 mils
|
|
14
|
+
DEFAULT_PIN_NAME_OFFSET = 0.508 # 20 mils - offset from pin endpoint to label text
|
|
15
|
+
DEFAULT_PIN_NUMBER_SIZE = 1.27 # 50 mils
|
|
16
|
+
|
|
17
|
+
# Text width ratio for proportional font rendering
|
|
18
|
+
# KiCad uses proportional fonts where average character width is ~0.65x height
|
|
19
|
+
# This prevents label text from extending beyond calculated bounding boxes
|
|
20
|
+
DEFAULT_PIN_TEXT_WIDTH_RATIO = 0.65 # Width to height ratio for pin text (proportional font average)
|