kicad-sch-api 0.3.0__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 (112) hide show
  1. kicad_sch_api/__init__.py +68 -3
  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/kicad_to_python.py +169 -0
  8. kicad_sch_api/cli/netlist.py +94 -0
  9. kicad_sch_api/cli/types.py +43 -0
  10. kicad_sch_api/collections/__init__.py +36 -0
  11. kicad_sch_api/collections/base.py +604 -0
  12. kicad_sch_api/collections/components.py +1623 -0
  13. kicad_sch_api/collections/junctions.py +206 -0
  14. kicad_sch_api/collections/labels.py +508 -0
  15. kicad_sch_api/collections/wires.py +292 -0
  16. kicad_sch_api/core/__init__.py +37 -2
  17. kicad_sch_api/core/collections/__init__.py +5 -0
  18. kicad_sch_api/core/collections/base.py +248 -0
  19. kicad_sch_api/core/component_bounds.py +34 -7
  20. kicad_sch_api/core/components.py +213 -52
  21. kicad_sch_api/core/config.py +110 -15
  22. kicad_sch_api/core/connectivity.py +692 -0
  23. kicad_sch_api/core/exceptions.py +175 -0
  24. kicad_sch_api/core/factories/__init__.py +5 -0
  25. kicad_sch_api/core/factories/element_factory.py +278 -0
  26. kicad_sch_api/core/formatter.py +60 -9
  27. kicad_sch_api/core/geometry.py +94 -5
  28. kicad_sch_api/core/junctions.py +26 -75
  29. kicad_sch_api/core/labels.py +324 -0
  30. kicad_sch_api/core/managers/__init__.py +30 -0
  31. kicad_sch_api/core/managers/base.py +76 -0
  32. kicad_sch_api/core/managers/file_io.py +246 -0
  33. kicad_sch_api/core/managers/format_sync.py +502 -0
  34. kicad_sch_api/core/managers/graphics.py +580 -0
  35. kicad_sch_api/core/managers/hierarchy.py +661 -0
  36. kicad_sch_api/core/managers/metadata.py +271 -0
  37. kicad_sch_api/core/managers/sheet.py +492 -0
  38. kicad_sch_api/core/managers/text_elements.py +537 -0
  39. kicad_sch_api/core/managers/validation.py +476 -0
  40. kicad_sch_api/core/managers/wire.py +410 -0
  41. kicad_sch_api/core/nets.py +305 -0
  42. kicad_sch_api/core/no_connects.py +252 -0
  43. kicad_sch_api/core/parser.py +194 -970
  44. kicad_sch_api/core/parsing_utils.py +63 -0
  45. kicad_sch_api/core/pin_utils.py +103 -9
  46. kicad_sch_api/core/schematic.py +1328 -1079
  47. kicad_sch_api/core/texts.py +316 -0
  48. kicad_sch_api/core/types.py +159 -23
  49. kicad_sch_api/core/wires.py +27 -75
  50. kicad_sch_api/exporters/__init__.py +10 -0
  51. kicad_sch_api/exporters/python_generator.py +610 -0
  52. kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
  53. kicad_sch_api/geometry/__init__.py +38 -0
  54. kicad_sch_api/geometry/font_metrics.py +22 -0
  55. kicad_sch_api/geometry/routing.py +211 -0
  56. kicad_sch_api/geometry/symbol_bbox.py +608 -0
  57. kicad_sch_api/interfaces/__init__.py +17 -0
  58. kicad_sch_api/interfaces/parser.py +76 -0
  59. kicad_sch_api/interfaces/repository.py +70 -0
  60. kicad_sch_api/interfaces/resolver.py +117 -0
  61. kicad_sch_api/parsers/__init__.py +14 -0
  62. kicad_sch_api/parsers/base.py +145 -0
  63. kicad_sch_api/parsers/elements/__init__.py +22 -0
  64. kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
  65. kicad_sch_api/parsers/elements/label_parser.py +216 -0
  66. kicad_sch_api/parsers/elements/library_parser.py +165 -0
  67. kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
  68. kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
  69. kicad_sch_api/parsers/elements/symbol_parser.py +485 -0
  70. kicad_sch_api/parsers/elements/text_parser.py +250 -0
  71. kicad_sch_api/parsers/elements/wire_parser.py +242 -0
  72. kicad_sch_api/parsers/registry.py +155 -0
  73. kicad_sch_api/parsers/utils.py +80 -0
  74. kicad_sch_api/symbols/__init__.py +18 -0
  75. kicad_sch_api/symbols/cache.py +467 -0
  76. kicad_sch_api/symbols/resolver.py +361 -0
  77. kicad_sch_api/symbols/validators.py +504 -0
  78. kicad_sch_api/utils/logging.py +555 -0
  79. kicad_sch_api/utils/logging_decorators.py +587 -0
  80. kicad_sch_api/utils/validation.py +16 -22
  81. kicad_sch_api/validation/__init__.py +25 -0
  82. kicad_sch_api/validation/erc.py +171 -0
  83. kicad_sch_api/validation/erc_models.py +203 -0
  84. kicad_sch_api/validation/pin_matrix.py +243 -0
  85. kicad_sch_api/validation/validators.py +391 -0
  86. kicad_sch_api/wrappers/__init__.py +14 -0
  87. kicad_sch_api/wrappers/base.py +89 -0
  88. kicad_sch_api/wrappers/wire.py +198 -0
  89. kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
  90. kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
  91. kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
  92. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
  93. mcp_server/__init__.py +34 -0
  94. mcp_server/example_logging_integration.py +506 -0
  95. mcp_server/models.py +252 -0
  96. mcp_server/server.py +357 -0
  97. mcp_server/tools/__init__.py +32 -0
  98. mcp_server/tools/component_tools.py +516 -0
  99. mcp_server/tools/connectivity_tools.py +532 -0
  100. mcp_server/tools/consolidated_tools.py +1216 -0
  101. mcp_server/tools/pin_discovery.py +333 -0
  102. mcp_server/utils/__init__.py +38 -0
  103. mcp_server/utils/logging.py +127 -0
  104. mcp_server/utils.py +36 -0
  105. kicad_sch_api/core/manhattan_routing.py +0 -430
  106. kicad_sch_api/core/simple_manhattan.py +0 -228
  107. kicad_sch_api/core/wire_routing.py +0 -380
  108. kicad_sch_api-0.3.0.dist-info/METADATA +0 -483
  109. kicad_sch_api-0.3.0.dist-info/RECORD +0 -31
  110. kicad_sch_api-0.3.0.dist-info/entry_points.txt +0 -2
  111. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
  112. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -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 []
@@ -0,0 +1,246 @@
1
+ """
2
+ File I/O Manager for KiCAD schematic operations.
3
+
4
+ Handles all file system interactions including loading, saving, and backup operations
5
+ while maintaining exact format preservation.
6
+ """
7
+
8
+ import logging
9
+ import time
10
+ from pathlib import Path
11
+ from typing import Any, Dict, Optional, Union
12
+
13
+ from ...utils.validation import ValidationError
14
+ from ..config import config
15
+ from ..formatter import ExactFormatter
16
+ from ..parser import SExpressionParser
17
+ from .base import BaseManager
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class FileIOManager(BaseManager):
23
+ """
24
+ Manages file I/O operations for KiCAD schematics.
25
+
26
+ Responsible for:
27
+ - Loading schematic files with validation
28
+ - Saving with format preservation
29
+ - Creating backup files
30
+ - Managing file paths and metadata
31
+ """
32
+
33
+ def __init__(self):
34
+ """Initialize the FileIOManager."""
35
+ super().__init__()
36
+ self._parser = SExpressionParser(preserve_format=True)
37
+ self._formatter = ExactFormatter()
38
+
39
+ def load_schematic(self, file_path: Union[str, Path]) -> Dict[str, Any]:
40
+ """
41
+ Load a KiCAD schematic file.
42
+
43
+ Args:
44
+ file_path: Path to .kicad_sch file
45
+
46
+ Returns:
47
+ Parsed schematic data
48
+
49
+ Raises:
50
+ FileNotFoundError: If file doesn't exist
51
+ ValidationError: If file is invalid or corrupted
52
+ """
53
+ start_time = time.time()
54
+ file_path = Path(file_path)
55
+
56
+ if not file_path.exists():
57
+ raise FileNotFoundError(f"Schematic file not found: {file_path}")
58
+
59
+ if not file_path.suffix == ".kicad_sch":
60
+ raise ValidationError(f"Not a KiCAD schematic file: {file_path}")
61
+
62
+ logger.info(f"Loading schematic: {file_path}")
63
+
64
+ try:
65
+ schematic_data = self._parser.parse_file(file_path)
66
+ load_time = time.time() - start_time
67
+ logger.info(f"Loaded schematic in {load_time:.3f}s")
68
+
69
+ return schematic_data
70
+
71
+ except Exception as e:
72
+ logger.error(f"Failed to load schematic {file_path}: {e}")
73
+ raise ValidationError(f"Invalid schematic file: {e}") from e
74
+
75
+ def save_schematic(
76
+ self,
77
+ schematic_data: Dict[str, Any],
78
+ file_path: Union[str, Path],
79
+ preserve_format: bool = True,
80
+ ) -> None:
81
+ """
82
+ Save schematic data to file.
83
+
84
+ Args:
85
+ schematic_data: Schematic data to save
86
+ file_path: Target file path
87
+ preserve_format: Whether to preserve exact formatting
88
+
89
+ Raises:
90
+ PermissionError: If file cannot be written
91
+ ValidationError: If data is invalid
92
+ """
93
+ start_time = time.time()
94
+ file_path = Path(file_path)
95
+
96
+ logger.info(f"Saving schematic: {file_path}")
97
+
98
+ try:
99
+ # Ensure parent directory exists
100
+ file_path.parent.mkdir(parents=True, exist_ok=True)
101
+
102
+ # Convert to S-expression format and save
103
+ sexp_data = self._parser._schematic_data_to_sexp(schematic_data)
104
+ formatted_content = self._formatter.format(sexp_data)
105
+
106
+ with open(file_path, "w", encoding="utf-8") as f:
107
+ f.write(formatted_content)
108
+
109
+ save_time = time.time() - start_time
110
+ logger.info(f"Saved schematic in {save_time:.3f}s")
111
+
112
+ except PermissionError as e:
113
+ logger.error(f"Permission denied saving to {file_path}: {e}")
114
+ raise
115
+ except Exception as e:
116
+ logger.error(f"Failed to save schematic to {file_path}: {e}")
117
+ raise ValidationError(f"Save failed: {e}") from e
118
+
119
+ def create_backup(self, file_path: Union[str, Path], suffix: str = ".backup") -> Path:
120
+ """
121
+ Create a backup copy of the schematic file.
122
+
123
+ Args:
124
+ file_path: Source file to backup
125
+ suffix: Backup file suffix
126
+
127
+ Returns:
128
+ Path to backup file
129
+
130
+ Raises:
131
+ FileNotFoundError: If source file doesn't exist
132
+ PermissionError: If backup cannot be created
133
+ """
134
+ file_path = Path(file_path)
135
+
136
+ if not file_path.exists():
137
+ raise FileNotFoundError(f"Cannot backup non-existent file: {file_path}")
138
+
139
+ # Create backup with timestamp if suffix doesn't include one
140
+ if suffix == ".backup":
141
+ timestamp = time.strftime("%Y%m%d_%H%M%S")
142
+ backup_path = file_path.with_suffix(f".{timestamp}.backup")
143
+ else:
144
+ backup_path = file_path.with_suffix(f"{file_path.suffix}{suffix}")
145
+
146
+ try:
147
+ # Copy file content
148
+ backup_path.write_bytes(file_path.read_bytes())
149
+ logger.info(f"Created backup: {backup_path}")
150
+ return backup_path
151
+
152
+ except Exception as e:
153
+ logger.error(f"Failed to create backup {backup_path}: {e}")
154
+ raise PermissionError(f"Backup failed: {e}") from e
155
+
156
+ def validate_file_path(self, file_path: Union[str, Path]) -> Path:
157
+ """
158
+ Validate and normalize a file path for schematic operations.
159
+
160
+ Args:
161
+ file_path: Path to validate
162
+
163
+ Returns:
164
+ Normalized Path object
165
+
166
+ Raises:
167
+ ValidationError: If path is invalid
168
+ """
169
+ file_path = Path(file_path)
170
+
171
+ # Ensure .kicad_sch extension
172
+ if not file_path.suffix:
173
+ file_path = file_path.with_suffix(".kicad_sch")
174
+ elif file_path.suffix != ".kicad_sch":
175
+ raise ValidationError(f"Invalid schematic file extension: {file_path.suffix}")
176
+
177
+ # Validate path characters
178
+ try:
179
+ file_path.resolve()
180
+ except (OSError, ValueError) as e:
181
+ raise ValidationError(f"Invalid file path: {e}") from e
182
+
183
+ return file_path
184
+
185
+ def get_file_info(self, file_path: Union[str, Path]) -> Dict[str, Any]:
186
+ """
187
+ Get file system information about a schematic file.
188
+
189
+ Args:
190
+ file_path: Path to analyze
191
+
192
+ Returns:
193
+ Dictionary with file information
194
+
195
+ Raises:
196
+ FileNotFoundError: If file doesn't exist
197
+ """
198
+ file_path = Path(file_path)
199
+
200
+ if not file_path.exists():
201
+ raise FileNotFoundError(f"File not found: {file_path}")
202
+
203
+ stat = file_path.stat()
204
+
205
+ return {
206
+ "path": str(file_path.resolve()),
207
+ "size": stat.st_size,
208
+ "modified": stat.st_mtime,
209
+ "created": getattr(stat, "st_birthtime", stat.st_ctime),
210
+ "readable": file_path.is_file() and file_path.exists(),
211
+ "writable": file_path.parent.exists() and file_path.parent.is_dir(),
212
+ "extension": file_path.suffix,
213
+ }
214
+
215
+ def create_empty_schematic_data(self) -> Dict[str, Any]:
216
+ """
217
+ Create empty schematic data structure.
218
+
219
+ Returns:
220
+ Empty schematic data dictionary
221
+ """
222
+ return {
223
+ "kicad_sch": {
224
+ "version": 20230819,
225
+ "generator": config.file_format.generator_default,
226
+ "uuid": None, # Will be set by calling code
227
+ "paper": config.paper.default,
228
+ "lib_symbols": {},
229
+ "symbol": [],
230
+ "wire": [],
231
+ "junction": [],
232
+ "label": [],
233
+ "hierarchical_label": [],
234
+ "global_label": [],
235
+ "text": [],
236
+ "text_box": [],
237
+ "polyline": [],
238
+ "rectangle": [],
239
+ "circle": [],
240
+ "arc": [],
241
+ "image": [],
242
+ "sheet": [],
243
+ "sheet_instances": [],
244
+ "symbol_instances": [],
245
+ }
246
+ }