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