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,579 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Graphics Manager for KiCAD schematic graphic elements.
|
|
3
|
+
|
|
4
|
+
Handles geometric shapes, drawing elements, and visual annotations including
|
|
5
|
+
rectangles, circles, polylines, arcs, and images while managing styling
|
|
6
|
+
and positioning.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import uuid
|
|
11
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
12
|
+
|
|
13
|
+
from ..types import Point
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GraphicsManager:
|
|
19
|
+
"""
|
|
20
|
+
Manages graphic elements and drawing shapes in KiCAD schematics.
|
|
21
|
+
|
|
22
|
+
Responsible for:
|
|
23
|
+
- Geometric shape creation (rectangles, circles, arcs)
|
|
24
|
+
- Polyline and path management
|
|
25
|
+
- Image placement and scaling
|
|
26
|
+
- Stroke and fill styling
|
|
27
|
+
- Layer management for graphics
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, schematic_data: Dict[str, Any]):
|
|
31
|
+
"""
|
|
32
|
+
Initialize GraphicsManager.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
schematic_data: Reference to schematic data
|
|
36
|
+
"""
|
|
37
|
+
self._data = schematic_data
|
|
38
|
+
|
|
39
|
+
def add_rectangle(
|
|
40
|
+
self,
|
|
41
|
+
start: Union[Point, Tuple[float, float]],
|
|
42
|
+
end: Union[Point, Tuple[float, float]],
|
|
43
|
+
stroke: Optional[Dict[str, Any]] = None,
|
|
44
|
+
fill: Optional[Dict[str, Any]] = None,
|
|
45
|
+
uuid_str: Optional[str] = None,
|
|
46
|
+
) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Add a rectangle to the schematic.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
start: Top-left corner position
|
|
52
|
+
end: Bottom-right corner position
|
|
53
|
+
stroke: Stroke properties (width, type, color)
|
|
54
|
+
fill: Fill properties (color, type)
|
|
55
|
+
uuid_str: Optional UUID
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
UUID of created rectangle
|
|
59
|
+
"""
|
|
60
|
+
if isinstance(start, tuple):
|
|
61
|
+
start = Point(start[0], start[1])
|
|
62
|
+
if isinstance(end, tuple):
|
|
63
|
+
end = Point(end[0], end[1])
|
|
64
|
+
|
|
65
|
+
if uuid_str is None:
|
|
66
|
+
uuid_str = str(uuid.uuid4())
|
|
67
|
+
|
|
68
|
+
if stroke is None:
|
|
69
|
+
stroke = self._get_default_stroke()
|
|
70
|
+
|
|
71
|
+
# Convert to parser format (flat keys, dict positions)
|
|
72
|
+
rectangle_data = {
|
|
73
|
+
"uuid": uuid_str,
|
|
74
|
+
"start": {"x": start.x, "y": start.y},
|
|
75
|
+
"end": {"x": end.x, "y": end.y},
|
|
76
|
+
"stroke_width": stroke.get("width", 0.127),
|
|
77
|
+
"stroke_type": stroke.get("type", "solid"),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Add stroke color if provided
|
|
81
|
+
if "color" in stroke:
|
|
82
|
+
rectangle_data["stroke_color"] = stroke["color"]
|
|
83
|
+
|
|
84
|
+
# Add fill type if provided
|
|
85
|
+
if fill is not None:
|
|
86
|
+
rectangle_data["fill_type"] = fill.get("type", "none")
|
|
87
|
+
# Add fill color if provided
|
|
88
|
+
if "color" in fill:
|
|
89
|
+
rectangle_data["fill_color"] = fill["color"]
|
|
90
|
+
else:
|
|
91
|
+
rectangle_data["fill_type"] = "none"
|
|
92
|
+
|
|
93
|
+
# Add to schematic data
|
|
94
|
+
if "rectangles" not in self._data:
|
|
95
|
+
self._data["rectangles"] = []
|
|
96
|
+
self._data["rectangles"].append(rectangle_data)
|
|
97
|
+
|
|
98
|
+
logger.debug(f"Added rectangle from {start} to {end}")
|
|
99
|
+
return uuid_str
|
|
100
|
+
|
|
101
|
+
def add_circle(
|
|
102
|
+
self,
|
|
103
|
+
center: Union[Point, Tuple[float, float]],
|
|
104
|
+
radius: float,
|
|
105
|
+
stroke: Optional[Dict[str, Any]] = None,
|
|
106
|
+
fill: Optional[Dict[str, Any]] = None,
|
|
107
|
+
uuid_str: Optional[str] = None,
|
|
108
|
+
) -> str:
|
|
109
|
+
"""
|
|
110
|
+
Add a circle to the schematic.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
center: Center position
|
|
114
|
+
radius: Circle radius
|
|
115
|
+
stroke: Stroke properties
|
|
116
|
+
fill: Fill properties
|
|
117
|
+
uuid_str: Optional UUID
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
UUID of created circle
|
|
121
|
+
"""
|
|
122
|
+
if isinstance(center, tuple):
|
|
123
|
+
center = Point(center[0], center[1])
|
|
124
|
+
|
|
125
|
+
if uuid_str is None:
|
|
126
|
+
uuid_str = str(uuid.uuid4())
|
|
127
|
+
|
|
128
|
+
if stroke is None:
|
|
129
|
+
stroke = self._get_default_stroke()
|
|
130
|
+
|
|
131
|
+
circle_data = {
|
|
132
|
+
"uuid": uuid_str,
|
|
133
|
+
"center": [center.x, center.y],
|
|
134
|
+
"radius": radius,
|
|
135
|
+
"stroke": stroke,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if fill is not None:
|
|
139
|
+
circle_data["fill"] = fill
|
|
140
|
+
|
|
141
|
+
# Add to schematic data
|
|
142
|
+
if "circle" not in self._data:
|
|
143
|
+
self._data["circle"] = []
|
|
144
|
+
self._data["circle"].append(circle_data)
|
|
145
|
+
|
|
146
|
+
logger.debug(f"Added circle at {center} with radius {radius}")
|
|
147
|
+
return uuid_str
|
|
148
|
+
|
|
149
|
+
def add_arc(
|
|
150
|
+
self,
|
|
151
|
+
start: Union[Point, Tuple[float, float]],
|
|
152
|
+
mid: Union[Point, Tuple[float, float]],
|
|
153
|
+
end: Union[Point, Tuple[float, float]],
|
|
154
|
+
stroke: Optional[Dict[str, Any]] = None,
|
|
155
|
+
uuid_str: Optional[str] = None,
|
|
156
|
+
) -> str:
|
|
157
|
+
"""
|
|
158
|
+
Add an arc to the schematic (defined by three points).
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
start: Arc start point
|
|
162
|
+
mid: Arc midpoint
|
|
163
|
+
end: Arc end point
|
|
164
|
+
stroke: Stroke properties
|
|
165
|
+
uuid_str: Optional UUID
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
UUID of created arc
|
|
169
|
+
"""
|
|
170
|
+
if isinstance(start, tuple):
|
|
171
|
+
start = Point(start[0], start[1])
|
|
172
|
+
if isinstance(mid, tuple):
|
|
173
|
+
mid = Point(mid[0], mid[1])
|
|
174
|
+
if isinstance(end, tuple):
|
|
175
|
+
end = Point(end[0], end[1])
|
|
176
|
+
|
|
177
|
+
if uuid_str is None:
|
|
178
|
+
uuid_str = str(uuid.uuid4())
|
|
179
|
+
|
|
180
|
+
if stroke is None:
|
|
181
|
+
stroke = self._get_default_stroke()
|
|
182
|
+
|
|
183
|
+
arc_data = {
|
|
184
|
+
"uuid": uuid_str,
|
|
185
|
+
"start": [start.x, start.y],
|
|
186
|
+
"mid": [mid.x, mid.y],
|
|
187
|
+
"end": [end.x, end.y],
|
|
188
|
+
"stroke": stroke,
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# Add to schematic data
|
|
192
|
+
if "arc" not in self._data:
|
|
193
|
+
self._data["arc"] = []
|
|
194
|
+
self._data["arc"].append(arc_data)
|
|
195
|
+
|
|
196
|
+
logger.debug(f"Added arc from {start} through {mid} to {end}")
|
|
197
|
+
return uuid_str
|
|
198
|
+
|
|
199
|
+
def add_polyline(
|
|
200
|
+
self,
|
|
201
|
+
points: List[Union[Point, Tuple[float, float]]],
|
|
202
|
+
stroke: Optional[Dict[str, Any]] = None,
|
|
203
|
+
fill: Optional[Dict[str, Any]] = None,
|
|
204
|
+
uuid_str: Optional[str] = None,
|
|
205
|
+
) -> str:
|
|
206
|
+
"""
|
|
207
|
+
Add a polyline (multi-segment line) to the schematic.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
points: List of points defining the polyline
|
|
211
|
+
stroke: Stroke properties
|
|
212
|
+
fill: Fill properties (for closed polylines)
|
|
213
|
+
uuid_str: Optional UUID
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
UUID of created polyline
|
|
217
|
+
"""
|
|
218
|
+
if len(points) < 2:
|
|
219
|
+
raise ValueError("Polyline must have at least 2 points")
|
|
220
|
+
|
|
221
|
+
# Convert tuples to Points
|
|
222
|
+
converted_points = []
|
|
223
|
+
for point in points:
|
|
224
|
+
if isinstance(point, tuple):
|
|
225
|
+
converted_points.append(Point(point[0], point[1]))
|
|
226
|
+
else:
|
|
227
|
+
converted_points.append(point)
|
|
228
|
+
|
|
229
|
+
if uuid_str is None:
|
|
230
|
+
uuid_str = str(uuid.uuid4())
|
|
231
|
+
|
|
232
|
+
if stroke is None:
|
|
233
|
+
stroke = self._get_default_stroke()
|
|
234
|
+
|
|
235
|
+
polyline_data = {
|
|
236
|
+
"uuid": uuid_str,
|
|
237
|
+
"pts": [[pt.x, pt.y] for pt in converted_points],
|
|
238
|
+
"stroke": stroke,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if fill is not None:
|
|
242
|
+
polyline_data["fill"] = fill
|
|
243
|
+
|
|
244
|
+
# Add to schematic data
|
|
245
|
+
if "polyline" not in self._data:
|
|
246
|
+
self._data["polyline"] = []
|
|
247
|
+
self._data["polyline"].append(polyline_data)
|
|
248
|
+
|
|
249
|
+
logger.debug(f"Added polyline with {len(points)} points")
|
|
250
|
+
return uuid_str
|
|
251
|
+
|
|
252
|
+
def add_image(
|
|
253
|
+
self,
|
|
254
|
+
position: Union[Point, Tuple[float, float]],
|
|
255
|
+
scale: float = 1.0,
|
|
256
|
+
image_data: Optional[str] = None,
|
|
257
|
+
uuid_str: Optional[str] = None,
|
|
258
|
+
) -> str:
|
|
259
|
+
"""
|
|
260
|
+
Add an image to the schematic.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
position: Image position
|
|
264
|
+
scale: Image scale factor
|
|
265
|
+
image_data: Base64 encoded image data (optional)
|
|
266
|
+
uuid_str: Optional UUID
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
UUID of created image
|
|
270
|
+
"""
|
|
271
|
+
if isinstance(position, tuple):
|
|
272
|
+
position = Point(position[0], position[1])
|
|
273
|
+
|
|
274
|
+
if uuid_str is None:
|
|
275
|
+
uuid_str = str(uuid.uuid4())
|
|
276
|
+
|
|
277
|
+
# Store in parser format (position as dict)
|
|
278
|
+
image_element = {
|
|
279
|
+
"uuid": uuid_str,
|
|
280
|
+
"position": {"x": position.x, "y": position.y},
|
|
281
|
+
"scale": scale,
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if image_data is not None:
|
|
285
|
+
image_element["data"] = image_data
|
|
286
|
+
|
|
287
|
+
# Add to schematic data
|
|
288
|
+
if "images" not in self._data:
|
|
289
|
+
self._data["images"] = []
|
|
290
|
+
self._data["images"].append(image_element)
|
|
291
|
+
|
|
292
|
+
logger.debug(f"Added image at {position} with scale {scale}")
|
|
293
|
+
return uuid_str
|
|
294
|
+
|
|
295
|
+
def remove_rectangle(self, uuid_str: str) -> bool:
|
|
296
|
+
"""
|
|
297
|
+
Remove a rectangle by UUID.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
uuid_str: UUID of rectangle to remove
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
True if removed, False if not found
|
|
304
|
+
"""
|
|
305
|
+
return self._remove_graphic_element("rectangle", uuid_str)
|
|
306
|
+
|
|
307
|
+
def remove_circle(self, uuid_str: str) -> bool:
|
|
308
|
+
"""
|
|
309
|
+
Remove a circle by UUID.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
uuid_str: UUID of circle to remove
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
True if removed, False if not found
|
|
316
|
+
"""
|
|
317
|
+
return self._remove_graphic_element("circle", uuid_str)
|
|
318
|
+
|
|
319
|
+
def remove_arc(self, uuid_str: str) -> bool:
|
|
320
|
+
"""
|
|
321
|
+
Remove an arc by UUID.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
uuid_str: UUID of arc to remove
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
True if removed, False if not found
|
|
328
|
+
"""
|
|
329
|
+
return self._remove_graphic_element("arc", uuid_str)
|
|
330
|
+
|
|
331
|
+
def remove_polyline(self, uuid_str: str) -> bool:
|
|
332
|
+
"""
|
|
333
|
+
Remove a polyline by UUID.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
uuid_str: UUID of polyline to remove
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
True if removed, False if not found
|
|
340
|
+
"""
|
|
341
|
+
return self._remove_graphic_element("polyline", uuid_str)
|
|
342
|
+
|
|
343
|
+
def remove_image(self, uuid_str: str) -> bool:
|
|
344
|
+
"""
|
|
345
|
+
Remove an image by UUID.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
uuid_str: UUID of image to remove
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
True if removed, False if not found
|
|
352
|
+
"""
|
|
353
|
+
return self._remove_graphic_element("image", uuid_str)
|
|
354
|
+
|
|
355
|
+
def update_stroke(self, uuid_str: str, stroke: Dict[str, Any]) -> bool:
|
|
356
|
+
"""
|
|
357
|
+
Update stroke properties for a graphic element.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
uuid_str: UUID of element
|
|
361
|
+
stroke: New stroke properties
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
True if updated, False if not found
|
|
365
|
+
"""
|
|
366
|
+
for element_type in ["rectangle", "circle", "arc", "polyline"]:
|
|
367
|
+
elements = self._data.get(element_type, [])
|
|
368
|
+
for element in elements:
|
|
369
|
+
if element.get("uuid") == uuid_str:
|
|
370
|
+
element["stroke"] = stroke
|
|
371
|
+
logger.debug(f"Updated stroke for {element_type} {uuid_str}")
|
|
372
|
+
return True
|
|
373
|
+
|
|
374
|
+
logger.warning(f"Graphic element not found for stroke update: {uuid_str}")
|
|
375
|
+
return False
|
|
376
|
+
|
|
377
|
+
def update_fill(self, uuid_str: str, fill: Dict[str, Any]) -> bool:
|
|
378
|
+
"""
|
|
379
|
+
Update fill properties for a graphic element.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
uuid_str: UUID of element
|
|
383
|
+
fill: New fill properties
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
True if updated, False if not found
|
|
387
|
+
"""
|
|
388
|
+
for element_type in ["rectangle", "circle", "polyline"]:
|
|
389
|
+
elements = self._data.get(element_type, [])
|
|
390
|
+
for element in elements:
|
|
391
|
+
if element.get("uuid") == uuid_str:
|
|
392
|
+
element["fill"] = fill
|
|
393
|
+
logger.debug(f"Updated fill for {element_type} {uuid_str}")
|
|
394
|
+
return True
|
|
395
|
+
|
|
396
|
+
logger.warning(f"Graphic element not found for fill update: {uuid_str}")
|
|
397
|
+
return False
|
|
398
|
+
|
|
399
|
+
def get_graphics_in_area(
|
|
400
|
+
self,
|
|
401
|
+
area_start: Union[Point, Tuple[float, float]],
|
|
402
|
+
area_end: Union[Point, Tuple[float, float]],
|
|
403
|
+
) -> List[Dict[str, Any]]:
|
|
404
|
+
"""
|
|
405
|
+
Get all graphic elements within a specified area.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
area_start: Area top-left corner
|
|
409
|
+
area_end: Area bottom-right corner
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
List of graphic elements in the area
|
|
413
|
+
"""
|
|
414
|
+
if isinstance(area_start, tuple):
|
|
415
|
+
area_start = Point(area_start[0], area_start[1])
|
|
416
|
+
if isinstance(area_end, tuple):
|
|
417
|
+
area_end = Point(area_end[0], area_end[1])
|
|
418
|
+
|
|
419
|
+
result = []
|
|
420
|
+
|
|
421
|
+
# Check rectangles
|
|
422
|
+
rectangles = self._data.get("rectangle", [])
|
|
423
|
+
for rect in rectangles:
|
|
424
|
+
rect_start = Point(rect["start"][0], rect["start"][1])
|
|
425
|
+
rect_end = Point(rect["end"][0], rect["end"][1])
|
|
426
|
+
if self._rectangles_overlap(area_start, area_end, rect_start, rect_end):
|
|
427
|
+
result.append({"type": "rectangle", "data": rect})
|
|
428
|
+
|
|
429
|
+
# Check circles
|
|
430
|
+
circles = self._data.get("circle", [])
|
|
431
|
+
for circle in circles:
|
|
432
|
+
center = Point(circle["center"][0], circle["center"][1])
|
|
433
|
+
radius = circle["radius"]
|
|
434
|
+
if self._circle_in_area(center, radius, area_start, area_end):
|
|
435
|
+
result.append({"type": "circle", "data": circle})
|
|
436
|
+
|
|
437
|
+
# Check polylines
|
|
438
|
+
polylines = self._data.get("polyline", [])
|
|
439
|
+
for polyline in polylines:
|
|
440
|
+
if self._polyline_in_area(polyline["pts"], area_start, area_end):
|
|
441
|
+
result.append({"type": "polyline", "data": polyline})
|
|
442
|
+
|
|
443
|
+
# Check arcs
|
|
444
|
+
arcs = self._data.get("arc", [])
|
|
445
|
+
for arc in arcs:
|
|
446
|
+
arc_start = Point(arc["start"][0], arc["start"][1])
|
|
447
|
+
arc_end = Point(arc["end"][0], arc["end"][1])
|
|
448
|
+
if self._point_in_area(arc_start, area_start, area_end) or self._point_in_area(
|
|
449
|
+
arc_end, area_start, area_end
|
|
450
|
+
):
|
|
451
|
+
result.append({"type": "arc", "data": arc})
|
|
452
|
+
|
|
453
|
+
# Check images
|
|
454
|
+
images = self._data.get("image", [])
|
|
455
|
+
for image in images:
|
|
456
|
+
pos = Point(image["at"][0], image["at"][1])
|
|
457
|
+
if self._point_in_area(pos, area_start, area_end):
|
|
458
|
+
result.append({"type": "image", "data": image})
|
|
459
|
+
|
|
460
|
+
return result
|
|
461
|
+
|
|
462
|
+
def list_all_graphics(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
463
|
+
"""
|
|
464
|
+
Get all graphic elements in the schematic.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Dictionary with graphic element types and their data
|
|
468
|
+
"""
|
|
469
|
+
result = {}
|
|
470
|
+
for element_type in ["rectangle", "circle", "arc", "polyline", "image"]:
|
|
471
|
+
elements = self._data.get(element_type, [])
|
|
472
|
+
result[element_type] = [{"uuid": elem.get("uuid"), "data": elem} for elem in elements]
|
|
473
|
+
|
|
474
|
+
return result
|
|
475
|
+
|
|
476
|
+
def get_graphics_statistics(self) -> Dict[str, Any]:
|
|
477
|
+
"""
|
|
478
|
+
Get statistics about graphic elements.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
Dictionary with graphics statistics
|
|
482
|
+
"""
|
|
483
|
+
stats = {}
|
|
484
|
+
total_elements = 0
|
|
485
|
+
|
|
486
|
+
for element_type in ["rectangle", "circle", "arc", "polyline", "image"]:
|
|
487
|
+
count = len(self._data.get(element_type, []))
|
|
488
|
+
stats[element_type] = count
|
|
489
|
+
total_elements += count
|
|
490
|
+
|
|
491
|
+
stats["total_graphics"] = total_elements
|
|
492
|
+
return stats
|
|
493
|
+
|
|
494
|
+
def _remove_graphic_element(self, element_type: str, uuid_str: str) -> bool:
|
|
495
|
+
"""Remove graphic element by UUID from specified type."""
|
|
496
|
+
elements = self._data.get(element_type, [])
|
|
497
|
+
for i, element in enumerate(elements):
|
|
498
|
+
if element.get("uuid") == uuid_str:
|
|
499
|
+
del elements[i]
|
|
500
|
+
logger.debug(f"Removed {element_type}: {uuid_str}")
|
|
501
|
+
return True
|
|
502
|
+
return False
|
|
503
|
+
|
|
504
|
+
def _get_default_stroke(self) -> Dict[str, Any]:
|
|
505
|
+
"""Get default stroke properties."""
|
|
506
|
+
return {"width": 0.254, "type": "default"}
|
|
507
|
+
|
|
508
|
+
def _rectangles_overlap(
|
|
509
|
+
self, area_start: Point, area_end: Point, rect_start: Point, rect_end: Point
|
|
510
|
+
) -> bool:
|
|
511
|
+
"""Check if two rectangles overlap."""
|
|
512
|
+
return not (
|
|
513
|
+
area_end.x < rect_start.x
|
|
514
|
+
or area_start.x > rect_end.x
|
|
515
|
+
or area_end.y < rect_start.y
|
|
516
|
+
or area_start.y > rect_end.y
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
def _circle_in_area(
|
|
520
|
+
self, center: Point, radius: float, area_start: Point, area_end: Point
|
|
521
|
+
) -> bool:
|
|
522
|
+
"""Check if circle intersects with area."""
|
|
523
|
+
# Check if circle center is in area or if circle overlaps area bounds
|
|
524
|
+
if self._point_in_area(center, area_start, area_end):
|
|
525
|
+
return True
|
|
526
|
+
|
|
527
|
+
# Check if circle intersects with area edges
|
|
528
|
+
closest_x = max(area_start.x, min(center.x, area_end.x))
|
|
529
|
+
closest_y = max(area_start.y, min(center.y, area_end.y))
|
|
530
|
+
distance = Point(closest_x, closest_y).distance_to(center)
|
|
531
|
+
|
|
532
|
+
return distance <= radius
|
|
533
|
+
|
|
534
|
+
def _polyline_in_area(
|
|
535
|
+
self, points: List[List[float]], area_start: Point, area_end: Point
|
|
536
|
+
) -> bool:
|
|
537
|
+
"""Check if any part of polyline is in area."""
|
|
538
|
+
for point_coords in points:
|
|
539
|
+
point = Point(point_coords[0], point_coords[1])
|
|
540
|
+
if self._point_in_area(point, area_start, area_end):
|
|
541
|
+
return True
|
|
542
|
+
return False
|
|
543
|
+
|
|
544
|
+
def _point_in_area(self, point: Point, area_start: Point, area_end: Point) -> bool:
|
|
545
|
+
"""Check if point is within area."""
|
|
546
|
+
return area_start.x <= point.x <= area_end.x and area_start.y <= point.y <= area_end.y
|
|
547
|
+
|
|
548
|
+
def validate_graphics(self) -> List[str]:
|
|
549
|
+
"""
|
|
550
|
+
Validate graphic elements for consistency and correctness.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
List of validation warnings
|
|
554
|
+
"""
|
|
555
|
+
warnings = []
|
|
556
|
+
|
|
557
|
+
# Check rectangles
|
|
558
|
+
rectangles = self._data.get("rectangle", [])
|
|
559
|
+
for rect in rectangles:
|
|
560
|
+
start = Point(rect["start"][0], rect["start"][1])
|
|
561
|
+
end = Point(rect["end"][0], rect["end"][1])
|
|
562
|
+
if start.x >= end.x or start.y >= end.y:
|
|
563
|
+
warnings.append(f"Rectangle {rect.get('uuid')} has invalid dimensions")
|
|
564
|
+
|
|
565
|
+
# Check circles
|
|
566
|
+
circles = self._data.get("circle", [])
|
|
567
|
+
for circle in circles:
|
|
568
|
+
radius = circle.get("radius", 0)
|
|
569
|
+
if radius <= 0:
|
|
570
|
+
warnings.append(f"Circle {circle.get('uuid')} has invalid radius: {radius}")
|
|
571
|
+
|
|
572
|
+
# Check polylines
|
|
573
|
+
polylines = self._data.get("polyline", [])
|
|
574
|
+
for polyline in polylines:
|
|
575
|
+
points = polyline.get("pts", [])
|
|
576
|
+
if len(points) < 2:
|
|
577
|
+
warnings.append(f"Polyline {polyline.get('uuid')} has too few points")
|
|
578
|
+
|
|
579
|
+
return warnings
|