kicad-sch-api 0.0.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.

Potentially problematic release.


This version of kicad-sch-api might be problematic. Click here for more details.

@@ -0,0 +1,369 @@
1
+ """
2
+ Core data types for KiCAD schematic manipulation.
3
+
4
+ This module defines the fundamental data structures used throughout kicad-sch-api,
5
+ providing a clean, type-safe interface for working with schematic elements.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from enum import Enum
10
+ from typing import Any, Dict, List, Optional, Tuple, Union
11
+ from uuid import uuid4
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class Point:
16
+ """2D point with x,y coordinates in mm."""
17
+
18
+ x: float
19
+ y: float
20
+
21
+ def __post_init__(self):
22
+ # Ensure coordinates are float
23
+ object.__setattr__(self, "x", float(self.x))
24
+ object.__setattr__(self, "y", float(self.y))
25
+
26
+ def distance_to(self, other: "Point") -> float:
27
+ """Calculate distance to another point."""
28
+ return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
29
+
30
+ def offset(self, dx: float, dy: float) -> "Point":
31
+ """Create new point offset by dx, dy."""
32
+ return Point(self.x + dx, self.y + dy)
33
+
34
+ def __str__(self) -> str:
35
+ return f"({self.x:.3f}, {self.y:.3f})"
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class Rectangle:
40
+ """Rectangle defined by two corner points."""
41
+
42
+ top_left: Point
43
+ bottom_right: Point
44
+
45
+ @property
46
+ def width(self) -> float:
47
+ """Rectangle width."""
48
+ return abs(self.bottom_right.x - self.top_left.x)
49
+
50
+ @property
51
+ def height(self) -> float:
52
+ """Rectangle height."""
53
+ return abs(self.bottom_right.y - self.top_left.y)
54
+
55
+ @property
56
+ def center(self) -> Point:
57
+ """Rectangle center point."""
58
+ return Point(
59
+ (self.top_left.x + self.bottom_right.x) / 2, (self.top_left.y + self.bottom_right.y) / 2
60
+ )
61
+
62
+ def contains(self, point: Point) -> bool:
63
+ """Check if point is inside rectangle."""
64
+ return (
65
+ self.top_left.x <= point.x <= self.bottom_right.x
66
+ and self.top_left.y <= point.y <= self.bottom_right.y
67
+ )
68
+
69
+
70
+ class PinType(Enum):
71
+ """KiCAD pin electrical types."""
72
+
73
+ INPUT = "input"
74
+ OUTPUT = "output"
75
+ BIDIRECTIONAL = "bidirectional"
76
+ TRISTATE = "tri_state"
77
+ PASSIVE = "passive"
78
+ FREE = "free"
79
+ UNSPECIFIED = "unspecified"
80
+ POWER_IN = "power_in"
81
+ POWER_OUT = "power_out"
82
+ OPEN_COLLECTOR = "open_collector"
83
+ OPEN_EMITTER = "open_emitter"
84
+ NO_CONNECT = "no_connect"
85
+
86
+
87
+ class PinShape(Enum):
88
+ """KiCAD pin graphical shapes."""
89
+
90
+ LINE = "line"
91
+ INVERTED = "inverted"
92
+ CLOCK = "clock"
93
+ INVERTED_CLOCK = "inverted_clock"
94
+ INPUT_LOW = "input_low"
95
+ CLOCK_LOW = "clock_low"
96
+ OUTPUT_LOW = "output_low"
97
+ EDGE_CLOCK_HIGH = "edge_clock_high"
98
+ NON_LOGIC = "non_logic"
99
+
100
+
101
+ @dataclass
102
+ class SchematicPin:
103
+ """Pin definition for schematic symbols."""
104
+
105
+ number: str
106
+ name: str
107
+ position: Point
108
+ pin_type: PinType = PinType.PASSIVE
109
+ pin_shape: PinShape = PinShape.LINE
110
+ length: float = 2.54 # Standard pin length in mm
111
+ rotation: float = 0.0 # Rotation in degrees
112
+
113
+ def __post_init__(self):
114
+ # Ensure types are correct
115
+ self.pin_type = PinType(self.pin_type) if isinstance(self.pin_type, str) else self.pin_type
116
+ self.pin_shape = (
117
+ PinShape(self.pin_shape) if isinstance(self.pin_shape, str) else self.pin_shape
118
+ )
119
+
120
+
121
+ @dataclass
122
+ class SchematicSymbol:
123
+ """Component symbol in a schematic."""
124
+
125
+ uuid: str
126
+ lib_id: str # e.g., "Device:R"
127
+ position: Point
128
+ reference: str # e.g., "R1"
129
+ value: str = ""
130
+ footprint: Optional[str] = None
131
+ properties: Dict[str, str] = field(default_factory=dict)
132
+ pins: List[SchematicPin] = field(default_factory=list)
133
+ rotation: float = 0.0
134
+ in_bom: bool = True
135
+ on_board: bool = True
136
+ unit: int = 1
137
+
138
+ def __post_init__(self):
139
+ # Generate UUID if not provided
140
+ if not self.uuid:
141
+ self.uuid = str(uuid4())
142
+
143
+ @property
144
+ def library(self) -> str:
145
+ """Extract library name from lib_id."""
146
+ return self.lib_id.split(":")[0] if ":" in self.lib_id else ""
147
+
148
+ @property
149
+ def symbol_name(self) -> str:
150
+ """Extract symbol name from lib_id."""
151
+ return self.lib_id.split(":")[-1] if ":" in self.lib_id else self.lib_id
152
+
153
+ def get_pin(self, pin_number: str) -> Optional[SchematicPin]:
154
+ """Get pin by number."""
155
+ for pin in self.pins:
156
+ if pin.number == pin_number:
157
+ return pin
158
+ return None
159
+
160
+ def get_pin_position(self, pin_number: str) -> Optional[Point]:
161
+ """Get absolute position of a pin."""
162
+ pin = self.get_pin(pin_number)
163
+ if not pin:
164
+ return None
165
+ # TODO: Apply rotation and symbol position transformation
166
+ return Point(self.position.x + pin.position.x, self.position.y + pin.position.y)
167
+
168
+
169
+ class WireType(Enum):
170
+ """Wire types in KiCAD schematics."""
171
+
172
+ WIRE = "wire"
173
+ BUS = "bus"
174
+
175
+
176
+ @dataclass
177
+ class Wire:
178
+ """Wire connection in schematic."""
179
+
180
+ uuid: str
181
+ start: Point
182
+ end: Point
183
+ wire_type: WireType = WireType.WIRE
184
+ stroke_width: float = 0.0
185
+
186
+ def __post_init__(self):
187
+ if not self.uuid:
188
+ self.uuid = str(uuid4())
189
+
190
+ self.wire_type = (
191
+ WireType(self.wire_type) if isinstance(self.wire_type, str) else self.wire_type
192
+ )
193
+
194
+ @property
195
+ def length(self) -> float:
196
+ """Wire length."""
197
+ return self.start.distance_to(self.end)
198
+
199
+ def is_horizontal(self) -> bool:
200
+ """Check if wire is horizontal."""
201
+ return abs(self.start.y - self.end.y) < 0.001
202
+
203
+ def is_vertical(self) -> bool:
204
+ """Check if wire is vertical."""
205
+ return abs(self.start.x - self.end.x) < 0.001
206
+
207
+
208
+ @dataclass
209
+ class Junction:
210
+ """Junction point where multiple wires meet."""
211
+
212
+ uuid: str
213
+ position: Point
214
+ diameter: float = 1.27 # Standard junction diameter
215
+
216
+ def __post_init__(self):
217
+ if not self.uuid:
218
+ self.uuid = str(uuid4())
219
+
220
+
221
+ class LabelType(Enum):
222
+ """Label types in KiCAD schematics."""
223
+
224
+ LOCAL = "label"
225
+ GLOBAL = "global_label"
226
+ HIERARCHICAL = "hierarchical_label"
227
+
228
+
229
+ @dataclass
230
+ class Label:
231
+ """Text label in schematic."""
232
+
233
+ uuid: str
234
+ position: Point
235
+ text: str
236
+ label_type: LabelType = LabelType.LOCAL
237
+ rotation: float = 0.0
238
+ size: float = 1.27
239
+
240
+ def __post_init__(self):
241
+ if not self.uuid:
242
+ self.uuid = str(uuid4())
243
+
244
+ self.label_type = (
245
+ LabelType(self.label_type) if isinstance(self.label_type, str) else self.label_type
246
+ )
247
+
248
+
249
+ @dataclass
250
+ class Net:
251
+ """Electrical net connecting components."""
252
+
253
+ name: str
254
+ components: List[Tuple[str, str]] = field(default_factory=list) # (reference, pin) tuples
255
+ wires: List[str] = field(default_factory=list) # Wire UUIDs
256
+ labels: List[str] = field(default_factory=list) # Label UUIDs
257
+
258
+ def add_connection(self, reference: str, pin: str):
259
+ """Add component pin to net."""
260
+ connection = (reference, pin)
261
+ if connection not in self.components:
262
+ self.components.append(connection)
263
+
264
+ def remove_connection(self, reference: str, pin: str):
265
+ """Remove component pin from net."""
266
+ connection = (reference, pin)
267
+ if connection in self.components:
268
+ self.components.remove(connection)
269
+
270
+
271
+ @dataclass
272
+ class Sheet:
273
+ """Hierarchical sheet in schematic."""
274
+
275
+ uuid: str
276
+ position: Point
277
+ size: Point # Width, height
278
+ name: str
279
+ filename: str
280
+ pins: List["SheetPin"] = field(default_factory=list)
281
+
282
+ def __post_init__(self):
283
+ if not self.uuid:
284
+ self.uuid = str(uuid4())
285
+
286
+
287
+ @dataclass
288
+ class SheetPin:
289
+ """Pin on hierarchical sheet."""
290
+
291
+ uuid: str
292
+ name: str
293
+ position: Point
294
+ pin_type: PinType = PinType.BIDIRECTIONAL
295
+ size: float = 1.27
296
+
297
+ def __post_init__(self):
298
+ if not self.uuid:
299
+ self.uuid = str(uuid4())
300
+
301
+
302
+ @dataclass
303
+ class SymbolInstance:
304
+ """Instance of a symbol from library."""
305
+
306
+ path: str # Hierarchical path
307
+ reference: str
308
+ unit: int = 1
309
+
310
+
311
+ @dataclass
312
+ class TitleBlock:
313
+ """Title block information."""
314
+
315
+ title: str = ""
316
+ company: str = ""
317
+ revision: str = ""
318
+ date: str = ""
319
+ size: str = "A4"
320
+ comments: Dict[int, str] = field(default_factory=dict)
321
+
322
+
323
+ @dataclass
324
+ class Schematic:
325
+ """Complete schematic data structure."""
326
+
327
+ version: Optional[str] = None
328
+ generator: Optional[str] = None
329
+ uuid: Optional[str] = None
330
+ title_block: TitleBlock = field(default_factory=TitleBlock)
331
+ components: List[SchematicSymbol] = field(default_factory=list)
332
+ wires: List[Wire] = field(default_factory=list)
333
+ junctions: List[Junction] = field(default_factory=list)
334
+ labels: List[Label] = field(default_factory=list)
335
+ nets: List[Net] = field(default_factory=list)
336
+ sheets: List[Sheet] = field(default_factory=list)
337
+ lib_symbols: Dict[str, Any] = field(default_factory=dict)
338
+
339
+ def __post_init__(self):
340
+ if not self.uuid:
341
+ self.uuid = str(uuid4())
342
+
343
+ def get_component(self, reference: str) -> Optional[SchematicSymbol]:
344
+ """Get component by reference."""
345
+ for component in self.components:
346
+ if component.reference == reference:
347
+ return component
348
+ return None
349
+
350
+ def get_net(self, name: str) -> Optional[Net]:
351
+ """Get net by name."""
352
+ for net in self.nets:
353
+ if net.name == name:
354
+ return net
355
+ return None
356
+
357
+ def component_count(self) -> int:
358
+ """Get total number of components."""
359
+ return len(self.components)
360
+
361
+ def connection_count(self) -> int:
362
+ """Get total number of connections (wires + net connections)."""
363
+ return len(self.wires) + sum(len(net.components) for net in self.nets)
364
+
365
+
366
+ # Type aliases for convenience
367
+ ComponentDict = Dict[str, Any] # Raw component data from parser
368
+ WireDict = Dict[str, Any] # Raw wire data from parser
369
+ SchematicDict = Dict[str, Any] # Raw schematic data from parser
@@ -0,0 +1,10 @@
1
+ """Library management for kicad-sch-api."""
2
+
3
+ from .cache import SymbolDefinition, SymbolLibraryCache, get_symbol_cache, set_symbol_cache
4
+
5
+ __all__ = [
6
+ "SymbolLibraryCache",
7
+ "SymbolDefinition",
8
+ "get_symbol_cache",
9
+ "set_symbol_cache",
10
+ ]