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.
Files changed (81) hide show
  1. kicad_sch_api/__init__.py +2 -2
  2. kicad_sch_api/cli/__init__.py +45 -0
  3. kicad_sch_api/cli/base.py +302 -0
  4. kicad_sch_api/cli/bom.py +164 -0
  5. kicad_sch_api/cli/erc.py +229 -0
  6. kicad_sch_api/cli/export_docs.py +289 -0
  7. kicad_sch_api/cli/netlist.py +94 -0
  8. kicad_sch_api/cli/types.py +43 -0
  9. kicad_sch_api/collections/__init__.py +2 -2
  10. kicad_sch_api/collections/base.py +5 -7
  11. kicad_sch_api/collections/components.py +24 -12
  12. kicad_sch_api/collections/junctions.py +31 -43
  13. kicad_sch_api/collections/labels.py +19 -27
  14. kicad_sch_api/collections/wires.py +17 -18
  15. kicad_sch_api/core/collections/__init__.py +5 -0
  16. kicad_sch_api/core/collections/base.py +248 -0
  17. kicad_sch_api/core/component_bounds.py +5 -0
  18. kicad_sch_api/core/components.py +67 -45
  19. kicad_sch_api/core/config.py +85 -3
  20. kicad_sch_api/core/factories/__init__.py +5 -0
  21. kicad_sch_api/core/factories/element_factory.py +276 -0
  22. kicad_sch_api/core/formatter.py +3 -1
  23. kicad_sch_api/core/junctions.py +26 -75
  24. kicad_sch_api/core/labels.py +29 -53
  25. kicad_sch_api/core/managers/__init__.py +26 -0
  26. kicad_sch_api/core/managers/file_io.py +244 -0
  27. kicad_sch_api/core/managers/format_sync.py +501 -0
  28. kicad_sch_api/core/managers/graphics.py +579 -0
  29. kicad_sch_api/core/managers/metadata.py +269 -0
  30. kicad_sch_api/core/managers/sheet.py +454 -0
  31. kicad_sch_api/core/managers/text_elements.py +536 -0
  32. kicad_sch_api/core/managers/validation.py +475 -0
  33. kicad_sch_api/core/managers/wire.py +352 -0
  34. kicad_sch_api/core/nets.py +38 -43
  35. kicad_sch_api/core/no_connects.py +33 -55
  36. kicad_sch_api/core/parser.py +75 -1731
  37. kicad_sch_api/core/schematic.py +951 -1192
  38. kicad_sch_api/core/texts.py +28 -55
  39. kicad_sch_api/core/types.py +60 -22
  40. kicad_sch_api/core/wires.py +27 -75
  41. kicad_sch_api/geometry/font_metrics.py +3 -1
  42. kicad_sch_api/geometry/symbol_bbox.py +40 -21
  43. kicad_sch_api/interfaces/__init__.py +1 -1
  44. kicad_sch_api/interfaces/parser.py +1 -1
  45. kicad_sch_api/interfaces/repository.py +1 -1
  46. kicad_sch_api/interfaces/resolver.py +1 -1
  47. kicad_sch_api/parsers/__init__.py +2 -2
  48. kicad_sch_api/parsers/base.py +7 -10
  49. kicad_sch_api/parsers/elements/__init__.py +22 -0
  50. kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
  51. kicad_sch_api/parsers/elements/label_parser.py +194 -0
  52. kicad_sch_api/parsers/elements/library_parser.py +165 -0
  53. kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
  54. kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
  55. kicad_sch_api/parsers/elements/symbol_parser.py +313 -0
  56. kicad_sch_api/parsers/elements/text_parser.py +250 -0
  57. kicad_sch_api/parsers/elements/wire_parser.py +242 -0
  58. kicad_sch_api/parsers/registry.py +4 -2
  59. kicad_sch_api/parsers/utils.py +80 -0
  60. kicad_sch_api/symbols/__init__.py +1 -1
  61. kicad_sch_api/symbols/cache.py +9 -12
  62. kicad_sch_api/symbols/resolver.py +20 -26
  63. kicad_sch_api/symbols/validators.py +188 -137
  64. kicad_sch_api/validation/__init__.py +25 -0
  65. kicad_sch_api/validation/erc.py +171 -0
  66. kicad_sch_api/validation/erc_models.py +203 -0
  67. kicad_sch_api/validation/pin_matrix.py +243 -0
  68. kicad_sch_api/validation/validators.py +391 -0
  69. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/METADATA +17 -9
  70. kicad_sch_api-0.4.1.dist-info/RECORD +87 -0
  71. kicad_sch_api/core/manhattan_routing.py +0 -430
  72. kicad_sch_api/core/simple_manhattan.py +0 -228
  73. kicad_sch_api/core/wire_routing.py +0 -380
  74. kicad_sch_api/parsers/label_parser.py +0 -254
  75. kicad_sch_api/parsers/symbol_parser.py +0 -227
  76. kicad_sch_api/parsers/wire_parser.py +0 -99
  77. kicad_sch_api-0.3.5.dist-info/RECORD +0 -58
  78. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/WHEEL +0 -0
  79. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/entry_points.txt +0 -0
  80. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/licenses/LICENSE +0 -0
  81. {kicad_sch_api-0.3.5.dist-info → kicad_sch_api-0.4.1.dist-info}/top_level.txt +0 -0
@@ -384,6 +384,11 @@ def get_component_bounding_box(
384
384
 
385
385
  # Transform to world coordinates
386
386
  # TODO: Handle component rotation in the future
387
+ # NOTE: Currently assumes 0° rotation. For rotated components, bounding box
388
+ # would need to be recalculated after applying rotation matrix. This is a
389
+ # known limitation but doesn't affect most use cases since components are
390
+ # typically placed without rotation, and routing avoids components regardless.
391
+ # Priority: LOW - Would improve accuracy for rotated component placement validation
387
392
  world_bbox = BoundingBox(
388
393
  component.position.x + symbol_bbox.min_x,
389
394
  component.position.y + symbol_bbox.min_y,
@@ -11,6 +11,7 @@ from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union
11
11
 
12
12
  from ..library.cache import SymbolDefinition, get_symbol_cache
13
13
  from ..utils.validation import SchematicValidator, ValidationError, ValidationIssue
14
+ from .collections import BaseCollection
14
15
  from .ic_manager import ICManager
15
16
  from .types import Point, SchematicPin, SchematicSymbol
16
17
 
@@ -38,6 +39,11 @@ class Component:
38
39
  self._validator = SchematicValidator()
39
40
 
40
41
  # Core properties with validation
42
+ @property
43
+ def uuid(self) -> str:
44
+ """Component UUID."""
45
+ return self._data.uuid
46
+
41
47
  @property
42
48
  def reference(self) -> str:
43
49
  """Component reference (e.g., 'R1')."""
@@ -256,10 +262,13 @@ class Component:
256
262
  )
257
263
 
258
264
 
259
- class ComponentCollection:
265
+ class ComponentCollection(BaseCollection[Component]):
260
266
  """
261
267
  Collection class for efficient component management.
262
268
 
269
+ Inherits from BaseCollection for standard operations and adds component-specific
270
+ functionality including reference, lib_id, and value-based indexing.
271
+
263
272
  Provides fast lookup, filtering, and bulk operations for schematic components.
264
273
  Optimized for schematics with hundreds or thousands of components.
265
274
  """
@@ -271,19 +280,19 @@ class ComponentCollection:
271
280
  Args:
272
281
  components: Initial list of component data
273
282
  """
274
- self._components: List[Component] = []
283
+ # Initialize base collection
284
+ super().__init__([], collection_name="components")
285
+
286
+ # Additional component-specific indexes
275
287
  self._reference_index: Dict[str, Component] = {}
276
288
  self._lib_id_index: Dict[str, List[Component]] = {}
277
289
  self._value_index: Dict[str, List[Component]] = {}
278
- self._modified = False
279
290
 
280
291
  # Add initial components
281
292
  if components:
282
293
  for comp_data in components:
283
294
  self._add_to_indexes(Component(comp_data, self))
284
295
 
285
- logger.debug(f"ComponentCollection initialized with {len(self._components)} components")
286
-
287
296
  def add(
288
297
  self,
289
298
  lib_id: str,
@@ -370,7 +379,7 @@ class ComponentCollection:
370
379
 
371
380
  # Add to collection
372
381
  self._add_to_indexes(component)
373
- self._modified = True
382
+ self._mark_modified()
374
383
 
375
384
  logger.info(f"Added component: {reference} ({lib_id})")
376
385
  return component
@@ -427,7 +436,7 @@ class ComponentCollection:
427
436
  component = Component(component_data, self)
428
437
  self._add_to_indexes(component)
429
438
 
430
- self._modified = True
439
+ self._mark_modified()
431
440
  logger.info(
432
441
  f"Added multi-unit IC: {reference_prefix} ({lib_id}) with {len(unit_components)} units"
433
442
  )
@@ -448,9 +457,11 @@ class ComponentCollection:
448
457
  if not component:
449
458
  return False
450
459
 
451
- # Remove from all indexes
460
+ # Remove from component-specific indexes
452
461
  self._remove_from_indexes(component)
453
- self._modified = True
462
+
463
+ # Remove from base collection using UUID
464
+ super().remove(component.uuid)
454
465
 
455
466
  logger.info(f"Removed component: {reference}")
456
467
  return True
@@ -474,7 +485,7 @@ class ComponentCollection:
474
485
  Returns:
475
486
  List of matching components
476
487
  """
477
- results = list(self._components)
488
+ results = list(self._items)
478
489
 
479
490
  # Apply filters
480
491
  if "lib_id" in criteria:
@@ -512,7 +523,7 @@ class ComponentCollection:
512
523
  def filter_by_type(self, component_type: str) -> List[Component]:
513
524
  """Filter components by type (e.g., 'R' for resistors)."""
514
525
  return [
515
- c for c in self._components if c.symbol_name.upper().startswith(component_type.upper())
526
+ c for c in self._items if c.symbol_name.upper().startswith(component_type.upper())
516
527
  ]
517
528
 
518
529
  def in_area(self, x1: float, y1: float, x2: float, y2: float) -> List[Component]:
@@ -527,7 +538,7 @@ class ComponentCollection:
527
538
  point = Point(point[0], point[1])
528
539
 
529
540
  results = []
530
- for component in self._components:
541
+ for component in self._items:
531
542
  if component.position.distance_to(point) <= radius:
532
543
  results.append(component)
533
544
  return results
@@ -559,21 +570,21 @@ class ComponentCollection:
559
570
  component.set_property(key, str(value))
560
571
 
561
572
  if matching:
562
- self._modified = True
573
+ self._mark_modified()
563
574
 
564
575
  logger.info(f"Bulk updated {len(matching)} components")
565
576
  return len(matching)
566
577
 
567
578
  def sort_by_reference(self):
568
579
  """Sort components by reference designator."""
569
- self._components.sort(key=lambda c: c.reference)
580
+ self._items.sort(key=lambda c: c.reference)
570
581
 
571
582
  def sort_by_position(self, by_x: bool = True):
572
583
  """Sort components by position."""
573
584
  if by_x:
574
- self._components.sort(key=lambda c: (c.position.x, c.position.y))
585
+ self._items.sort(key=lambda c: (c.position.x, c.position.y))
575
586
  else:
576
- self._components.sort(key=lambda c: (c.position.y, c.position.x))
587
+ self._items.sort(key=lambda c: (c.position.y, c.position.x))
577
588
 
578
589
  def validate_all(self) -> List[ValidationIssue]:
579
590
  """Validate all components in collection."""
@@ -581,12 +592,12 @@ class ComponentCollection:
581
592
  validator = SchematicValidator()
582
593
 
583
594
  # Validate individual components
584
- for component in self._components:
595
+ for component in self._items:
585
596
  issues = component.validate()
586
597
  all_issues.extend(issues)
587
598
 
588
599
  # Validate collection-level rules
589
- references = [c.reference for c in self._components]
600
+ references = [c.reference for c in self._items]
590
601
  if len(references) != len(set(references)):
591
602
  # Find duplicates
592
603
  seen = set()
@@ -610,7 +621,7 @@ class ComponentCollection:
610
621
  lib_counts = {}
611
622
  value_counts = {}
612
623
 
613
- for component in self._components:
624
+ for component in self._items:
614
625
  # Count by library
615
626
  lib = component.library
616
627
  lib_counts[lib] = lib_counts.get(lib, 0) + 1
@@ -621,36 +632,47 @@ class ComponentCollection:
621
632
  value_counts[value] = value_counts.get(value, 0) + 1
622
633
 
623
634
  return {
624
- "total_components": len(self._components),
635
+ "total_components": len(self._items),
625
636
  "unique_references": len(self._reference_index),
626
637
  "libraries_used": len(lib_counts),
627
638
  "library_breakdown": lib_counts,
628
639
  "most_common_values": sorted(value_counts.items(), key=lambda x: x[1], reverse=True)[
629
640
  :10
630
641
  ],
631
- "modified": self._modified,
642
+ "modified": self.is_modified(),
632
643
  }
633
644
 
634
645
  # Collection interface
635
- def __len__(self) -> int:
636
- """Number of components."""
637
- return len(self._components)
638
-
639
- def __iter__(self) -> Iterator[Component]:
640
- """Iterate over components."""
641
- return iter(self._components)
646
+ # __len__, __iter__ inherited from BaseCollection
642
647
 
643
648
  def __getitem__(self, key: Union[int, str]) -> Component:
644
- """Get component by index or reference."""
645
- if isinstance(key, int):
646
- return self._components[key]
647
- elif isinstance(key, str):
649
+ """
650
+ Get component by index, UUID, or reference.
651
+
652
+ Args:
653
+ key: Integer index, UUID string, or reference string
654
+
655
+ Returns:
656
+ Component at the specified location
657
+
658
+ Raises:
659
+ KeyError: If UUID or reference not found
660
+ IndexError: If index out of range
661
+ TypeError: If key is invalid type
662
+ """
663
+ if isinstance(key, str):
664
+ # Try reference first (most common use case)
648
665
  component = self._reference_index.get(key)
649
- if component is None:
666
+ if component is not None:
667
+ return component
668
+ # Fall back to UUID lookup (from base class)
669
+ try:
670
+ return super().__getitem__(key)
671
+ except KeyError:
650
672
  raise KeyError(f"Component not found: {key}")
651
- return component
652
673
  else:
653
- raise TypeError(f"Invalid key type: {type(key)}")
674
+ # Integer index (from base class)
675
+ return super().__getitem__(key)
654
676
 
655
677
  def __contains__(self, reference: str) -> bool:
656
678
  """Check if reference exists."""
@@ -658,8 +680,11 @@ class ComponentCollection:
658
680
 
659
681
  # Internal methods
660
682
  def _add_to_indexes(self, component: Component):
661
- """Add component to all indexes."""
662
- self._components.append(component)
683
+ """Add component to all indexes (base + component-specific)."""
684
+ # Add to base collection (UUID index)
685
+ self._add_item(component)
686
+
687
+ # Add to reference index
663
688
  self._reference_index[component.reference] = component
664
689
 
665
690
  # Add to lib_id index
@@ -676,8 +701,8 @@ class ComponentCollection:
676
701
  self._value_index[value].append(component)
677
702
 
678
703
  def _remove_from_indexes(self, component: Component):
679
- """Remove component from all indexes."""
680
- self._components.remove(component)
704
+ """Remove component from component-specific indexes (not base UUID index)."""
705
+ # Remove from reference index
681
706
  del self._reference_index[component.reference]
682
707
 
683
708
  # Remove from lib_id index
@@ -700,10 +725,7 @@ class ComponentCollection:
700
725
  component = self._reference_index[old_ref]
701
726
  del self._reference_index[old_ref]
702
727
  self._reference_index[new_ref] = component
703
-
704
- def _mark_modified(self):
705
- """Mark collection as modified."""
706
- self._modified = True
728
+ # Note: UUID doesn't change when reference changes, so base index is unaffected
707
729
 
708
730
  def _generate_reference(self, lib_id: str) -> str:
709
731
  """Generate unique reference for component."""
@@ -725,7 +747,7 @@ class ComponentCollection:
725
747
  grid_size = 10.0 # 10mm grid
726
748
  max_per_row = 10
727
749
 
728
- row = len(self._components) // max_per_row
729
- col = len(self._components) % max_per_row
750
+ row = len(self._items) // max_per_row
751
+ col = len(self._items) % max_per_row
730
752
 
731
753
  return Point(col * grid_size, row * grid_size)
@@ -6,8 +6,8 @@ This module centralizes all magic numbers and configuration values
6
6
  to make them easily configurable and maintainable.
7
7
  """
8
8
 
9
- from dataclasses import dataclass
10
- from typing import Any, Dict, Tuple
9
+ from dataclasses import dataclass, field
10
+ from typing import Any, Dict, List, Tuple
11
11
 
12
12
 
13
13
  @dataclass
@@ -57,20 +57,102 @@ class DefaultValues:
57
57
 
58
58
  project_name: str = "untitled"
59
59
  stroke_width: float = 0.0
60
+ stroke_type: str = "default"
61
+ fill_type: str = "none"
60
62
  font_size: float = 1.27
61
63
  pin_name_size: float = 1.27
62
64
  pin_number_size: float = 1.27
63
65
 
64
66
 
67
+ @dataclass
68
+ class FileFormatConstants:
69
+ """KiCAD file format identifiers and version strings."""
70
+
71
+ file_type: str = "kicad_sch"
72
+ generator_default: str = "eeschema"
73
+ version_default: str = "20250114"
74
+ generator_version_default: str = "9.0"
75
+
76
+
77
+ @dataclass
78
+ class PaperSizeConstants:
79
+ """Standard paper size definitions."""
80
+
81
+ default: str = "A4"
82
+ valid_sizes: List[str] = field(
83
+ default_factory=lambda: ["A4", "A3", "A2", "A1", "A0", "Letter", "Legal", "Tabloid"]
84
+ )
85
+
86
+
87
+ @dataclass
88
+ class FieldNames:
89
+ """Common S-expression field names to avoid typos."""
90
+
91
+ # File structure
92
+ version: str = "version"
93
+ generator: str = "generator"
94
+ generator_version: str = "generator_version"
95
+ uuid: str = "uuid"
96
+ paper: str = "paper"
97
+
98
+ # Positioning
99
+ at: str = "at"
100
+ xy: str = "xy"
101
+ pts: str = "pts"
102
+ start: str = "start"
103
+ end: str = "end"
104
+ mid: str = "mid"
105
+ center: str = "center"
106
+ radius: str = "radius"
107
+
108
+ # Styling
109
+ stroke: str = "stroke"
110
+ fill: str = "fill"
111
+ width: str = "width"
112
+ type: str = "type"
113
+ color: str = "color"
114
+
115
+ # Text/Font
116
+ font: str = "font"
117
+ size: str = "size"
118
+ effects: str = "effects"
119
+
120
+ # Components
121
+ pin: str = "pin"
122
+ property: str = "property"
123
+ symbol: str = "symbol"
124
+ lib_id: str = "lib_id"
125
+
126
+ # Graphics
127
+ polyline: str = "polyline"
128
+ arc: str = "arc"
129
+ circle: str = "circle"
130
+ rectangle: str = "rectangle"
131
+ bezier: str = "bezier"
132
+
133
+ # Connection elements
134
+ wire: str = "wire"
135
+ junction: str = "junction"
136
+ no_connect: str = "no_connect"
137
+ label: str = "label"
138
+
139
+ # Hierarchical
140
+ sheet: str = "sheet"
141
+ sheet_instances: str = "sheet_instances"
142
+
143
+
65
144
  class KiCADConfig:
66
145
  """Central configuration class for KiCAD schematic API."""
67
146
 
68
- def __init__(self):
147
+ def __init__(self) -> None:
69
148
  self.properties = PropertyOffsets()
70
149
  self.grid = GridSettings()
71
150
  self.sheet = SheetSettings()
72
151
  self.tolerance = ToleranceSettings()
73
152
  self.defaults = DefaultValues()
153
+ self.file_format = FileFormatConstants()
154
+ self.paper = PaperSizeConstants()
155
+ self.fields = FieldNames()
74
156
 
75
157
  # Names that should not generate title_block (for backward compatibility)
76
158
  # Include test schematic names to maintain reference compatibility
@@ -0,0 +1,5 @@
1
+ """Factory classes for creating schematic elements."""
2
+
3
+ from .element_factory import ElementFactory
4
+
5
+ __all__ = ["ElementFactory"]
@@ -0,0 +1,276 @@
1
+ """
2
+ Element Factory for creating schematic elements from dictionaries.
3
+
4
+ Centralizes object creation logic that was previously duplicated in Schematic.__init__.
5
+ """
6
+
7
+ import uuid
8
+ from typing import Any, Dict, List
9
+
10
+ from ..types import (
11
+ HierarchicalLabelShape,
12
+ Junction,
13
+ Label,
14
+ LabelType,
15
+ Net,
16
+ NoConnect,
17
+ Point,
18
+ Text,
19
+ Wire,
20
+ WireType,
21
+ )
22
+
23
+
24
+ def point_from_dict_or_tuple(position: Any) -> Point:
25
+ """Convert position data (dict or tuple) to Point object."""
26
+ if isinstance(position, dict):
27
+ return Point(position.get("x", 0), position.get("y", 0))
28
+ elif isinstance(position, (list, tuple)):
29
+ return Point(position[0], position[1])
30
+ elif isinstance(position, Point):
31
+ return position
32
+ else:
33
+ return Point(0, 0)
34
+
35
+
36
+ class ElementFactory:
37
+ """Factory for creating schematic elements from dictionary data."""
38
+
39
+ @staticmethod
40
+ def create_wire(wire_dict: Dict[str, Any]) -> Wire:
41
+ """
42
+ Create Wire object from dictionary.
43
+
44
+ Args:
45
+ wire_dict: Dictionary containing wire data
46
+
47
+ Returns:
48
+ Wire object
49
+ """
50
+ points = []
51
+ for point_data in wire_dict.get("points", []):
52
+ if isinstance(point_data, dict):
53
+ points.append(Point(point_data["x"], point_data["y"]))
54
+ elif isinstance(point_data, (list, tuple)):
55
+ points.append(Point(point_data[0], point_data[1]))
56
+ else:
57
+ points.append(point_data)
58
+
59
+ return Wire(
60
+ uuid=wire_dict.get("uuid", str(uuid.uuid4())),
61
+ points=points,
62
+ wire_type=WireType(wire_dict.get("wire_type", "wire")),
63
+ stroke_width=wire_dict.get("stroke_width", 0.0),
64
+ stroke_type=wire_dict.get("stroke_type", "default"),
65
+ )
66
+
67
+ @staticmethod
68
+ def create_junction(junction_dict: Dict[str, Any]) -> Junction:
69
+ """
70
+ Create Junction object from dictionary.
71
+
72
+ Args:
73
+ junction_dict: Dictionary containing junction data
74
+
75
+ Returns:
76
+ Junction object
77
+ """
78
+ position = junction_dict.get("position", {"x": 0, "y": 0})
79
+ pos = point_from_dict_or_tuple(position)
80
+
81
+ return Junction(
82
+ uuid=junction_dict.get("uuid", str(uuid.uuid4())),
83
+ position=pos,
84
+ diameter=junction_dict.get("diameter", 0),
85
+ color=junction_dict.get("color", (0, 0, 0, 0)),
86
+ )
87
+
88
+ @staticmethod
89
+ def create_text(text_dict: Dict[str, Any]) -> Text:
90
+ """
91
+ Create Text object from dictionary.
92
+
93
+ Args:
94
+ text_dict: Dictionary containing text data
95
+
96
+ Returns:
97
+ Text object
98
+ """
99
+ position = text_dict.get("position", {"x": 0, "y": 0})
100
+ pos = point_from_dict_or_tuple(position)
101
+
102
+ return Text(
103
+ uuid=text_dict.get("uuid", str(uuid.uuid4())),
104
+ position=pos,
105
+ text=text_dict.get("text", ""),
106
+ rotation=text_dict.get("rotation", 0.0),
107
+ size=text_dict.get("size", 1.27),
108
+ exclude_from_sim=text_dict.get("exclude_from_sim", False),
109
+ )
110
+
111
+ @staticmethod
112
+ def create_label(label_dict: Dict[str, Any]) -> Label:
113
+ """
114
+ Create Label object from dictionary.
115
+
116
+ Args:
117
+ label_dict: Dictionary containing label data
118
+
119
+ Returns:
120
+ Label object
121
+ """
122
+ position = label_dict.get("position", {"x": 0, "y": 0})
123
+ pos = point_from_dict_or_tuple(position)
124
+
125
+ return Label(
126
+ uuid=label_dict.get("uuid", str(uuid.uuid4())),
127
+ position=pos,
128
+ text=label_dict.get("text", ""),
129
+ label_type=LabelType(label_dict.get("label_type", "local")),
130
+ rotation=label_dict.get("rotation", 0.0),
131
+ size=label_dict.get("size", 1.27),
132
+ shape=(
133
+ HierarchicalLabelShape(label_dict.get("shape"))
134
+ if label_dict.get("shape")
135
+ else None
136
+ ),
137
+ )
138
+
139
+ @staticmethod
140
+ def create_no_connect(no_connect_dict: Dict[str, Any]) -> NoConnect:
141
+ """
142
+ Create NoConnect object from dictionary.
143
+
144
+ Args:
145
+ no_connect_dict: Dictionary containing no-connect data
146
+
147
+ Returns:
148
+ NoConnect object
149
+ """
150
+ position = no_connect_dict.get("position", {"x": 0, "y": 0})
151
+ pos = point_from_dict_or_tuple(position)
152
+
153
+ return NoConnect(
154
+ uuid=no_connect_dict.get("uuid", str(uuid.uuid4())),
155
+ position=pos,
156
+ )
157
+
158
+ @staticmethod
159
+ def create_net(net_dict: Dict[str, Any]) -> Net:
160
+ """
161
+ Create Net object from dictionary.
162
+
163
+ Args:
164
+ net_dict: Dictionary containing net data
165
+
166
+ Returns:
167
+ Net object
168
+ """
169
+ return Net(
170
+ name=net_dict.get("name", ""),
171
+ components=net_dict.get("components", []),
172
+ wires=net_dict.get("wires", []),
173
+ labels=net_dict.get("labels", []),
174
+ )
175
+
176
+ @staticmethod
177
+ def create_wires_from_list(wire_data: List[Any]) -> List[Wire]:
178
+ """
179
+ Create list of Wire objects from list of dictionaries.
180
+
181
+ Args:
182
+ wire_data: List of wire dictionaries
183
+
184
+ Returns:
185
+ List of Wire objects
186
+ """
187
+ wires = []
188
+ for wire_dict in wire_data:
189
+ if isinstance(wire_dict, dict):
190
+ wires.append(ElementFactory.create_wire(wire_dict))
191
+ return wires
192
+
193
+ @staticmethod
194
+ def create_junctions_from_list(junction_data: List[Any]) -> List[Junction]:
195
+ """
196
+ Create list of Junction objects from list of dictionaries.
197
+
198
+ Args:
199
+ junction_data: List of junction dictionaries
200
+
201
+ Returns:
202
+ List of Junction objects
203
+ """
204
+ junctions = []
205
+ for junction_dict in junction_data:
206
+ if isinstance(junction_dict, dict):
207
+ junctions.append(ElementFactory.create_junction(junction_dict))
208
+ return junctions
209
+
210
+ @staticmethod
211
+ def create_texts_from_list(text_data: List[Any]) -> List[Text]:
212
+ """
213
+ Create list of Text objects from list of dictionaries.
214
+
215
+ Args:
216
+ text_data: List of text dictionaries
217
+
218
+ Returns:
219
+ List of Text objects
220
+ """
221
+ texts = []
222
+ for text_dict in text_data:
223
+ if isinstance(text_dict, dict):
224
+ texts.append(ElementFactory.create_text(text_dict))
225
+ return texts
226
+
227
+ @staticmethod
228
+ def create_labels_from_list(label_data: List[Any]) -> List[Label]:
229
+ """
230
+ Create list of Label objects from list of dictionaries.
231
+
232
+ Args:
233
+ label_data: List of label dictionaries
234
+
235
+ Returns:
236
+ List of Label objects
237
+ """
238
+ labels = []
239
+ for label_dict in label_data:
240
+ if isinstance(label_dict, dict):
241
+ labels.append(ElementFactory.create_label(label_dict))
242
+ return labels
243
+
244
+ @staticmethod
245
+ def create_no_connects_from_list(no_connect_data: List[Any]) -> List[NoConnect]:
246
+ """
247
+ Create list of NoConnect objects from list of dictionaries.
248
+
249
+ Args:
250
+ no_connect_data: List of no-connect dictionaries
251
+
252
+ Returns:
253
+ List of NoConnect objects
254
+ """
255
+ no_connects = []
256
+ for no_connect_dict in no_connect_data:
257
+ if isinstance(no_connect_dict, dict):
258
+ no_connects.append(ElementFactory.create_no_connect(no_connect_dict))
259
+ return no_connects
260
+
261
+ @staticmethod
262
+ def create_nets_from_list(net_data: List[Any]) -> List[Net]:
263
+ """
264
+ Create list of Net objects from list of dictionaries.
265
+
266
+ Args:
267
+ net_data: List of net dictionaries
268
+
269
+ Returns:
270
+ List of Net objects
271
+ """
272
+ nets = []
273
+ for net_dict in net_data:
274
+ if isinstance(net_dict, dict):
275
+ nets.append(ElementFactory.create_net(net_dict))
276
+ return nets
@@ -55,7 +55,9 @@ class ExactFormatter:
55
55
  self.rules["generator"] = FormatRule(inline=True, quote_indices={1})
56
56
  self.rules["generator_version"] = FormatRule(inline=True, quote_indices={1})
57
57
  self.rules["uuid"] = FormatRule(inline=True, quote_indices={1})
58
- self.rules["paper"] = FormatRule(inline=True, quote_indices={1}) # Paper size should be quoted per KiCad format
58
+ self.rules["paper"] = FormatRule(
59
+ inline=True, quote_indices={1}
60
+ ) # Paper size should be quoted per KiCad format
59
61
 
60
62
  # Title block
61
63
  self.rules["title_block"] = FormatRule(inline=False)