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.

Files changed (100) hide show
  1. {kicad_sch_api-0.3.2/kicad_sch_api.egg-info → kicad_sch_api-0.3.4}/PKG-INFO +1 -1
  2. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/__init__.py +2 -2
  3. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/formatter.py +31 -0
  4. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/parser.py +72 -0
  5. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/schematic.py +49 -7
  6. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/types.py +14 -0
  7. kicad_sch_api-0.3.4/kicad_sch_api/geometry/__init__.py +26 -0
  8. kicad_sch_api-0.3.4/kicad_sch_api/geometry/font_metrics.py +20 -0
  9. kicad_sch_api-0.3.4/kicad_sch_api/geometry/symbol_bbox.py +595 -0
  10. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4/kicad_sch_api.egg-info}/PKG-INFO +1 -1
  11. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api.egg-info/SOURCES.txt +5 -0
  12. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/pyproject.toml +1 -1
  13. kicad_sch_api-0.3.4/tests/test_geometry.py +566 -0
  14. kicad_sch_api-0.3.4/tests/test_image_support.py +119 -0
  15. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/dead-code-analysis.md +0 -0
  16. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/publish-pypi.md +0 -0
  17. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/review-implementation.md +0 -0
  18. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/run-tests.md +0 -0
  19. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/update-and-commit.md +0 -0
  20. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/dev/update-memory-bank.md +0 -0
  21. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/.claude/commands/test/run-reference-tests.md +0 -0
  22. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/CHANGELOG.md +0 -0
  23. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/LICENSE +0 -0
  24. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/MANIFEST.in +0 -0
  25. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/README.md +0 -0
  26. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/advanced_usage.py +0 -0
  27. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/basic_usage.py +0 -0
  28. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/mcp_basic_example.py +0 -0
  29. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/mcp_integration.py +0 -0
  30. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/pin_to_pin_wiring_demo.py +0 -0
  31. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/simple_circuit_with_pin_wiring.py +0 -0
  32. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/examples/simple_two_resistor_routing.py +0 -0
  33. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/cli.py +0 -0
  34. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/__init__.py +0 -0
  35. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/component_bounds.py +0 -0
  36. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/components.py +0 -0
  37. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/config.py +0 -0
  38. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/geometry.py +0 -0
  39. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/ic_manager.py +0 -0
  40. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/junctions.py +0 -0
  41. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/manhattan_routing.py +0 -0
  42. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/pin_utils.py +0 -0
  43. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/simple_manhattan.py +0 -0
  44. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/wire_routing.py +0 -0
  45. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/core/wires.py +0 -0
  46. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/discovery/__init__.py +0 -0
  47. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/discovery/search_index.py +0 -0
  48. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/library/__init__.py +0 -0
  49. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/library/cache.py +0 -0
  50. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/py.typed +0 -0
  51. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/utils/__init__.py +0 -0
  52. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api/utils/validation.py +0 -0
  53. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api.egg-info/dependency_links.txt +0 -0
  54. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api.egg-info/entry_points.txt +0 -0
  55. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api.egg-info/requires.txt +0 -0
  56. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/kicad_sch_api.egg-info/top_level.txt +0 -0
  57. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/setup.cfg +0 -0
  58. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/reference_tests/reference_kicad_projects/README.md +0 -0
  59. {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
  60. {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
  61. {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
  62. {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
  63. {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
  64. {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
  65. {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
  66. {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
  67. {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
  68. {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
  69. {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
  70. {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
  71. {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
  72. {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
  73. {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
  74. {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
  75. {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
  76. {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
  77. {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
  78. {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
  79. {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
  80. {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
  81. {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
  82. {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
  83. {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
  84. {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
  85. {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
  86. {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
  87. {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
  88. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_bounding_box_rectangles.py +0 -0
  89. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_component_removal.py +0 -0
  90. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_element_removal.py +0 -0
  91. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_grid_snapping.py +0 -0
  92. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_kicad_validation.py +0 -0
  93. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_manhattan_routing.py +0 -0
  94. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_parse_reference_rectangles.py +0 -0
  95. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_pin_positioning.py +0 -0
  96. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_pin_to_pin_wiring.py +0 -0
  97. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_rectangle.py +0 -0
  98. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_rectangle_roundtrip.py +0 -0
  99. {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.4}/tests/test_removal_against_references.py +0 -0
  100. {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.2
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.2"
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, 2)
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)