kicad-sch-api 0.3.0__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kicad_sch_api/__init__.py +68 -3
- kicad_sch_api/cli/__init__.py +45 -0
- kicad_sch_api/cli/base.py +302 -0
- kicad_sch_api/cli/bom.py +164 -0
- kicad_sch_api/cli/erc.py +229 -0
- kicad_sch_api/cli/export_docs.py +289 -0
- kicad_sch_api/cli/kicad_to_python.py +169 -0
- kicad_sch_api/cli/netlist.py +94 -0
- kicad_sch_api/cli/types.py +43 -0
- kicad_sch_api/collections/__init__.py +36 -0
- kicad_sch_api/collections/base.py +604 -0
- kicad_sch_api/collections/components.py +1623 -0
- kicad_sch_api/collections/junctions.py +206 -0
- kicad_sch_api/collections/labels.py +508 -0
- kicad_sch_api/collections/wires.py +292 -0
- kicad_sch_api/core/__init__.py +37 -2
- kicad_sch_api/core/collections/__init__.py +5 -0
- kicad_sch_api/core/collections/base.py +248 -0
- kicad_sch_api/core/component_bounds.py +34 -7
- kicad_sch_api/core/components.py +213 -52
- kicad_sch_api/core/config.py +110 -15
- kicad_sch_api/core/connectivity.py +692 -0
- kicad_sch_api/core/exceptions.py +175 -0
- kicad_sch_api/core/factories/__init__.py +5 -0
- kicad_sch_api/core/factories/element_factory.py +278 -0
- kicad_sch_api/core/formatter.py +60 -9
- kicad_sch_api/core/geometry.py +94 -5
- kicad_sch_api/core/junctions.py +26 -75
- kicad_sch_api/core/labels.py +324 -0
- kicad_sch_api/core/managers/__init__.py +30 -0
- kicad_sch_api/core/managers/base.py +76 -0
- kicad_sch_api/core/managers/file_io.py +246 -0
- kicad_sch_api/core/managers/format_sync.py +502 -0
- kicad_sch_api/core/managers/graphics.py +580 -0
- kicad_sch_api/core/managers/hierarchy.py +661 -0
- kicad_sch_api/core/managers/metadata.py +271 -0
- kicad_sch_api/core/managers/sheet.py +492 -0
- kicad_sch_api/core/managers/text_elements.py +537 -0
- kicad_sch_api/core/managers/validation.py +476 -0
- kicad_sch_api/core/managers/wire.py +410 -0
- kicad_sch_api/core/nets.py +305 -0
- kicad_sch_api/core/no_connects.py +252 -0
- kicad_sch_api/core/parser.py +194 -970
- kicad_sch_api/core/parsing_utils.py +63 -0
- kicad_sch_api/core/pin_utils.py +103 -9
- kicad_sch_api/core/schematic.py +1328 -1079
- kicad_sch_api/core/texts.py +316 -0
- kicad_sch_api/core/types.py +159 -23
- kicad_sch_api/core/wires.py +27 -75
- 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 +38 -0
- kicad_sch_api/geometry/font_metrics.py +22 -0
- kicad_sch_api/geometry/routing.py +211 -0
- kicad_sch_api/geometry/symbol_bbox.py +608 -0
- kicad_sch_api/interfaces/__init__.py +17 -0
- kicad_sch_api/interfaces/parser.py +76 -0
- kicad_sch_api/interfaces/repository.py +70 -0
- kicad_sch_api/interfaces/resolver.py +117 -0
- kicad_sch_api/parsers/__init__.py +14 -0
- kicad_sch_api/parsers/base.py +145 -0
- kicad_sch_api/parsers/elements/__init__.py +22 -0
- kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
- kicad_sch_api/parsers/elements/label_parser.py +216 -0
- kicad_sch_api/parsers/elements/library_parser.py +165 -0
- kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
- kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
- kicad_sch_api/parsers/elements/symbol_parser.py +485 -0
- kicad_sch_api/parsers/elements/text_parser.py +250 -0
- kicad_sch_api/parsers/elements/wire_parser.py +242 -0
- kicad_sch_api/parsers/registry.py +155 -0
- kicad_sch_api/parsers/utils.py +80 -0
- kicad_sch_api/symbols/__init__.py +18 -0
- kicad_sch_api/symbols/cache.py +467 -0
- kicad_sch_api/symbols/resolver.py +361 -0
- kicad_sch_api/symbols/validators.py +504 -0
- 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/validation/__init__.py +25 -0
- kicad_sch_api/validation/erc.py +171 -0
- kicad_sch_api/validation/erc_models.py +203 -0
- kicad_sch_api/validation/pin_matrix.py +243 -0
- kicad_sch_api/validation/validators.py +391 -0
- 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.3.0.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/core/manhattan_routing.py +0 -430
- kicad_sch_api/core/simple_manhattan.py +0 -228
- kicad_sch_api/core/wire_routing.py +0 -380
- kicad_sch_api-0.3.0.dist-info/METADATA +0 -483
- kicad_sch_api-0.3.0.dist-info/RECORD +0 -31
- kicad_sch_api-0.3.0.dist-info/entry_points.txt +0 -2
- {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP tools for component management.
|
|
3
|
+
|
|
4
|
+
Provides MCP-compatible tools for adding, updating, listing, filtering, and
|
|
5
|
+
removing components in KiCAD schematics.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import TYPE_CHECKING, List, Optional, Tuple, Dict, Any
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from fastmcp import Context
|
|
13
|
+
else:
|
|
14
|
+
try:
|
|
15
|
+
from fastmcp import Context
|
|
16
|
+
except ImportError:
|
|
17
|
+
Context = None # type: ignore
|
|
18
|
+
|
|
19
|
+
import kicad_sch_api as ksa
|
|
20
|
+
from kicad_sch_api.core.exceptions import LibraryError, ValidationError
|
|
21
|
+
from mcp_server.models import ComponentInfoOutput, ErrorOutput, PointModel
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Import global schematic state from pin_discovery
|
|
28
|
+
from mcp_server.tools.pin_discovery import get_current_schematic
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _component_to_output(component: Any) -> ComponentInfoOutput:
|
|
32
|
+
"""Convert a Component to ComponentInfoOutput."""
|
|
33
|
+
return ComponentInfoOutput(
|
|
34
|
+
reference=component.reference,
|
|
35
|
+
lib_id=component.lib_id,
|
|
36
|
+
value=component.value,
|
|
37
|
+
position=PointModel(x=component.position.x, y=component.position.y),
|
|
38
|
+
rotation=component.rotation,
|
|
39
|
+
footprint=component.footprint,
|
|
40
|
+
uuid=str(component.uuid),
|
|
41
|
+
success=True,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def add_component(
|
|
46
|
+
lib_id: str,
|
|
47
|
+
value: str,
|
|
48
|
+
reference: Optional[str] = None,
|
|
49
|
+
position: Optional[Tuple[float, float]] = None,
|
|
50
|
+
rotation: float = 0.0,
|
|
51
|
+
footprint: Optional[str] = None,
|
|
52
|
+
ctx: Optional[Context] = None,
|
|
53
|
+
) -> ComponentInfoOutput | ErrorOutput:
|
|
54
|
+
"""
|
|
55
|
+
Add a component to the current schematic.
|
|
56
|
+
|
|
57
|
+
Creates a new component with the specified library ID, value, and optional
|
|
58
|
+
parameters. If no reference is provided, one will be auto-generated based on
|
|
59
|
+
the component type (e.g., R1, C1, U1). If no position is provided, the
|
|
60
|
+
component will be auto-placed.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
lib_id: Library identifier (e.g., "Device:R", "Amplifier_Operational:TL072")
|
|
64
|
+
value: Component value or part description (e.g., "10k", "100nF", "TL072")
|
|
65
|
+
reference: Component reference designator (e.g., "R1", "U2") - auto-generated if None
|
|
66
|
+
position: Component position as (x, y) tuple in mm - auto-placed if None
|
|
67
|
+
rotation: Component rotation in degrees (0, 90, 180, or 270), defaults to 0
|
|
68
|
+
footprint: PCB footprint identifier (e.g., "Resistor_SMD:R_0603_1608Metric")
|
|
69
|
+
ctx: MCP context for progress reporting (optional)
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
ComponentInfoOutput with component information, or ErrorOutput on failure
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
>>> # Add a resistor with auto-generated reference
|
|
76
|
+
>>> result = await add_component(
|
|
77
|
+
... lib_id="Device:R",
|
|
78
|
+
... value="10k",
|
|
79
|
+
... position=(100.0, 100.0)
|
|
80
|
+
... )
|
|
81
|
+
>>> print(f"Added {result.reference}")
|
|
82
|
+
|
|
83
|
+
>>> # Add a capacitor with specific reference and footprint
|
|
84
|
+
>>> result = await add_component(
|
|
85
|
+
... lib_id="Device:C",
|
|
86
|
+
... value="100nF",
|
|
87
|
+
... reference="C1",
|
|
88
|
+
... position=(120.0, 100.0),
|
|
89
|
+
... footprint="Capacitor_SMD:C_0603_1608Metric"
|
|
90
|
+
... )
|
|
91
|
+
"""
|
|
92
|
+
logger.info(
|
|
93
|
+
f"[MCP] add_component called: lib_id={lib_id}, value={value}, "
|
|
94
|
+
f"reference={reference}, position={position}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Report progress if context available
|
|
98
|
+
if ctx:
|
|
99
|
+
await ctx.report_progress(0, 100, f"Adding component {lib_id}")
|
|
100
|
+
|
|
101
|
+
# Check if schematic is loaded
|
|
102
|
+
schematic = get_current_schematic()
|
|
103
|
+
if schematic is None:
|
|
104
|
+
logger.error("[MCP] No schematic loaded")
|
|
105
|
+
return ErrorOutput(
|
|
106
|
+
error="NO_SCHEMATIC_LOADED",
|
|
107
|
+
message="No schematic is currently loaded. Please load or create a schematic first.",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
if ctx:
|
|
112
|
+
await ctx.report_progress(25, 100, f"Validating component parameters")
|
|
113
|
+
|
|
114
|
+
# Validate rotation
|
|
115
|
+
if rotation not in [0.0, 90.0, 180.0, 270.0]:
|
|
116
|
+
logger.warning(f"[MCP] Invalid rotation {rotation}, must be 0, 90, 180, or 270")
|
|
117
|
+
return ErrorOutput(
|
|
118
|
+
error="VALIDATION_ERROR",
|
|
119
|
+
message=f"Rotation must be 0, 90, 180, or 270 degrees, got {rotation}",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if ctx:
|
|
123
|
+
await ctx.report_progress(50, 100, f"Adding component to schematic")
|
|
124
|
+
|
|
125
|
+
# Add component using library API
|
|
126
|
+
component = schematic.components.add(
|
|
127
|
+
lib_id=lib_id,
|
|
128
|
+
reference=reference,
|
|
129
|
+
value=value,
|
|
130
|
+
position=position,
|
|
131
|
+
rotation=rotation,
|
|
132
|
+
footprint=footprint,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if ctx:
|
|
136
|
+
await ctx.report_progress(75, 100, f"Converting to MCP output format")
|
|
137
|
+
|
|
138
|
+
# Convert to MCP output model
|
|
139
|
+
result = _component_to_output(component)
|
|
140
|
+
result.message = f"Added component {component.reference}"
|
|
141
|
+
|
|
142
|
+
if ctx:
|
|
143
|
+
await ctx.report_progress(100, 100, f"Complete: added {component.reference}")
|
|
144
|
+
|
|
145
|
+
logger.info(f"[MCP] Successfully added component {component.reference}")
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
except ValidationError as e:
|
|
149
|
+
logger.error(f"[MCP] Validation error: {e}")
|
|
150
|
+
return ErrorOutput(
|
|
151
|
+
error="VALIDATION_ERROR",
|
|
152
|
+
message=f"Component validation failed: {str(e)}",
|
|
153
|
+
)
|
|
154
|
+
except LibraryError as e:
|
|
155
|
+
logger.error(f"[MCP] Library error: {e}")
|
|
156
|
+
return ErrorOutput(
|
|
157
|
+
error="LIBRARY_ERROR",
|
|
158
|
+
message=f"Symbol library error: {str(e)}",
|
|
159
|
+
)
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(f"[MCP] Unexpected error: {e}", exc_info=True)
|
|
162
|
+
return ErrorOutput(
|
|
163
|
+
error="INTERNAL_ERROR",
|
|
164
|
+
message=f"Unexpected error adding component: {str(e)}",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
async def list_components(
|
|
169
|
+
ctx: Optional[Context] = None,
|
|
170
|
+
) -> dict:
|
|
171
|
+
"""
|
|
172
|
+
List all components in the current schematic.
|
|
173
|
+
|
|
174
|
+
Returns all components with their complete metadata including position,
|
|
175
|
+
rotation, footprint, and properties.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
ctx: MCP context for progress reporting (optional)
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Dictionary with success status and list of components, or error information
|
|
182
|
+
|
|
183
|
+
Examples:
|
|
184
|
+
>>> result = await list_components()
|
|
185
|
+
>>> print(f"Found {result['count']} components")
|
|
186
|
+
>>> for comp in result['components']:
|
|
187
|
+
... print(f"{comp['reference']}: {comp['value']}")
|
|
188
|
+
"""
|
|
189
|
+
logger.info("[MCP] list_components called")
|
|
190
|
+
|
|
191
|
+
if ctx:
|
|
192
|
+
await ctx.report_progress(0, 100, "Listing all components")
|
|
193
|
+
|
|
194
|
+
# Check if schematic is loaded
|
|
195
|
+
schematic = get_current_schematic()
|
|
196
|
+
if schematic is None:
|
|
197
|
+
logger.error("[MCP] No schematic loaded")
|
|
198
|
+
return {
|
|
199
|
+
"success": False,
|
|
200
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
201
|
+
"message": "No schematic is currently loaded",
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
if ctx:
|
|
206
|
+
await ctx.report_progress(50, 100, "Retrieving components")
|
|
207
|
+
|
|
208
|
+
# Get all components (iterate directly)
|
|
209
|
+
components = list(schematic.components)
|
|
210
|
+
|
|
211
|
+
if ctx:
|
|
212
|
+
await ctx.report_progress(75, 100, f"Converting {len(components)} components")
|
|
213
|
+
|
|
214
|
+
# Convert to output format
|
|
215
|
+
component_list = []
|
|
216
|
+
for comp in components:
|
|
217
|
+
comp_output = _component_to_output(comp)
|
|
218
|
+
component_list.append(comp_output.model_dump())
|
|
219
|
+
|
|
220
|
+
if ctx:
|
|
221
|
+
await ctx.report_progress(100, 100, f"Complete: {len(components)} components")
|
|
222
|
+
|
|
223
|
+
logger.info(f"[MCP] Listed {len(components)} components")
|
|
224
|
+
return {
|
|
225
|
+
"success": True,
|
|
226
|
+
"count": len(components),
|
|
227
|
+
"components": component_list,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logger.error(f"[MCP] Unexpected error: {e}", exc_info=True)
|
|
232
|
+
return {
|
|
233
|
+
"success": False,
|
|
234
|
+
"error": "INTERNAL_ERROR",
|
|
235
|
+
"message": f"Unexpected error listing components: {str(e)}",
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
async def update_component(
|
|
240
|
+
reference: str,
|
|
241
|
+
value: Optional[str] = None,
|
|
242
|
+
position: Optional[Tuple[float, float]] = None,
|
|
243
|
+
rotation: Optional[float] = None,
|
|
244
|
+
footprint: Optional[str] = None,
|
|
245
|
+
ctx: Optional[Context] = None,
|
|
246
|
+
) -> ComponentInfoOutput | ErrorOutput:
|
|
247
|
+
"""
|
|
248
|
+
Update component properties.
|
|
249
|
+
|
|
250
|
+
Updates one or more properties of an existing component. Only provided
|
|
251
|
+
parameters will be updated.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
reference: Component reference designator to update (e.g., "R1")
|
|
255
|
+
value: New component value (if provided)
|
|
256
|
+
position: New position as (x, y) tuple in mm (if provided)
|
|
257
|
+
rotation: New rotation in degrees (0, 90, 180, or 270) (if provided)
|
|
258
|
+
footprint: New footprint identifier (if provided)
|
|
259
|
+
ctx: MCP context for progress reporting (optional)
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
ComponentInfoOutput with updated component information, or ErrorOutput on failure
|
|
263
|
+
|
|
264
|
+
Examples:
|
|
265
|
+
>>> # Update value only
|
|
266
|
+
>>> result = await update_component("R1", value="20k")
|
|
267
|
+
|
|
268
|
+
>>> # Update multiple properties
|
|
269
|
+
>>> result = await update_component(
|
|
270
|
+
... "R1",
|
|
271
|
+
... value="20k",
|
|
272
|
+
... footprint="Resistor_SMD:R_0805_2012Metric",
|
|
273
|
+
... rotation=90.0
|
|
274
|
+
... )
|
|
275
|
+
"""
|
|
276
|
+
logger.info(f"[MCP] update_component called for {reference}")
|
|
277
|
+
|
|
278
|
+
if ctx:
|
|
279
|
+
await ctx.report_progress(0, 100, f"Updating component {reference}")
|
|
280
|
+
|
|
281
|
+
# Check if schematic is loaded
|
|
282
|
+
schematic = get_current_schematic()
|
|
283
|
+
if schematic is None:
|
|
284
|
+
logger.error("[MCP] No schematic loaded")
|
|
285
|
+
return ErrorOutput(
|
|
286
|
+
error="NO_SCHEMATIC_LOADED",
|
|
287
|
+
message="No schematic is currently loaded",
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
try:
|
|
291
|
+
if ctx:
|
|
292
|
+
await ctx.report_progress(25, 100, f"Finding component {reference}")
|
|
293
|
+
|
|
294
|
+
# Get component
|
|
295
|
+
component = schematic.components.get(reference)
|
|
296
|
+
if component is None:
|
|
297
|
+
logger.warning(f"[MCP] Component not found: {reference}")
|
|
298
|
+
return ErrorOutput(
|
|
299
|
+
error="COMPONENT_NOT_FOUND",
|
|
300
|
+
message=f"Component '{reference}' not found in schematic",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
if ctx:
|
|
304
|
+
await ctx.report_progress(50, 100, f"Validating updates")
|
|
305
|
+
|
|
306
|
+
# Validate rotation if provided
|
|
307
|
+
if rotation is not None and rotation not in [0.0, 90.0, 180.0, 270.0]:
|
|
308
|
+
logger.warning(f"[MCP] Invalid rotation {rotation}")
|
|
309
|
+
return ErrorOutput(
|
|
310
|
+
error="VALIDATION_ERROR",
|
|
311
|
+
message=f"Rotation must be 0, 90, 180, or 270 degrees, got {rotation}",
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if ctx:
|
|
315
|
+
await ctx.report_progress(75, 100, f"Applying updates")
|
|
316
|
+
|
|
317
|
+
# Update provided properties
|
|
318
|
+
if value is not None:
|
|
319
|
+
component.value = value
|
|
320
|
+
if position is not None:
|
|
321
|
+
component.position = position
|
|
322
|
+
if rotation is not None:
|
|
323
|
+
component.rotation = rotation
|
|
324
|
+
if footprint is not None:
|
|
325
|
+
component.footprint = footprint
|
|
326
|
+
|
|
327
|
+
# Convert to output
|
|
328
|
+
result = _component_to_output(component)
|
|
329
|
+
result.message = f"Updated component {reference}"
|
|
330
|
+
|
|
331
|
+
if ctx:
|
|
332
|
+
await ctx.report_progress(100, 100, f"Complete: updated {reference}")
|
|
333
|
+
|
|
334
|
+
logger.info(f"[MCP] Successfully updated component {reference}")
|
|
335
|
+
return result
|
|
336
|
+
|
|
337
|
+
except ValidationError as e:
|
|
338
|
+
logger.error(f"[MCP] Validation error: {e}")
|
|
339
|
+
return ErrorOutput(
|
|
340
|
+
error="VALIDATION_ERROR",
|
|
341
|
+
message=f"Update validation failed: {str(e)}",
|
|
342
|
+
)
|
|
343
|
+
except Exception as e:
|
|
344
|
+
logger.error(f"[MCP] Unexpected error: {e}", exc_info=True)
|
|
345
|
+
return ErrorOutput(
|
|
346
|
+
error="INTERNAL_ERROR",
|
|
347
|
+
message=f"Unexpected error updating component: {str(e)}",
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
async def remove_component(
|
|
352
|
+
reference: str,
|
|
353
|
+
ctx: Optional[Context] = None,
|
|
354
|
+
) -> dict:
|
|
355
|
+
"""
|
|
356
|
+
Remove a component from the schematic.
|
|
357
|
+
|
|
358
|
+
Removes the component with the specified reference designator. This also
|
|
359
|
+
removes all associated wires and connections.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
reference: Component reference designator to remove (e.g., "R1")
|
|
363
|
+
ctx: MCP context for progress reporting (optional)
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Dictionary with success status
|
|
367
|
+
|
|
368
|
+
Examples:
|
|
369
|
+
>>> result = await remove_component("R1")
|
|
370
|
+
>>> if result['success']:
|
|
371
|
+
... print(f"Removed component {result['reference']}")
|
|
372
|
+
"""
|
|
373
|
+
logger.info(f"[MCP] remove_component called for {reference}")
|
|
374
|
+
|
|
375
|
+
if ctx:
|
|
376
|
+
await ctx.report_progress(0, 100, f"Removing component {reference}")
|
|
377
|
+
|
|
378
|
+
# Check if schematic is loaded
|
|
379
|
+
schematic = get_current_schematic()
|
|
380
|
+
if schematic is None:
|
|
381
|
+
logger.error("[MCP] No schematic loaded")
|
|
382
|
+
return {
|
|
383
|
+
"success": False,
|
|
384
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
385
|
+
"message": "No schematic is currently loaded",
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
try:
|
|
389
|
+
if ctx:
|
|
390
|
+
await ctx.report_progress(50, 100, f"Removing {reference}")
|
|
391
|
+
|
|
392
|
+
# Remove component
|
|
393
|
+
removed = schematic.components.remove(reference)
|
|
394
|
+
|
|
395
|
+
if not removed:
|
|
396
|
+
logger.warning(f"[MCP] Component not found: {reference}")
|
|
397
|
+
return {
|
|
398
|
+
"success": False,
|
|
399
|
+
"error": "COMPONENT_NOT_FOUND",
|
|
400
|
+
"message": f"Component '{reference}' not found in schematic",
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if ctx:
|
|
404
|
+
await ctx.report_progress(100, 100, f"Complete: removed {reference}")
|
|
405
|
+
|
|
406
|
+
logger.info(f"[MCP] Successfully removed component {reference}")
|
|
407
|
+
return {
|
|
408
|
+
"success": True,
|
|
409
|
+
"reference": reference,
|
|
410
|
+
"message": f"Removed component {reference}",
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
except Exception as e:
|
|
414
|
+
logger.error(f"[MCP] Unexpected error: {e}", exc_info=True)
|
|
415
|
+
return {
|
|
416
|
+
"success": False,
|
|
417
|
+
"error": "INTERNAL_ERROR",
|
|
418
|
+
"message": f"Unexpected error removing component: {str(e)}",
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
async def filter_components(
|
|
423
|
+
lib_id: Optional[str] = None,
|
|
424
|
+
value: Optional[str] = None,
|
|
425
|
+
value_pattern: Optional[str] = None,
|
|
426
|
+
footprint: Optional[str] = None,
|
|
427
|
+
ctx: Optional[Context] = None,
|
|
428
|
+
) -> dict:
|
|
429
|
+
"""
|
|
430
|
+
Filter components by various criteria.
|
|
431
|
+
|
|
432
|
+
Returns components matching the specified filter criteria. All provided
|
|
433
|
+
criteria must match (AND logic).
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
lib_id: Filter by library ID (exact match, e.g., "Device:R")
|
|
437
|
+
value: Filter by value (exact match, e.g., "10k")
|
|
438
|
+
value_pattern: Filter by value pattern (substring match, e.g., "10")
|
|
439
|
+
footprint: Filter by footprint (exact match)
|
|
440
|
+
ctx: MCP context for progress reporting (optional)
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
Dictionary with success status and list of matching components
|
|
444
|
+
|
|
445
|
+
Examples:
|
|
446
|
+
>>> # Find all resistors
|
|
447
|
+
>>> result = await filter_components(lib_id="Device:R")
|
|
448
|
+
>>> print(f"Found {result['count']} resistors")
|
|
449
|
+
|
|
450
|
+
>>> # Find all 10k resistors
|
|
451
|
+
>>> result = await filter_components(lib_id="Device:R", value="10k")
|
|
452
|
+
|
|
453
|
+
>>> # Find all components with "100" in value
|
|
454
|
+
>>> result = await filter_components(value_pattern="100")
|
|
455
|
+
"""
|
|
456
|
+
logger.info(f"[MCP] filter_components called with criteria: "
|
|
457
|
+
f"lib_id={lib_id}, value={value}, value_pattern={value_pattern}")
|
|
458
|
+
|
|
459
|
+
if ctx:
|
|
460
|
+
await ctx.report_progress(0, 100, "Filtering components")
|
|
461
|
+
|
|
462
|
+
# Check if schematic is loaded
|
|
463
|
+
schematic = get_current_schematic()
|
|
464
|
+
if schematic is None:
|
|
465
|
+
logger.error("[MCP] No schematic loaded")
|
|
466
|
+
return {
|
|
467
|
+
"success": False,
|
|
468
|
+
"error": "NO_SCHEMATIC_LOADED",
|
|
469
|
+
"message": "No schematic is currently loaded",
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
try:
|
|
473
|
+
if ctx:
|
|
474
|
+
await ctx.report_progress(50, 100, "Applying filters")
|
|
475
|
+
|
|
476
|
+
# Build filter criteria
|
|
477
|
+
criteria = {}
|
|
478
|
+
if lib_id is not None:
|
|
479
|
+
criteria["lib_id"] = lib_id
|
|
480
|
+
if value is not None:
|
|
481
|
+
criteria["value"] = value
|
|
482
|
+
if value_pattern is not None:
|
|
483
|
+
criteria["value_pattern"] = value_pattern
|
|
484
|
+
if footprint is not None:
|
|
485
|
+
criteria["footprint"] = footprint
|
|
486
|
+
|
|
487
|
+
# Apply filter
|
|
488
|
+
components = schematic.components.filter(**criteria)
|
|
489
|
+
|
|
490
|
+
if ctx:
|
|
491
|
+
await ctx.report_progress(75, 100, f"Converting {len(components)} components")
|
|
492
|
+
|
|
493
|
+
# Convert to output format
|
|
494
|
+
component_list = []
|
|
495
|
+
for comp in components:
|
|
496
|
+
comp_output = _component_to_output(comp)
|
|
497
|
+
component_list.append(comp_output.model_dump())
|
|
498
|
+
|
|
499
|
+
if ctx:
|
|
500
|
+
await ctx.report_progress(100, 100, f"Complete: {len(components)} matches")
|
|
501
|
+
|
|
502
|
+
logger.info(f"[MCP] Found {len(components)} matching components")
|
|
503
|
+
return {
|
|
504
|
+
"success": True,
|
|
505
|
+
"count": len(components),
|
|
506
|
+
"components": component_list,
|
|
507
|
+
"criteria": criteria,
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
except Exception as e:
|
|
511
|
+
logger.error(f"[MCP] Unexpected error: {e}", exc_info=True)
|
|
512
|
+
return {
|
|
513
|
+
"success": False,
|
|
514
|
+
"error": "INTERNAL_ERROR",
|
|
515
|
+
"message": f"Unexpected error filtering components: {str(e)}",
|
|
516
|
+
}
|