kicad-sch-api 0.4.1__py3-none-any.whl → 0.5.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 (66) hide show
  1. kicad_sch_api/__init__.py +67 -2
  2. kicad_sch_api/cli/kicad_to_python.py +169 -0
  3. kicad_sch_api/collections/__init__.py +23 -8
  4. kicad_sch_api/collections/base.py +369 -59
  5. kicad_sch_api/collections/components.py +1376 -187
  6. kicad_sch_api/collections/junctions.py +129 -289
  7. kicad_sch_api/collections/labels.py +391 -287
  8. kicad_sch_api/collections/wires.py +202 -316
  9. kicad_sch_api/core/__init__.py +37 -2
  10. kicad_sch_api/core/component_bounds.py +34 -12
  11. kicad_sch_api/core/components.py +146 -7
  12. kicad_sch_api/core/config.py +25 -12
  13. kicad_sch_api/core/connectivity.py +692 -0
  14. kicad_sch_api/core/exceptions.py +175 -0
  15. kicad_sch_api/core/factories/element_factory.py +3 -1
  16. kicad_sch_api/core/formatter.py +24 -7
  17. kicad_sch_api/core/geometry.py +94 -5
  18. kicad_sch_api/core/managers/__init__.py +4 -0
  19. kicad_sch_api/core/managers/base.py +76 -0
  20. kicad_sch_api/core/managers/file_io.py +3 -1
  21. kicad_sch_api/core/managers/format_sync.py +3 -2
  22. kicad_sch_api/core/managers/graphics.py +3 -2
  23. kicad_sch_api/core/managers/hierarchy.py +661 -0
  24. kicad_sch_api/core/managers/metadata.py +4 -2
  25. kicad_sch_api/core/managers/sheet.py +52 -14
  26. kicad_sch_api/core/managers/text_elements.py +3 -2
  27. kicad_sch_api/core/managers/validation.py +3 -2
  28. kicad_sch_api/core/managers/wire.py +112 -54
  29. kicad_sch_api/core/parsing_utils.py +63 -0
  30. kicad_sch_api/core/pin_utils.py +103 -9
  31. kicad_sch_api/core/schematic.py +343 -29
  32. kicad_sch_api/core/types.py +79 -7
  33. kicad_sch_api/exporters/__init__.py +10 -0
  34. kicad_sch_api/exporters/python_generator.py +610 -0
  35. kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
  36. kicad_sch_api/geometry/__init__.py +15 -3
  37. kicad_sch_api/geometry/routing.py +211 -0
  38. kicad_sch_api/parsers/elements/label_parser.py +30 -8
  39. kicad_sch_api/parsers/elements/symbol_parser.py +255 -83
  40. kicad_sch_api/utils/logging.py +555 -0
  41. kicad_sch_api/utils/logging_decorators.py +587 -0
  42. kicad_sch_api/utils/validation.py +16 -22
  43. kicad_sch_api/wrappers/__init__.py +14 -0
  44. kicad_sch_api/wrappers/base.py +89 -0
  45. kicad_sch_api/wrappers/wire.py +198 -0
  46. kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
  47. kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
  48. kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
  49. {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
  50. mcp_server/__init__.py +34 -0
  51. mcp_server/example_logging_integration.py +506 -0
  52. mcp_server/models.py +252 -0
  53. mcp_server/server.py +357 -0
  54. mcp_server/tools/__init__.py +32 -0
  55. mcp_server/tools/component_tools.py +516 -0
  56. mcp_server/tools/connectivity_tools.py +532 -0
  57. mcp_server/tools/consolidated_tools.py +1216 -0
  58. mcp_server/tools/pin_discovery.py +333 -0
  59. mcp_server/utils/__init__.py +38 -0
  60. mcp_server/utils/logging.py +127 -0
  61. mcp_server/utils.py +36 -0
  62. kicad_sch_api-0.4.1.dist-info/METADATA +0 -491
  63. kicad_sch_api-0.4.1.dist-info/RECORD +0 -87
  64. kicad_sch_api-0.4.1.dist-info/entry_points.txt +0 -2
  65. {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
  66. {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,175 @@
1
+ """
2
+ Exception hierarchy for kicad-sch-api.
3
+
4
+ Provides a structured exception hierarchy for better error handling and debugging.
5
+ All exceptions inherit from the base KiCadSchError class.
6
+ """
7
+
8
+ from typing import Any, List, Optional, TYPE_CHECKING
9
+
10
+ # Import validation types for type hints
11
+ # ValidationLevel is imported at runtime in methods that need it
12
+ if TYPE_CHECKING:
13
+ from ..utils.validation import ValidationIssue
14
+
15
+
16
+ class KiCadSchError(Exception):
17
+ """Base exception for all kicad-sch-api errors."""
18
+
19
+ pass
20
+
21
+
22
+ class ValidationError(KiCadSchError):
23
+ """
24
+ Raised when validation fails.
25
+
26
+ Supports rich error context with field/value information and can collect
27
+ multiple validation issues.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ message: str,
33
+ issues: Optional[List["ValidationIssue"]] = None,
34
+ field: str = "",
35
+ value: Any = None,
36
+ ):
37
+ """
38
+ Initialize validation error with context.
39
+
40
+ Args:
41
+ message: Error message describing the validation failure
42
+ issues: List of validation issues (for collecting multiple errors)
43
+ field: The field name that failed validation
44
+ value: The invalid value that was provided
45
+ """
46
+ self.issues = issues or []
47
+ self.field = field
48
+ self.value = value
49
+ super().__init__(message)
50
+
51
+ def add_issue(self, issue: "ValidationIssue") -> None:
52
+ """Add a validation issue to this error."""
53
+ self.issues.append(issue)
54
+
55
+ def get_errors(self) -> List["ValidationIssue"]:
56
+ """Get only error-level issues."""
57
+ # Import here to avoid circular dependency
58
+ from ..utils.validation import ValidationLevel
59
+
60
+ return [
61
+ issue
62
+ for issue in self.issues
63
+ if hasattr(issue, 'level') and issue.level in (ValidationLevel.ERROR, ValidationLevel.CRITICAL)
64
+ ]
65
+
66
+ def get_warnings(self) -> List["ValidationIssue"]:
67
+ """Get only warning-level issues."""
68
+ # Import here to avoid circular dependency
69
+ from ..utils.validation import ValidationLevel
70
+
71
+ return [issue for issue in self.issues if hasattr(issue, 'level') and issue.level == ValidationLevel.WARNING]
72
+
73
+
74
+ class ReferenceError(ValidationError):
75
+ """Raised when a component reference is invalid."""
76
+
77
+ pass
78
+
79
+
80
+ class LibraryError(ValidationError):
81
+ """Raised when a library or symbol reference is invalid."""
82
+
83
+ pass
84
+
85
+
86
+ class GeometryError(ValidationError):
87
+ """Raised when geometry validation fails (positions, shapes, dimensions)."""
88
+
89
+ pass
90
+
91
+
92
+ class NetError(ValidationError):
93
+ """Raised when a net specification or operation is invalid."""
94
+
95
+ pass
96
+
97
+
98
+ class ParseError(KiCadSchError):
99
+ """Raised when parsing a schematic file fails."""
100
+
101
+ pass
102
+
103
+
104
+ class FormatError(KiCadSchError):
105
+ """Raised when formatting a schematic file fails."""
106
+
107
+ pass
108
+
109
+
110
+ class CollectionError(KiCadSchError):
111
+ """Raised when a collection operation fails."""
112
+
113
+ pass
114
+
115
+
116
+ class ElementNotFoundError(CollectionError):
117
+ """Raised when an element is not found in a collection."""
118
+
119
+ def __init__(self, message: str, element_type: str = "", identifier: str = ""):
120
+ """
121
+ Initialize element not found error.
122
+
123
+ Args:
124
+ message: Error message
125
+ element_type: Type of element (e.g., 'component', 'wire', 'junction')
126
+ identifier: The identifier used to search (e.g., 'R1', UUID)
127
+ """
128
+ self.element_type = element_type
129
+ self.identifier = identifier
130
+ super().__init__(message)
131
+
132
+
133
+ class DuplicateElementError(CollectionError):
134
+ """Raised when attempting to add a duplicate element."""
135
+
136
+ def __init__(self, message: str, element_type: str = "", identifier: str = ""):
137
+ """
138
+ Initialize duplicate element error.
139
+
140
+ Args:
141
+ message: Error message
142
+ element_type: Type of element (e.g., 'component', 'wire', 'junction')
143
+ identifier: The duplicate identifier (e.g., 'R1', UUID)
144
+ """
145
+ self.element_type = element_type
146
+ self.identifier = identifier
147
+ super().__init__(message)
148
+
149
+
150
+ class CollectionOperationError(CollectionError):
151
+ """Raised when a collection operation fails for reasons other than not found/duplicate."""
152
+
153
+ pass
154
+
155
+
156
+ class FileOperationError(KiCadSchError):
157
+ """Raised when a file I/O operation fails."""
158
+
159
+ pass
160
+
161
+
162
+ class CLIError(KiCadSchError):
163
+ """Raised when KiCad CLI execution fails."""
164
+
165
+ pass
166
+
167
+
168
+ class SchematicStateError(KiCadSchError):
169
+ """
170
+ Raised when an operation requires specific schematic state.
171
+
172
+ Examples: schematic must be saved before export, etc.
173
+ """
174
+
175
+ pass
@@ -126,7 +126,7 @@ class ElementFactory:
126
126
  uuid=label_dict.get("uuid", str(uuid.uuid4())),
127
127
  position=pos,
128
128
  text=label_dict.get("text", ""),
129
- label_type=LabelType(label_dict.get("label_type", "local")),
129
+ label_type=LabelType(label_dict.get("label_type", "label")),
130
130
  rotation=label_dict.get("rotation", 0.0),
131
131
  size=label_dict.get("size", 1.27),
132
132
  shape=(
@@ -134,6 +134,8 @@ class ElementFactory:
134
134
  if label_dict.get("shape")
135
135
  else None
136
136
  ),
137
+ justify_h=label_dict.get("justify_h", "left"),
138
+ justify_v=label_dict.get("justify_v", "bottom"),
137
139
  )
138
140
 
139
141
  @staticmethod
@@ -125,6 +125,10 @@ class ExactFormatter:
125
125
  self.rules["global_label"] = FormatRule(inline=False, quote_indices={1})
126
126
  self.rules["hierarchical_label"] = FormatRule(inline=False, quote_indices={1})
127
127
 
128
+ # Text elements
129
+ self.rules["text"] = FormatRule(inline=False, quote_indices={1})
130
+ self.rules["text_box"] = FormatRule(inline=False, quote_indices={1})
131
+
128
132
  # Effects and text formatting
129
133
  self.rules["effects"] = FormatRule(inline=False)
130
134
  self.rules["font"] = FormatRule(inline=False)
@@ -279,6 +283,8 @@ class ExactFormatter:
279
283
  "junction",
280
284
  "label",
281
285
  "hierarchical_label",
286
+ "text",
287
+ "text_box",
282
288
  "polyline",
283
289
  "rectangle",
284
290
  ):
@@ -384,7 +390,8 @@ class ExactFormatter:
384
390
  result += f"\n{next_indent}{self._format_element(element, indent_level + 1)}"
385
391
  else:
386
392
  if i in rule.quote_indices and isinstance(element, str):
387
- result += f' "{element}"'
393
+ escaped_element = self._escape_string(element)
394
+ result += f' "{escaped_element}"'
388
395
  else:
389
396
  result += f" {self._format_element(element, 0)}"
390
397
 
@@ -396,7 +403,8 @@ class ExactFormatter:
396
403
  indent = "\t" * indent_level
397
404
  next_indent = "\t" * (indent_level + 1)
398
405
 
399
- result = f"({lst[0]}"
406
+ tag = str(lst[0])
407
+ result = f"({tag}"
400
408
 
401
409
  for i, element in enumerate(lst[1:], 1):
402
410
  if isinstance(element, list):
@@ -425,9 +433,18 @@ class ExactFormatter:
425
433
  return True
426
434
 
427
435
  def _escape_string(self, text: str) -> str:
428
- """Escape quotes in string for S-expression formatting."""
429
- # Replace double quotes with escaped quotes
430
- return text.replace('"', '\\"')
436
+ """Escape special characters in string for S-expression formatting."""
437
+ # Escape backslashes first (must be done before other replacements)
438
+ text = text.replace('\\', '\\\\')
439
+ # Escape double quotes
440
+ text = text.replace('"', '\\"')
441
+ # Escape newlines (convert actual newlines to escaped representation)
442
+ text = text.replace('\n', '\\n')
443
+ # Escape carriage returns
444
+ text = text.replace('\r', '\\r')
445
+ # Escape tabs
446
+ text = text.replace('\t', '\\t')
447
+ return text
431
448
 
432
449
  def _needs_quoting(self, text: str) -> bool:
433
450
  """Check if string needs to be quoted."""
@@ -466,8 +483,8 @@ class ExactFormatter:
466
483
  for item in lst[1:]:
467
484
  if isinstance(item, list) and len(item) >= 1:
468
485
  tag = str(item[0])
469
- if tag in ["version", "generator", "generator_version"] and len(item) >= 2:
470
- if tag in ["generator", "generator_version"]:
486
+ if tag in ["version", "generator", "generator_version", "uuid"] and len(item) >= 2:
487
+ if tag in ["generator", "generator_version", "uuid"]:
471
488
  header_parts.append(f'({tag} "{item[1]}")')
472
489
  else:
473
490
  header_parts.append(f"({tag} {item[1]})")
@@ -7,7 +7,7 @@ migrated from circuit-synth for improved maintainability.
7
7
 
8
8
  import logging
9
9
  import math
10
- from typing import Optional, Tuple
10
+ from typing import Optional, Tuple, Union
11
11
 
12
12
  from .types import Point
13
13
 
@@ -71,20 +71,29 @@ def apply_transformation(
71
71
 
72
72
  Migrated from circuit-synth for accurate pin position calculation.
73
73
 
74
+ CRITICAL: Symbol coordinates use normal Y-axis (+Y is up), but schematic
75
+ coordinates use inverted Y-axis (+Y is down). We must negate Y from symbol
76
+ space before applying transformations.
77
+
74
78
  Args:
75
- point: Point to transform (x, y) relative to origin
76
- origin: Component origin point
79
+ point: Point to transform (x, y) relative to origin in SYMBOL space
80
+ origin: Component origin point in SCHEMATIC space
77
81
  rotation: Rotation in degrees (0, 90, 180, 270)
78
82
  mirror: Mirror axis ("x" or "y" or None)
79
83
 
80
84
  Returns:
81
- Transformed absolute position (x, y)
85
+ Transformed absolute position (x, y) in SCHEMATIC space
82
86
  """
83
87
  x, y = point
84
88
 
85
89
  logger.debug(f"Transforming point ({x}, {y}) with rotation={rotation}°, mirror={mirror}")
86
90
 
87
- # Apply mirroring first
91
+ # CRITICAL: Negate Y to convert from symbol space (normal Y) to schematic space (inverted Y)
92
+ # This must happen BEFORE rotation/mirroring
93
+ y = -y
94
+ logger.debug(f"After Y-axis inversion (symbol→schematic): ({x}, {y})")
95
+
96
+ # Apply mirroring
88
97
  if mirror == "x":
89
98
  x = -x
90
99
  logger.debug(f"After X mirror: ({x}, {y})")
@@ -109,3 +118,83 @@ def apply_transformation(
109
118
 
110
119
  logger.debug(f"Final absolute position: ({final_x}, {final_y})")
111
120
  return (final_x, final_y)
121
+
122
+
123
+ def calculate_position_for_pin(
124
+ pin_local_position: Union[Point, Tuple[float, float]],
125
+ desired_pin_position: Union[Point, Tuple[float, float]],
126
+ rotation: float = 0.0,
127
+ mirror: Optional[str] = None,
128
+ grid_size: float = 1.27,
129
+ ) -> Point:
130
+ """
131
+ Calculate component position needed to place a specific pin at a desired location.
132
+
133
+ This is the inverse of get_pin_position() - given where you want a pin to be,
134
+ it calculates where the component center needs to be placed.
135
+
136
+ Useful for aligning components by their pins rather than their centers, which
137
+ is essential for clean horizontal signal flows without unnecessary wire jogs.
138
+
139
+ Args:
140
+ pin_local_position: Pin position in symbol space (from symbol definition)
141
+ desired_pin_position: Where you want the pin to be in schematic space
142
+ rotation: Component rotation in degrees (0, 90, 180, 270)
143
+ mirror: Mirror axis ("x" or "y" or None) - currently unused
144
+ grid_size: Grid size for snapping result (default 1.27mm = 50mil)
145
+
146
+ Returns:
147
+ Component position that will place the pin at desired_pin_position
148
+
149
+ Example:
150
+ >>> # Place resistor so pin 2 is at (150, 100)
151
+ >>> pin_pos = Point(0, -3.81) # Pin 2 local position from symbol
152
+ >>> comp_pos = calculate_position_for_pin(pin_pos, (150, 100))
153
+ >>> # Now add component at comp_pos, and pin 2 will be at (150, 100)
154
+
155
+ Note:
156
+ The result is automatically snapped to the KiCAD grid for proper connectivity.
157
+ This function matches the behavior of SchematicSymbol.get_pin_position().
158
+ """
159
+ # Convert inputs to proper types
160
+ if isinstance(pin_local_position, Point):
161
+ pin_x, pin_y = pin_local_position.x, pin_local_position.y
162
+ else:
163
+ pin_x, pin_y = pin_local_position
164
+
165
+ if isinstance(desired_pin_position, Point):
166
+ target_x, target_y = desired_pin_position.x, desired_pin_position.y
167
+ else:
168
+ target_x, target_y = desired_pin_position
169
+
170
+ logger.debug(
171
+ f"Calculating component position for pin at local ({pin_x}, {pin_y}) "
172
+ f"to reach target ({target_x}, {target_y}) with rotation={rotation}°"
173
+ )
174
+
175
+ # Apply the same transformation that get_pin_position() uses
176
+ # This is a standard 2D rotation matrix (NO Y-axis inversion)
177
+ angle_rad = math.radians(rotation)
178
+ cos_a = math.cos(angle_rad)
179
+ sin_a = math.sin(angle_rad)
180
+
181
+ # Calculate rotated offset (same as get_pin_position)
182
+ rotated_x = pin_x * cos_a - pin_y * sin_a
183
+ rotated_y = pin_x * sin_a + pin_y * cos_a
184
+
185
+ logger.debug(f"Pin offset after rotation: ({rotated_x:.3f}, {rotated_y:.3f})")
186
+
187
+ # Calculate component origin
188
+ # Since: target = component + rotated_offset
189
+ # Therefore: component = target - rotated_offset
190
+ component_x = target_x - rotated_x
191
+ component_y = target_y - rotated_y
192
+
193
+ logger.debug(f"Calculated component position (before grid snap): ({component_x:.3f}, {component_y:.3f})")
194
+
195
+ # Snap to grid for proper KiCAD connectivity
196
+ snapped_x, snapped_y = snap_to_grid((component_x, component_y), grid_size=grid_size)
197
+
198
+ logger.debug(f"Final component position (after grid snap): ({snapped_x:.3f}, {snapped_y:.3f})")
199
+
200
+ return Point(snapped_x, snapped_y)
@@ -5,9 +5,11 @@ This package contains specialized managers for different aspects of schematic
5
5
  manipulation, enabling clean separation of concerns and better maintainability.
6
6
  """
7
7
 
8
+ from .base import BaseManager
8
9
  from .file_io import FileIOManager
9
10
  from .format_sync import FormatSyncManager
10
11
  from .graphics import GraphicsManager
12
+ from .hierarchy import HierarchyManager
11
13
  from .metadata import MetadataManager
12
14
  from .sheet import SheetManager
13
15
  from .text_elements import TextElementManager
@@ -15,9 +17,11 @@ from .validation import ValidationManager
15
17
  from .wire import WireManager
16
18
 
17
19
  __all__ = [
20
+ "BaseManager",
18
21
  "FileIOManager",
19
22
  "FormatSyncManager",
20
23
  "GraphicsManager",
24
+ "HierarchyManager",
21
25
  "MetadataManager",
22
26
  "SheetManager",
23
27
  "TextElementManager",
@@ -0,0 +1,76 @@
1
+ """Base manager class for schematic operations.
2
+
3
+ Provides a consistent interface for all manager classes and enforces
4
+ common patterns for validation and data access.
5
+ """
6
+
7
+ from abc import ABC
8
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
9
+
10
+ from ...utils.validation import ValidationIssue
11
+
12
+ if TYPE_CHECKING:
13
+ from ..schematic import Schematic
14
+
15
+
16
+ class BaseManager(ABC):
17
+ """Base class for all schematic managers.
18
+
19
+ Managers encapsulate complex operations and keep Schematic focused.
20
+ This base class provides a consistent interface and common utilities
21
+ for all managers.
22
+
23
+ Attributes:
24
+ _data: Reference to schematic data (optional, varies by manager)
25
+ _schematic: Reference to parent Schematic instance (optional)
26
+ """
27
+
28
+ def __init__(self, schematic_data: Optional[Dict[str, Any]] = None, **kwargs):
29
+ """Initialize manager with schematic data reference.
30
+
31
+ Args:
32
+ schematic_data: Reference to schematic data dictionary (optional)
33
+ **kwargs: Additional manager-specific parameters
34
+ """
35
+ self._data = schematic_data
36
+ self._schematic: Optional["Schematic"] = None
37
+
38
+ def set_schematic(self, schematic: "Schematic") -> None:
39
+ """Set reference to parent schematic.
40
+
41
+ This is called by Schematic after manager initialization to establish
42
+ bidirectional relationship.
43
+
44
+ Args:
45
+ schematic: The parent Schematic instance
46
+ """
47
+ self._schematic = schematic
48
+
49
+ @property
50
+ def schematic(self) -> Optional["Schematic"]:
51
+ """Get the parent schematic instance.
52
+
53
+ Returns:
54
+ The parent Schematic, or None if not set
55
+ """
56
+ return self._schematic
57
+
58
+ @property
59
+ def data(self) -> Optional[Dict[str, Any]]:
60
+ """Get the schematic data dictionary.
61
+
62
+ Returns:
63
+ The schematic data, or None if not set
64
+ """
65
+ return self._data
66
+
67
+ def validate(self) -> List[ValidationIssue]:
68
+ """Validate managed elements.
69
+
70
+ This is an optional method that managers can override to provide
71
+ validation. Default implementation returns empty list (no issues).
72
+
73
+ Returns:
74
+ List of validation issues found
75
+ """
76
+ return []
@@ -14,11 +14,12 @@ from ...utils.validation import ValidationError
14
14
  from ..config import config
15
15
  from ..formatter import ExactFormatter
16
16
  from ..parser import SExpressionParser
17
+ from .base import BaseManager
17
18
 
18
19
  logger = logging.getLogger(__name__)
19
20
 
20
21
 
21
- class FileIOManager:
22
+ class FileIOManager(BaseManager):
22
23
  """
23
24
  Manages file I/O operations for KiCAD schematics.
24
25
 
@@ -31,6 +32,7 @@ class FileIOManager:
31
32
 
32
33
  def __init__(self):
33
34
  """Initialize the FileIOManager."""
35
+ super().__init__()
34
36
  self._parser = SExpressionParser(preserve_format=True)
35
37
  self._formatter = ExactFormatter()
36
38
 
@@ -11,11 +11,12 @@ from typing import Any, Dict, List, Optional, Set, Union
11
11
 
12
12
  from ..components import Component
13
13
  from ..types import Point, Wire
14
+ from .base import BaseManager
14
15
 
15
16
  logger = logging.getLogger(__name__)
16
17
 
17
18
 
18
- class FormatSyncManager:
19
+ class FormatSyncManager(BaseManager):
19
20
  """
20
21
  Manages synchronization between object models and S-expression data.
21
22
 
@@ -34,7 +35,7 @@ class FormatSyncManager:
34
35
  Args:
35
36
  schematic_data: Reference to schematic data
36
37
  """
37
- self._data = schematic_data
38
+ super().__init__(schematic_data)
38
39
  self._dirty_flags: Set[str] = set()
39
40
  self._change_log: List[Dict[str, Any]] = []
40
41
  self._sync_lock = False
@@ -11,11 +11,12 @@ import uuid
11
11
  from typing import Any, Dict, List, Optional, Tuple, Union
12
12
 
13
13
  from ..types import Point
14
+ from .base import BaseManager
14
15
 
15
16
  logger = logging.getLogger(__name__)
16
17
 
17
18
 
18
- class GraphicsManager:
19
+ class GraphicsManager(BaseManager):
19
20
  """
20
21
  Manages graphic elements and drawing shapes in KiCAD schematics.
21
22
 
@@ -34,7 +35,7 @@ class GraphicsManager:
34
35
  Args:
35
36
  schematic_data: Reference to schematic data
36
37
  """
37
- self._data = schematic_data
38
+ super().__init__(schematic_data)
38
39
 
39
40
  def add_rectangle(
40
41
  self,