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.
- kicad_sch_api/__init__.py +67 -2
- kicad_sch_api/cli/kicad_to_python.py +169 -0
- kicad_sch_api/collections/__init__.py +23 -8
- kicad_sch_api/collections/base.py +369 -59
- kicad_sch_api/collections/components.py +1376 -187
- kicad_sch_api/collections/junctions.py +129 -289
- kicad_sch_api/collections/labels.py +391 -287
- kicad_sch_api/collections/wires.py +202 -316
- kicad_sch_api/core/__init__.py +37 -2
- kicad_sch_api/core/component_bounds.py +34 -12
- kicad_sch_api/core/components.py +146 -7
- kicad_sch_api/core/config.py +25 -12
- kicad_sch_api/core/connectivity.py +692 -0
- kicad_sch_api/core/exceptions.py +175 -0
- kicad_sch_api/core/factories/element_factory.py +3 -1
- kicad_sch_api/core/formatter.py +24 -7
- kicad_sch_api/core/geometry.py +94 -5
- kicad_sch_api/core/managers/__init__.py +4 -0
- kicad_sch_api/core/managers/base.py +76 -0
- kicad_sch_api/core/managers/file_io.py +3 -1
- kicad_sch_api/core/managers/format_sync.py +3 -2
- kicad_sch_api/core/managers/graphics.py +3 -2
- kicad_sch_api/core/managers/hierarchy.py +661 -0
- kicad_sch_api/core/managers/metadata.py +4 -2
- kicad_sch_api/core/managers/sheet.py +52 -14
- kicad_sch_api/core/managers/text_elements.py +3 -2
- kicad_sch_api/core/managers/validation.py +3 -2
- kicad_sch_api/core/managers/wire.py +112 -54
- kicad_sch_api/core/parsing_utils.py +63 -0
- kicad_sch_api/core/pin_utils.py +103 -9
- kicad_sch_api/core/schematic.py +343 -29
- kicad_sch_api/core/types.py +79 -7
- kicad_sch_api/exporters/__init__.py +10 -0
- kicad_sch_api/exporters/python_generator.py +610 -0
- kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
- kicad_sch_api/geometry/__init__.py +15 -3
- kicad_sch_api/geometry/routing.py +211 -0
- kicad_sch_api/parsers/elements/label_parser.py +30 -8
- kicad_sch_api/parsers/elements/symbol_parser.py +255 -83
- kicad_sch_api/utils/logging.py +555 -0
- kicad_sch_api/utils/logging_decorators.py +587 -0
- kicad_sch_api/utils/validation.py +16 -22
- kicad_sch_api/wrappers/__init__.py +14 -0
- kicad_sch_api/wrappers/base.py +89 -0
- kicad_sch_api/wrappers/wire.py +198 -0
- kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
- kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
- kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
- {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
- mcp_server/__init__.py +34 -0
- mcp_server/example_logging_integration.py +506 -0
- mcp_server/models.py +252 -0
- mcp_server/server.py +357 -0
- mcp_server/tools/__init__.py +32 -0
- mcp_server/tools/component_tools.py +516 -0
- mcp_server/tools/connectivity_tools.py +532 -0
- mcp_server/tools/consolidated_tools.py +1216 -0
- mcp_server/tools/pin_discovery.py +333 -0
- mcp_server/utils/__init__.py +38 -0
- mcp_server/utils/logging.py +127 -0
- mcp_server/utils.py +36 -0
- kicad_sch_api-0.4.1.dist-info/METADATA +0 -491
- kicad_sch_api-0.4.1.dist-info/RECORD +0 -87
- kicad_sch_api-0.4.1.dist-info/entry_points.txt +0 -2
- {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,1216 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Consolidated MCP tools for KiCAD schematic management.
|
|
3
|
+
|
|
4
|
+
This module provides 8 consolidated MCP tools that handle all CRUD operations
|
|
5
|
+
for schematic entities (schematics, components, wires, labels, text boxes,
|
|
6
|
+
power symbols, hierarchical sheets, and global labels).
|
|
7
|
+
|
|
8
|
+
Each tool uses an `action` parameter to specify the operation (create, read,
|
|
9
|
+
update, delete) and returns a standardized response dictionary.
|
|
10
|
+
|
|
11
|
+
Design: 8 tools × multiple actions = complete schematic management coverage
|
|
12
|
+
Benefit: Minimal tool count for optimal LLM performance
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from fastmcp import Context
|
|
20
|
+
else:
|
|
21
|
+
try:
|
|
22
|
+
from fastmcp import Context
|
|
23
|
+
except ImportError:
|
|
24
|
+
Context = None # type: ignore
|
|
25
|
+
|
|
26
|
+
import kicad_sch_api as ksa
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# Import global schematic state
|
|
31
|
+
from mcp_server.tools.pin_discovery import get_current_schematic, set_current_schematic
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ============================================================================
|
|
35
|
+
# 1. MANAGE SCHEMATIC (create, read, save, load)
|
|
36
|
+
# ============================================================================
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def manage_schematic(
|
|
40
|
+
action: str,
|
|
41
|
+
name: Optional[str] = None,
|
|
42
|
+
file_path: Optional[str] = None,
|
|
43
|
+
ctx: Optional[Context] = None,
|
|
44
|
+
) -> dict:
|
|
45
|
+
"""
|
|
46
|
+
Manage schematic project (create, read, save, load).
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
action: Operation to perform ("create", "read", "save", "load")
|
|
50
|
+
name: Project name (required for "create")
|
|
51
|
+
file_path: File path (required for "load"/"save", optional for others)
|
|
52
|
+
ctx: MCP context for progress reporting (optional)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Dictionary with success status and operation results
|
|
56
|
+
"""
|
|
57
|
+
logger.info(f"[MCP] manage_schematic called: action={action}")
|
|
58
|
+
|
|
59
|
+
if action == "create":
|
|
60
|
+
if not name:
|
|
61
|
+
return {
|
|
62
|
+
"success": False,
|
|
63
|
+
"error": "INVALID_PARAMS",
|
|
64
|
+
"message": "name parameter required for create action",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
if ctx:
|
|
69
|
+
await ctx.report_progress(0, 100, f"Creating schematic: {name}")
|
|
70
|
+
|
|
71
|
+
sch = ksa.create_schematic(name)
|
|
72
|
+
set_current_schematic(sch)
|
|
73
|
+
|
|
74
|
+
if ctx:
|
|
75
|
+
await ctx.report_progress(100, 100, f"Created: {name}")
|
|
76
|
+
|
|
77
|
+
logger.info(f"[MCP] Created schematic: {name}")
|
|
78
|
+
return {
|
|
79
|
+
"success": True,
|
|
80
|
+
"project_name": sch.title_block.get('title') or name,
|
|
81
|
+
"uuid": str(sch.uuid),
|
|
82
|
+
"message": f"Created schematic: {name}",
|
|
83
|
+
}
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.error(f"[MCP] Error creating schematic: {e}", exc_info=True)
|
|
86
|
+
return {
|
|
87
|
+
"success": False,
|
|
88
|
+
"error": "CREATE_ERROR",
|
|
89
|
+
"message": f"Failed to create schematic: {str(e)}",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
elif action == "read":
|
|
93
|
+
schematic = get_current_schematic()
|
|
94
|
+
if schematic is None:
|
|
95
|
+
return {
|
|
96
|
+
"success": False,
|
|
97
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
98
|
+
"message": "No schematic is currently loaded",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
component_refs = [
|
|
103
|
+
c.reference for c in schematic.components
|
|
104
|
+
]
|
|
105
|
+
return {
|
|
106
|
+
"success": True,
|
|
107
|
+
"project_name": schematic.title_block.get('title') or "Untitled",
|
|
108
|
+
"uuid": str(schematic.uuid),
|
|
109
|
+
"component_count": len(schematic.components),
|
|
110
|
+
"component_references": component_refs,
|
|
111
|
+
}
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.error(f"[MCP] Error reading schematic: {e}", exc_info=True)
|
|
114
|
+
return {
|
|
115
|
+
"success": False,
|
|
116
|
+
"error": "READ_ERROR",
|
|
117
|
+
"message": f"Failed to read schematic: {str(e)}",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
elif action == "save":
|
|
121
|
+
schematic = get_current_schematic()
|
|
122
|
+
if schematic is None:
|
|
123
|
+
return {
|
|
124
|
+
"success": False,
|
|
125
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
126
|
+
"message": "No schematic is currently loaded",
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
if ctx:
|
|
131
|
+
await ctx.report_progress(0, 100, "Saving schematic")
|
|
132
|
+
|
|
133
|
+
schematic.save(file_path) if file_path else schematic.save()
|
|
134
|
+
actual_path = file_path or getattr(schematic, "_file_path", "")
|
|
135
|
+
|
|
136
|
+
if ctx:
|
|
137
|
+
await ctx.report_progress(100, 100, "Save complete")
|
|
138
|
+
|
|
139
|
+
logger.info(f"[MCP] Saved schematic to: {actual_path}")
|
|
140
|
+
return {
|
|
141
|
+
"success": True,
|
|
142
|
+
"message": f"Saved to {actual_path or 'original location'}",
|
|
143
|
+
"file_path": actual_path,
|
|
144
|
+
}
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.error(f"[MCP] Error saving schematic: {e}", exc_info=True)
|
|
147
|
+
return {
|
|
148
|
+
"success": False,
|
|
149
|
+
"error": "SAVE_ERROR",
|
|
150
|
+
"message": f"Failed to save schematic: {str(e)}",
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
elif action == "load":
|
|
154
|
+
if not file_path:
|
|
155
|
+
return {
|
|
156
|
+
"success": False,
|
|
157
|
+
"error": "INVALID_PARAMS",
|
|
158
|
+
"message": "file_path parameter required for load action",
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
if ctx:
|
|
163
|
+
await ctx.report_progress(0, 100, f"Loading schematic: {file_path}")
|
|
164
|
+
|
|
165
|
+
sch = ksa.Schematic.load(file_path)
|
|
166
|
+
set_current_schematic(sch)
|
|
167
|
+
|
|
168
|
+
if ctx:
|
|
169
|
+
await ctx.report_progress(100, 100, "Load complete")
|
|
170
|
+
|
|
171
|
+
logger.info(f"[MCP] Loaded schematic from: {file_path}")
|
|
172
|
+
return {
|
|
173
|
+
"success": True,
|
|
174
|
+
"project_name": sch.title_block.get('title') or "Untitled",
|
|
175
|
+
"uuid": str(sch.uuid),
|
|
176
|
+
"component_count": len(sch.components),
|
|
177
|
+
"file_path": file_path,
|
|
178
|
+
"message": f"Loaded schematic from {file_path}",
|
|
179
|
+
}
|
|
180
|
+
except Exception as e:
|
|
181
|
+
logger.error(f"[MCP] Error loading schematic: {e}", exc_info=True)
|
|
182
|
+
return {
|
|
183
|
+
"success": False,
|
|
184
|
+
"error": "LOAD_ERROR",
|
|
185
|
+
"message": f"Failed to load schematic: {str(e)}",
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
else:
|
|
189
|
+
return {
|
|
190
|
+
"success": False,
|
|
191
|
+
"error": "INVALID_ACTION",
|
|
192
|
+
"message": f"Unknown action: {action}. Valid actions: create, read, save, load",
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ============================================================================
|
|
197
|
+
# 2. MANAGE COMPONENTS (add, list, get_pins, update, remove)
|
|
198
|
+
# ============================================================================
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
async def manage_components(
|
|
202
|
+
action: str,
|
|
203
|
+
lib_id: Optional[str] = None,
|
|
204
|
+
value: Optional[str] = None,
|
|
205
|
+
reference: Optional[str] = None,
|
|
206
|
+
position: Optional[Tuple[float, float]] = None,
|
|
207
|
+
rotation: Optional[float] = None,
|
|
208
|
+
footprint: Optional[str] = None,
|
|
209
|
+
ctx: Optional[Context] = None,
|
|
210
|
+
) -> dict:
|
|
211
|
+
"""
|
|
212
|
+
Manage components (add, list, get_pins, update, remove).
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
action: Operation ("add", "list", "get_pins", "update", "remove")
|
|
216
|
+
lib_id: Library ID (required for "add")
|
|
217
|
+
value: Component value (required for "add")
|
|
218
|
+
reference: Reference designator (optional for "add", required for others)
|
|
219
|
+
position: Position tuple (for "add"/"update")
|
|
220
|
+
rotation: Rotation in degrees (for "add"/"update")
|
|
221
|
+
footprint: Footprint string (for "add"/"update")
|
|
222
|
+
ctx: MCP context (optional)
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Dictionary with component data or error
|
|
226
|
+
"""
|
|
227
|
+
logger.info(f"[MCP] manage_components called: action={action}")
|
|
228
|
+
|
|
229
|
+
schematic = get_current_schematic()
|
|
230
|
+
if schematic is None and action != "add":
|
|
231
|
+
return {
|
|
232
|
+
"success": False,
|
|
233
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
234
|
+
"message": "No schematic is currently loaded",
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if action == "add":
|
|
238
|
+
if not lib_id or not value:
|
|
239
|
+
return {
|
|
240
|
+
"success": False,
|
|
241
|
+
"error": "INVALID_PARAMS",
|
|
242
|
+
"message": "lib_id and value are required for add action",
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
if ctx:
|
|
247
|
+
await ctx.report_progress(0, 100, f"Adding component: {lib_id}")
|
|
248
|
+
|
|
249
|
+
component = schematic.components.add(
|
|
250
|
+
lib_id=lib_id,
|
|
251
|
+
reference=reference,
|
|
252
|
+
value=value,
|
|
253
|
+
position=position,
|
|
254
|
+
rotation=rotation or 0.0,
|
|
255
|
+
footprint=footprint,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if ctx:
|
|
259
|
+
await ctx.report_progress(100, 100, f"Added {component.reference}")
|
|
260
|
+
|
|
261
|
+
logger.info(f"[MCP] Added component: {component.reference}")
|
|
262
|
+
return {
|
|
263
|
+
"success": True,
|
|
264
|
+
"reference": component.reference,
|
|
265
|
+
"lib_id": component.lib_id,
|
|
266
|
+
"value": component.value,
|
|
267
|
+
"position": {"x": component.position.x, "y": component.position.y},
|
|
268
|
+
"rotation": component.rotation,
|
|
269
|
+
"uuid": str(component.uuid),
|
|
270
|
+
"message": f"Added {component.reference} ({value})",
|
|
271
|
+
}
|
|
272
|
+
except Exception as e:
|
|
273
|
+
logger.error(f"[MCP] Error adding component: {e}", exc_info=True)
|
|
274
|
+
return {
|
|
275
|
+
"success": False,
|
|
276
|
+
"error": "ADD_ERROR",
|
|
277
|
+
"message": f"Failed to add component: {str(e)}",
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
elif action == "list":
|
|
281
|
+
try:
|
|
282
|
+
components = []
|
|
283
|
+
for comp in schematic.components:
|
|
284
|
+
components.append({
|
|
285
|
+
"reference": comp.reference,
|
|
286
|
+
"lib_id": comp.lib_id,
|
|
287
|
+
"value": comp.value,
|
|
288
|
+
"position": {"x": comp.position.x, "y": comp.position.y},
|
|
289
|
+
"rotation": comp.rotation,
|
|
290
|
+
"uuid": str(comp.uuid),
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
logger.info(f"[MCP] Listed {len(components)} components")
|
|
294
|
+
return {
|
|
295
|
+
"success": True,
|
|
296
|
+
"count": len(components),
|
|
297
|
+
"components": components,
|
|
298
|
+
}
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.error(f"[MCP] Error listing components: {e}", exc_info=True)
|
|
301
|
+
return {
|
|
302
|
+
"success": False,
|
|
303
|
+
"error": "LIST_ERROR",
|
|
304
|
+
"message": f"Failed to list components: {str(e)}",
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
elif action == "get_pins":
|
|
308
|
+
if not reference:
|
|
309
|
+
return {
|
|
310
|
+
"success": False,
|
|
311
|
+
"error": "INVALID_PARAMS",
|
|
312
|
+
"message": "reference parameter required for get_pins action",
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
# Get pin information using the schematic's pin discovery method
|
|
317
|
+
pins_data = schematic.components.get_pins_info(reference)
|
|
318
|
+
if not pins_data:
|
|
319
|
+
return {
|
|
320
|
+
"success": False,
|
|
321
|
+
"error": "NOT_FOUND",
|
|
322
|
+
"message": f"Component {reference} not found",
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
pins = []
|
|
326
|
+
for pin in pins_data:
|
|
327
|
+
pins.append({
|
|
328
|
+
"number": str(pin.number),
|
|
329
|
+
"name": pin.name,
|
|
330
|
+
"position": {
|
|
331
|
+
"x": pin.position.x,
|
|
332
|
+
"y": pin.position.y,
|
|
333
|
+
},
|
|
334
|
+
"electrical_type": pin.electrical_type.value if hasattr(pin.electrical_type, 'value') else str(pin.electrical_type),
|
|
335
|
+
"pin_type": pin.shape.value if hasattr(pin.shape, 'value') else str(pin.shape),
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
logger.info(f"[MCP] Got pins for {reference}: {len(pins)} pins")
|
|
339
|
+
return {
|
|
340
|
+
"success": True,
|
|
341
|
+
"reference": reference,
|
|
342
|
+
"pin_count": len(pins),
|
|
343
|
+
"pins": pins,
|
|
344
|
+
}
|
|
345
|
+
except Exception as e:
|
|
346
|
+
logger.error(
|
|
347
|
+
f"[MCP] Error getting pins for {reference}: {e}", exc_info=True
|
|
348
|
+
)
|
|
349
|
+
return {
|
|
350
|
+
"success": False,
|
|
351
|
+
"error": "GET_PINS_ERROR",
|
|
352
|
+
"message": f"Failed to get pins: {str(e)}",
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
elif action == "update":
|
|
356
|
+
if not reference:
|
|
357
|
+
return {
|
|
358
|
+
"success": False,
|
|
359
|
+
"error": "INVALID_PARAMS",
|
|
360
|
+
"message": "reference parameter required for update action",
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
component = schematic.components.get(reference)
|
|
365
|
+
if not component:
|
|
366
|
+
return {
|
|
367
|
+
"success": False,
|
|
368
|
+
"error": "NOT_FOUND",
|
|
369
|
+
"message": f"Component {reference} not found",
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
updated_fields = []
|
|
373
|
+
if value is not None:
|
|
374
|
+
component.value = value
|
|
375
|
+
updated_fields.append("value")
|
|
376
|
+
if position is not None:
|
|
377
|
+
component.position = position
|
|
378
|
+
updated_fields.append("position")
|
|
379
|
+
if rotation is not None:
|
|
380
|
+
component.rotation = rotation
|
|
381
|
+
updated_fields.append("rotation")
|
|
382
|
+
if footprint is not None:
|
|
383
|
+
component.footprint = footprint
|
|
384
|
+
updated_fields.append("footprint")
|
|
385
|
+
|
|
386
|
+
logger.info(f"[MCP] Updated {reference}: {updated_fields}")
|
|
387
|
+
return {
|
|
388
|
+
"success": True,
|
|
389
|
+
"reference": reference,
|
|
390
|
+
"message": f"Updated {reference}",
|
|
391
|
+
"updated_fields": updated_fields,
|
|
392
|
+
}
|
|
393
|
+
except Exception as e:
|
|
394
|
+
logger.error(f"[MCP] Error updating {reference}: {e}", exc_info=True)
|
|
395
|
+
return {
|
|
396
|
+
"success": False,
|
|
397
|
+
"error": "UPDATE_ERROR",
|
|
398
|
+
"message": f"Failed to update component: {str(e)}",
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
elif action == "remove":
|
|
402
|
+
if not reference:
|
|
403
|
+
return {
|
|
404
|
+
"success": False,
|
|
405
|
+
"error": "INVALID_PARAMS",
|
|
406
|
+
"message": "reference parameter required for remove action",
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
try:
|
|
410
|
+
schematic.components.remove(reference)
|
|
411
|
+
|
|
412
|
+
logger.info(f"[MCP] Removed component: {reference}")
|
|
413
|
+
return {
|
|
414
|
+
"success": True,
|
|
415
|
+
"reference": reference,
|
|
416
|
+
"message": f"Removed {reference} and all connected wires",
|
|
417
|
+
}
|
|
418
|
+
except Exception as e:
|
|
419
|
+
logger.error(f"[MCP] Error removing {reference}: {e}", exc_info=True)
|
|
420
|
+
return {
|
|
421
|
+
"success": False,
|
|
422
|
+
"error": "REMOVE_ERROR",
|
|
423
|
+
"message": f"Failed to remove component: {str(e)}",
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
else:
|
|
427
|
+
return {
|
|
428
|
+
"success": False,
|
|
429
|
+
"error": "INVALID_ACTION",
|
|
430
|
+
"message": f"Unknown action: {action}. Valid actions: add, list, get_pins, update, remove",
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# ============================================================================
|
|
435
|
+
# 3. MANAGE WIRES (add, remove)
|
|
436
|
+
# ============================================================================
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
async def manage_wires(
|
|
440
|
+
action: str,
|
|
441
|
+
start: Optional[Tuple[float, float]] = None,
|
|
442
|
+
end: Optional[Tuple[float, float]] = None,
|
|
443
|
+
wire_uuid: Optional[str] = None,
|
|
444
|
+
ctx: Optional[Context] = None,
|
|
445
|
+
) -> dict:
|
|
446
|
+
"""
|
|
447
|
+
Manage wires (add, remove).
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
action: Operation ("add", "remove")
|
|
451
|
+
start: Start point (required for "add")
|
|
452
|
+
end: End point (required for "add")
|
|
453
|
+
wire_uuid: Wire UUID (required for "remove")
|
|
454
|
+
ctx: MCP context (optional)
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
Dictionary with wire data or error
|
|
458
|
+
"""
|
|
459
|
+
logger.info(f"[MCP] manage_wires called: action={action}")
|
|
460
|
+
|
|
461
|
+
schematic = get_current_schematic()
|
|
462
|
+
if schematic is None:
|
|
463
|
+
return {
|
|
464
|
+
"success": False,
|
|
465
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
466
|
+
"message": "No schematic is currently loaded",
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if action == "add":
|
|
470
|
+
if not start or not end:
|
|
471
|
+
return {
|
|
472
|
+
"success": False,
|
|
473
|
+
"error": "INVALID_PARAMS",
|
|
474
|
+
"message": "start and end parameters required for add action",
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
try:
|
|
478
|
+
if ctx:
|
|
479
|
+
await ctx.report_progress(0, 100, "Adding wire")
|
|
480
|
+
|
|
481
|
+
wire_uuid = schematic.wires.add(start=start, end=end)
|
|
482
|
+
|
|
483
|
+
if ctx:
|
|
484
|
+
await ctx.report_progress(100, 100, "Wire added")
|
|
485
|
+
|
|
486
|
+
logger.info(f"[MCP] Added wire: {start} -> {end}")
|
|
487
|
+
return {
|
|
488
|
+
"success": True,
|
|
489
|
+
"wire_uuid": str(wire_uuid),
|
|
490
|
+
"start": {"x": start[0], "y": start[1]},
|
|
491
|
+
"end": {"x": end[0], "y": end[1]},
|
|
492
|
+
"message": "Added wire connection",
|
|
493
|
+
}
|
|
494
|
+
except Exception as e:
|
|
495
|
+
logger.error(f"[MCP] Error adding wire: {e}", exc_info=True)
|
|
496
|
+
return {
|
|
497
|
+
"success": False,
|
|
498
|
+
"error": "ADD_ERROR",
|
|
499
|
+
"message": f"Failed to add wire: {str(e)}",
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
elif action == "remove":
|
|
503
|
+
if not wire_uuid:
|
|
504
|
+
return {
|
|
505
|
+
"success": False,
|
|
506
|
+
"error": "INVALID_PARAMS",
|
|
507
|
+
"message": "wire_uuid parameter required for remove action",
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
try:
|
|
511
|
+
schematic.remove_wire(wire_uuid)
|
|
512
|
+
|
|
513
|
+
logger.info(f"[MCP] Removed wire: {wire_uuid}")
|
|
514
|
+
return {
|
|
515
|
+
"success": True,
|
|
516
|
+
"wire_uuid": wire_uuid,
|
|
517
|
+
"message": "Removed wire",
|
|
518
|
+
}
|
|
519
|
+
except Exception as e:
|
|
520
|
+
logger.error(f"[MCP] Error removing wire: {e}", exc_info=True)
|
|
521
|
+
return {
|
|
522
|
+
"success": False,
|
|
523
|
+
"error": "REMOVE_ERROR",
|
|
524
|
+
"message": f"Failed to remove wire: {str(e)}",
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
else:
|
|
528
|
+
return {
|
|
529
|
+
"success": False,
|
|
530
|
+
"error": "INVALID_ACTION",
|
|
531
|
+
"message": f"Unknown action: {action}. Valid actions: add, remove",
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
# ============================================================================
|
|
536
|
+
# 4. MANAGE LABELS (add, remove)
|
|
537
|
+
# ============================================================================
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
async def manage_labels(
|
|
541
|
+
action: str,
|
|
542
|
+
text: Optional[str] = None,
|
|
543
|
+
position: Optional[Tuple[float, float]] = None,
|
|
544
|
+
rotation: float = 0.0,
|
|
545
|
+
label_uuid: Optional[str] = None,
|
|
546
|
+
ctx: Optional[Context] = None,
|
|
547
|
+
) -> dict:
|
|
548
|
+
"""
|
|
549
|
+
Manage labels (add, remove).
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
action: Operation ("add", "remove")
|
|
553
|
+
text: Label text (required for "add")
|
|
554
|
+
position: Position (required for "add")
|
|
555
|
+
rotation: Rotation in degrees (optional)
|
|
556
|
+
label_uuid: Label UUID (required for "remove")
|
|
557
|
+
ctx: MCP context (optional)
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
Dictionary with label data or error
|
|
561
|
+
"""
|
|
562
|
+
logger.info(f"[MCP] manage_labels called: action={action}")
|
|
563
|
+
|
|
564
|
+
schematic = get_current_schematic()
|
|
565
|
+
if schematic is None:
|
|
566
|
+
return {
|
|
567
|
+
"success": False,
|
|
568
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
569
|
+
"message": "No schematic is currently loaded",
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if action == "add":
|
|
573
|
+
if not text or not position:
|
|
574
|
+
return {
|
|
575
|
+
"success": False,
|
|
576
|
+
"error": "INVALID_PARAMS",
|
|
577
|
+
"message": "text and position parameters required for add action",
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
try:
|
|
581
|
+
if ctx:
|
|
582
|
+
await ctx.report_progress(0, 100, f"Adding label: {text}")
|
|
583
|
+
|
|
584
|
+
label_uuid = schematic.add_label(text=text, position=position, rotation=rotation)
|
|
585
|
+
|
|
586
|
+
if ctx:
|
|
587
|
+
await ctx.report_progress(100, 100, f"Label added: {text}")
|
|
588
|
+
|
|
589
|
+
logger.info(f"[MCP] Added label: {text}")
|
|
590
|
+
return {
|
|
591
|
+
"success": True,
|
|
592
|
+
"label_uuid": str(label_uuid),
|
|
593
|
+
"text": text,
|
|
594
|
+
"position": {"x": position[0], "y": position[1]},
|
|
595
|
+
"rotation": rotation,
|
|
596
|
+
"message": f"Added label: {text}",
|
|
597
|
+
}
|
|
598
|
+
except Exception as e:
|
|
599
|
+
logger.error(f"[MCP] Error adding label: {e}", exc_info=True)
|
|
600
|
+
return {
|
|
601
|
+
"success": False,
|
|
602
|
+
"error": "ADD_ERROR",
|
|
603
|
+
"message": f"Failed to add label: {str(e)}",
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
elif action == "remove":
|
|
607
|
+
if not label_uuid:
|
|
608
|
+
return {
|
|
609
|
+
"success": False,
|
|
610
|
+
"error": "INVALID_PARAMS",
|
|
611
|
+
"message": "label_uuid parameter required for remove action",
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
try:
|
|
615
|
+
schematic.remove_label(label_uuid)
|
|
616
|
+
|
|
617
|
+
logger.info(f"[MCP] Removed label: {label_uuid}")
|
|
618
|
+
return {
|
|
619
|
+
"success": True,
|
|
620
|
+
"label_uuid": label_uuid,
|
|
621
|
+
"message": "Removed label",
|
|
622
|
+
}
|
|
623
|
+
except Exception as e:
|
|
624
|
+
logger.error(f"[MCP] Error removing label: {e}", exc_info=True)
|
|
625
|
+
return {
|
|
626
|
+
"success": False,
|
|
627
|
+
"error": "REMOVE_ERROR",
|
|
628
|
+
"message": f"Failed to remove label: {str(e)}",
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
else:
|
|
632
|
+
return {
|
|
633
|
+
"success": False,
|
|
634
|
+
"error": "INVALID_ACTION",
|
|
635
|
+
"message": f"Unknown action: {action}. Valid actions: add, remove",
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# ============================================================================
|
|
640
|
+
# 5. MANAGE TEXT BOXES (add, update, remove)
|
|
641
|
+
# ============================================================================
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
async def manage_text_boxes(
|
|
645
|
+
action: str,
|
|
646
|
+
text: Optional[str] = None,
|
|
647
|
+
position: Optional[Tuple[float, float]] = None,
|
|
648
|
+
size: Optional[Tuple[float, float]] = None,
|
|
649
|
+
rotation: float = 0.0,
|
|
650
|
+
font_size: Optional[float] = None,
|
|
651
|
+
text_box_uuid: Optional[str] = None,
|
|
652
|
+
ctx: Optional[Context] = None,
|
|
653
|
+
) -> dict:
|
|
654
|
+
"""
|
|
655
|
+
Manage text boxes (add, update, remove).
|
|
656
|
+
|
|
657
|
+
Args:
|
|
658
|
+
action: Operation ("add", "update", "remove")
|
|
659
|
+
text: Text content (required for "add", optional for "update")
|
|
660
|
+
position: Position (required for "add")
|
|
661
|
+
size: Size tuple (required for "add")
|
|
662
|
+
rotation: Rotation in degrees (optional)
|
|
663
|
+
font_size: Font size (optional)
|
|
664
|
+
text_box_uuid: TextBox UUID (required for "update"/"remove")
|
|
665
|
+
ctx: MCP context (optional)
|
|
666
|
+
|
|
667
|
+
Returns:
|
|
668
|
+
Dictionary with text box data or error
|
|
669
|
+
"""
|
|
670
|
+
logger.info(f"[MCP] manage_text_boxes called: action={action}")
|
|
671
|
+
|
|
672
|
+
schematic = get_current_schematic()
|
|
673
|
+
if schematic is None:
|
|
674
|
+
return {
|
|
675
|
+
"success": False,
|
|
676
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
677
|
+
"message": "No schematic is currently loaded",
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if action == "add":
|
|
681
|
+
if not text or not position or not size:
|
|
682
|
+
return {
|
|
683
|
+
"success": False,
|
|
684
|
+
"error": "INVALID_PARAMS",
|
|
685
|
+
"message": "text, position, and size parameters required for add action",
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
try:
|
|
689
|
+
if ctx:
|
|
690
|
+
await ctx.report_progress(0, 100, "Adding text box")
|
|
691
|
+
|
|
692
|
+
text_box_uuid = schematic.add_text_box(
|
|
693
|
+
text=text,
|
|
694
|
+
position=position,
|
|
695
|
+
size=size,
|
|
696
|
+
rotation=rotation,
|
|
697
|
+
font_size=font_size,
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
if ctx:
|
|
701
|
+
await ctx.report_progress(100, 100, "Text box added")
|
|
702
|
+
|
|
703
|
+
logger.info(f"[MCP] Added text box")
|
|
704
|
+
return {
|
|
705
|
+
"success": True,
|
|
706
|
+
"text_box_uuid": str(text_box_uuid),
|
|
707
|
+
"text": text,
|
|
708
|
+
"position": {"x": position[0], "y": position[1]},
|
|
709
|
+
"size": {"width": size[0], "height": size[1]},
|
|
710
|
+
"message": "Added text box",
|
|
711
|
+
}
|
|
712
|
+
except Exception as e:
|
|
713
|
+
logger.error(f"[MCP] Error adding text box: {e}", exc_info=True)
|
|
714
|
+
return {
|
|
715
|
+
"success": False,
|
|
716
|
+
"error": "ADD_ERROR",
|
|
717
|
+
"message": f"Failed to add text box: {str(e)}",
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
elif action == "update":
|
|
721
|
+
if not text_box_uuid:
|
|
722
|
+
return {
|
|
723
|
+
"success": False,
|
|
724
|
+
"error": "INVALID_PARAMS",
|
|
725
|
+
"message": "text_box_uuid parameter required for update action",
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
try:
|
|
729
|
+
# Try to update text box via Python API
|
|
730
|
+
text_box = schematic.update_text_box(
|
|
731
|
+
text_box_uuid=text_box_uuid,
|
|
732
|
+
text=text,
|
|
733
|
+
font_size=font_size,
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
updated_fields = []
|
|
737
|
+
if text is not None:
|
|
738
|
+
updated_fields.append("text")
|
|
739
|
+
if font_size is not None:
|
|
740
|
+
updated_fields.append("font_size")
|
|
741
|
+
|
|
742
|
+
logger.info(f"[MCP] Updated text box: {updated_fields}")
|
|
743
|
+
return {
|
|
744
|
+
"success": True,
|
|
745
|
+
"text_box_uuid": text_box_uuid,
|
|
746
|
+
"message": "Updated text box",
|
|
747
|
+
"updated_fields": updated_fields,
|
|
748
|
+
}
|
|
749
|
+
except Exception as e:
|
|
750
|
+
logger.error(f"[MCP] Error updating text box: {e}", exc_info=True)
|
|
751
|
+
return {
|
|
752
|
+
"success": False,
|
|
753
|
+
"error": "UPDATE_ERROR",
|
|
754
|
+
"message": f"Failed to update text box: {str(e)}",
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
elif action == "remove":
|
|
758
|
+
if not text_box_uuid:
|
|
759
|
+
return {
|
|
760
|
+
"success": False,
|
|
761
|
+
"error": "INVALID_PARAMS",
|
|
762
|
+
"message": "text_box_uuid parameter required for remove action",
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
try:
|
|
766
|
+
schematic.remove_text_box(text_box_uuid)
|
|
767
|
+
|
|
768
|
+
logger.info(f"[MCP] Removed text box: {text_box_uuid}")
|
|
769
|
+
return {
|
|
770
|
+
"success": True,
|
|
771
|
+
"text_box_uuid": text_box_uuid,
|
|
772
|
+
"message": "Removed text box",
|
|
773
|
+
}
|
|
774
|
+
except Exception as e:
|
|
775
|
+
logger.error(f"[MCP] Error removing text box: {e}", exc_info=True)
|
|
776
|
+
return {
|
|
777
|
+
"success": False,
|
|
778
|
+
"error": "REMOVE_ERROR",
|
|
779
|
+
"message": f"Failed to remove text box: {str(e)}",
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
else:
|
|
783
|
+
return {
|
|
784
|
+
"success": False,
|
|
785
|
+
"error": "INVALID_ACTION",
|
|
786
|
+
"message": f"Unknown action: {action}. Valid actions: add, update, remove",
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
# ============================================================================
|
|
791
|
+
# 6. MANAGE POWER (add, list, remove)
|
|
792
|
+
# ============================================================================
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
async def manage_power(
|
|
796
|
+
action: str,
|
|
797
|
+
power_net: Optional[str] = None,
|
|
798
|
+
position: Optional[Tuple[float, float]] = None,
|
|
799
|
+
symbol_variant: str = "auto",
|
|
800
|
+
power_symbol_uuid: Optional[str] = None,
|
|
801
|
+
ctx: Optional[Context] = None,
|
|
802
|
+
) -> dict:
|
|
803
|
+
"""
|
|
804
|
+
Manage power symbols (add, list, remove).
|
|
805
|
+
|
|
806
|
+
Args:
|
|
807
|
+
action: Operation ("add", "list", "remove")
|
|
808
|
+
power_net: Power net name (required for "add")
|
|
809
|
+
position: Position (required for "add")
|
|
810
|
+
symbol_variant: Symbol variant (optional)
|
|
811
|
+
power_symbol_uuid: Power symbol UUID (required for "remove")
|
|
812
|
+
ctx: MCP context (optional)
|
|
813
|
+
|
|
814
|
+
Returns:
|
|
815
|
+
Dictionary with power data or error
|
|
816
|
+
"""
|
|
817
|
+
logger.info(f"[MCP] manage_power called: action={action}")
|
|
818
|
+
|
|
819
|
+
schematic = get_current_schematic()
|
|
820
|
+
if schematic is None:
|
|
821
|
+
return {
|
|
822
|
+
"success": False,
|
|
823
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
824
|
+
"message": "No schematic is currently loaded",
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if action == "add":
|
|
828
|
+
if not power_net or not position:
|
|
829
|
+
return {
|
|
830
|
+
"success": False,
|
|
831
|
+
"error": "INVALID_PARAMS",
|
|
832
|
+
"message": "power_net and position parameters required for add action",
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
try:
|
|
836
|
+
if ctx:
|
|
837
|
+
await ctx.report_progress(0, 100, f"Adding power symbol: {power_net}")
|
|
838
|
+
|
|
839
|
+
symbol = schematic.add_power_symbol(
|
|
840
|
+
power_net=power_net,
|
|
841
|
+
position=position,
|
|
842
|
+
symbol_variant=symbol_variant,
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
if ctx:
|
|
846
|
+
await ctx.report_progress(100, 100, f"Power symbol added: {power_net}")
|
|
847
|
+
|
|
848
|
+
logger.info(f"[MCP] Added power symbol: {power_net}")
|
|
849
|
+
return {
|
|
850
|
+
"success": True,
|
|
851
|
+
"symbol_uuid": str(symbol.uuid) if hasattr(symbol, 'uuid') else power_net,
|
|
852
|
+
"power_net": power_net,
|
|
853
|
+
"position": {"x": position[0], "y": position[1]},
|
|
854
|
+
"message": f"Added power symbol: {power_net}",
|
|
855
|
+
}
|
|
856
|
+
except Exception as e:
|
|
857
|
+
logger.error(f"[MCP] Error adding power symbol: {e}", exc_info=True)
|
|
858
|
+
return {
|
|
859
|
+
"success": False,
|
|
860
|
+
"error": "ADD_ERROR",
|
|
861
|
+
"message": f"Failed to add power symbol: {str(e)}",
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
elif action == "list":
|
|
865
|
+
try:
|
|
866
|
+
# Try to list power nets from schematic
|
|
867
|
+
power_nets = []
|
|
868
|
+
# This is a placeholder; actual implementation depends on Python API
|
|
869
|
+
try:
|
|
870
|
+
for power_net in schematic.power_nets: # If method exists
|
|
871
|
+
power_nets.append({
|
|
872
|
+
"name": power_net.name,
|
|
873
|
+
"position": {"x": power_net.position.x, "y": power_net.position.y},
|
|
874
|
+
"uuid": str(power_net.uuid),
|
|
875
|
+
})
|
|
876
|
+
except AttributeError:
|
|
877
|
+
# Fallback: return empty list if power_nets not available
|
|
878
|
+
power_nets = []
|
|
879
|
+
|
|
880
|
+
logger.info(f"[MCP] Listed {len(power_nets)} power nets")
|
|
881
|
+
return {
|
|
882
|
+
"success": True,
|
|
883
|
+
"count": len(power_nets),
|
|
884
|
+
"power_nets": power_nets,
|
|
885
|
+
}
|
|
886
|
+
except Exception as e:
|
|
887
|
+
logger.error(f"[MCP] Error listing power nets: {e}", exc_info=True)
|
|
888
|
+
return {
|
|
889
|
+
"success": False,
|
|
890
|
+
"error": "LIST_ERROR",
|
|
891
|
+
"message": f"Failed to list power nets: {str(e)}",
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
elif action == "remove":
|
|
895
|
+
if not power_symbol_uuid:
|
|
896
|
+
return {
|
|
897
|
+
"success": False,
|
|
898
|
+
"error": "INVALID_PARAMS",
|
|
899
|
+
"message": "power_symbol_uuid parameter required for remove action",
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
try:
|
|
903
|
+
schematic.remove_power_symbol(power_symbol_uuid)
|
|
904
|
+
|
|
905
|
+
logger.info(f"[MCP] Removed power symbol: {power_symbol_uuid}")
|
|
906
|
+
return {
|
|
907
|
+
"success": True,
|
|
908
|
+
"power_symbol_uuid": power_symbol_uuid,
|
|
909
|
+
"message": "Removed power symbol",
|
|
910
|
+
}
|
|
911
|
+
except Exception as e:
|
|
912
|
+
logger.error(f"[MCP] Error removing power symbol: {e}", exc_info=True)
|
|
913
|
+
return {
|
|
914
|
+
"success": False,
|
|
915
|
+
"error": "REMOVE_ERROR",
|
|
916
|
+
"message": f"Failed to remove power symbol: {str(e)}",
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
else:
|
|
920
|
+
return {
|
|
921
|
+
"success": False,
|
|
922
|
+
"error": "INVALID_ACTION",
|
|
923
|
+
"message": f"Unknown action: {action}. Valid actions: add, list, remove",
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
# ============================================================================
|
|
928
|
+
# 7. MANAGE SHEETS (add, set_context, list, remove)
|
|
929
|
+
# ============================================================================
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
async def manage_sheets(
|
|
933
|
+
action: str,
|
|
934
|
+
name: Optional[str] = None,
|
|
935
|
+
filename: Optional[str] = None,
|
|
936
|
+
position: Optional[Tuple[float, float]] = None,
|
|
937
|
+
size: Optional[Tuple[float, float]] = None,
|
|
938
|
+
project_name: Optional[str] = None,
|
|
939
|
+
parent_uuid: Optional[str] = None,
|
|
940
|
+
sheet_uuid: Optional[str] = None,
|
|
941
|
+
ctx: Optional[Context] = None,
|
|
942
|
+
) -> dict:
|
|
943
|
+
"""
|
|
944
|
+
Manage hierarchical sheets (add, set_context, list, remove).
|
|
945
|
+
|
|
946
|
+
Args:
|
|
947
|
+
action: Operation ("add", "set_context", "list", "remove")
|
|
948
|
+
name: Sheet name (required for "add")
|
|
949
|
+
filename: Filename (required for "add")
|
|
950
|
+
position: Position (required for "add")
|
|
951
|
+
size: Size (required for "add")
|
|
952
|
+
project_name: Project name (optional for "add")
|
|
953
|
+
parent_uuid: Parent UUID (required for "set_context")
|
|
954
|
+
sheet_uuid: Sheet UUID (required for "set_context"/"remove")
|
|
955
|
+
ctx: MCP context (optional)
|
|
956
|
+
|
|
957
|
+
Returns:
|
|
958
|
+
Dictionary with sheet data or error
|
|
959
|
+
"""
|
|
960
|
+
logger.info(f"[MCP] manage_sheets called: action={action}")
|
|
961
|
+
|
|
962
|
+
schematic = get_current_schematic()
|
|
963
|
+
if schematic is None:
|
|
964
|
+
return {
|
|
965
|
+
"success": False,
|
|
966
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
967
|
+
"message": "No schematic is currently loaded",
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if action == "add":
|
|
971
|
+
if not name or not filename or not position or not size:
|
|
972
|
+
return {
|
|
973
|
+
"success": False,
|
|
974
|
+
"error": "INVALID_PARAMS",
|
|
975
|
+
"message": "name, filename, position, and size are required for add action",
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
try:
|
|
979
|
+
if ctx:
|
|
980
|
+
await ctx.report_progress(0, 100, f"Adding hierarchical sheet: {name}")
|
|
981
|
+
|
|
982
|
+
sheet_uuid = schematic.sheets.add_sheet(
|
|
983
|
+
name=name,
|
|
984
|
+
filename=filename,
|
|
985
|
+
position=position,
|
|
986
|
+
size=size,
|
|
987
|
+
project_name=project_name or schematic.title_block.get('title'),
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
if ctx:
|
|
991
|
+
await ctx.report_progress(100, 100, f"Sheet added: {name}")
|
|
992
|
+
|
|
993
|
+
logger.info(f"[MCP] Added hierarchical sheet: {name}")
|
|
994
|
+
return {
|
|
995
|
+
"success": True,
|
|
996
|
+
"sheet_uuid": str(sheet_uuid),
|
|
997
|
+
"name": name,
|
|
998
|
+
"filename": filename,
|
|
999
|
+
"position": {"x": position[0], "y": position[1]},
|
|
1000
|
+
"size": {"width": size[0], "height": size[1]},
|
|
1001
|
+
"message": f"Added hierarchical sheet: {name}",
|
|
1002
|
+
}
|
|
1003
|
+
except Exception as e:
|
|
1004
|
+
logger.error(f"[MCP] Error adding hierarchical sheet: {e}", exc_info=True)
|
|
1005
|
+
return {
|
|
1006
|
+
"success": False,
|
|
1007
|
+
"error": "ADD_ERROR",
|
|
1008
|
+
"message": f"Failed to add hierarchical sheet: {str(e)}",
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
elif action == "set_context":
|
|
1012
|
+
if not parent_uuid or not sheet_uuid:
|
|
1013
|
+
return {
|
|
1014
|
+
"success": False,
|
|
1015
|
+
"error": "INVALID_PARAMS",
|
|
1016
|
+
"message": "parent_uuid and sheet_uuid are required for set_context action",
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
try:
|
|
1020
|
+
if ctx:
|
|
1021
|
+
await ctx.report_progress(0, 100, "Setting hierarchy context")
|
|
1022
|
+
|
|
1023
|
+
schematic.set_hierarchy_context(parent_uuid, sheet_uuid)
|
|
1024
|
+
|
|
1025
|
+
if ctx:
|
|
1026
|
+
await ctx.report_progress(100, 100, "Context set")
|
|
1027
|
+
|
|
1028
|
+
logger.info(f"[MCP] Set hierarchy context: parent={parent_uuid}, sheet={sheet_uuid}")
|
|
1029
|
+
return {
|
|
1030
|
+
"success": True,
|
|
1031
|
+
"message": "Hierarchy context set",
|
|
1032
|
+
"parent_uuid": parent_uuid,
|
|
1033
|
+
"sheet_uuid": sheet_uuid,
|
|
1034
|
+
}
|
|
1035
|
+
except Exception as e:
|
|
1036
|
+
logger.error(f"[MCP] Error setting hierarchy context: {e}", exc_info=True)
|
|
1037
|
+
return {
|
|
1038
|
+
"success": False,
|
|
1039
|
+
"error": "CONTEXT_ERROR",
|
|
1040
|
+
"message": f"Failed to set hierarchy context: {str(e)}",
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
elif action == "list":
|
|
1044
|
+
try:
|
|
1045
|
+
sheets = []
|
|
1046
|
+
# Try to iterate through sheets - handle different API versions
|
|
1047
|
+
try:
|
|
1048
|
+
# Try getting all sheets
|
|
1049
|
+
for sheet in schematic.sheets.values() if hasattr(schematic.sheets, 'values') else schematic.sheets:
|
|
1050
|
+
sheets.append({
|
|
1051
|
+
"uuid": str(sheet.uuid) if hasattr(sheet, 'uuid') else str(sheet),
|
|
1052
|
+
"name": sheet.name if hasattr(sheet, 'name') else '',
|
|
1053
|
+
"filename": sheet.filename if hasattr(sheet, 'filename') else '',
|
|
1054
|
+
"position": {"x": sheet.position.x if hasattr(sheet, 'position') else 0, "y": sheet.position.y if hasattr(sheet, 'position') else 0},
|
|
1055
|
+
})
|
|
1056
|
+
except (TypeError, AttributeError):
|
|
1057
|
+
# If iteration doesn't work, try using a method
|
|
1058
|
+
pass
|
|
1059
|
+
|
|
1060
|
+
logger.info(f"[MCP] Listed {len(sheets)} hierarchical sheets")
|
|
1061
|
+
return {
|
|
1062
|
+
"success": True,
|
|
1063
|
+
"count": len(sheets),
|
|
1064
|
+
"sheets": sheets,
|
|
1065
|
+
}
|
|
1066
|
+
except Exception as e:
|
|
1067
|
+
logger.error(f"[MCP] Error listing sheets: {e}", exc_info=True)
|
|
1068
|
+
return {
|
|
1069
|
+
"success": False,
|
|
1070
|
+
"error": "LIST_ERROR",
|
|
1071
|
+
"message": f"Failed to list sheets: {str(e)}",
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
elif action == "remove":
|
|
1075
|
+
if not sheet_uuid:
|
|
1076
|
+
return {
|
|
1077
|
+
"success": False,
|
|
1078
|
+
"error": "INVALID_PARAMS",
|
|
1079
|
+
"message": "sheet_uuid parameter required for remove action",
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
try:
|
|
1083
|
+
schematic.sheets.remove(sheet_uuid)
|
|
1084
|
+
|
|
1085
|
+
logger.info(f"[MCP] Removed hierarchical sheet: {sheet_uuid}")
|
|
1086
|
+
return {
|
|
1087
|
+
"success": True,
|
|
1088
|
+
"sheet_uuid": sheet_uuid,
|
|
1089
|
+
"message": "Removed hierarchical sheet",
|
|
1090
|
+
}
|
|
1091
|
+
except Exception as e:
|
|
1092
|
+
logger.error(f"[MCP] Error removing sheet: {e}", exc_info=True)
|
|
1093
|
+
return {
|
|
1094
|
+
"success": False,
|
|
1095
|
+
"error": "REMOVE_ERROR",
|
|
1096
|
+
"message": f"Failed to remove sheet: {str(e)}",
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
else:
|
|
1100
|
+
return {
|
|
1101
|
+
"success": False,
|
|
1102
|
+
"error": "INVALID_ACTION",
|
|
1103
|
+
"message": f"Unknown action: {action}. Valid actions: add, set_context, list, remove",
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
# ============================================================================
|
|
1108
|
+
# 8. MANAGE GLOBAL LABELS (add, remove)
|
|
1109
|
+
# ============================================================================
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
async def manage_global_labels(
|
|
1113
|
+
action: str,
|
|
1114
|
+
text: Optional[str] = None,
|
|
1115
|
+
position: Optional[Tuple[float, float]] = None,
|
|
1116
|
+
shape: str = "input",
|
|
1117
|
+
rotation: float = 0.0,
|
|
1118
|
+
label_uuid: Optional[str] = None,
|
|
1119
|
+
ctx: Optional[Context] = None,
|
|
1120
|
+
) -> dict:
|
|
1121
|
+
"""
|
|
1122
|
+
Manage global labels (add, remove).
|
|
1123
|
+
|
|
1124
|
+
Args:
|
|
1125
|
+
action: Operation ("add", "remove")
|
|
1126
|
+
text: Label text (required for "add")
|
|
1127
|
+
position: Position (required for "add")
|
|
1128
|
+
shape: Label shape (optional)
|
|
1129
|
+
rotation: Rotation in degrees (optional)
|
|
1130
|
+
label_uuid: Label UUID (required for "remove")
|
|
1131
|
+
ctx: MCP context (optional)
|
|
1132
|
+
|
|
1133
|
+
Returns:
|
|
1134
|
+
Dictionary with label data or error
|
|
1135
|
+
"""
|
|
1136
|
+
logger.info(f"[MCP] manage_global_labels called: action={action}")
|
|
1137
|
+
|
|
1138
|
+
schematic = get_current_schematic()
|
|
1139
|
+
if schematic is None:
|
|
1140
|
+
return {
|
|
1141
|
+
"success": False,
|
|
1142
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
1143
|
+
"message": "No schematic is currently loaded",
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
if action == "add":
|
|
1147
|
+
if not text or not position:
|
|
1148
|
+
return {
|
|
1149
|
+
"success": False,
|
|
1150
|
+
"error": "INVALID_PARAMS",
|
|
1151
|
+
"message": "text and position parameters required for add action",
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
try:
|
|
1155
|
+
if ctx:
|
|
1156
|
+
await ctx.report_progress(0, 100, f"Adding global label: {text}")
|
|
1157
|
+
|
|
1158
|
+
label_uuid = schematic.add_global_label(
|
|
1159
|
+
text=text,
|
|
1160
|
+
position=position,
|
|
1161
|
+
shape=shape,
|
|
1162
|
+
rotation=rotation,
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
if ctx:
|
|
1166
|
+
await ctx.report_progress(100, 100, f"Global label added: {text}")
|
|
1167
|
+
|
|
1168
|
+
logger.info(f"[MCP] Added global label: {text}")
|
|
1169
|
+
return {
|
|
1170
|
+
"success": True,
|
|
1171
|
+
"label_uuid": str(label_uuid),
|
|
1172
|
+
"text": text,
|
|
1173
|
+
"position": {"x": position[0], "y": position[1]},
|
|
1174
|
+
"shape": shape,
|
|
1175
|
+
"rotation": rotation,
|
|
1176
|
+
"message": f"Added global label: {text}",
|
|
1177
|
+
}
|
|
1178
|
+
except Exception as e:
|
|
1179
|
+
logger.error(f"[MCP] Error adding global label: {e}", exc_info=True)
|
|
1180
|
+
return {
|
|
1181
|
+
"success": False,
|
|
1182
|
+
"error": "ADD_ERROR",
|
|
1183
|
+
"message": f"Failed to add global label: {str(e)}",
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
elif action == "remove":
|
|
1187
|
+
if not label_uuid:
|
|
1188
|
+
return {
|
|
1189
|
+
"success": False,
|
|
1190
|
+
"error": "INVALID_PARAMS",
|
|
1191
|
+
"message": "label_uuid parameter required for remove action",
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
try:
|
|
1195
|
+
schematic.remove_global_label(label_uuid)
|
|
1196
|
+
|
|
1197
|
+
logger.info(f"[MCP] Removed global label: {label_uuid}")
|
|
1198
|
+
return {
|
|
1199
|
+
"success": True,
|
|
1200
|
+
"label_uuid": label_uuid,
|
|
1201
|
+
"message": "Removed global label",
|
|
1202
|
+
}
|
|
1203
|
+
except Exception as e:
|
|
1204
|
+
logger.error(f"[MCP] Error removing global label: {e}", exc_info=True)
|
|
1205
|
+
return {
|
|
1206
|
+
"success": False,
|
|
1207
|
+
"error": "REMOVE_ERROR",
|
|
1208
|
+
"message": f"Failed to remove global label: {str(e)}",
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
else:
|
|
1212
|
+
return {
|
|
1213
|
+
"success": False,
|
|
1214
|
+
"error": "INVALID_ACTION",
|
|
1215
|
+
"message": f"Unknown action: {action}. Valid actions: add, remove",
|
|
1216
|
+
}
|