kicad-sch-api 0.3.1__py3-none-any.whl → 0.3.2__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.
Potentially problematic release.
This version of kicad-sch-api might be problematic. Click here for more details.
- kicad_sch_api/__init__.py +2 -2
- kicad_sch_api/core/formatter.py +2 -1
- kicad_sch_api/core/parser.py +716 -12
- {kicad_sch_api-0.3.1.dist-info → kicad_sch_api-0.3.2.dist-info}/METADATA +1 -1
- {kicad_sch_api-0.3.1.dist-info → kicad_sch_api-0.3.2.dist-info}/RECORD +9 -9
- {kicad_sch_api-0.3.1.dist-info → kicad_sch_api-0.3.2.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.3.1.dist-info → kicad_sch_api-0.3.2.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.3.1.dist-info → kicad_sch_api-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {kicad_sch_api-0.3.1.dist-info → kicad_sch_api-0.3.2.dist-info}/top_level.txt +0 -0
kicad_sch_api/__init__.py
CHANGED
|
@@ -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.2"
|
|
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, 2)
|
|
59
59
|
|
|
60
60
|
# Public API
|
|
61
61
|
__all__ = [
|
kicad_sch_api/core/formatter.py
CHANGED
|
@@ -189,7 +189,8 @@ class ExactFormatter:
|
|
|
189
189
|
elif isinstance(element, str):
|
|
190
190
|
# Quote strings that need quoting
|
|
191
191
|
if self._needs_quoting(element):
|
|
192
|
-
|
|
192
|
+
escaped = self._escape_string(element)
|
|
193
|
+
return f'"{escaped}"'
|
|
193
194
|
return element
|
|
194
195
|
elif isinstance(element, float):
|
|
195
196
|
# Custom float formatting for KiCAD compatibility
|
kicad_sch_api/core/parser.py
CHANGED
|
@@ -187,6 +187,15 @@ class SExpressionParser:
|
|
|
187
187
|
"wires": [],
|
|
188
188
|
"junctions": [],
|
|
189
189
|
"labels": [],
|
|
190
|
+
"hierarchical_labels": [],
|
|
191
|
+
"no_connects": [],
|
|
192
|
+
"texts": [],
|
|
193
|
+
"text_boxes": [],
|
|
194
|
+
"sheets": [],
|
|
195
|
+
"polylines": [],
|
|
196
|
+
"arcs": [],
|
|
197
|
+
"circles": [],
|
|
198
|
+
"beziers": [],
|
|
190
199
|
"rectangles": [],
|
|
191
200
|
"nets": [],
|
|
192
201
|
"lib_symbols": {},
|
|
@@ -233,6 +242,42 @@ class SExpressionParser:
|
|
|
233
242
|
label = self._parse_label(item)
|
|
234
243
|
if label:
|
|
235
244
|
schematic_data["labels"].append(label)
|
|
245
|
+
elif element_type == "hierarchical_label":
|
|
246
|
+
hlabel = self._parse_hierarchical_label(item)
|
|
247
|
+
if hlabel:
|
|
248
|
+
schematic_data["hierarchical_labels"].append(hlabel)
|
|
249
|
+
elif element_type == "no_connect":
|
|
250
|
+
no_connect = self._parse_no_connect(item)
|
|
251
|
+
if no_connect:
|
|
252
|
+
schematic_data["no_connects"].append(no_connect)
|
|
253
|
+
elif element_type == "text":
|
|
254
|
+
text = self._parse_text(item)
|
|
255
|
+
if text:
|
|
256
|
+
schematic_data["texts"].append(text)
|
|
257
|
+
elif element_type == "text_box":
|
|
258
|
+
text_box = self._parse_text_box(item)
|
|
259
|
+
if text_box:
|
|
260
|
+
schematic_data["text_boxes"].append(text_box)
|
|
261
|
+
elif element_type == "sheet":
|
|
262
|
+
sheet = self._parse_sheet(item)
|
|
263
|
+
if sheet:
|
|
264
|
+
schematic_data["sheets"].append(sheet)
|
|
265
|
+
elif element_type == "polyline":
|
|
266
|
+
polyline = self._parse_polyline(item)
|
|
267
|
+
if polyline:
|
|
268
|
+
schematic_data["polylines"].append(polyline)
|
|
269
|
+
elif element_type == "arc":
|
|
270
|
+
arc = self._parse_arc(item)
|
|
271
|
+
if arc:
|
|
272
|
+
schematic_data["arcs"].append(arc)
|
|
273
|
+
elif element_type == "circle":
|
|
274
|
+
circle = self._parse_circle(item)
|
|
275
|
+
if circle:
|
|
276
|
+
schematic_data["circles"].append(circle)
|
|
277
|
+
elif element_type == "bezier":
|
|
278
|
+
bezier = self._parse_bezier(item)
|
|
279
|
+
if bezier:
|
|
280
|
+
schematic_data["beziers"].append(bezier)
|
|
236
281
|
elif element_type == "rectangle":
|
|
237
282
|
rectangle = self._parse_rectangle(item)
|
|
238
283
|
if rectangle:
|
|
@@ -297,25 +342,44 @@ class SExpressionParser:
|
|
|
297
342
|
for hlabel in schematic_data.get("hierarchical_labels", []):
|
|
298
343
|
sexp_data.append(self._hierarchical_label_to_sexp(hlabel))
|
|
299
344
|
|
|
300
|
-
# Add
|
|
301
|
-
for
|
|
302
|
-
sexp_data.append(self.
|
|
345
|
+
# Add no_connects
|
|
346
|
+
for no_connect in schematic_data.get("no_connects", []):
|
|
347
|
+
sexp_data.append(self._no_connect_to_sexp(no_connect))
|
|
348
|
+
|
|
349
|
+
# Add graphical elements (in KiCad element order)
|
|
350
|
+
# Beziers
|
|
351
|
+
for bezier in schematic_data.get("beziers", []):
|
|
352
|
+
sexp_data.append(self._bezier_to_sexp(bezier))
|
|
353
|
+
|
|
354
|
+
# Rectangles (both from API and graphics)
|
|
355
|
+
for rectangle in schematic_data.get("rectangles", []):
|
|
356
|
+
sexp_data.append(self._rectangle_to_sexp(rectangle))
|
|
357
|
+
for graphic in schematic_data.get("graphics", []):
|
|
358
|
+
sexp_data.append(self._graphic_to_sexp(graphic))
|
|
359
|
+
|
|
360
|
+
# Circles
|
|
361
|
+
for circle in schematic_data.get("circles", []):
|
|
362
|
+
sexp_data.append(self._circle_to_sexp(circle))
|
|
303
363
|
|
|
304
|
-
#
|
|
364
|
+
# Arcs
|
|
365
|
+
for arc in schematic_data.get("arcs", []):
|
|
366
|
+
sexp_data.append(self._arc_to_sexp(arc))
|
|
367
|
+
|
|
368
|
+
# Polylines
|
|
369
|
+
for polyline in schematic_data.get("polylines", []):
|
|
370
|
+
sexp_data.append(self._polyline_to_sexp(polyline))
|
|
371
|
+
|
|
372
|
+
# Text elements
|
|
305
373
|
for text in schematic_data.get("texts", []):
|
|
306
374
|
sexp_data.append(self._text_to_sexp(text))
|
|
307
375
|
|
|
308
|
-
#
|
|
376
|
+
# Text boxes
|
|
309
377
|
for text_box in schematic_data.get("text_boxes", []):
|
|
310
378
|
sexp_data.append(self._text_box_to_sexp(text_box))
|
|
311
379
|
|
|
312
|
-
#
|
|
313
|
-
for
|
|
314
|
-
sexp_data.append(self.
|
|
315
|
-
|
|
316
|
-
# Add rectangles (rectangles from add_rectangle API)
|
|
317
|
-
for rectangle in schematic_data.get("rectangles", []):
|
|
318
|
-
sexp_data.append(self._rectangle_to_sexp(rectangle))
|
|
380
|
+
# Hierarchical sheets
|
|
381
|
+
for sheet in schematic_data.get("sheets", []):
|
|
382
|
+
sexp_data.append(self._sheet_to_sexp(sheet, schematic_data.get("uuid")))
|
|
319
383
|
|
|
320
384
|
# Add sheet_instances (required by KiCAD)
|
|
321
385
|
sheet_instances = schematic_data.get("sheet_instances", [])
|
|
@@ -538,6 +602,481 @@ class SExpressionParser:
|
|
|
538
602
|
|
|
539
603
|
return label_data
|
|
540
604
|
|
|
605
|
+
def _parse_hierarchical_label(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
606
|
+
"""Parse a hierarchical label definition."""
|
|
607
|
+
# Format: (hierarchical_label "text" (shape input) (at x y rotation) (effects ...) (uuid ...))
|
|
608
|
+
if len(item) < 2:
|
|
609
|
+
return None
|
|
610
|
+
|
|
611
|
+
hlabel_data = {
|
|
612
|
+
"text": str(item[1]), # Hierarchical label text is second element
|
|
613
|
+
"shape": "input", # input/output/bidirectional/tri_state/passive
|
|
614
|
+
"position": {"x": 0, "y": 0},
|
|
615
|
+
"rotation": 0,
|
|
616
|
+
"size": 1.27,
|
|
617
|
+
"justify": "left",
|
|
618
|
+
"uuid": None
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
for elem in item[2:]: # Skip hierarchical_label keyword and text
|
|
622
|
+
if not isinstance(elem, list):
|
|
623
|
+
continue
|
|
624
|
+
|
|
625
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
626
|
+
|
|
627
|
+
if elem_type == "shape":
|
|
628
|
+
# Parse shape: (shape input)
|
|
629
|
+
if len(elem) >= 2:
|
|
630
|
+
hlabel_data["shape"] = str(elem[1])
|
|
631
|
+
|
|
632
|
+
elif elem_type == "at":
|
|
633
|
+
# Parse position: (at x y rotation)
|
|
634
|
+
if len(elem) >= 3:
|
|
635
|
+
hlabel_data["position"] = {"x": float(elem[1]), "y": float(elem[2])}
|
|
636
|
+
if len(elem) >= 4:
|
|
637
|
+
hlabel_data["rotation"] = float(elem[3])
|
|
638
|
+
|
|
639
|
+
elif elem_type == "effects":
|
|
640
|
+
# Parse effects for font size and justification: (effects (font (size x y)) (justify left))
|
|
641
|
+
for effect_elem in elem[1:]:
|
|
642
|
+
if isinstance(effect_elem, list):
|
|
643
|
+
effect_type = str(effect_elem[0]) if isinstance(effect_elem[0], sexpdata.Symbol) else None
|
|
644
|
+
|
|
645
|
+
if effect_type == "font":
|
|
646
|
+
# Parse font size
|
|
647
|
+
for font_elem in effect_elem[1:]:
|
|
648
|
+
if isinstance(font_elem, list) and str(font_elem[0]) == "size":
|
|
649
|
+
if len(font_elem) >= 2:
|
|
650
|
+
hlabel_data["size"] = float(font_elem[1])
|
|
651
|
+
|
|
652
|
+
elif effect_type == "justify":
|
|
653
|
+
# Parse justification (e.g., "left", "right")
|
|
654
|
+
if len(effect_elem) >= 2:
|
|
655
|
+
hlabel_data["justify"] = str(effect_elem[1])
|
|
656
|
+
|
|
657
|
+
elif elem_type == "uuid":
|
|
658
|
+
hlabel_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
659
|
+
|
|
660
|
+
return hlabel_data
|
|
661
|
+
|
|
662
|
+
def _parse_no_connect(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
663
|
+
"""Parse a no_connect symbol."""
|
|
664
|
+
# Format: (no_connect (at x y) (uuid ...))
|
|
665
|
+
no_connect_data = {
|
|
666
|
+
"position": {"x": 0, "y": 0},
|
|
667
|
+
"uuid": None
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
for elem in item[1:]:
|
|
671
|
+
if not isinstance(elem, list):
|
|
672
|
+
continue
|
|
673
|
+
|
|
674
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
675
|
+
|
|
676
|
+
if elem_type == "at":
|
|
677
|
+
if len(elem) >= 3:
|
|
678
|
+
no_connect_data["position"] = {"x": float(elem[1]), "y": float(elem[2])}
|
|
679
|
+
elif elem_type == "uuid":
|
|
680
|
+
no_connect_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
681
|
+
|
|
682
|
+
return no_connect_data
|
|
683
|
+
|
|
684
|
+
def _parse_text(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
685
|
+
"""Parse a text element."""
|
|
686
|
+
# Format: (text "text" (exclude_from_sim no) (at x y rotation) (effects ...) (uuid ...))
|
|
687
|
+
if len(item) < 2:
|
|
688
|
+
return None
|
|
689
|
+
|
|
690
|
+
text_data = {
|
|
691
|
+
"text": str(item[1]),
|
|
692
|
+
"exclude_from_sim": False,
|
|
693
|
+
"position": {"x": 0, "y": 0},
|
|
694
|
+
"rotation": 0,
|
|
695
|
+
"size": 1.27,
|
|
696
|
+
"uuid": None
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
for elem in item[2:]:
|
|
700
|
+
if not isinstance(elem, list):
|
|
701
|
+
continue
|
|
702
|
+
|
|
703
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
704
|
+
|
|
705
|
+
if elem_type == "exclude_from_sim":
|
|
706
|
+
if len(elem) >= 2:
|
|
707
|
+
text_data["exclude_from_sim"] = str(elem[1]) == "yes"
|
|
708
|
+
elif elem_type == "at":
|
|
709
|
+
if len(elem) >= 3:
|
|
710
|
+
text_data["position"] = {"x": float(elem[1]), "y": float(elem[2])}
|
|
711
|
+
if len(elem) >= 4:
|
|
712
|
+
text_data["rotation"] = float(elem[3])
|
|
713
|
+
elif elem_type == "effects":
|
|
714
|
+
for effect_elem in elem[1:]:
|
|
715
|
+
if isinstance(effect_elem, list) and str(effect_elem[0]) == "font":
|
|
716
|
+
for font_elem in effect_elem[1:]:
|
|
717
|
+
if isinstance(font_elem, list) and str(font_elem[0]) == "size":
|
|
718
|
+
if len(font_elem) >= 2:
|
|
719
|
+
text_data["size"] = float(font_elem[1])
|
|
720
|
+
elif elem_type == "uuid":
|
|
721
|
+
text_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
722
|
+
|
|
723
|
+
return text_data
|
|
724
|
+
|
|
725
|
+
def _parse_text_box(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
726
|
+
"""Parse a text_box element."""
|
|
727
|
+
# Format: (text_box "text" (exclude_from_sim no) (at x y rotation) (size w h) (margins ...) (stroke ...) (fill ...) (effects ...) (uuid ...))
|
|
728
|
+
if len(item) < 2:
|
|
729
|
+
return None
|
|
730
|
+
|
|
731
|
+
text_box_data = {
|
|
732
|
+
"text": str(item[1]),
|
|
733
|
+
"exclude_from_sim": False,
|
|
734
|
+
"position": {"x": 0, "y": 0},
|
|
735
|
+
"rotation": 0,
|
|
736
|
+
"size": {"width": 0, "height": 0},
|
|
737
|
+
"margins": (0.9525, 0.9525, 0.9525, 0.9525),
|
|
738
|
+
"stroke_width": 0,
|
|
739
|
+
"stroke_type": "solid",
|
|
740
|
+
"fill_type": "none",
|
|
741
|
+
"font_size": 1.27,
|
|
742
|
+
"justify_horizontal": "left",
|
|
743
|
+
"justify_vertical": "top",
|
|
744
|
+
"uuid": None
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
for elem in item[2:]:
|
|
748
|
+
if not isinstance(elem, list):
|
|
749
|
+
continue
|
|
750
|
+
|
|
751
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
752
|
+
|
|
753
|
+
if elem_type == "exclude_from_sim":
|
|
754
|
+
if len(elem) >= 2:
|
|
755
|
+
text_box_data["exclude_from_sim"] = str(elem[1]) == "yes"
|
|
756
|
+
elif elem_type == "at":
|
|
757
|
+
if len(elem) >= 3:
|
|
758
|
+
text_box_data["position"] = {"x": float(elem[1]), "y": float(elem[2])}
|
|
759
|
+
if len(elem) >= 4:
|
|
760
|
+
text_box_data["rotation"] = float(elem[3])
|
|
761
|
+
elif elem_type == "size":
|
|
762
|
+
if len(elem) >= 3:
|
|
763
|
+
text_box_data["size"] = {"width": float(elem[1]), "height": float(elem[2])}
|
|
764
|
+
elif elem_type == "margins":
|
|
765
|
+
if len(elem) >= 5:
|
|
766
|
+
text_box_data["margins"] = (float(elem[1]), float(elem[2]), float(elem[3]), float(elem[4]))
|
|
767
|
+
elif elem_type == "stroke":
|
|
768
|
+
for stroke_elem in elem[1:]:
|
|
769
|
+
if isinstance(stroke_elem, list):
|
|
770
|
+
stroke_type = str(stroke_elem[0])
|
|
771
|
+
if stroke_type == "width" and len(stroke_elem) >= 2:
|
|
772
|
+
text_box_data["stroke_width"] = float(stroke_elem[1])
|
|
773
|
+
elif stroke_type == "type" and len(stroke_elem) >= 2:
|
|
774
|
+
text_box_data["stroke_type"] = str(stroke_elem[1])
|
|
775
|
+
elif elem_type == "fill":
|
|
776
|
+
for fill_elem in elem[1:]:
|
|
777
|
+
if isinstance(fill_elem, list) and str(fill_elem[0]) == "type":
|
|
778
|
+
text_box_data["fill_type"] = str(fill_elem[1]) if len(fill_elem) >= 2 else "none"
|
|
779
|
+
elif elem_type == "effects":
|
|
780
|
+
for effect_elem in elem[1:]:
|
|
781
|
+
if isinstance(effect_elem, list):
|
|
782
|
+
effect_type = str(effect_elem[0])
|
|
783
|
+
if effect_type == "font":
|
|
784
|
+
for font_elem in effect_elem[1:]:
|
|
785
|
+
if isinstance(font_elem, list) and str(font_elem[0]) == "size":
|
|
786
|
+
if len(font_elem) >= 2:
|
|
787
|
+
text_box_data["font_size"] = float(font_elem[1])
|
|
788
|
+
elif effect_type == "justify":
|
|
789
|
+
if len(effect_elem) >= 2:
|
|
790
|
+
text_box_data["justify_horizontal"] = str(effect_elem[1])
|
|
791
|
+
if len(effect_elem) >= 3:
|
|
792
|
+
text_box_data["justify_vertical"] = str(effect_elem[2])
|
|
793
|
+
elif elem_type == "uuid":
|
|
794
|
+
text_box_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
795
|
+
|
|
796
|
+
return text_box_data
|
|
797
|
+
|
|
798
|
+
def _parse_sheet(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
799
|
+
"""Parse a hierarchical sheet."""
|
|
800
|
+
# Complex format with position, size, properties, pins, instances
|
|
801
|
+
sheet_data = {
|
|
802
|
+
"position": {"x": 0, "y": 0},
|
|
803
|
+
"size": {"width": 0, "height": 0},
|
|
804
|
+
"exclude_from_sim": False,
|
|
805
|
+
"in_bom": True,
|
|
806
|
+
"on_board": True,
|
|
807
|
+
"dnp": False,
|
|
808
|
+
"fields_autoplaced": True,
|
|
809
|
+
"stroke_width": 0.1524,
|
|
810
|
+
"stroke_type": "solid",
|
|
811
|
+
"fill_color": (0, 0, 0, 0.0),
|
|
812
|
+
"uuid": None,
|
|
813
|
+
"name": "Sheet",
|
|
814
|
+
"filename": "sheet.kicad_sch",
|
|
815
|
+
"pins": [],
|
|
816
|
+
"project_name": "",
|
|
817
|
+
"page_number": "2"
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
for elem in item[1:]:
|
|
821
|
+
if not isinstance(elem, list):
|
|
822
|
+
continue
|
|
823
|
+
|
|
824
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
825
|
+
|
|
826
|
+
if elem_type == "at":
|
|
827
|
+
if len(elem) >= 3:
|
|
828
|
+
sheet_data["position"] = {"x": float(elem[1]), "y": float(elem[2])}
|
|
829
|
+
elif elem_type == "size":
|
|
830
|
+
if len(elem) >= 3:
|
|
831
|
+
sheet_data["size"] = {"width": float(elem[1]), "height": float(elem[2])}
|
|
832
|
+
elif elem_type == "exclude_from_sim":
|
|
833
|
+
sheet_data["exclude_from_sim"] = str(elem[1]) == "yes" if len(elem) > 1 else False
|
|
834
|
+
elif elem_type == "in_bom":
|
|
835
|
+
sheet_data["in_bom"] = str(elem[1]) == "yes" if len(elem) > 1 else True
|
|
836
|
+
elif elem_type == "on_board":
|
|
837
|
+
sheet_data["on_board"] = str(elem[1]) == "yes" if len(elem) > 1 else True
|
|
838
|
+
elif elem_type == "dnp":
|
|
839
|
+
sheet_data["dnp"] = str(elem[1]) == "yes" if len(elem) > 1 else False
|
|
840
|
+
elif elem_type == "fields_autoplaced":
|
|
841
|
+
sheet_data["fields_autoplaced"] = str(elem[1]) == "yes" if len(elem) > 1 else True
|
|
842
|
+
elif elem_type == "stroke":
|
|
843
|
+
for stroke_elem in elem[1:]:
|
|
844
|
+
if isinstance(stroke_elem, list):
|
|
845
|
+
stroke_type = str(stroke_elem[0])
|
|
846
|
+
if stroke_type == "width" and len(stroke_elem) >= 2:
|
|
847
|
+
sheet_data["stroke_width"] = float(stroke_elem[1])
|
|
848
|
+
elif stroke_type == "type" and len(stroke_elem) >= 2:
|
|
849
|
+
sheet_data["stroke_type"] = str(stroke_elem[1])
|
|
850
|
+
elif elem_type == "fill":
|
|
851
|
+
for fill_elem in elem[1:]:
|
|
852
|
+
if isinstance(fill_elem, list) and str(fill_elem[0]) == "color":
|
|
853
|
+
if len(fill_elem) >= 5:
|
|
854
|
+
sheet_data["fill_color"] = (int(fill_elem[1]), int(fill_elem[2]), int(fill_elem[3]), float(fill_elem[4]))
|
|
855
|
+
elif elem_type == "uuid":
|
|
856
|
+
sheet_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
857
|
+
elif elem_type == "property":
|
|
858
|
+
if len(elem) >= 3:
|
|
859
|
+
prop_name = str(elem[1])
|
|
860
|
+
prop_value = str(elem[2])
|
|
861
|
+
if prop_name == "Sheetname":
|
|
862
|
+
sheet_data["name"] = prop_value
|
|
863
|
+
elif prop_name == "Sheetfile":
|
|
864
|
+
sheet_data["filename"] = prop_value
|
|
865
|
+
elif elem_type == "pin":
|
|
866
|
+
# Parse sheet pin - reuse existing _parse_sheet_pin helper
|
|
867
|
+
pin_data = self._parse_sheet_pin_for_read(elem)
|
|
868
|
+
if pin_data:
|
|
869
|
+
sheet_data["pins"].append(pin_data)
|
|
870
|
+
elif elem_type == "instances":
|
|
871
|
+
# Parse instances for project name and page number
|
|
872
|
+
for inst_elem in elem[1:]:
|
|
873
|
+
if isinstance(inst_elem, list) and str(inst_elem[0]) == "project":
|
|
874
|
+
if len(inst_elem) >= 2:
|
|
875
|
+
sheet_data["project_name"] = str(inst_elem[1])
|
|
876
|
+
for path_elem in inst_elem[2:]:
|
|
877
|
+
if isinstance(path_elem, list) and str(path_elem[0]) == "path":
|
|
878
|
+
for page_elem in path_elem[1:]:
|
|
879
|
+
if isinstance(page_elem, list) and str(page_elem[0]) == "page":
|
|
880
|
+
sheet_data["page_number"] = str(page_elem[1]) if len(page_elem) > 1 else "2"
|
|
881
|
+
|
|
882
|
+
return sheet_data
|
|
883
|
+
|
|
884
|
+
def _parse_sheet_pin_for_read(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
885
|
+
"""Parse a sheet pin (for reading during sheet parsing)."""
|
|
886
|
+
# Format: (pin "name" type (at x y rotation) (uuid ...) (effects ...))
|
|
887
|
+
if len(item) < 3:
|
|
888
|
+
return None
|
|
889
|
+
|
|
890
|
+
pin_data = {
|
|
891
|
+
"name": str(item[1]),
|
|
892
|
+
"pin_type": str(item[2]) if len(item) > 2 else "input",
|
|
893
|
+
"position": {"x": 0, "y": 0},
|
|
894
|
+
"rotation": 0,
|
|
895
|
+
"size": 1.27,
|
|
896
|
+
"justify": "right",
|
|
897
|
+
"uuid": None
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
for elem in item[3:]:
|
|
901
|
+
if not isinstance(elem, list):
|
|
902
|
+
continue
|
|
903
|
+
|
|
904
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
905
|
+
|
|
906
|
+
if elem_type == "at":
|
|
907
|
+
if len(elem) >= 3:
|
|
908
|
+
pin_data["position"] = {"x": float(elem[1]), "y": float(elem[2])}
|
|
909
|
+
if len(elem) >= 4:
|
|
910
|
+
pin_data["rotation"] = float(elem[3])
|
|
911
|
+
elif elem_type == "uuid":
|
|
912
|
+
pin_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
913
|
+
elif elem_type == "effects":
|
|
914
|
+
for effect_elem in elem[1:]:
|
|
915
|
+
if isinstance(effect_elem, list):
|
|
916
|
+
effect_type = str(effect_elem[0])
|
|
917
|
+
if effect_type == "font":
|
|
918
|
+
for font_elem in effect_elem[1:]:
|
|
919
|
+
if isinstance(font_elem, list) and str(font_elem[0]) == "size":
|
|
920
|
+
if len(font_elem) >= 2:
|
|
921
|
+
pin_data["size"] = float(font_elem[1])
|
|
922
|
+
elif effect_type == "justify":
|
|
923
|
+
if len(effect_elem) >= 2:
|
|
924
|
+
pin_data["justify"] = str(effect_elem[1])
|
|
925
|
+
|
|
926
|
+
return pin_data
|
|
927
|
+
|
|
928
|
+
def _parse_polyline(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
929
|
+
"""Parse a polyline graphical element."""
|
|
930
|
+
# Format: (polyline (pts (xy x1 y1) (xy x2 y2) ...) (stroke ...) (uuid ...))
|
|
931
|
+
polyline_data = {
|
|
932
|
+
"points": [],
|
|
933
|
+
"stroke_width": 0,
|
|
934
|
+
"stroke_type": "default",
|
|
935
|
+
"uuid": None
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
for elem in item[1:]:
|
|
939
|
+
if not isinstance(elem, list):
|
|
940
|
+
continue
|
|
941
|
+
|
|
942
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
943
|
+
|
|
944
|
+
if elem_type == "pts":
|
|
945
|
+
for pt in elem[1:]:
|
|
946
|
+
if isinstance(pt, list) and len(pt) >= 3 and str(pt[0]) == "xy":
|
|
947
|
+
polyline_data["points"].append({"x": float(pt[1]), "y": float(pt[2])})
|
|
948
|
+
elif elem_type == "stroke":
|
|
949
|
+
for stroke_elem in elem[1:]:
|
|
950
|
+
if isinstance(stroke_elem, list):
|
|
951
|
+
stroke_type = str(stroke_elem[0])
|
|
952
|
+
if stroke_type == "width" and len(stroke_elem) >= 2:
|
|
953
|
+
polyline_data["stroke_width"] = float(stroke_elem[1])
|
|
954
|
+
elif stroke_type == "type" and len(stroke_elem) >= 2:
|
|
955
|
+
polyline_data["stroke_type"] = str(stroke_elem[1])
|
|
956
|
+
elif elem_type == "uuid":
|
|
957
|
+
polyline_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
958
|
+
|
|
959
|
+
return polyline_data if polyline_data["points"] else None
|
|
960
|
+
|
|
961
|
+
def _parse_arc(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
962
|
+
"""Parse an arc graphical element."""
|
|
963
|
+
# Format: (arc (start x y) (mid x y) (end x y) (stroke ...) (fill ...) (uuid ...))
|
|
964
|
+
arc_data = {
|
|
965
|
+
"start": {"x": 0, "y": 0},
|
|
966
|
+
"mid": {"x": 0, "y": 0},
|
|
967
|
+
"end": {"x": 0, "y": 0},
|
|
968
|
+
"stroke_width": 0,
|
|
969
|
+
"stroke_type": "default",
|
|
970
|
+
"fill_type": "none",
|
|
971
|
+
"uuid": None
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
for elem in item[1:]:
|
|
975
|
+
if not isinstance(elem, list):
|
|
976
|
+
continue
|
|
977
|
+
|
|
978
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
979
|
+
|
|
980
|
+
if elem_type == "start" and len(elem) >= 3:
|
|
981
|
+
arc_data["start"] = {"x": float(elem[1]), "y": float(elem[2])}
|
|
982
|
+
elif elem_type == "mid" and len(elem) >= 3:
|
|
983
|
+
arc_data["mid"] = {"x": float(elem[1]), "y": float(elem[2])}
|
|
984
|
+
elif elem_type == "end" and len(elem) >= 3:
|
|
985
|
+
arc_data["end"] = {"x": float(elem[1]), "y": float(elem[2])}
|
|
986
|
+
elif elem_type == "stroke":
|
|
987
|
+
for stroke_elem in elem[1:]:
|
|
988
|
+
if isinstance(stroke_elem, list):
|
|
989
|
+
stroke_type = str(stroke_elem[0])
|
|
990
|
+
if stroke_type == "width" and len(stroke_elem) >= 2:
|
|
991
|
+
arc_data["stroke_width"] = float(stroke_elem[1])
|
|
992
|
+
elif stroke_type == "type" and len(stroke_elem) >= 2:
|
|
993
|
+
arc_data["stroke_type"] = str(stroke_elem[1])
|
|
994
|
+
elif elem_type == "fill":
|
|
995
|
+
for fill_elem in elem[1:]:
|
|
996
|
+
if isinstance(fill_elem, list) and str(fill_elem[0]) == "type":
|
|
997
|
+
arc_data["fill_type"] = str(fill_elem[1]) if len(fill_elem) >= 2 else "none"
|
|
998
|
+
elif elem_type == "uuid":
|
|
999
|
+
arc_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
1000
|
+
|
|
1001
|
+
return arc_data
|
|
1002
|
+
|
|
1003
|
+
def _parse_circle(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
1004
|
+
"""Parse a circle graphical element."""
|
|
1005
|
+
# Format: (circle (center x y) (radius r) (stroke ...) (fill ...) (uuid ...))
|
|
1006
|
+
circle_data = {
|
|
1007
|
+
"center": {"x": 0, "y": 0},
|
|
1008
|
+
"radius": 0,
|
|
1009
|
+
"stroke_width": 0,
|
|
1010
|
+
"stroke_type": "default",
|
|
1011
|
+
"fill_type": "none",
|
|
1012
|
+
"uuid": None
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
for elem in item[1:]:
|
|
1016
|
+
if not isinstance(elem, list):
|
|
1017
|
+
continue
|
|
1018
|
+
|
|
1019
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
1020
|
+
|
|
1021
|
+
if elem_type == "center" and len(elem) >= 3:
|
|
1022
|
+
circle_data["center"] = {"x": float(elem[1]), "y": float(elem[2])}
|
|
1023
|
+
elif elem_type == "radius" and len(elem) >= 2:
|
|
1024
|
+
circle_data["radius"] = float(elem[1])
|
|
1025
|
+
elif elem_type == "stroke":
|
|
1026
|
+
for stroke_elem in elem[1:]:
|
|
1027
|
+
if isinstance(stroke_elem, list):
|
|
1028
|
+
stroke_type = str(stroke_elem[0])
|
|
1029
|
+
if stroke_type == "width" and len(stroke_elem) >= 2:
|
|
1030
|
+
circle_data["stroke_width"] = float(stroke_elem[1])
|
|
1031
|
+
elif stroke_type == "type" and len(stroke_elem) >= 2:
|
|
1032
|
+
circle_data["stroke_type"] = str(stroke_elem[1])
|
|
1033
|
+
elif elem_type == "fill":
|
|
1034
|
+
for fill_elem in elem[1:]:
|
|
1035
|
+
if isinstance(fill_elem, list) and str(fill_elem[0]) == "type":
|
|
1036
|
+
circle_data["fill_type"] = str(fill_elem[1]) if len(fill_elem) >= 2 else "none"
|
|
1037
|
+
elif elem_type == "uuid":
|
|
1038
|
+
circle_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
1039
|
+
|
|
1040
|
+
return circle_data
|
|
1041
|
+
|
|
1042
|
+
def _parse_bezier(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
1043
|
+
"""Parse a bezier curve graphical element."""
|
|
1044
|
+
# Format: (bezier (pts (xy x1 y1) (xy x2 y2) ...) (stroke ...) (fill ...) (uuid ...))
|
|
1045
|
+
bezier_data = {
|
|
1046
|
+
"points": [],
|
|
1047
|
+
"stroke_width": 0,
|
|
1048
|
+
"stroke_type": "default",
|
|
1049
|
+
"fill_type": "none",
|
|
1050
|
+
"uuid": None
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
for elem in item[1:]:
|
|
1054
|
+
if not isinstance(elem, list):
|
|
1055
|
+
continue
|
|
1056
|
+
|
|
1057
|
+
elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None
|
|
1058
|
+
|
|
1059
|
+
if elem_type == "pts":
|
|
1060
|
+
for pt in elem[1:]:
|
|
1061
|
+
if isinstance(pt, list) and len(pt) >= 3 and str(pt[0]) == "xy":
|
|
1062
|
+
bezier_data["points"].append({"x": float(pt[1]), "y": float(pt[2])})
|
|
1063
|
+
elif elem_type == "stroke":
|
|
1064
|
+
for stroke_elem in elem[1:]:
|
|
1065
|
+
if isinstance(stroke_elem, list):
|
|
1066
|
+
stroke_type = str(stroke_elem[0])
|
|
1067
|
+
if stroke_type == "width" and len(stroke_elem) >= 2:
|
|
1068
|
+
bezier_data["stroke_width"] = float(stroke_elem[1])
|
|
1069
|
+
elif stroke_type == "type" and len(stroke_elem) >= 2:
|
|
1070
|
+
bezier_data["stroke_type"] = str(stroke_elem[1])
|
|
1071
|
+
elif elem_type == "fill":
|
|
1072
|
+
for fill_elem in elem[1:]:
|
|
1073
|
+
if isinstance(fill_elem, list) and str(fill_elem[0]) == "type":
|
|
1074
|
+
bezier_data["fill_type"] = str(fill_elem[1]) if len(fill_elem) >= 2 else "none"
|
|
1075
|
+
elif elem_type == "uuid":
|
|
1076
|
+
bezier_data["uuid"] = str(elem[1]) if len(elem) > 1 else None
|
|
1077
|
+
|
|
1078
|
+
return bezier_data if bezier_data["points"] else None
|
|
1079
|
+
|
|
541
1080
|
def _parse_rectangle(self, item: List[Any]) -> Optional[Dict[str, Any]]:
|
|
542
1081
|
"""Parse a rectangle graphical element."""
|
|
543
1082
|
rectangle = {}
|
|
@@ -942,6 +1481,171 @@ class SExpressionParser:
|
|
|
942
1481
|
|
|
943
1482
|
return sexp
|
|
944
1483
|
|
|
1484
|
+
def _no_connect_to_sexp(self, no_connect_data: Dict[str, Any]) -> List[Any]:
|
|
1485
|
+
"""Convert no_connect to S-expression."""
|
|
1486
|
+
sexp = [sexpdata.Symbol("no_connect")]
|
|
1487
|
+
|
|
1488
|
+
# Add position
|
|
1489
|
+
pos = no_connect_data["position"]
|
|
1490
|
+
x, y = pos["x"], pos["y"]
|
|
1491
|
+
|
|
1492
|
+
# Format coordinates properly
|
|
1493
|
+
if isinstance(x, float) and x.is_integer():
|
|
1494
|
+
x = int(x)
|
|
1495
|
+
if isinstance(y, float) and y.is_integer():
|
|
1496
|
+
y = int(y)
|
|
1497
|
+
|
|
1498
|
+
sexp.append([sexpdata.Symbol("at"), x, y])
|
|
1499
|
+
|
|
1500
|
+
# Add UUID
|
|
1501
|
+
if "uuid" in no_connect_data:
|
|
1502
|
+
sexp.append([sexpdata.Symbol("uuid"), no_connect_data["uuid"]])
|
|
1503
|
+
|
|
1504
|
+
return sexp
|
|
1505
|
+
|
|
1506
|
+
def _polyline_to_sexp(self, polyline_data: Dict[str, Any]) -> List[Any]:
|
|
1507
|
+
"""Convert polyline to S-expression."""
|
|
1508
|
+
sexp = [sexpdata.Symbol("polyline")]
|
|
1509
|
+
|
|
1510
|
+
# Add points
|
|
1511
|
+
points = polyline_data.get("points", [])
|
|
1512
|
+
if points:
|
|
1513
|
+
pts_sexp = [sexpdata.Symbol("pts")]
|
|
1514
|
+
for point in points:
|
|
1515
|
+
x, y = point["x"], point["y"]
|
|
1516
|
+
# Format coordinates properly
|
|
1517
|
+
if isinstance(x, float) and x.is_integer():
|
|
1518
|
+
x = int(x)
|
|
1519
|
+
if isinstance(y, float) and y.is_integer():
|
|
1520
|
+
y = int(y)
|
|
1521
|
+
pts_sexp.append([sexpdata.Symbol("xy"), x, y])
|
|
1522
|
+
sexp.append(pts_sexp)
|
|
1523
|
+
|
|
1524
|
+
# Add stroke
|
|
1525
|
+
stroke_width = polyline_data.get("stroke_width", 0)
|
|
1526
|
+
stroke_type = polyline_data.get("stroke_type", "default")
|
|
1527
|
+
stroke_sexp = [sexpdata.Symbol("stroke")]
|
|
1528
|
+
stroke_sexp.append([sexpdata.Symbol("width"), stroke_width])
|
|
1529
|
+
stroke_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(stroke_type)])
|
|
1530
|
+
sexp.append(stroke_sexp)
|
|
1531
|
+
|
|
1532
|
+
# Add UUID
|
|
1533
|
+
if "uuid" in polyline_data:
|
|
1534
|
+
sexp.append([sexpdata.Symbol("uuid"), polyline_data["uuid"]])
|
|
1535
|
+
|
|
1536
|
+
return sexp
|
|
1537
|
+
|
|
1538
|
+
def _arc_to_sexp(self, arc_data: Dict[str, Any]) -> List[Any]:
|
|
1539
|
+
"""Convert arc to S-expression."""
|
|
1540
|
+
sexp = [sexpdata.Symbol("arc")]
|
|
1541
|
+
|
|
1542
|
+
# Add start, mid, end points
|
|
1543
|
+
for point_name in ["start", "mid", "end"]:
|
|
1544
|
+
point = arc_data.get(point_name, {"x": 0, "y": 0})
|
|
1545
|
+
x, y = point["x"], point["y"]
|
|
1546
|
+
# Format coordinates properly
|
|
1547
|
+
if isinstance(x, float) and x.is_integer():
|
|
1548
|
+
x = int(x)
|
|
1549
|
+
if isinstance(y, float) and y.is_integer():
|
|
1550
|
+
y = int(y)
|
|
1551
|
+
sexp.append([sexpdata.Symbol(point_name), x, y])
|
|
1552
|
+
|
|
1553
|
+
# Add stroke
|
|
1554
|
+
stroke_width = arc_data.get("stroke_width", 0)
|
|
1555
|
+
stroke_type = arc_data.get("stroke_type", "default")
|
|
1556
|
+
stroke_sexp = [sexpdata.Symbol("stroke")]
|
|
1557
|
+
stroke_sexp.append([sexpdata.Symbol("width"), stroke_width])
|
|
1558
|
+
stroke_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(stroke_type)])
|
|
1559
|
+
sexp.append(stroke_sexp)
|
|
1560
|
+
|
|
1561
|
+
# Add fill
|
|
1562
|
+
fill_type = arc_data.get("fill_type", "none")
|
|
1563
|
+
fill_sexp = [sexpdata.Symbol("fill")]
|
|
1564
|
+
fill_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(fill_type)])
|
|
1565
|
+
sexp.append(fill_sexp)
|
|
1566
|
+
|
|
1567
|
+
# Add UUID
|
|
1568
|
+
if "uuid" in arc_data:
|
|
1569
|
+
sexp.append([sexpdata.Symbol("uuid"), arc_data["uuid"]])
|
|
1570
|
+
|
|
1571
|
+
return sexp
|
|
1572
|
+
|
|
1573
|
+
def _circle_to_sexp(self, circle_data: Dict[str, Any]) -> List[Any]:
|
|
1574
|
+
"""Convert circle to S-expression."""
|
|
1575
|
+
sexp = [sexpdata.Symbol("circle")]
|
|
1576
|
+
|
|
1577
|
+
# Add center
|
|
1578
|
+
center = circle_data.get("center", {"x": 0, "y": 0})
|
|
1579
|
+
x, y = center["x"], center["y"]
|
|
1580
|
+
# Format coordinates properly
|
|
1581
|
+
if isinstance(x, float) and x.is_integer():
|
|
1582
|
+
x = int(x)
|
|
1583
|
+
if isinstance(y, float) and y.is_integer():
|
|
1584
|
+
y = int(y)
|
|
1585
|
+
sexp.append([sexpdata.Symbol("center"), x, y])
|
|
1586
|
+
|
|
1587
|
+
# Add radius
|
|
1588
|
+
radius = circle_data.get("radius", 0)
|
|
1589
|
+
sexp.append([sexpdata.Symbol("radius"), radius])
|
|
1590
|
+
|
|
1591
|
+
# Add stroke
|
|
1592
|
+
stroke_width = circle_data.get("stroke_width", 0)
|
|
1593
|
+
stroke_type = circle_data.get("stroke_type", "default")
|
|
1594
|
+
stroke_sexp = [sexpdata.Symbol("stroke")]
|
|
1595
|
+
stroke_sexp.append([sexpdata.Symbol("width"), stroke_width])
|
|
1596
|
+
stroke_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(stroke_type)])
|
|
1597
|
+
sexp.append(stroke_sexp)
|
|
1598
|
+
|
|
1599
|
+
# Add fill
|
|
1600
|
+
fill_type = circle_data.get("fill_type", "none")
|
|
1601
|
+
fill_sexp = [sexpdata.Symbol("fill")]
|
|
1602
|
+
fill_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(fill_type)])
|
|
1603
|
+
sexp.append(fill_sexp)
|
|
1604
|
+
|
|
1605
|
+
# Add UUID
|
|
1606
|
+
if "uuid" in circle_data:
|
|
1607
|
+
sexp.append([sexpdata.Symbol("uuid"), circle_data["uuid"]])
|
|
1608
|
+
|
|
1609
|
+
return sexp
|
|
1610
|
+
|
|
1611
|
+
def _bezier_to_sexp(self, bezier_data: Dict[str, Any]) -> List[Any]:
|
|
1612
|
+
"""Convert bezier curve to S-expression."""
|
|
1613
|
+
sexp = [sexpdata.Symbol("bezier")]
|
|
1614
|
+
|
|
1615
|
+
# Add points
|
|
1616
|
+
points = bezier_data.get("points", [])
|
|
1617
|
+
if points:
|
|
1618
|
+
pts_sexp = [sexpdata.Symbol("pts")]
|
|
1619
|
+
for point in points:
|
|
1620
|
+
x, y = point["x"], point["y"]
|
|
1621
|
+
# Format coordinates properly
|
|
1622
|
+
if isinstance(x, float) and x.is_integer():
|
|
1623
|
+
x = int(x)
|
|
1624
|
+
if isinstance(y, float) and y.is_integer():
|
|
1625
|
+
y = int(y)
|
|
1626
|
+
pts_sexp.append([sexpdata.Symbol("xy"), x, y])
|
|
1627
|
+
sexp.append(pts_sexp)
|
|
1628
|
+
|
|
1629
|
+
# Add stroke
|
|
1630
|
+
stroke_width = bezier_data.get("stroke_width", 0)
|
|
1631
|
+
stroke_type = bezier_data.get("stroke_type", "default")
|
|
1632
|
+
stroke_sexp = [sexpdata.Symbol("stroke")]
|
|
1633
|
+
stroke_sexp.append([sexpdata.Symbol("width"), stroke_width])
|
|
1634
|
+
stroke_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(stroke_type)])
|
|
1635
|
+
sexp.append(stroke_sexp)
|
|
1636
|
+
|
|
1637
|
+
# Add fill
|
|
1638
|
+
fill_type = bezier_data.get("fill_type", "none")
|
|
1639
|
+
fill_sexp = [sexpdata.Symbol("fill")]
|
|
1640
|
+
fill_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(fill_type)])
|
|
1641
|
+
sexp.append(fill_sexp)
|
|
1642
|
+
|
|
1643
|
+
# Add UUID
|
|
1644
|
+
if "uuid" in bezier_data:
|
|
1645
|
+
sexp.append([sexpdata.Symbol("uuid"), bezier_data["uuid"]])
|
|
1646
|
+
|
|
1647
|
+
return sexp
|
|
1648
|
+
|
|
945
1649
|
def _sheet_to_sexp(self, sheet_data: Dict[str, Any], schematic_uuid: str) -> List[Any]:
|
|
946
1650
|
"""Convert hierarchical sheet to S-expression."""
|
|
947
1651
|
sexp = [sexpdata.Symbol("sheet")]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kicad-sch-api
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
kicad_sch_api/__init__.py,sha256=
|
|
1
|
+
kicad_sch_api/__init__.py,sha256=6yDjQeaoOdZatCgDaIULVxwXqT1fjLSNjYU0yKRh9f4,2919
|
|
2
2
|
kicad_sch_api/cli.py,sha256=ZzmwzfHEvPgGfCiQBU4G2LBAyRtMNiBRoY21pivJSYc,7621
|
|
3
3
|
kicad_sch_api/py.typed,sha256=e4ldqxwpY7pNDG1olbvj4HSKr8sZ9vxgA_2ek8xXn-Q,70
|
|
4
4
|
kicad_sch_api/core/__init__.py,sha256=ur_KeYBlGKl-e1hLpLdxAhGV2A-PCCGkcqd0r6KSeBA,566
|
|
5
5
|
kicad_sch_api/core/component_bounds.py,sha256=BFYJYULyzs5it2hN7bHTimyS9Vet4dxsMklRStob-F4,17509
|
|
6
6
|
kicad_sch_api/core/components.py,sha256=tXRL18GObl2u94wl5jP-1ID56s_UD9F1gQ_iRIyZ_Kw,25290
|
|
7
7
|
kicad_sch_api/core/config.py,sha256=itw0j3DeIEHaFVf8p3mfAS1SP6jclBwvMv7NPdkThE4,4309
|
|
8
|
-
kicad_sch_api/core/formatter.py,sha256=
|
|
8
|
+
kicad_sch_api/core/formatter.py,sha256=7kwM7WdbVjT8biirWSSFO44OMjeByvzYYr_-mpGiEA4,20862
|
|
9
9
|
kicad_sch_api/core/geometry.py,sha256=27SgN0padLbQuTi8MV6UUCp6Pyaiv8V9gmYDOhfwny8,2947
|
|
10
10
|
kicad_sch_api/core/ic_manager.py,sha256=Kg0HIOMU-TGXiIkrnwcHFQ1Kfv_3rW2U1cwBKJsKopc,7219
|
|
11
11
|
kicad_sch_api/core/junctions.py,sha256=Ay6BsWX_DLs-wB0eMA2CytKKq0N8Ja41ZubJWpAqNgM,6122
|
|
12
12
|
kicad_sch_api/core/manhattan_routing.py,sha256=t_T2u0zsQB-a8dTijFmY-qFq-oDt2qDebYyXzD_pBWI,15989
|
|
13
|
-
kicad_sch_api/core/parser.py,sha256=
|
|
13
|
+
kicad_sch_api/core/parser.py,sha256=UMTpwrxtyFM5cjAkgLWts6vBJ254gc4Hvx-puZydkVQ,92209
|
|
14
14
|
kicad_sch_api/core/pin_utils.py,sha256=XGEow3HzBTyT8a0B_ZC8foMvwzYaENSaqTUwDW1rz24,5417
|
|
15
15
|
kicad_sch_api/core/schematic.py,sha256=U9-wrhuGtgRqZJfc76Dj-g1_ZTjrT8R9LmfX-BIBH8w,61201
|
|
16
16
|
kicad_sch_api/core/simple_manhattan.py,sha256=CvIHvwmfABPF-COzhblYxEgRoR_R_eD-lmBFHHjDuMI,7241
|
|
@@ -23,9 +23,9 @@ kicad_sch_api/library/__init__.py,sha256=NG9UTdcpn25Bl9tPsYs9ED7bvpaVPVdtLMbnxkQ
|
|
|
23
23
|
kicad_sch_api/library/cache.py,sha256=7na88grl465WHwUOGuOzYrrWwjsMBXhXVtxhnaJ9GBY,33208
|
|
24
24
|
kicad_sch_api/utils/__init__.py,sha256=1V_yGgI7jro6MUc4Pviux_WIeJ1wmiYFID186SZwWLQ,277
|
|
25
25
|
kicad_sch_api/utils/validation.py,sha256=XlWGRZJb3cOPYpU9sLQQgC_NASwbi6W-LCN7PzUmaPY,15626
|
|
26
|
-
kicad_sch_api-0.3.
|
|
27
|
-
kicad_sch_api-0.3.
|
|
28
|
-
kicad_sch_api-0.3.
|
|
29
|
-
kicad_sch_api-0.3.
|
|
30
|
-
kicad_sch_api-0.3.
|
|
31
|
-
kicad_sch_api-0.3.
|
|
26
|
+
kicad_sch_api-0.3.2.dist-info/licenses/LICENSE,sha256=Em65Nvte1G9MHc0rHqtYuGkCPcshD588itTa358J6gs,1070
|
|
27
|
+
kicad_sch_api-0.3.2.dist-info/METADATA,sha256=m8NfvoWy9m4AfShTSlHkn2AMUzy1cZkxiQXPnF3e0a0,17183
|
|
28
|
+
kicad_sch_api-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
kicad_sch_api-0.3.2.dist-info/entry_points.txt,sha256=VWKsFi2Jv7G_tmio3cNVhhIBfv_OZFaKa-T_ED84lc8,57
|
|
30
|
+
kicad_sch_api-0.3.2.dist-info/top_level.txt,sha256=n0ex4gOJ1b_fARowcGqRzyOGZcHRhc5LZa6_vVgGxcI,14
|
|
31
|
+
kicad_sch_api-0.3.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|