solidworks-mcp-python 1.5.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.
- solidworks_mcp/__init__.py +57 -0
- solidworks_mcp/adapters/__init__.py +40 -0
- solidworks_mcp/adapters/base.py +1882 -0
- solidworks_mcp/adapters/circuit_breaker.py +2052 -0
- solidworks_mcp/adapters/com_executor.py +252 -0
- solidworks_mcp/adapters/complexity_analyzer.py +272 -0
- solidworks_mcp/adapters/connection_pool.py +1884 -0
- solidworks_mcp/adapters/factory.py +366 -0
- solidworks_mcp/adapters/intelligent_router.py +218 -0
- solidworks_mcp/adapters/mock_adapter.py +4016 -0
- solidworks_mcp/adapters/pywin32_adapter.py +2842 -0
- solidworks_mcp/adapters/solidworks/__init__.py +17 -0
- solidworks_mcp/adapters/solidworks/bitmap.py +180 -0
- solidworks_mcp/adapters/solidworks/com_utils.py +56 -0
- solidworks_mcp/adapters/solidworks/dimension_layout.py +753 -0
- solidworks_mcp/adapters/solidworks/documentation.py +1476 -0
- solidworks_mcp/adapters/solidworks/drawing_standard.py +1993 -0
- solidworks_mcp/adapters/solidworks/face_selection.py +392 -0
- solidworks_mcp/adapters/solidworks/features.py +3362 -0
- solidworks_mcp/adapters/solidworks/io.py +3649 -0
- solidworks_mcp/adapters/solidworks/rebuild_diagnostics.py +269 -0
- solidworks_mcp/adapters/solidworks/rebuild_inspector.py +146 -0
- solidworks_mcp/adapters/solidworks/selection.py +308 -0
- solidworks_mcp/adapters/solidworks/sketch.py +2545 -0
- solidworks_mcp/adapters/sw_feature_errors.py +66 -0
- solidworks_mcp/adapters/sw_type_info.py +317 -0
- solidworks_mcp/adapters/vba_adapter.py +318 -0
- solidworks_mcp/adapters/vba_macro_executor.py +215 -0
- solidworks_mcp/agents/__init__.py +68 -0
- solidworks_mcp/agents/design_knowledge/assembly_patterns.md +127 -0
- solidworks_mcp/agents/design_knowledge/hinges.md +118 -0
- solidworks_mcp/agents/design_knowledge/snap_fits.md +93 -0
- solidworks_mcp/agents/design_knowledge/solidworks_basics.md +3 -0
- solidworks_mcp/agents/design_knowledge/tolerancing.md +128 -0
- solidworks_mcp/agents/harness.py +216 -0
- solidworks_mcp/agents/history_db.py +1508 -0
- solidworks_mcp/agents/retrieval_index.py +164 -0
- solidworks_mcp/agents/schemas.py +165 -0
- solidworks_mcp/agents/smoke_test.py +285 -0
- solidworks_mcp/agents/soc_exporter.py +650 -0
- solidworks_mcp/agents/soc_pickup.py +332 -0
- solidworks_mcp/agents/soc_rewind.py +221 -0
- solidworks_mcp/agents/vector_rag.py +665 -0
- solidworks_mcp/cache/__init__.py +5 -0
- solidworks_mcp/cache/response_cache.py +164 -0
- solidworks_mcp/config.py +729 -0
- solidworks_mcp/exceptions.py +37 -0
- solidworks_mcp/security/__init__.py +68 -0
- solidworks_mcp/security/auth.py +128 -0
- solidworks_mcp/security/cors.py +27 -0
- solidworks_mcp/security/rate_limiting.py +90 -0
- solidworks_mcp/security/runtime.py +155 -0
- solidworks_mcp/server.py +777 -0
- solidworks_mcp/server_cli_fixed.py +120 -0
- solidworks_mcp/setup/__init__.py +1 -0
- solidworks_mcp/setup/cli.py +399 -0
- solidworks_mcp/setup/clients.py +157 -0
- solidworks_mcp/setup/config_file.py +230 -0
- solidworks_mcp/setup/handshake.py +202 -0
- solidworks_mcp/setup/launcher.py +102 -0
- solidworks_mcp/setup/legacy.py +100 -0
- solidworks_mcp/setup/registration.py +193 -0
- solidworks_mcp/setup/solidworks.py +49 -0
- solidworks_mcp/tools/__init__.py +64 -0
- solidworks_mcp/tools/analysis.py +723 -0
- solidworks_mcp/tools/automation.py +578 -0
- solidworks_mcp/tools/docs_discovery.py +1476 -0
- solidworks_mcp/tools/drawing.py +1015 -0
- solidworks_mcp/tools/drawing_analysis.py +456 -0
- solidworks_mcp/tools/drawing_automation.py +627 -0
- solidworks_mcp/tools/engineering_data.py +466 -0
- solidworks_mcp/tools/error_classifier.py +232 -0
- solidworks_mcp/tools/export.py +833 -0
- solidworks_mcp/tools/file_facts.py +40 -0
- solidworks_mcp/tools/file_management.py +1473 -0
- solidworks_mcp/tools/input_compat.py +17 -0
- solidworks_mcp/tools/macro_recording.py +464 -0
- solidworks_mcp/tools/modeling.py +2360 -0
- solidworks_mcp/tools/sketching.py +1422 -0
- solidworks_mcp/tools/template_management.py +367 -0
- solidworks_mcp/tools/tool_boundary.py +280 -0
- solidworks_mcp/tools/vba_generation.py +1145 -0
- solidworks_mcp/ui/__init__.py +9 -0
- solidworks_mcp/ui/local_llm.py +725 -0
- solidworks_mcp/ui/routers/__init__.py +10 -0
- solidworks_mcp/ui/routers/checkpoint.py +24 -0
- solidworks_mcp/ui/routers/docs.py +47 -0
- solidworks_mcp/ui/routers/llm.py +83 -0
- solidworks_mcp/ui/routers/local_model.py +69 -0
- solidworks_mcp/ui/routers/model.py +75 -0
- solidworks_mcp/ui/routers/preview.py +38 -0
- solidworks_mcp/ui/routers/session.py +161 -0
- solidworks_mcp/ui/routers/viewer.py +154 -0
- solidworks_mcp/ui/schemas.py +502 -0
- solidworks_mcp/ui/server.py +298 -0
- solidworks_mcp/ui/service.py +481 -0
- solidworks_mcp/ui/services/__init__.py +136 -0
- solidworks_mcp/ui/services/_utils.py +958 -0
- solidworks_mcp/ui/services/checkpoint_service.py +1110 -0
- solidworks_mcp/ui/services/docs_service.py +282 -0
- solidworks_mcp/ui/services/llm_service.py +956 -0
- solidworks_mcp/ui/services/model_service.py +459 -0
- solidworks_mcp/ui/services/parameter_repair_service.py +314 -0
- solidworks_mcp/ui/services/preview_service.py +489 -0
- solidworks_mcp/ui/services/session_service.py +1389 -0
- solidworks_mcp/utils/__init__.py +9 -0
- solidworks_mcp/utils/feature_tree_classifier.py +342 -0
- solidworks_mcp/utils/logging.py +76 -0
- solidworks_mcp/utils/validation.py +77 -0
- solidworks_mcp/version.py +8 -0
- solidworks_mcp_python-1.5.0.dist-info/METADATA +410 -0
- solidworks_mcp_python-1.5.0.dist-info/RECORD +115 -0
- solidworks_mcp_python-1.5.0.dist-info/WHEEL +4 -0
- solidworks_mcp_python-1.5.0.dist-info/entry_points.txt +3 -0
- solidworks_mcp_python-1.5.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,2360 @@
|
|
|
1
|
+
"""Modeling tools for SolidWorks MCP Server.
|
|
2
|
+
|
|
3
|
+
Provides tools for creating and manipulating SolidWorks models, including parts,
|
|
4
|
+
assemblies, drawings, and features like extrusions, revolves, etc.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from fastmcp import FastMCP
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
from ..adapters.base import (
|
|
13
|
+
AdapterResult,
|
|
14
|
+
ExtrusionParameters,
|
|
15
|
+
LoftParameters,
|
|
16
|
+
RevolveParameters,
|
|
17
|
+
SolidWorksAdapter,
|
|
18
|
+
SweepParameters,
|
|
19
|
+
)
|
|
20
|
+
from .input_compat import CompatInput
|
|
21
|
+
from .tool_boundary import (
|
|
22
|
+
normalize_input,
|
|
23
|
+
require_success,
|
|
24
|
+
with_tool_boundary,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Input schemas using Python 3.14 built-in types
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _result_value(data: Any, *keys: str, default: Any = None) -> Any:
|
|
31
|
+
"""Build internal result value.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
data (Any): The data value.
|
|
35
|
+
*keys (str): Additional positional arguments forwarded to the call.
|
|
36
|
+
default (Any): Fallback value returned when the operation fails. Defaults to None.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Any: The result produced by the operation.
|
|
40
|
+
"""
|
|
41
|
+
if isinstance(data, dict):
|
|
42
|
+
for key in keys:
|
|
43
|
+
if key in data and data[key] is not None:
|
|
44
|
+
return data[key]
|
|
45
|
+
return default
|
|
46
|
+
|
|
47
|
+
for key in keys:
|
|
48
|
+
if hasattr(data, key):
|
|
49
|
+
value = getattr(data, key)
|
|
50
|
+
if value is not None:
|
|
51
|
+
return value
|
|
52
|
+
return default
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def _read_summary(adapter: Any) -> AdapterResult[dict[str, Any]] | None:
|
|
56
|
+
"""Read the geometry summary, or ``None`` if the adapter has no such read.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
adapter: The active adapter.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
AdapterResult[dict[str, Any]] | None: The summary result.
|
|
63
|
+
"""
|
|
64
|
+
reader = getattr(adapter, "get_model_geometry_summary", None)
|
|
65
|
+
if reader is None:
|
|
66
|
+
return None
|
|
67
|
+
result: AdapterResult[dict[str, Any]] = await reader()
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _box_size(summary: dict[str, Any]) -> Any:
|
|
72
|
+
"""Return a summary's bounding-box size ``[x, y, z]``, or ``None``."""
|
|
73
|
+
box = summary.get("bounding_box_mm")
|
|
74
|
+
return box.get("size") if isinstance(box, dict) else None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _attach_measured_change(
|
|
78
|
+
reply: dict[str, Any],
|
|
79
|
+
before: AdapterResult[dict[str, Any]] | None,
|
|
80
|
+
after: AdapterResult[dict[str, Any]] | None,
|
|
81
|
+
) -> dict[str, Any]:
|
|
82
|
+
"""Add the feature's measured volume and size change to its reply.
|
|
83
|
+
|
|
84
|
+
Best-effort: when the "before" read failed (or the adapter has none) the
|
|
85
|
+
reply is returned unchanged; anything else unmeasurable is ``None`` with a
|
|
86
|
+
``note``. A feature that succeeded without changing the volume gets a
|
|
87
|
+
``warning``, since that is how SolidWorks reports a silent no-op.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
reply: The feature tool's success reply.
|
|
91
|
+
before: Summary read before the feature.
|
|
92
|
+
after: Summary read after the feature.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
dict[str, Any]: ``reply``, with ``measured_change`` when measurable.
|
|
96
|
+
"""
|
|
97
|
+
if before is None or not before.is_success:
|
|
98
|
+
return reply
|
|
99
|
+
old = before.data or {}
|
|
100
|
+
new = (after.data or {}) if after is not None and after.is_success else None
|
|
101
|
+
volume_before = old.get("volume_mm3")
|
|
102
|
+
volume_after = new.get("volume_mm3") if new is not None else None
|
|
103
|
+
numbers = (int, float)
|
|
104
|
+
delta = (
|
|
105
|
+
round(volume_after - volume_before, 4)
|
|
106
|
+
if isinstance(volume_before, numbers) and isinstance(volume_after, numbers)
|
|
107
|
+
else None
|
|
108
|
+
)
|
|
109
|
+
change: dict[str, Any] = {
|
|
110
|
+
"volume_before_mm3": volume_before,
|
|
111
|
+
"volume_after_mm3": volume_after,
|
|
112
|
+
"volume_delta_mm3": delta,
|
|
113
|
+
"bounding_box_before_mm": _box_size(old),
|
|
114
|
+
"bounding_box_after_mm": _box_size(new) if new is not None else None,
|
|
115
|
+
"solid_body_count_before": old.get("solid_body_count"),
|
|
116
|
+
"solid_body_count_after": (
|
|
117
|
+
new.get("solid_body_count") if new is not None else None
|
|
118
|
+
),
|
|
119
|
+
}
|
|
120
|
+
if new is None:
|
|
121
|
+
error = after.error if after is not None else None
|
|
122
|
+
change["note"] = f"Could not read the model after the feature: {error}"
|
|
123
|
+
elif delta is None:
|
|
124
|
+
change["note"] = "Volume is not determinable with the active adapter"
|
|
125
|
+
reply["measured_change"] = change
|
|
126
|
+
if delta == 0:
|
|
127
|
+
reply["warning"] = (
|
|
128
|
+
"The feature reported success but changed no volume; SolidWorks "
|
|
129
|
+
"may have accepted it without doing anything. Check the sketch, "
|
|
130
|
+
"the direction and the depth."
|
|
131
|
+
)
|
|
132
|
+
return reply
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class OpenModelInput(BaseModel):
|
|
136
|
+
"""Input schema for opening a SolidWorks model.
|
|
137
|
+
|
|
138
|
+
Attributes:
|
|
139
|
+
file_path (str): The file path value.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
file_path: str = Field(
|
|
143
|
+
description="Full path to the SolidWorks file (.sldprt, .sldasm, .slddrw)"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class CreatePartInput(CompatInput):
|
|
148
|
+
"""Input schema for creating a new SolidWorks part.
|
|
149
|
+
|
|
150
|
+
Attributes:
|
|
151
|
+
material (str | None): The material value.
|
|
152
|
+
name (str): The name value.
|
|
153
|
+
template (str | None): The template value.
|
|
154
|
+
units (str | None): The units value.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
name: str = Field(description="Name for the new part")
|
|
158
|
+
template: str | None = Field(
|
|
159
|
+
default=None,
|
|
160
|
+
description=(
|
|
161
|
+
"Part template (.prtdot) path; omit to use the SolidWorks default"
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
units: str | None = Field(default=None, description="Document units")
|
|
165
|
+
material: str | None = Field(default=None, description="Material name")
|
|
166
|
+
|
|
167
|
+
def model_post_init(self, __context: Any) -> None:
|
|
168
|
+
"""Provide model post init support for the create part input.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
__context (Any): The context value.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
None: None.
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
ValueError: Name is required.
|
|
178
|
+
"""
|
|
179
|
+
if not self.name.strip():
|
|
180
|
+
raise ValueError("name is required")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class CreateExtrusionInput(CompatInput):
|
|
184
|
+
"""Input schema for creating an extrusion feature.
|
|
185
|
+
|
|
186
|
+
Attributes:
|
|
187
|
+
both_directions (bool): The both directions value.
|
|
188
|
+
depth (float): The depth value.
|
|
189
|
+
direction (str): The direction value.
|
|
190
|
+
draft_angle (float): The draft angle value.
|
|
191
|
+
end_condition (str): The end condition value.
|
|
192
|
+
merge_result (bool): The merge result value.
|
|
193
|
+
reverse (bool | None): The reverse value.
|
|
194
|
+
reverse_direction (bool): The reverse direction value.
|
|
195
|
+
sketch_name (str): The sketch name value.
|
|
196
|
+
thin_feature (bool): The thin feature value.
|
|
197
|
+
thin_thickness (float | None): The thin thickness value.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
sketch_name: str = Field(description="Sketch name to extrude")
|
|
201
|
+
depth: float = Field(description="Extrusion depth in millimeters")
|
|
202
|
+
direction: str = Field(default="blind", description="Extrusion direction")
|
|
203
|
+
reverse: bool | None = Field(default=None, description="Reverse direction alias")
|
|
204
|
+
draft_angle: float = Field(default=0.0, description="Draft angle in degrees")
|
|
205
|
+
reverse_direction: bool = Field(
|
|
206
|
+
default=False, description="Reverse extrusion direction"
|
|
207
|
+
)
|
|
208
|
+
both_directions: bool = Field(
|
|
209
|
+
default=False, description="Extrude in both directions"
|
|
210
|
+
)
|
|
211
|
+
thin_feature: bool = Field(default=False, description="Create as thin wall feature")
|
|
212
|
+
thin_thickness: float | None = Field(
|
|
213
|
+
default=None, description="Thickness for thin wall feature in mm"
|
|
214
|
+
)
|
|
215
|
+
end_condition: str = Field(default="Blind", description="End condition type")
|
|
216
|
+
merge_result: bool = Field(default=True, description="Merge with existing geometry")
|
|
217
|
+
|
|
218
|
+
def model_post_init(self, __context: Any) -> None:
|
|
219
|
+
"""Provide model post init support for the create extrusion input.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
__context (Any): The context value.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
None: None.
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
ValueError: Sketch_name is required.
|
|
229
|
+
"""
|
|
230
|
+
if self.depth <= 0:
|
|
231
|
+
raise ValueError("depth must be positive")
|
|
232
|
+
if not self.sketch_name.strip():
|
|
233
|
+
raise ValueError("sketch_name is required")
|
|
234
|
+
if self.reverse is not None:
|
|
235
|
+
self.reverse_direction = self.reverse
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class CreateRevolveInput(CompatInput):
|
|
239
|
+
"""Input schema for creating a revolve feature.
|
|
240
|
+
|
|
241
|
+
Attributes:
|
|
242
|
+
angle (float): The angle value.
|
|
243
|
+
axis_entity (str): The axis entity value.
|
|
244
|
+
both_directions (bool): The both directions value.
|
|
245
|
+
direction (str): The direction value.
|
|
246
|
+
merge_result (bool): The merge result value.
|
|
247
|
+
reverse_direction (bool): The reverse direction value.
|
|
248
|
+
sketch_name (str): The sketch name value.
|
|
249
|
+
thin_feature (bool): The thin feature value.
|
|
250
|
+
thin_thickness (float | None): The thin thickness value.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
sketch_name: str = Field(description="Sketch name to revolve")
|
|
254
|
+
axis_entity: str = Field(description="Axis entity for the revolve")
|
|
255
|
+
angle: float = Field(description="Revolve angle in degrees")
|
|
256
|
+
direction: str = Field(default="one_direction", description="Revolve direction")
|
|
257
|
+
reverse_direction: bool = Field(
|
|
258
|
+
default=False, description="Reverse revolve direction"
|
|
259
|
+
)
|
|
260
|
+
both_directions: bool = Field(
|
|
261
|
+
default=False, description="Revolve in both directions"
|
|
262
|
+
)
|
|
263
|
+
thin_feature: bool = Field(default=False, description="Create as thin wall feature")
|
|
264
|
+
thin_thickness: float | None = Field(
|
|
265
|
+
default=None, description="Thickness for thin wall feature in mm"
|
|
266
|
+
)
|
|
267
|
+
merge_result: bool = Field(default=True, description="Merge with existing geometry")
|
|
268
|
+
|
|
269
|
+
def model_post_init(self, __context: Any) -> None:
|
|
270
|
+
"""Provide model post init support for the create revolve input.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
__context (Any): The context value.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
None: None.
|
|
277
|
+
|
|
278
|
+
Raises:
|
|
279
|
+
ValueError: Angle must be positive.
|
|
280
|
+
"""
|
|
281
|
+
if self.angle <= 0:
|
|
282
|
+
raise ValueError("angle must be positive")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class CreateSweepInput(BaseModel):
|
|
286
|
+
"""Input schema for creating a sweep feature.
|
|
287
|
+
|
|
288
|
+
Attributes:
|
|
289
|
+
merge_result (bool): The merge result value.
|
|
290
|
+
path (str): The path value.
|
|
291
|
+
twist_along_path (bool): The twist along path value.
|
|
292
|
+
twist_angle (float): The twist angle value.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
path: str = Field(description="Name or ID of the sweep path")
|
|
296
|
+
twist_along_path: bool = Field(default=False, description="Twist along path")
|
|
297
|
+
twist_angle: float = Field(default=0.0, description="Twist angle in degrees")
|
|
298
|
+
merge_result: bool = Field(default=True, description="Merge with existing geometry")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class CreateLoftInput(BaseModel):
|
|
302
|
+
"""Input schema for creating a loft feature.
|
|
303
|
+
|
|
304
|
+
Attributes:
|
|
305
|
+
end_tangent (str | None): The end tangent value.
|
|
306
|
+
guide_curves (list[str] | None): The guide curves value.
|
|
307
|
+
merge_result (bool): The merge result value.
|
|
308
|
+
profiles (list[str]): The profiles value.
|
|
309
|
+
start_tangent (str | None): The start tangent value.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
profiles: list[str] = Field(description="List of profile names or IDs")
|
|
313
|
+
guide_curves: list[str] | None = Field(
|
|
314
|
+
default=None, description="List of guide curve names or IDs"
|
|
315
|
+
)
|
|
316
|
+
start_tangent: str | None = Field(
|
|
317
|
+
default=None, description="Start tangent condition"
|
|
318
|
+
)
|
|
319
|
+
end_tangent: str | None = Field(default=None, description="End tangent condition")
|
|
320
|
+
merge_result: bool = Field(default=True, description="Merge with existing geometry")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class GetDimensionInput(CompatInput):
|
|
324
|
+
"""Input schema for getting a dimension value.
|
|
325
|
+
|
|
326
|
+
Attributes:
|
|
327
|
+
dimension_name (str | None): The dimension name value.
|
|
328
|
+
name (str | None): The name value.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
name: str | None = Field(
|
|
332
|
+
default=None,
|
|
333
|
+
description="Dimension name (e.g., 'D1@Sketch1', 'D1@Boss-Extrude1')",
|
|
334
|
+
)
|
|
335
|
+
dimension_name: str | None = Field(default=None, description="Dimension name alias")
|
|
336
|
+
|
|
337
|
+
def model_post_init(self, __context: Any) -> None:
|
|
338
|
+
"""Provide model post init support for the get dimension input.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
__context (Any): The context value.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
None: None.
|
|
345
|
+
|
|
346
|
+
Raises:
|
|
347
|
+
ValueError: Name is required.
|
|
348
|
+
"""
|
|
349
|
+
if self.name is None:
|
|
350
|
+
self.name = self.dimension_name
|
|
351
|
+
if not self.name:
|
|
352
|
+
raise ValueError("name is required")
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class SetDimensionInput(CompatInput):
|
|
356
|
+
"""Input schema for setting a dimension value.
|
|
357
|
+
|
|
358
|
+
Attributes:
|
|
359
|
+
dimension_name (str | None): The dimension name value.
|
|
360
|
+
name (str | None): The name value.
|
|
361
|
+
units (str | None): The units value.
|
|
362
|
+
value (float): The value value.
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
name: str | None = Field(
|
|
366
|
+
default=None,
|
|
367
|
+
description="Dimension name (e.g., 'D1@Sketch1', 'D1@Boss-Extrude1')",
|
|
368
|
+
)
|
|
369
|
+
dimension_name: str | None = Field(default=None, description="Dimension name alias")
|
|
370
|
+
value: float = Field(description="New dimension value in millimeters")
|
|
371
|
+
units: str | None = Field(default=None, description="Units alias")
|
|
372
|
+
|
|
373
|
+
def model_post_init(self, __context: Any) -> None:
|
|
374
|
+
"""Provide model post init support for the set dimension input.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
__context (Any): The context value.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
None: None.
|
|
381
|
+
|
|
382
|
+
Raises:
|
|
383
|
+
ValueError: Name is required.
|
|
384
|
+
"""
|
|
385
|
+
if self.name is None:
|
|
386
|
+
self.name = self.dimension_name
|
|
387
|
+
if not self.name:
|
|
388
|
+
raise ValueError("name is required")
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class SetUnitsInput(CompatInput):
|
|
392
|
+
"""Input schema for setting the active document's unit system.
|
|
393
|
+
|
|
394
|
+
Attributes:
|
|
395
|
+
unit_system (str): Target linear unit - ``mm``, ``cm``, ``m``, ``in``
|
|
396
|
+
or ``ft`` (aliases such as ``inch`` / ``millimeters`` accepted).
|
|
397
|
+
"""
|
|
398
|
+
|
|
399
|
+
unit_system: str = Field(description="Linear unit system: mm, cm, m, in, or ft")
|
|
400
|
+
|
|
401
|
+
def model_post_init(self, __context: Any) -> None:
|
|
402
|
+
if not self.unit_system.strip():
|
|
403
|
+
raise ValueError("unit_system is required")
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class CloseModelInput(BaseModel):
|
|
407
|
+
"""Input schema for closing a model.
|
|
408
|
+
|
|
409
|
+
Attributes:
|
|
410
|
+
save (bool): The save value.
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
save: bool = Field(default=False, description="Save the model before closing")
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
class CreateCutExtrudeInput(CompatInput):
|
|
417
|
+
"""Input schema for creating a cut-extrude feature.
|
|
418
|
+
|
|
419
|
+
Attributes:
|
|
420
|
+
depth (float): Cut depth in millimeters.
|
|
421
|
+
draft_angle (float): Draft angle in degrees.
|
|
422
|
+
end_condition (str): End condition type.
|
|
423
|
+
reverse_direction (bool): Reverse cut direction.
|
|
424
|
+
both_directions (bool): With ``"ThroughAll"``, cut both ways.
|
|
425
|
+
sketch_name (str | None): Sketch to cut from.
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
depth: float = Field(description="Cut depth in millimeters")
|
|
429
|
+
draft_angle: float = Field(default=0.0, description="Draft angle in degrees")
|
|
430
|
+
reverse_direction: bool = Field(
|
|
431
|
+
default=False,
|
|
432
|
+
description=(
|
|
433
|
+
"Cut against the sketch normal instead of along it. Usually not "
|
|
434
|
+
"needed: if the requested direction finds no material, the cut is "
|
|
435
|
+
"retried the other way and the reply sets direction_flipped"
|
|
436
|
+
),
|
|
437
|
+
)
|
|
438
|
+
end_condition: str = Field(
|
|
439
|
+
default="Blind",
|
|
440
|
+
description=(
|
|
441
|
+
"'Blind' (cut 'depth' mm), 'ThroughAll' (through the body, one "
|
|
442
|
+
"way) or 'ThroughAllBoth' (through the body both ways, for a "
|
|
443
|
+
"sketch inside the body)"
|
|
444
|
+
),
|
|
445
|
+
)
|
|
446
|
+
both_directions: bool = Field(
|
|
447
|
+
default=False,
|
|
448
|
+
description=(
|
|
449
|
+
"With end_condition 'ThroughAll', cut through the body both ways "
|
|
450
|
+
"(same as 'ThroughAllBoth')"
|
|
451
|
+
),
|
|
452
|
+
)
|
|
453
|
+
sketch_name: str | None = Field(
|
|
454
|
+
default=None,
|
|
455
|
+
description=(
|
|
456
|
+
"Sketch to cut from, e.g. 'Sketch3'. If omitted, the sketch most "
|
|
457
|
+
"recently created/exited in this session is used, but only if it "
|
|
458
|
+
"still exists in the model — there is no other fallback. Pass "
|
|
459
|
+
"this explicitly whenever more than one unconsumed sketch could "
|
|
460
|
+
"exist, or after any operation that might have changed the "
|
|
461
|
+
"active sketch."
|
|
462
|
+
),
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
def model_post_init(self, __context: Any) -> None:
|
|
466
|
+
if self.depth <= 0:
|
|
467
|
+
raise ValueError("depth must be positive")
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class AddFilletInput(CompatInput):
|
|
471
|
+
"""Input schema for adding a fillet feature.
|
|
472
|
+
|
|
473
|
+
Attributes:
|
|
474
|
+
edge_names (list[str]): Edge names to fillet.
|
|
475
|
+
radius (float): Fillet radius in millimeters.
|
|
476
|
+
"""
|
|
477
|
+
|
|
478
|
+
radius: float = Field(description="Fillet radius in millimeters")
|
|
479
|
+
edge_names: list[str] = Field(
|
|
480
|
+
default_factory=list,
|
|
481
|
+
description=(
|
|
482
|
+
"Edges (or faces) to fillet. Accepts a topology name (e.g. "
|
|
483
|
+
"'Edge<1>'), a coordinate hint in metres ('x,y,z') to select the "
|
|
484
|
+
"nearest edge, or 'face:x,y,z' to select a whole face — which "
|
|
485
|
+
"fillets every edge bounding it in one call. Prefer 'face:' over "
|
|
486
|
+
"a coordinate on an edge that sits exactly on a shared boundary "
|
|
487
|
+
"between two faces (e.g. a hole rim flush with a flat face): the "
|
|
488
|
+
"nearest-edge match there can silently resolve to a different, "
|
|
489
|
+
"unrelated edge. Leave empty to use whatever is *currently "
|
|
490
|
+
"selected* in the live SolidWorks UI (e.g. entities read back "
|
|
491
|
+
"via get_selected_entities and left selected) without touching "
|
|
492
|
+
"the selection — this is the most reliable option when the user "
|
|
493
|
+
"has hand-picked specific edges, since it never round-trips "
|
|
494
|
+
"through coordinates. Falls back to filleting all edges only if "
|
|
495
|
+
"nothing at all is selected."
|
|
496
|
+
),
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
def model_post_init(self, __context: Any) -> None:
|
|
500
|
+
if self.radius <= 0:
|
|
501
|
+
raise ValueError("radius must be positive")
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
class AddChamferInput(CompatInput):
|
|
505
|
+
"""Input schema for adding an equal-distance chamfer feature.
|
|
506
|
+
|
|
507
|
+
Attributes:
|
|
508
|
+
distance (float): Chamfer distance in millimeters.
|
|
509
|
+
edge_names (list[str]): Edge names to chamfer.
|
|
510
|
+
"""
|
|
511
|
+
|
|
512
|
+
distance: float = Field(description="Chamfer distance in millimeters")
|
|
513
|
+
edge_names: list[str] = Field(
|
|
514
|
+
default_factory=list,
|
|
515
|
+
description=(
|
|
516
|
+
"Edges (or faces) to chamfer. Accepts a topology name (e.g. "
|
|
517
|
+
"'Edge<1>'), a coordinate hint in metres ('x,y,z') to select the "
|
|
518
|
+
"nearest edge, or 'face:x,y,z' to select a whole face — which "
|
|
519
|
+
"chamfers every edge bounding it in one call. Prefer 'face:' over "
|
|
520
|
+
"a coordinate on an edge that sits exactly on a shared boundary "
|
|
521
|
+
"between two faces (e.g. a hole rim flush with a flat face): the "
|
|
522
|
+
"nearest-edge match there can silently resolve to a different, "
|
|
523
|
+
"unrelated edge. Leave empty to use whatever is *currently "
|
|
524
|
+
"selected* in the live SolidWorks UI (e.g. entities read back "
|
|
525
|
+
"via get_selected_entities and left selected) without touching "
|
|
526
|
+
"the selection — this is the most reliable option when the user "
|
|
527
|
+
"has hand-picked specific edges, since it never round-trips "
|
|
528
|
+
"through coordinates. Falls back to chamfering all edges only if "
|
|
529
|
+
"nothing at all is selected."
|
|
530
|
+
),
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
def model_post_init(self, __context: Any) -> None:
|
|
534
|
+
if self.distance <= 0:
|
|
535
|
+
raise ValueError("distance must be positive")
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
class CreateAssemblyInput(CompatInput):
|
|
539
|
+
"""Input schema for creating a new assembly.
|
|
540
|
+
|
|
541
|
+
Attributes:
|
|
542
|
+
components (list[str]): The components value.
|
|
543
|
+
name (str): The name value.
|
|
544
|
+
template (str | None): The template value.
|
|
545
|
+
"""
|
|
546
|
+
|
|
547
|
+
name: str = Field(description="Name for the new assembly")
|
|
548
|
+
template: str | None = Field(
|
|
549
|
+
default=None,
|
|
550
|
+
description=(
|
|
551
|
+
"Assembly template (.asmdot) path; omit to use the SolidWorks default"
|
|
552
|
+
),
|
|
553
|
+
)
|
|
554
|
+
components: list[str] = Field(default_factory=list, description="Component list")
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
class InsertComponentInput(CompatInput):
|
|
558
|
+
"""Input schema for inserting a component into an assembly.
|
|
559
|
+
|
|
560
|
+
Attributes:
|
|
561
|
+
file_path (str): Path to the part or sub-assembly.
|
|
562
|
+
x (float): X position in millimetres.
|
|
563
|
+
y (float): Y position in millimetres.
|
|
564
|
+
z (float): Z position in millimetres.
|
|
565
|
+
"""
|
|
566
|
+
|
|
567
|
+
file_path: str = Field(
|
|
568
|
+
description="Absolute path to the .sldprt or .sldasm to insert"
|
|
569
|
+
)
|
|
570
|
+
x: float = Field(default=0.0, description="X position in millimetres")
|
|
571
|
+
y: float = Field(default=0.0, description="Y position in millimetres")
|
|
572
|
+
z: float = Field(default=0.0, description="Z position in millimetres")
|
|
573
|
+
|
|
574
|
+
def model_post_init(self, __context: Any) -> None:
|
|
575
|
+
if not self.file_path.strip():
|
|
576
|
+
raise ValueError("file_path is required")
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
class DeleteFeatureInput(CompatInput):
|
|
580
|
+
"""Input schema for deleting a feature.
|
|
581
|
+
|
|
582
|
+
Attributes:
|
|
583
|
+
name (str): Feature or sketch name to delete.
|
|
584
|
+
"""
|
|
585
|
+
|
|
586
|
+
name: str = Field(
|
|
587
|
+
description="Feature or sketch name, e.g. 'Boss-Extrude1' or 'Sketch1'"
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
class SuppressFeatureInput(CompatInput):
|
|
592
|
+
"""Input schema for suppressing or unsuppressing a feature.
|
|
593
|
+
|
|
594
|
+
Attributes:
|
|
595
|
+
name (str): Feature name to toggle.
|
|
596
|
+
suppress (bool): True to suppress, False to unsuppress.
|
|
597
|
+
"""
|
|
598
|
+
|
|
599
|
+
name: str = Field(description="Feature name, e.g. 'Fillet1'")
|
|
600
|
+
suppress: bool = Field(
|
|
601
|
+
default=True, description="True to suppress, False to unsuppress"
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
class RenameFeatureInput(CompatInput):
|
|
606
|
+
"""Input schema for renaming a feature.
|
|
607
|
+
|
|
608
|
+
Attributes:
|
|
609
|
+
old_name (str): Current feature name.
|
|
610
|
+
new_name (str): New feature name.
|
|
611
|
+
"""
|
|
612
|
+
|
|
613
|
+
old_name: str = Field(description="Current feature name, e.g. 'Fillet1'")
|
|
614
|
+
new_name: str = Field(description="New feature name, e.g. 'MountingEdgeFillet'")
|
|
615
|
+
|
|
616
|
+
def model_post_init(self, __context: Any) -> None:
|
|
617
|
+
if not self.old_name.strip():
|
|
618
|
+
raise ValueError("old_name is required")
|
|
619
|
+
if not self.new_name.strip():
|
|
620
|
+
raise ValueError("new_name is required")
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
class SelectFeatureInput(CompatInput):
|
|
624
|
+
"""Input schema for selecting a named feature.
|
|
625
|
+
|
|
626
|
+
Attributes:
|
|
627
|
+
feature_name (str): Feature, sketch, plane or component name.
|
|
628
|
+
append (bool): Add to the current selection instead of replacing it.
|
|
629
|
+
"""
|
|
630
|
+
|
|
631
|
+
feature_name: str = Field(
|
|
632
|
+
description="Feature, sketch, plane or component name, e.g. "
|
|
633
|
+
"'Boss-Extrude1', 'Sketch1', 'Front Plane' (an '@Part1' suffix is ok)"
|
|
634
|
+
)
|
|
635
|
+
append: bool = Field(
|
|
636
|
+
default=False,
|
|
637
|
+
description="Add to the current selection instead of replacing it",
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
def model_post_init(self, __context: Any) -> None:
|
|
641
|
+
self.feature_name = self.feature_name.strip()
|
|
642
|
+
if not self.feature_name:
|
|
643
|
+
raise ValueError("feature_name is required")
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
class SelectFaceEdgesInput(CompatInput):
|
|
647
|
+
"""Input schema for multi-selecting every edge of one face.
|
|
648
|
+
|
|
649
|
+
Attributes:
|
|
650
|
+
point (list[float] | None): A point on the face, [x, y, z] in mm.
|
|
651
|
+
side (str | None): Which way the face points.
|
|
652
|
+
plane (str | None): Reference plane the face lies on.
|
|
653
|
+
edge_type (str): Edge filter: all, linear or circular.
|
|
654
|
+
append (bool): Add to the current selection instead of replacing it.
|
|
655
|
+
"""
|
|
656
|
+
|
|
657
|
+
point: list[float] | None = Field(
|
|
658
|
+
default=None, description="A point on the face, [x, y, z] in mm"
|
|
659
|
+
)
|
|
660
|
+
side: str | None = Field(
|
|
661
|
+
default=None,
|
|
662
|
+
description="Outward direction of the face: +X/-X/+Y/-Y/+Z/-Z or "
|
|
663
|
+
"right/left/top/bottom/front/back (top=+Y, front=+Z, right=+X, as in "
|
|
664
|
+
"SolidWorks' default views); picks the furthest flat face that way",
|
|
665
|
+
)
|
|
666
|
+
plane: str | None = Field(
|
|
667
|
+
default=None,
|
|
668
|
+
description="Reference plane name, e.g. 'Front Plane'; picks the body "
|
|
669
|
+
"face(s) lying on it",
|
|
670
|
+
)
|
|
671
|
+
edge_type: str = Field(
|
|
672
|
+
default="all", description="'all', 'linear' or 'circular' (e.g. hole rims)"
|
|
673
|
+
)
|
|
674
|
+
append: bool = Field(
|
|
675
|
+
default=False,
|
|
676
|
+
description="Add to the current selection instead of replacing it",
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
def model_post_init(self, __context: Any) -> None:
|
|
680
|
+
from ..adapters.solidworks.face_selection import validate_face_query
|
|
681
|
+
|
|
682
|
+
self.edge_type = str(self.edge_type or "all").strip().lower()
|
|
683
|
+
problem = validate_face_query(self.point, self.side, self.plane, self.edge_type)
|
|
684
|
+
if problem:
|
|
685
|
+
raise ValueError(problem)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
class RebuildModelInput(CompatInput):
|
|
689
|
+
"""Input schema for forcing a model rebuild.
|
|
690
|
+
|
|
691
|
+
Attributes:
|
|
692
|
+
top_level_only (bool): Rebuild only the top level of an assembly.
|
|
693
|
+
"""
|
|
694
|
+
|
|
695
|
+
top_level_only: bool = Field(
|
|
696
|
+
default=False,
|
|
697
|
+
description="Rebuild only the top level of an assembly (default: full "
|
|
698
|
+
"rebuild including sub-assemblies)",
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
class UndoInput(CompatInput):
|
|
703
|
+
"""Input schema for undoing model operations.
|
|
704
|
+
|
|
705
|
+
Attributes:
|
|
706
|
+
count (int): Number of operations to undo.
|
|
707
|
+
"""
|
|
708
|
+
|
|
709
|
+
count: int = Field(default=1, ge=1, description="Number of operations to undo")
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
class AddMateInput(CompatInput):
|
|
713
|
+
"""Input schema for mating two assembly components.
|
|
714
|
+
|
|
715
|
+
Attributes:
|
|
716
|
+
component_a (str): First component instance name.
|
|
717
|
+
component_b (str): Second component instance name.
|
|
718
|
+
entity_a (str): Named feature on the first component.
|
|
719
|
+
entity_b (str): Named feature on the second component.
|
|
720
|
+
mate_type (str): Mate type.
|
|
721
|
+
alignment (str): Mate alignment.
|
|
722
|
+
distance (float): Distance in millimetres for a distance mate.
|
|
723
|
+
angle (float): Angle in degrees for an angle mate.
|
|
724
|
+
"""
|
|
725
|
+
|
|
726
|
+
component_a: str = Field(
|
|
727
|
+
description="First component instance name, as reported by list_components"
|
|
728
|
+
)
|
|
729
|
+
component_b: str = Field(description="Second component instance name")
|
|
730
|
+
entity_a: str = Field(
|
|
731
|
+
default="Front Plane",
|
|
732
|
+
description=(
|
|
733
|
+
"Named feature on the first component. Only tree features "
|
|
734
|
+
"(reference planes and axes) can be selected"
|
|
735
|
+
),
|
|
736
|
+
)
|
|
737
|
+
entity_b: str = Field(
|
|
738
|
+
default="Front Plane", description="Named feature on the second component"
|
|
739
|
+
)
|
|
740
|
+
mate_type: str = Field(
|
|
741
|
+
default="coincident",
|
|
742
|
+
description=(
|
|
743
|
+
"coincident, concentric, perpendicular, parallel, tangent, "
|
|
744
|
+
"distance or angle"
|
|
745
|
+
),
|
|
746
|
+
)
|
|
747
|
+
alignment: str = Field(
|
|
748
|
+
default="aligned", description="aligned, anti_aligned or closest"
|
|
749
|
+
)
|
|
750
|
+
distance: float = Field(
|
|
751
|
+
default=0.0, description="Distance in millimetres, for a distance mate"
|
|
752
|
+
)
|
|
753
|
+
angle: float = Field(default=0.0, description="Angle in degrees, for an angle mate")
|
|
754
|
+
|
|
755
|
+
def model_post_init(self, __context: Any) -> None:
|
|
756
|
+
if not self.component_a.strip() or not self.component_b.strip():
|
|
757
|
+
raise ValueError("component_a and component_b are required")
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
class CreateReferencePlaneInput(CompatInput):
|
|
761
|
+
"""Input schema for creating a reference plane.
|
|
762
|
+
|
|
763
|
+
Attributes:
|
|
764
|
+
reference (str): Name of the reference plane or planar face.
|
|
765
|
+
offset (float): Offset distance in millimetres.
|
|
766
|
+
angle (float): Angle in degrees.
|
|
767
|
+
flip (bool): Reverse the offset or angle direction.
|
|
768
|
+
"""
|
|
769
|
+
|
|
770
|
+
reference: str = Field(
|
|
771
|
+
description="Name of the reference plane or planar face to offset "
|
|
772
|
+
"from, e.g. 'Front Plane'"
|
|
773
|
+
)
|
|
774
|
+
offset: float = Field(
|
|
775
|
+
default=0.0,
|
|
776
|
+
description="Offset distance in millimetres. Must be non-zero — a "
|
|
777
|
+
"plane coincident with its reference is not useful",
|
|
778
|
+
)
|
|
779
|
+
angle: float = Field(
|
|
780
|
+
default=0.0,
|
|
781
|
+
description="Angle in degrees. Not yet supported: an angled plane "
|
|
782
|
+
"needs a second reference this tool cannot take, so a non-zero "
|
|
783
|
+
"value errors. Use offset for parallel planes",
|
|
784
|
+
)
|
|
785
|
+
flip: bool = Field(default=False, description="Reverse the offset direction")
|
|
786
|
+
|
|
787
|
+
def model_post_init(self, __context: Any) -> None:
|
|
788
|
+
if not self.reference.strip():
|
|
789
|
+
raise ValueError("reference is required")
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
class CreateAxisInput(CompatInput):
|
|
793
|
+
"""Input schema for creating a reference axis.
|
|
794
|
+
|
|
795
|
+
Attributes:
|
|
796
|
+
reference (str): Principal axis direction.
|
|
797
|
+
"""
|
|
798
|
+
|
|
799
|
+
reference: str = Field(
|
|
800
|
+
description="Principal axis direction: 'x', 'y' or 'z' "
|
|
801
|
+
"(case-insensitive; a leading +/- is stripped)"
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
def model_post_init(self, __context: Any) -> None:
|
|
805
|
+
if not self.reference.strip():
|
|
806
|
+
raise ValueError("reference is required")
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
class CreateReferencePointInput(CompatInput):
|
|
810
|
+
"""Input schema for creating a reference point.
|
|
811
|
+
|
|
812
|
+
Attributes:
|
|
813
|
+
mode (str): "along_curve" or "face_center".
|
|
814
|
+
x (float): X of a point on the target edge/face, in millimetres.
|
|
815
|
+
y (float): Y of a point on the target edge/face, in millimetres.
|
|
816
|
+
z (float): Z of a point on the target edge/face, in millimetres.
|
|
817
|
+
distance (float | None): For "along_curve", offset from the edge
|
|
818
|
+
start in millimetres.
|
|
819
|
+
percent (float | None): For "along_curve", position as 0-100 of the
|
|
820
|
+
edge length.
|
|
821
|
+
"""
|
|
822
|
+
|
|
823
|
+
mode: str = Field(
|
|
824
|
+
description="'along_curve' (a point on an edge) or 'face_center' "
|
|
825
|
+
"(the centroid of a face)"
|
|
826
|
+
)
|
|
827
|
+
x: float = Field(description="X of a point on the target edge/face, in mm")
|
|
828
|
+
y: float = Field(description="Y of a point on the target edge/face, in mm")
|
|
829
|
+
z: float = Field(description="Z of a point on the target edge/face, in mm")
|
|
830
|
+
distance: float | None = Field(
|
|
831
|
+
default=None,
|
|
832
|
+
description="along_curve only: offset from the edge start in mm "
|
|
833
|
+
"(give exactly one of distance or percent)",
|
|
834
|
+
)
|
|
835
|
+
percent: float | None = Field(
|
|
836
|
+
default=None,
|
|
837
|
+
description="along_curve only: position as 0-100 of the edge length",
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
def model_post_init(self, __context: Any) -> None:
|
|
841
|
+
if str(self.mode or "").strip().lower() not in (
|
|
842
|
+
"along_curve",
|
|
843
|
+
"face_center",
|
|
844
|
+
):
|
|
845
|
+
raise ValueError("mode must be 'along_curve' or 'face_center'")
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
class MirrorFeatureInput(CompatInput):
|
|
849
|
+
"""Input schema for mirroring solid bodies or features.
|
|
850
|
+
|
|
851
|
+
Attributes:
|
|
852
|
+
features (list[str]): Body or feature names to mirror.
|
|
853
|
+
mirror_plane (str): Plane name to mirror about.
|
|
854
|
+
merge (bool): Merge the mirrored result with the original.
|
|
855
|
+
mirror_bodies (bool): Mirror whole bodies rather than features.
|
|
856
|
+
"""
|
|
857
|
+
|
|
858
|
+
features: list[str] = Field(
|
|
859
|
+
default_factory=list,
|
|
860
|
+
description="Body or feature names to mirror. At least one required. "
|
|
861
|
+
"For a body mirror these are the names as they appear under Solid "
|
|
862
|
+
"Bodies, which for a lofted wing is the loft's own name",
|
|
863
|
+
)
|
|
864
|
+
mirror_plane: str = Field(
|
|
865
|
+
description="Plane name to mirror about, e.g. 'Right Plane'"
|
|
866
|
+
)
|
|
867
|
+
merge: bool = Field(
|
|
868
|
+
default=True,
|
|
869
|
+
description="Merge the mirrored result with the original body",
|
|
870
|
+
)
|
|
871
|
+
mirror_bodies: bool = Field(
|
|
872
|
+
default=True,
|
|
873
|
+
description="Mirror whole solid bodies rather than individual "
|
|
874
|
+
"features. Defaults to True, which is what works for anything "
|
|
875
|
+
"built from a loft or sweep - a feature mirror only resolves when "
|
|
876
|
+
"the feature's own sketch sits on the mirror plane",
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
def model_post_init(self, __context: Any) -> None:
|
|
880
|
+
if not self.features:
|
|
881
|
+
raise ValueError("features must contain at least one name")
|
|
882
|
+
if not self.mirror_plane.strip():
|
|
883
|
+
raise ValueError("mirror_plane is required")
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
class PatternCircularInput(CompatInput):
|
|
887
|
+
"""Input schema for patterning features around an axis.
|
|
888
|
+
|
|
889
|
+
Attributes:
|
|
890
|
+
features (list[str]): Names of the features to pattern.
|
|
891
|
+
axis (str): Name of the axis to rotate about.
|
|
892
|
+
count (int): Total number of instances including the original.
|
|
893
|
+
angle (float): Total angle in degrees to spread instances over.
|
|
894
|
+
equal_spacing (bool): Space instances evenly across angle.
|
|
895
|
+
"""
|
|
896
|
+
|
|
897
|
+
features: list[str] = Field(
|
|
898
|
+
default_factory=list,
|
|
899
|
+
description="Names of the features to pattern. At least one required.",
|
|
900
|
+
)
|
|
901
|
+
axis: str = Field(
|
|
902
|
+
description="Name of the axis to rotate about, e.g. 'Axis1' as "
|
|
903
|
+
"created by create_axis"
|
|
904
|
+
)
|
|
905
|
+
count: int = Field(
|
|
906
|
+
description="Total number of instances including the original. "
|
|
907
|
+
"Must be at least 2 - a pattern of one is just the original feature"
|
|
908
|
+
)
|
|
909
|
+
angle: float = Field(
|
|
910
|
+
default=360.0,
|
|
911
|
+
description="Total angle in degrees to spread the instances over. "
|
|
912
|
+
"Defaults to a full 360",
|
|
913
|
+
)
|
|
914
|
+
equal_spacing: bool = Field(
|
|
915
|
+
default=True,
|
|
916
|
+
description="Space instances evenly across 'angle'",
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
def model_post_init(self, __context: Any) -> None:
|
|
920
|
+
if not self.features:
|
|
921
|
+
raise ValueError("features must contain at least one name")
|
|
922
|
+
if not self.axis.strip():
|
|
923
|
+
raise ValueError("axis is required")
|
|
924
|
+
if self.count < 2:
|
|
925
|
+
raise ValueError("count must be at least 2")
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
class CreateDrawingInput(CompatInput):
|
|
929
|
+
"""Input schema for creating a new drawing.
|
|
930
|
+
|
|
931
|
+
Attributes:
|
|
932
|
+
model_path (str | None): The model path value.
|
|
933
|
+
name (str): The name value.
|
|
934
|
+
sheet_format (str | None): The sheet format value.
|
|
935
|
+
template (str | None): The template value.
|
|
936
|
+
"""
|
|
937
|
+
|
|
938
|
+
name: str = Field(description="Name for the new drawing")
|
|
939
|
+
template: str | None = Field(
|
|
940
|
+
default=None,
|
|
941
|
+
description=(
|
|
942
|
+
"Drawing template (.drwdot) path; omit to use the SolidWorks default"
|
|
943
|
+
),
|
|
944
|
+
)
|
|
945
|
+
model_path: str | None = Field(default=None, description="Source model path")
|
|
946
|
+
sheet_format: str | None = Field(default=None, description="Sheet format template")
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
async def register_modeling_tools(
|
|
950
|
+
mcp: FastMCP, adapter: SolidWorksAdapter, config: dict[str, Any]
|
|
951
|
+
) -> int:
|
|
952
|
+
"""Register modeling tools with FastMCP.
|
|
953
|
+
|
|
954
|
+
Registers comprehensive modeling tools for SolidWorks automation including model
|
|
955
|
+
creation, feature creation, and model management operations.
|
|
956
|
+
|
|
957
|
+
Args:
|
|
958
|
+
mcp (FastMCP): The mcp value.
|
|
959
|
+
adapter (SolidWorksAdapter): Adapter instance used for the operation.
|
|
960
|
+
config (dict[str, Any]): Configuration values for the operation.
|
|
961
|
+
|
|
962
|
+
Returns:
|
|
963
|
+
int: The computed numeric result.
|
|
964
|
+
|
|
965
|
+
Example:
|
|
966
|
+
```python
|
|
967
|
+
from solidworks_mcp.tools.modeling import register_modeling_tools
|
|
968
|
+
|
|
969
|
+
tool_count = await register_modeling_tools(mcp, adapter, config)
|
|
970
|
+
print(f"Registered {tool_count} modeling tools")
|
|
971
|
+
```
|
|
972
|
+
"""
|
|
973
|
+
mcp = with_tool_boundary(mcp)
|
|
974
|
+
tool_count = 0
|
|
975
|
+
|
|
976
|
+
@mcp.tool()
|
|
977
|
+
async def open_model(input_data: OpenModelInput) -> dict[str, Any]:
|
|
978
|
+
"""Open a SolidWorks model (part, assembly, or drawing).
|
|
979
|
+
|
|
980
|
+
Opens an existing SolidWorks file and makes it the active document for further
|
|
981
|
+
operations. Supports all standard SolidWorks file formats and provides detailed model
|
|
982
|
+
information upon successful opening.
|
|
983
|
+
|
|
984
|
+
Args:
|
|
985
|
+
input_data (OpenModelInput): The input data value.
|
|
986
|
+
|
|
987
|
+
Returns:
|
|
988
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
989
|
+
|
|
990
|
+
Example:
|
|
991
|
+
```python
|
|
992
|
+
result = await open_model({
|
|
993
|
+
"file_path": "C:/Models/bracket.sldprt"
|
|
994
|
+
})
|
|
995
|
+
|
|
996
|
+
if result["status"] == "success":
|
|
997
|
+
model = result["model"]
|
|
998
|
+
print(f"Opened {model['type']}: {model['name']}")
|
|
999
|
+
print(f"Configuration: {model['configuration']}")
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
Note:
|
|
1003
|
+
File path must be absolute and accessible to SolidWorks.
|
|
1004
|
+
Model becomes the active document for subsequent operations.
|
|
1005
|
+
"""
|
|
1006
|
+
input_data = normalize_input(input_data, OpenModelInput)
|
|
1007
|
+
result = await adapter.open_model(input_data.file_path)
|
|
1008
|
+
|
|
1009
|
+
require_success(result, context="Failed to open model")
|
|
1010
|
+
model = result.data
|
|
1011
|
+
title = _result_value(model, "title", "name", default=input_data.file_path)
|
|
1012
|
+
model_type = _result_value(model, "type", default="Part")
|
|
1013
|
+
path = _result_value(model, "path", "file_path", default=input_data.file_path)
|
|
1014
|
+
configuration = _result_value(model, "configuration", default="Default")
|
|
1015
|
+
return {
|
|
1016
|
+
"status": "success",
|
|
1017
|
+
"message": f"Opened {model_type}: {title}",
|
|
1018
|
+
"model": {
|
|
1019
|
+
"title": title,
|
|
1020
|
+
"name": title,
|
|
1021
|
+
"type": model_type,
|
|
1022
|
+
"path": path,
|
|
1023
|
+
"configuration": configuration,
|
|
1024
|
+
},
|
|
1025
|
+
"execution_time": result.execution_time,
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
@mcp.tool()
|
|
1029
|
+
async def create_part(input_data: CreatePartInput) -> dict[str, Any]:
|
|
1030
|
+
"""Create a new SolidWorks part document.
|
|
1031
|
+
|
|
1032
|
+
Creates a new SolidWorks part document from ``template`` (a .prtdot path) or,
|
|
1033
|
+
without one, the default part template. The new part becomes the active document
|
|
1034
|
+
and is ready for modeling operations such as sketch creation and feature addition.
|
|
1035
|
+
|
|
1036
|
+
Args:
|
|
1037
|
+
input_data (CreatePartInput): The input data value.
|
|
1038
|
+
|
|
1039
|
+
Returns:
|
|
1040
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
1041
|
+
|
|
1042
|
+
Example:
|
|
1043
|
+
```python
|
|
1044
|
+
result = await create_part()
|
|
1045
|
+
|
|
1046
|
+
if result["status"] == "success":
|
|
1047
|
+
part = result["model"]
|
|
1048
|
+
print(f"Created new part: {part['name']}")
|
|
1049
|
+
# Ready for sketching and feature creation
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
Note:
|
|
1053
|
+
- Uses ``template`` when given, else the default part template
|
|
1054
|
+
- Part document is created in memory (not saved)
|
|
1055
|
+
- Use save operations to persist to disk
|
|
1056
|
+
- Subsequent modeling operations will apply to this part
|
|
1057
|
+
"""
|
|
1058
|
+
input_data = normalize_input(input_data, CreatePartInput)
|
|
1059
|
+
result = await adapter.create_part(
|
|
1060
|
+
input_data.name, input_data.units, input_data.template
|
|
1061
|
+
)
|
|
1062
|
+
|
|
1063
|
+
require_success(result, context="Failed to create part")
|
|
1064
|
+
model = result.data
|
|
1065
|
+
part_name = _result_value(model, "name", default=input_data.name)
|
|
1066
|
+
units = _result_value(model, "units", default=input_data.units or "mm")
|
|
1067
|
+
return {
|
|
1068
|
+
"status": "success",
|
|
1069
|
+
"message": f"Created new part: {part_name}",
|
|
1070
|
+
"part": {
|
|
1071
|
+
"name": part_name,
|
|
1072
|
+
"units": units,
|
|
1073
|
+
"material": input_data.material,
|
|
1074
|
+
"template": input_data.template,
|
|
1075
|
+
},
|
|
1076
|
+
"execution_time": result.execution_time,
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
@mcp.tool()
|
|
1080
|
+
async def create_assembly(input_data: CreateAssemblyInput) -> dict[str, Any]:
|
|
1081
|
+
"""Create a new SolidWorks assembly document.
|
|
1082
|
+
|
|
1083
|
+
Creates a new SolidWorks assembly document from ``template`` (a .asmdot path) or,
|
|
1084
|
+
without one, the default assembly template. The new assembly becomes the active
|
|
1085
|
+
document and is ready for component insertion, mating, and assembly-level operations.
|
|
1086
|
+
|
|
1087
|
+
Args:
|
|
1088
|
+
input_data (CreateAssemblyInput): The input data value.
|
|
1089
|
+
|
|
1090
|
+
Returns:
|
|
1091
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
1092
|
+
|
|
1093
|
+
Example:
|
|
1094
|
+
```python
|
|
1095
|
+
result = await create_assembly()
|
|
1096
|
+
|
|
1097
|
+
if result["status"] == "success":
|
|
1098
|
+
assembly = result["model"]
|
|
1099
|
+
print(f"Created new assembly: {assembly['name']}")
|
|
1100
|
+
# Ready for component insertion and mating
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
Note:
|
|
1104
|
+
- Uses ``template`` when given, else the default assembly template
|
|
1105
|
+
- Assembly document is created in memory (not saved)
|
|
1106
|
+
- Use save operations to persist to disk
|
|
1107
|
+
- Ready for component insertion and mate creation
|
|
1108
|
+
- Assembly tree will initially be empty
|
|
1109
|
+
|
|
1110
|
+
The new assembly will become the active document.
|
|
1111
|
+
"""
|
|
1112
|
+
input_data = normalize_input(input_data, CreateAssemblyInput)
|
|
1113
|
+
result = await adapter.create_assembly(input_data.name, input_data.template)
|
|
1114
|
+
|
|
1115
|
+
require_success(result, context="Failed to create assembly")
|
|
1116
|
+
model = result.data
|
|
1117
|
+
assembly_name = _result_value(model, "name", default=input_data.name)
|
|
1118
|
+
return {
|
|
1119
|
+
"status": "success",
|
|
1120
|
+
"message": f"Created new assembly: {assembly_name}",
|
|
1121
|
+
"assembly": {
|
|
1122
|
+
"name": assembly_name,
|
|
1123
|
+
"components": input_data.components,
|
|
1124
|
+
"template": input_data.template,
|
|
1125
|
+
},
|
|
1126
|
+
"execution_time": result.execution_time,
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
@mcp.tool()
|
|
1130
|
+
async def create_drawing(input_data: CreateDrawingInput) -> dict[str, Any]:
|
|
1131
|
+
"""Create a new SolidWorks drawing document.
|
|
1132
|
+
|
|
1133
|
+
This tool creates a new drawing document from ``template`` (a .drwdot path) or,
|
|
1134
|
+
without one, the default drawing template. The new drawing will become the active
|
|
1135
|
+
document.
|
|
1136
|
+
|
|
1137
|
+
Args:
|
|
1138
|
+
input_data (CreateDrawingInput): The input data value.
|
|
1139
|
+
|
|
1140
|
+
Returns:
|
|
1141
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
1142
|
+
"""
|
|
1143
|
+
input_data = normalize_input(input_data, CreateDrawingInput)
|
|
1144
|
+
result = await adapter.create_drawing(input_data.name, input_data.template)
|
|
1145
|
+
|
|
1146
|
+
require_success(result, context="Failed to create drawing")
|
|
1147
|
+
model = result.data
|
|
1148
|
+
drawing_name = _result_value(model, "name", default=input_data.name)
|
|
1149
|
+
sheet_format = _result_value(
|
|
1150
|
+
model, "sheet_format", default=input_data.sheet_format
|
|
1151
|
+
)
|
|
1152
|
+
return {
|
|
1153
|
+
"status": "success",
|
|
1154
|
+
"message": f"Created new drawing: {drawing_name}",
|
|
1155
|
+
"drawing": {
|
|
1156
|
+
"name": drawing_name,
|
|
1157
|
+
"model_path": input_data.model_path,
|
|
1158
|
+
"sheet_format": sheet_format,
|
|
1159
|
+
"template": input_data.template,
|
|
1160
|
+
},
|
|
1161
|
+
"execution_time": result.execution_time,
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
@mcp.tool()
|
|
1165
|
+
async def close_model(input_data: CloseModelInput) -> dict[str, Any]:
|
|
1166
|
+
"""Close the current SolidWorks model.
|
|
1167
|
+
|
|
1168
|
+
Closes the currently active SolidWorks document with an option to save changes before
|
|
1169
|
+
closing. This is essential for proper model lifecycle management and preventing data
|
|
1170
|
+
loss.
|
|
1171
|
+
|
|
1172
|
+
Args:
|
|
1173
|
+
input_data (CloseModelInput): The input data value.
|
|
1174
|
+
|
|
1175
|
+
Returns:
|
|
1176
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
1177
|
+
|
|
1178
|
+
Example:
|
|
1179
|
+
```python
|
|
1180
|
+
# Close without saving
|
|
1181
|
+
result = await close_model({"save": False})
|
|
1182
|
+
|
|
1183
|
+
# Save and close
|
|
1184
|
+
result = await close_model({"save": True})
|
|
1185
|
+
|
|
1186
|
+
if result["status"] == "success":
|
|
1187
|
+
print(f"Model closed, saved: {result['saved']}")
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
Note:
|
|
1191
|
+
- Unsaved changes will be lost if save=False
|
|
1192
|
+
- Always save important work before closing
|
|
1193
|
+
- Model must be open to close it
|
|
1194
|
+
"""
|
|
1195
|
+
input_data = normalize_input(input_data, CloseModelInput)
|
|
1196
|
+
result = await adapter.close_model(input_data.save)
|
|
1197
|
+
|
|
1198
|
+
require_success(result, context="Failed to close model")
|
|
1199
|
+
return {
|
|
1200
|
+
"status": "success",
|
|
1201
|
+
"message": "Model closed successfully",
|
|
1202
|
+
"saved": input_data.save,
|
|
1203
|
+
"execution_time": result.execution_time,
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
@mcp.tool()
|
|
1207
|
+
async def create_extrusion(input_data: CreateExtrusionInput) -> dict[str, Any]:
|
|
1208
|
+
"""Create an extrusion feature from the active sketch.
|
|
1209
|
+
|
|
1210
|
+
Creates a 3D extrusion feature (boss or cut) from the currently active 2D sketch.
|
|
1211
|
+
Supports advanced options like draft angles, thin features, bidirectional extrusion, and
|
|
1212
|
+
various end conditions for professional modeling workflows.
|
|
1213
|
+
|
|
1214
|
+
Args:
|
|
1215
|
+
input_data (CreateExtrusionInput): The input data value.
|
|
1216
|
+
|
|
1217
|
+
Returns:
|
|
1218
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
1219
|
+
|
|
1220
|
+
Example:
|
|
1221
|
+
```python
|
|
1222
|
+
# Simple boss extrusion
|
|
1223
|
+
result = await create_extrusion({
|
|
1224
|
+
"depth": 25.0,
|
|
1225
|
+
"merge_result": True
|
|
1226
|
+
})
|
|
1227
|
+
|
|
1228
|
+
# Cut with draft angle
|
|
1229
|
+
result = await create_extrusion({
|
|
1230
|
+
"depth": 10.0,
|
|
1231
|
+
"reverse_direction": True,
|
|
1232
|
+
"draft_angle": 2.0
|
|
1233
|
+
})
|
|
1234
|
+
|
|
1235
|
+
# Thin wall feature
|
|
1236
|
+
result = await create_extrusion({
|
|
1237
|
+
"depth": 50.0,
|
|
1238
|
+
"thin_feature": True,
|
|
1239
|
+
"thin_thickness": 2.0
|
|
1240
|
+
})
|
|
1241
|
+
```
|
|
1242
|
+
"""
|
|
1243
|
+
input_data = normalize_input(input_data, CreateExtrusionInput)
|
|
1244
|
+
# Convert input to ExtrusionParameters
|
|
1245
|
+
params = ExtrusionParameters(
|
|
1246
|
+
depth=input_data.depth,
|
|
1247
|
+
draft_angle=input_data.draft_angle,
|
|
1248
|
+
reverse_direction=input_data.reverse_direction,
|
|
1249
|
+
both_directions=input_data.both_directions,
|
|
1250
|
+
thin_feature=input_data.thin_feature,
|
|
1251
|
+
thin_thickness=input_data.thin_thickness,
|
|
1252
|
+
end_condition=input_data.end_condition,
|
|
1253
|
+
merge_result=input_data.merge_result,
|
|
1254
|
+
feature_scope=False,
|
|
1255
|
+
auto_select=True,
|
|
1256
|
+
)
|
|
1257
|
+
|
|
1258
|
+
before = await _read_summary(adapter)
|
|
1259
|
+
result = await adapter.create_extrusion(params)
|
|
1260
|
+
|
|
1261
|
+
require_success(result, context="Failed to create extrusion")
|
|
1262
|
+
after = await _read_summary(adapter) if before is not None else None
|
|
1263
|
+
feature = result.data
|
|
1264
|
+
reply = {
|
|
1265
|
+
"status": "success",
|
|
1266
|
+
"message": f"Created extrusion: {_result_value(feature, 'feature_name', 'name', default='Extrusion')}",
|
|
1267
|
+
"extrusion": {
|
|
1268
|
+
"name": _result_value(
|
|
1269
|
+
feature, "feature_name", "name", default="Extrusion"
|
|
1270
|
+
),
|
|
1271
|
+
"sketch": input_data.sketch_name,
|
|
1272
|
+
"depth": input_data.depth,
|
|
1273
|
+
"direction": input_data.direction,
|
|
1274
|
+
},
|
|
1275
|
+
"execution_time": result.execution_time,
|
|
1276
|
+
}
|
|
1277
|
+
return _attach_measured_change(reply, before, after)
|
|
1278
|
+
|
|
1279
|
+
@mcp.tool()
|
|
1280
|
+
async def create_revolve(input_data: CreateRevolveInput) -> dict[str, Any]:
|
|
1281
|
+
"""Create a revolve feature from the active sketch.
|
|
1282
|
+
|
|
1283
|
+
Creates a 3D revolve feature by rotating the active 2D sketch profile around a specified
|
|
1284
|
+
axis of revolution. Supports full and partial revolves, thin features, and bidirectional
|
|
1285
|
+
revolution for comprehensive rotational modeling.
|
|
1286
|
+
|
|
1287
|
+
Args:
|
|
1288
|
+
input_data (CreateRevolveInput): The input data value.
|
|
1289
|
+
|
|
1290
|
+
Returns:
|
|
1291
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
1292
|
+
|
|
1293
|
+
Example:
|
|
1294
|
+
```python
|
|
1295
|
+
# Full revolution (cylinder)
|
|
1296
|
+
result = await create_revolve({
|
|
1297
|
+
"angle": 360.0,
|
|
1298
|
+
"merge_result": True
|
|
1299
|
+
})
|
|
1300
|
+
|
|
1301
|
+
# Partial revolution (arc section)
|
|
1302
|
+
result = await create_revolve({
|
|
1303
|
+
"angle": 120.0,
|
|
1304
|
+
"both_directions": True
|
|
1305
|
+
})
|
|
1306
|
+
|
|
1307
|
+
# Thin wall revolution (pipe)
|
|
1308
|
+
result = await create_revolve({
|
|
1309
|
+
"angle": 360.0,
|
|
1310
|
+
"thin_feature": True,
|
|
1311
|
+
"thin_thickness": 3.0
|
|
1312
|
+
})
|
|
1313
|
+
```
|
|
1314
|
+
"""
|
|
1315
|
+
input_data = normalize_input(input_data, CreateRevolveInput)
|
|
1316
|
+
# Convert input to RevolveParameters
|
|
1317
|
+
params = RevolveParameters(
|
|
1318
|
+
angle=input_data.angle,
|
|
1319
|
+
reverse_direction=input_data.reverse_direction,
|
|
1320
|
+
both_directions=input_data.both_directions,
|
|
1321
|
+
thin_feature=input_data.thin_feature,
|
|
1322
|
+
thin_thickness=input_data.thin_thickness,
|
|
1323
|
+
merge_result=input_data.merge_result,
|
|
1324
|
+
)
|
|
1325
|
+
|
|
1326
|
+
before = await _read_summary(adapter)
|
|
1327
|
+
result = await adapter.create_revolve(params)
|
|
1328
|
+
|
|
1329
|
+
require_success(result, context="Failed to create revolve")
|
|
1330
|
+
after = await _read_summary(adapter) if before is not None else None
|
|
1331
|
+
feature = result.data
|
|
1332
|
+
reply = {
|
|
1333
|
+
"status": "success",
|
|
1334
|
+
"message": f"Created revolve: {_result_value(feature, 'feature_name', 'name', default='Revolve')}",
|
|
1335
|
+
"revolve": {
|
|
1336
|
+
"name": _result_value(
|
|
1337
|
+
feature, "feature_name", "name", default="Revolve"
|
|
1338
|
+
),
|
|
1339
|
+
"sketch": input_data.sketch_name,
|
|
1340
|
+
"axis_entity": input_data.axis_entity,
|
|
1341
|
+
"angle": input_data.angle,
|
|
1342
|
+
"direction": input_data.direction,
|
|
1343
|
+
},
|
|
1344
|
+
"execution_time": result.execution_time,
|
|
1345
|
+
}
|
|
1346
|
+
return _attach_measured_change(reply, before, after)
|
|
1347
|
+
|
|
1348
|
+
@mcp.tool()
|
|
1349
|
+
async def get_dimension(input_data: GetDimensionInput) -> dict[str, Any]:
|
|
1350
|
+
"""Get the value of a dimension from the current model.
|
|
1351
|
+
|
|
1352
|
+
Retrieves the current value of a named dimension from the active SolidWorks model.
|
|
1353
|
+
Dimensions can be from sketches, features, or global dimensions. Useful for parametric
|
|
1354
|
+
modeling and design validation.
|
|
1355
|
+
|
|
1356
|
+
Args:
|
|
1357
|
+
input_data (GetDimensionInput): The input data value.
|
|
1358
|
+
|
|
1359
|
+
Returns:
|
|
1360
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
1361
|
+
|
|
1362
|
+
Example:
|
|
1363
|
+
```python
|
|
1364
|
+
# Get sketch dimension
|
|
1365
|
+
result = await get_dimension({
|
|
1366
|
+
"name": "D1@Sketch1"
|
|
1367
|
+
})
|
|
1368
|
+
|
|
1369
|
+
if result["status"] == "success":
|
|
1370
|
+
dim = result["dimension"]
|
|
1371
|
+
print(f"Dimension {dim['name']}: {dim['value']} {dim['units']}")
|
|
1372
|
+
|
|
1373
|
+
# Get feature dimension
|
|
1374
|
+
result = await get_dimension({
|
|
1375
|
+
"name": "D1@Boss-Extrude1"
|
|
1376
|
+
})
|
|
1377
|
+
```
|
|
1378
|
+
"""
|
|
1379
|
+
input_data = normalize_input(input_data, GetDimensionInput)
|
|
1380
|
+
result = await adapter.get_dimension(input_data.name)
|
|
1381
|
+
|
|
1382
|
+
require_success(result, context="Failed to get dimension")
|
|
1383
|
+
value = result.data
|
|
1384
|
+
dimension_value = _result_value(value, "value", default=value)
|
|
1385
|
+
dimension_units = _result_value(value, "units", default="mm")
|
|
1386
|
+
return {
|
|
1387
|
+
"status": "success",
|
|
1388
|
+
"message": f"Dimension {input_data.name} = {dimension_value} {dimension_units}",
|
|
1389
|
+
"dimension": {
|
|
1390
|
+
"name": input_data.name,
|
|
1391
|
+
"value": dimension_value,
|
|
1392
|
+
"units": dimension_units,
|
|
1393
|
+
},
|
|
1394
|
+
"execution_time": result.execution_time,
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
@mcp.tool()
|
|
1398
|
+
async def set_dimension(input_data: SetDimensionInput) -> dict[str, Any]:
|
|
1399
|
+
"""Set the value of a dimension in the current model.
|
|
1400
|
+
|
|
1401
|
+
This tool modifies the value of a named dimension and rebuilds the model. Use this to
|
|
1402
|
+
parametrically modify your model dimensions.
|
|
1403
|
+
|
|
1404
|
+
Args:
|
|
1405
|
+
input_data (SetDimensionInput): The input data value.
|
|
1406
|
+
|
|
1407
|
+
Returns:
|
|
1408
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
1409
|
+
"""
|
|
1410
|
+
input_data = normalize_input(input_data, SetDimensionInput)
|
|
1411
|
+
result = await adapter.set_dimension(input_data.name, input_data.value)
|
|
1412
|
+
|
|
1413
|
+
require_success(result, context="Failed to set dimension")
|
|
1414
|
+
payload = result.data
|
|
1415
|
+
return {
|
|
1416
|
+
"status": "success",
|
|
1417
|
+
"message": f"Set dimension {input_data.name} = {input_data.value} mm",
|
|
1418
|
+
"dimension_update": {
|
|
1419
|
+
"name": input_data.name,
|
|
1420
|
+
"old_value": _result_value(payload, "old_value"),
|
|
1421
|
+
"new_value": _result_value(
|
|
1422
|
+
payload, "new_value", default=input_data.value
|
|
1423
|
+
),
|
|
1424
|
+
"units": input_data.units or "mm",
|
|
1425
|
+
},
|
|
1426
|
+
"execution_time": result.execution_time,
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
@mcp.tool()
|
|
1430
|
+
async def set_units(input_data: SetUnitsInput) -> dict[str, Any]:
|
|
1431
|
+
"""Set the active document's linear unit system.
|
|
1432
|
+
|
|
1433
|
+
Accepts ``mm``, ``cm``, ``m``, ``in`` or ``ft`` (plus common aliases
|
|
1434
|
+
such as ``inch`` or ``millimeters``). The setting is applied and then
|
|
1435
|
+
read back from the document; if the readback disagrees the tool
|
|
1436
|
+
reports an error rather than a false success.
|
|
1437
|
+
|
|
1438
|
+
Args:
|
|
1439
|
+
input_data (SetUnitsInput): The target unit system.
|
|
1440
|
+
|
|
1441
|
+
Returns:
|
|
1442
|
+
dict[str, Any]: Status and what was applied.
|
|
1443
|
+
|
|
1444
|
+
Example:
|
|
1445
|
+
```python
|
|
1446
|
+
await set_units({"unit_system": "in"})
|
|
1447
|
+
```
|
|
1448
|
+
"""
|
|
1449
|
+
input_data = normalize_input(input_data, SetUnitsInput)
|
|
1450
|
+
result = await adapter.set_units(input_data.unit_system)
|
|
1451
|
+
require_success(result, context="Failed to set units")
|
|
1452
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
1453
|
+
applied = data.get("unit_system", input_data.unit_system)
|
|
1454
|
+
message = f"Set document units to {applied}"
|
|
1455
|
+
if data.get("verified") is None:
|
|
1456
|
+
message += " (could not be read back to confirm)"
|
|
1457
|
+
return {
|
|
1458
|
+
"status": "success",
|
|
1459
|
+
"message": message,
|
|
1460
|
+
"data": data,
|
|
1461
|
+
"execution_time": result.execution_time,
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
@mcp.tool()
|
|
1465
|
+
async def create_cut_extrude(input_data: CreateCutExtrudeInput) -> dict[str, Any]:
|
|
1466
|
+
"""Cut material from a sketch profile in the active model.
|
|
1467
|
+
|
|
1468
|
+
Creates a Cut-Extrude feature (Insert > Cut > Extrude). Use this after
|
|
1469
|
+
exit_sketch when you want to remove material — e.g. to create windows,
|
|
1470
|
+
holes, slots, or any through/blind cut in an existing solid body.
|
|
1471
|
+
|
|
1472
|
+
Sketch selection is never a silent guess: pass ``sketch_name``
|
|
1473
|
+
explicitly whenever you're not certain which sketch this should act
|
|
1474
|
+
on (e.g. more than one sketch could be unconsumed, or other tool
|
|
1475
|
+
calls ran in between). If omitted, only the sketch most recently
|
|
1476
|
+
created/exited in this same session is used, and only when it still
|
|
1477
|
+
exists in the model — there is no further fallback. Any ambiguity or
|
|
1478
|
+
mismatch is reported back as an error naming the sketch(es) actually
|
|
1479
|
+
found in the model, rather than cutting the wrong profile.
|
|
1480
|
+
|
|
1481
|
+
Direction: a one-way cut (Blind or ThroughAll) goes along the sketch
|
|
1482
|
+
plane's normal. From a sketch on a face, or on a plane that touches
|
|
1483
|
+
the body only on one side (e.g. a Top Plane sketch under a block
|
|
1484
|
+
extruded up from it), that can point away from the material. The
|
|
1485
|
+
tool then cuts the other way and returns
|
|
1486
|
+
``cut_extrude.direction_flipped: true``, so you don't need to guess
|
|
1487
|
+
``reverse_direction``. For a sketch on a plane through the middle of
|
|
1488
|
+
the body, use ``ThroughAllBoth`` (or ``ThroughAll`` with
|
|
1489
|
+
``both_directions``) to cut both sides.
|
|
1490
|
+
|
|
1491
|
+
Args:
|
|
1492
|
+
input_data (CreateCutExtrudeInput): Depth, direction, draft, and
|
|
1493
|
+
optional sketch_name parameters.
|
|
1494
|
+
|
|
1495
|
+
Returns:
|
|
1496
|
+
dict[str, Any]: Status and ``cut_extrude`` (name, depth,
|
|
1497
|
+
sketch_name, direction_flipped), plus ``measured_change``
|
|
1498
|
+
(volume and bounding box before and after) when the model
|
|
1499
|
+
can be read. On error, ``message`` identifies exactly which
|
|
1500
|
+
stage failed (sketch not found, sketch selection rejected by
|
|
1501
|
+
SolidWorks, the FeatureCut COM error, or ``error_type:
|
|
1502
|
+
feature_not_built`` when SolidWorks built nothing in either
|
|
1503
|
+
direction) rather than a generic failure string.
|
|
1504
|
+
|
|
1505
|
+
Example:
|
|
1506
|
+
```python
|
|
1507
|
+
# Cut a blind pocket 10 mm deep from a named sketch
|
|
1508
|
+
result = await create_cut_extrude({"depth": 10.0, "sketch_name": "Sketch3"})
|
|
1509
|
+
|
|
1510
|
+
# Through-all cut using the most recently exited sketch
|
|
1511
|
+
result = await create_cut_extrude({"depth": 200.0})
|
|
1512
|
+
```
|
|
1513
|
+
"""
|
|
1514
|
+
input_data = normalize_input(input_data, CreateCutExtrudeInput)
|
|
1515
|
+
params = ExtrusionParameters(
|
|
1516
|
+
depth=input_data.depth,
|
|
1517
|
+
draft_angle=input_data.draft_angle,
|
|
1518
|
+
reverse_direction=input_data.reverse_direction,
|
|
1519
|
+
both_directions=input_data.both_directions,
|
|
1520
|
+
thin_feature=False,
|
|
1521
|
+
thin_thickness=None,
|
|
1522
|
+
end_condition=input_data.end_condition,
|
|
1523
|
+
merge_result=False,
|
|
1524
|
+
feature_scope=False,
|
|
1525
|
+
auto_select=True,
|
|
1526
|
+
sketch_name=input_data.sketch_name,
|
|
1527
|
+
)
|
|
1528
|
+
before = await _read_summary(adapter)
|
|
1529
|
+
result = await adapter.create_cut_extrude(params)
|
|
1530
|
+
require_success(result, context="Failed to create cut-extrude")
|
|
1531
|
+
after = await _read_summary(adapter) if before is not None else None
|
|
1532
|
+
feature = result.data
|
|
1533
|
+
feature_params = _result_value(feature, "parameters", default={})
|
|
1534
|
+
sketch_used = (
|
|
1535
|
+
feature_params.get("sketch_name")
|
|
1536
|
+
if isinstance(feature_params, dict)
|
|
1537
|
+
else None
|
|
1538
|
+
) or input_data.sketch_name
|
|
1539
|
+
flipped = bool(
|
|
1540
|
+
isinstance(feature_params, dict) and feature_params.get("direction_flipped")
|
|
1541
|
+
)
|
|
1542
|
+
name = _result_value(feature, "feature_name", "name", default="Cut-Extrude")
|
|
1543
|
+
message = f"Created cut-extrude: {name}"
|
|
1544
|
+
if flipped:
|
|
1545
|
+
message += (
|
|
1546
|
+
" (the requested direction found no material, so it cut the other way)"
|
|
1547
|
+
)
|
|
1548
|
+
reply = {
|
|
1549
|
+
"status": "success",
|
|
1550
|
+
"message": message,
|
|
1551
|
+
"cut_extrude": {
|
|
1552
|
+
"name": name,
|
|
1553
|
+
"depth": input_data.depth,
|
|
1554
|
+
"sketch_name": sketch_used,
|
|
1555
|
+
"direction_flipped": flipped,
|
|
1556
|
+
},
|
|
1557
|
+
"execution_time": result.execution_time,
|
|
1558
|
+
}
|
|
1559
|
+
return _attach_measured_change(reply, before, after)
|
|
1560
|
+
|
|
1561
|
+
@mcp.tool()
|
|
1562
|
+
async def add_fillet(input_data: AddFilletInput) -> dict[str, Any]:
|
|
1563
|
+
"""Add a fillet (rounded edge) to selected edges of the current model.
|
|
1564
|
+
|
|
1565
|
+
Rounds the specified named edges with the given radius. Edge names use the
|
|
1566
|
+
SolidWorks convention, e.g. 'Edge<1>', or you can leave edge_names empty to
|
|
1567
|
+
fillet all edges if the adapter supports it.
|
|
1568
|
+
|
|
1569
|
+
Args:
|
|
1570
|
+
input_data (AddFilletInput): Radius and edge names.
|
|
1571
|
+
|
|
1572
|
+
Returns:
|
|
1573
|
+
dict[str, Any]: Status and feature details.
|
|
1574
|
+
|
|
1575
|
+
Example:
|
|
1576
|
+
```python
|
|
1577
|
+
# Fillet two specific edges with 2 mm radius
|
|
1578
|
+
result = await add_fillet({"radius": 2.0, "edge_names": ["Edge<1>", "Edge<2>"]})
|
|
1579
|
+
```
|
|
1580
|
+
"""
|
|
1581
|
+
input_data = normalize_input(input_data, AddFilletInput)
|
|
1582
|
+
result = await adapter.add_fillet(input_data.radius, input_data.edge_names)
|
|
1583
|
+
require_success(result, context="Failed to add fillet")
|
|
1584
|
+
feature = result.data
|
|
1585
|
+
return {
|
|
1586
|
+
"status": "success",
|
|
1587
|
+
"message": f"Created fillet: {_result_value(feature, 'feature_name', 'name', default='Fillet')}",
|
|
1588
|
+
"fillet": {
|
|
1589
|
+
"name": _result_value(
|
|
1590
|
+
feature, "feature_name", "name", default="Fillet"
|
|
1591
|
+
),
|
|
1592
|
+
"radius": input_data.radius,
|
|
1593
|
+
"edges": input_data.edge_names,
|
|
1594
|
+
},
|
|
1595
|
+
"execution_time": result.execution_time,
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
@mcp.tool()
|
|
1599
|
+
async def add_chamfer(input_data: AddChamferInput) -> dict[str, Any]:
|
|
1600
|
+
"""Add an equal-distance chamfer to selected edges of the current model.
|
|
1601
|
+
|
|
1602
|
+
Chamfers the specified named edges with the given distance. Edge names use
|
|
1603
|
+
the SolidWorks convention, e.g. 'Edge<1>', or you can leave edge_names empty
|
|
1604
|
+
to chamfer all edges if the adapter supports it.
|
|
1605
|
+
|
|
1606
|
+
Args:
|
|
1607
|
+
input_data (AddChamferInput): Distance and edge names.
|
|
1608
|
+
|
|
1609
|
+
Returns:
|
|
1610
|
+
dict[str, Any]: Status and feature details.
|
|
1611
|
+
|
|
1612
|
+
Example:
|
|
1613
|
+
```python
|
|
1614
|
+
# Chamfer two specific edges with a 1.5 mm distance
|
|
1615
|
+
result = await add_chamfer({"distance": 1.5, "edge_names": ["Edge<1>", "Edge<2>"]})
|
|
1616
|
+
```
|
|
1617
|
+
"""
|
|
1618
|
+
input_data = normalize_input(input_data, AddChamferInput)
|
|
1619
|
+
result = await adapter.add_chamfer(input_data.distance, input_data.edge_names)
|
|
1620
|
+
require_success(result, context="Failed to add chamfer")
|
|
1621
|
+
feature = result.data
|
|
1622
|
+
return {
|
|
1623
|
+
"status": "success",
|
|
1624
|
+
"message": f"Created chamfer: {_result_value(feature, 'feature_name', 'name', default='Chamfer')}",
|
|
1625
|
+
"chamfer": {
|
|
1626
|
+
"name": _result_value(
|
|
1627
|
+
feature, "feature_name", "name", default="Chamfer"
|
|
1628
|
+
),
|
|
1629
|
+
"distance": input_data.distance,
|
|
1630
|
+
"edges": input_data.edge_names,
|
|
1631
|
+
},
|
|
1632
|
+
"execution_time": result.execution_time,
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
@mcp.tool()
|
|
1636
|
+
async def create_sweep(input_data: CreateSweepInput) -> dict[str, Any]:
|
|
1637
|
+
"""Sweep the active profile sketch along a named path sketch.
|
|
1638
|
+
|
|
1639
|
+
Creates a swept boss/protrusion (Insert > Boss/Base > Sweep). Requires
|
|
1640
|
+
two sketches in the active part: a closed profile sketch and an open
|
|
1641
|
+
path sketch named by ``path``. The profile is inferred as the sketch
|
|
1642
|
+
that is not the path (in the usual "draw profile, draw path, sweep"
|
|
1643
|
+
flow this is unambiguous). Optionally applies a constant twist along
|
|
1644
|
+
the path.
|
|
1645
|
+
|
|
1646
|
+
Args:
|
|
1647
|
+
input_data (CreateSweepInput): Path name and twist/merge options.
|
|
1648
|
+
|
|
1649
|
+
Returns:
|
|
1650
|
+
dict[str, Any]: Status and feature details.
|
|
1651
|
+
|
|
1652
|
+
Example:
|
|
1653
|
+
```python
|
|
1654
|
+
# Sweep a circular profile along "Sketch2"
|
|
1655
|
+
result = await create_sweep({"path": "Sketch2"})
|
|
1656
|
+
|
|
1657
|
+
# Sweep with a 90-degree twist along the path
|
|
1658
|
+
result = await create_sweep({
|
|
1659
|
+
"path": "Sketch2",
|
|
1660
|
+
"twist_along_path": True,
|
|
1661
|
+
"twist_angle": 90.0,
|
|
1662
|
+
})
|
|
1663
|
+
```
|
|
1664
|
+
"""
|
|
1665
|
+
input_data = normalize_input(input_data, CreateSweepInput)
|
|
1666
|
+
params = SweepParameters(
|
|
1667
|
+
path=input_data.path,
|
|
1668
|
+
twist_along_path=input_data.twist_along_path,
|
|
1669
|
+
twist_angle=input_data.twist_angle,
|
|
1670
|
+
merge_result=input_data.merge_result,
|
|
1671
|
+
)
|
|
1672
|
+
result = await adapter.create_sweep(params)
|
|
1673
|
+
require_success(result, context="Failed to create sweep")
|
|
1674
|
+
feature = result.data
|
|
1675
|
+
return {
|
|
1676
|
+
"status": "success",
|
|
1677
|
+
"message": f"Created sweep: {_result_value(feature, 'feature_name', 'name', default='Sweep')}",
|
|
1678
|
+
"sweep": {
|
|
1679
|
+
"name": _result_value(feature, "feature_name", "name", default="Sweep"),
|
|
1680
|
+
"path": input_data.path,
|
|
1681
|
+
"twist_along_path": input_data.twist_along_path,
|
|
1682
|
+
"twist_angle": input_data.twist_angle,
|
|
1683
|
+
},
|
|
1684
|
+
"execution_time": result.execution_time,
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
@mcp.tool()
|
|
1688
|
+
async def create_loft(input_data: CreateLoftInput) -> dict[str, Any]:
|
|
1689
|
+
"""Loft a solid between two or more profile sketches.
|
|
1690
|
+
|
|
1691
|
+
Creates a lofted boss/protrusion (Insert > Boss/Base > Loft) blending
|
|
1692
|
+
the listed profile sketches in order. Each profile must be a closed
|
|
1693
|
+
contour. Optional guide curves shape the transition between profiles.
|
|
1694
|
+
|
|
1695
|
+
Args:
|
|
1696
|
+
input_data (CreateLoftInput): Profile names, optional guide curves,
|
|
1697
|
+
and tangency/merge options.
|
|
1698
|
+
|
|
1699
|
+
Returns:
|
|
1700
|
+
dict[str, Any]: Status and feature details.
|
|
1701
|
+
|
|
1702
|
+
Example:
|
|
1703
|
+
```python
|
|
1704
|
+
# Loft between two profiles (e.g. a tapered bevel)
|
|
1705
|
+
result = await create_loft({"profiles": ["Sketch1", "Sketch2"]})
|
|
1706
|
+
|
|
1707
|
+
# Loft with guide curves
|
|
1708
|
+
result = await create_loft({
|
|
1709
|
+
"profiles": ["Sketch1", "Sketch2"],
|
|
1710
|
+
"guide_curves": ["Sketch3"],
|
|
1711
|
+
})
|
|
1712
|
+
```
|
|
1713
|
+
"""
|
|
1714
|
+
input_data = normalize_input(input_data, CreateLoftInput)
|
|
1715
|
+
params = LoftParameters(
|
|
1716
|
+
profiles=input_data.profiles,
|
|
1717
|
+
guide_curves=input_data.guide_curves,
|
|
1718
|
+
start_tangent=input_data.start_tangent,
|
|
1719
|
+
end_tangent=input_data.end_tangent,
|
|
1720
|
+
merge_result=input_data.merge_result,
|
|
1721
|
+
)
|
|
1722
|
+
result = await adapter.create_loft(params)
|
|
1723
|
+
require_success(result, context="Failed to create loft")
|
|
1724
|
+
feature = result.data
|
|
1725
|
+
return {
|
|
1726
|
+
"status": "success",
|
|
1727
|
+
"message": f"Created loft: {_result_value(feature, 'feature_name', 'name', default='Loft')}",
|
|
1728
|
+
"loft": {
|
|
1729
|
+
"name": _result_value(feature, "feature_name", "name", default="Loft"),
|
|
1730
|
+
"profiles": input_data.profiles,
|
|
1731
|
+
"guide_curves": input_data.guide_curves,
|
|
1732
|
+
},
|
|
1733
|
+
"execution_time": result.execution_time,
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
@mcp.tool()
|
|
1737
|
+
async def insert_component(input_data: InsertComponentInput) -> dict[str, Any]:
|
|
1738
|
+
"""Insert a part or sub-assembly into the active assembly.
|
|
1739
|
+
|
|
1740
|
+
Call ``create_assembly`` first. Position is in millimetres from the
|
|
1741
|
+
assembly origin.
|
|
1742
|
+
|
|
1743
|
+
**The component file must contain solid geometry** — SolidWorks
|
|
1744
|
+
silently refuses to insert an empty part, so a missing or empty
|
|
1745
|
+
component surfaces as an error rather than a fabricated success.
|
|
1746
|
+
|
|
1747
|
+
Args:
|
|
1748
|
+
input_data (InsertComponentInput): File path and position.
|
|
1749
|
+
|
|
1750
|
+
Returns:
|
|
1751
|
+
dict[str, Any]: Status, the component's instance name and counts.
|
|
1752
|
+
|
|
1753
|
+
Example:
|
|
1754
|
+
```python
|
|
1755
|
+
await create_assembly({"name": "bracket_asm"})
|
|
1756
|
+
await insert_component({"file_path": "C:/parts/plate.sldprt"})
|
|
1757
|
+
await insert_component({"file_path": "C:/parts/plate.sldprt", "x": 100.0})
|
|
1758
|
+
```
|
|
1759
|
+
"""
|
|
1760
|
+
input_data = normalize_input(input_data, InsertComponentInput)
|
|
1761
|
+
result = await adapter.insert_component(
|
|
1762
|
+
input_data.file_path, input_data.x, input_data.y, input_data.z
|
|
1763
|
+
)
|
|
1764
|
+
require_success(result, context="Failed to insert component")
|
|
1765
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
1766
|
+
return {
|
|
1767
|
+
"status": "success",
|
|
1768
|
+
"message": f"Inserted {data.get('component', 'component')}",
|
|
1769
|
+
"component": data,
|
|
1770
|
+
"execution_time": result.execution_time,
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
@mcp.tool()
|
|
1774
|
+
async def add_mate(input_data: AddMateInput) -> dict[str, Any]:
|
|
1775
|
+
"""Mate two components in the active assembly.
|
|
1776
|
+
|
|
1777
|
+
Positions components relative to one another — coincident planes to
|
|
1778
|
+
stack or align them, concentric to line up axes, distance to hold a
|
|
1779
|
+
gap.
|
|
1780
|
+
|
|
1781
|
+
Entities are selected by feature name, so only each component's
|
|
1782
|
+
reference planes and axes can be mated. Mating to a specific face or
|
|
1783
|
+
edge needs entity names this adapter cannot enumerate.
|
|
1784
|
+
|
|
1785
|
+
Args:
|
|
1786
|
+
input_data (AddMateInput): Components, entities and mate type.
|
|
1787
|
+
|
|
1788
|
+
Returns:
|
|
1789
|
+
dict[str, Any]: Status and mate details.
|
|
1790
|
+
|
|
1791
|
+
Example:
|
|
1792
|
+
```python
|
|
1793
|
+
components = await list_components()
|
|
1794
|
+
await add_mate({
|
|
1795
|
+
"component_a": components["components"][0],
|
|
1796
|
+
"component_b": components["components"][1],
|
|
1797
|
+
"mate_type": "coincident",
|
|
1798
|
+
})
|
|
1799
|
+
```
|
|
1800
|
+
"""
|
|
1801
|
+
input_data = normalize_input(input_data, AddMateInput)
|
|
1802
|
+
result = await adapter.add_mate(
|
|
1803
|
+
input_data.component_a,
|
|
1804
|
+
input_data.component_b,
|
|
1805
|
+
input_data.entity_a,
|
|
1806
|
+
input_data.entity_b,
|
|
1807
|
+
input_data.mate_type,
|
|
1808
|
+
input_data.alignment,
|
|
1809
|
+
input_data.distance,
|
|
1810
|
+
input_data.angle,
|
|
1811
|
+
)
|
|
1812
|
+
require_success(result, context="Failed to add mate")
|
|
1813
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
1814
|
+
message = (
|
|
1815
|
+
f"Added {input_data.mate_type} mate between "
|
|
1816
|
+
f"{input_data.component_a} and {input_data.component_b}"
|
|
1817
|
+
)
|
|
1818
|
+
# SolidWorks accepts a mate whose constraint is already
|
|
1819
|
+
# satisfied and moves nothing. That is a real success, but
|
|
1820
|
+
# saying so plainly beats leaving the caller to infer it.
|
|
1821
|
+
if data.get("geometry_moved") is False:
|
|
1822
|
+
message += " (no component moved - the constraint was already satisfied)"
|
|
1823
|
+
return {
|
|
1824
|
+
"status": "success",
|
|
1825
|
+
"message": message,
|
|
1826
|
+
"mate": data,
|
|
1827
|
+
"execution_time": result.execution_time,
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
@mcp.tool()
|
|
1831
|
+
async def list_components() -> dict[str, Any]:
|
|
1832
|
+
"""List the top-level components of the active assembly.
|
|
1833
|
+
|
|
1834
|
+
Returns:
|
|
1835
|
+
dict[str, Any]: Status and the component instance names.
|
|
1836
|
+
"""
|
|
1837
|
+
result = await adapter.list_components()
|
|
1838
|
+
require_success(result, context="Failed to list components")
|
|
1839
|
+
components = result.data if isinstance(result.data, list) else []
|
|
1840
|
+
return {
|
|
1841
|
+
"status": "success",
|
|
1842
|
+
"message": f"{len(components)} component(s) in the assembly",
|
|
1843
|
+
"components": components,
|
|
1844
|
+
"execution_time": result.execution_time,
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
@mcp.tool()
|
|
1848
|
+
async def delete_feature(input_data: DeleteFeatureInput) -> dict[str, Any]:
|
|
1849
|
+
"""Delete a feature or sketch from the active model.
|
|
1850
|
+
|
|
1851
|
+
Deleting a parent feature also removes everything that depends on it —
|
|
1852
|
+
SolidWorks' normal cascade — so the response lists every feature that
|
|
1853
|
+
went, not just the one named.
|
|
1854
|
+
|
|
1855
|
+
Args:
|
|
1856
|
+
input_data (DeleteFeatureInput): The feature to delete.
|
|
1857
|
+
|
|
1858
|
+
Returns:
|
|
1859
|
+
dict[str, Any]: Status and what was removed.
|
|
1860
|
+
|
|
1861
|
+
Example:
|
|
1862
|
+
```python
|
|
1863
|
+
await delete_feature({"name": "Boss-Extrude1"})
|
|
1864
|
+
```
|
|
1865
|
+
"""
|
|
1866
|
+
input_data = normalize_input(input_data, DeleteFeatureInput)
|
|
1867
|
+
result = await adapter.delete_feature(input_data.name)
|
|
1868
|
+
require_success(result, context="Failed to delete feature")
|
|
1869
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
1870
|
+
removed = data.get("removed_features") or [input_data.name]
|
|
1871
|
+
return {
|
|
1872
|
+
"status": "success",
|
|
1873
|
+
"message": (
|
|
1874
|
+
f"Deleted {input_data.name}"
|
|
1875
|
+
+ (
|
|
1876
|
+
f" and {len(removed) - 1} dependent feature(s)"
|
|
1877
|
+
if len(removed) > 1
|
|
1878
|
+
else ""
|
|
1879
|
+
)
|
|
1880
|
+
),
|
|
1881
|
+
"data": data,
|
|
1882
|
+
"execution_time": result.execution_time,
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
@mcp.tool()
|
|
1886
|
+
async def suppress_feature(input_data: SuppressFeatureInput) -> dict[str, Any]:
|
|
1887
|
+
"""Suppress or unsuppress a feature in the active model.
|
|
1888
|
+
|
|
1889
|
+
Suppressing rolls a feature and its children out of the model without
|
|
1890
|
+
deleting it — the reversible way to turn off a feature that is causing
|
|
1891
|
+
trouble.
|
|
1892
|
+
|
|
1893
|
+
Args:
|
|
1894
|
+
input_data (SuppressFeatureInput): The feature and desired state.
|
|
1895
|
+
|
|
1896
|
+
Returns:
|
|
1897
|
+
dict[str, Any]: Status and the resulting suppression state.
|
|
1898
|
+
|
|
1899
|
+
Example:
|
|
1900
|
+
```python
|
|
1901
|
+
await suppress_feature({"name": "Fillet1"})
|
|
1902
|
+
await suppress_feature({"name": "Fillet1", "suppress": False})
|
|
1903
|
+
```
|
|
1904
|
+
"""
|
|
1905
|
+
input_data = normalize_input(input_data, SuppressFeatureInput)
|
|
1906
|
+
result = await adapter.suppress_feature(input_data.name, input_data.suppress)
|
|
1907
|
+
require_success(result, context="Failed to suppress feature")
|
|
1908
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
1909
|
+
verb = "Suppressed" if input_data.suppress else "Unsuppressed"
|
|
1910
|
+
message = f"{verb} {input_data.name}"
|
|
1911
|
+
if data.get("suppressed") is None:
|
|
1912
|
+
message += " (state could not be read back)"
|
|
1913
|
+
return {
|
|
1914
|
+
"status": "success",
|
|
1915
|
+
"message": message,
|
|
1916
|
+
"data": data,
|
|
1917
|
+
"execution_time": result.execution_time,
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
@mcp.tool()
|
|
1921
|
+
async def rename_feature(input_data: RenameFeatureInput) -> dict[str, Any]:
|
|
1922
|
+
"""Rename a feature on the feature tree.
|
|
1923
|
+
|
|
1924
|
+
SolidWorks silently refuses a rename onto a name another feature
|
|
1925
|
+
already uses, so the new name is read back and a mismatch is reported
|
|
1926
|
+
as an error rather than a quiet success. Renaming a feature to its
|
|
1927
|
+
current name is a no-op success.
|
|
1928
|
+
|
|
1929
|
+
Args:
|
|
1930
|
+
input_data (RenameFeatureInput): The feature and its new name.
|
|
1931
|
+
|
|
1932
|
+
Returns:
|
|
1933
|
+
dict[str, Any]: Status and the old and new names.
|
|
1934
|
+
|
|
1935
|
+
Example:
|
|
1936
|
+
```python
|
|
1937
|
+
await rename_feature({"old_name": "Fillet1", "new_name": "EdgeBreak"})
|
|
1938
|
+
```
|
|
1939
|
+
"""
|
|
1940
|
+
input_data = normalize_input(input_data, RenameFeatureInput)
|
|
1941
|
+
result = await adapter.rename_feature(input_data.old_name, input_data.new_name)
|
|
1942
|
+
require_success(result, context="Failed to rename feature")
|
|
1943
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
1944
|
+
if data.get("renamed") is False:
|
|
1945
|
+
return {
|
|
1946
|
+
"status": "success",
|
|
1947
|
+
"message": (
|
|
1948
|
+
f"{input_data.old_name} unchanged: "
|
|
1949
|
+
+ str(data.get("reason") or "no change")
|
|
1950
|
+
),
|
|
1951
|
+
"data": data,
|
|
1952
|
+
"execution_time": result.execution_time,
|
|
1953
|
+
}
|
|
1954
|
+
return {
|
|
1955
|
+
"status": "success",
|
|
1956
|
+
"message": (
|
|
1957
|
+
f"Renamed {data.get('old_name', input_data.old_name)} to "
|
|
1958
|
+
f"{data.get('new_name', input_data.new_name)}"
|
|
1959
|
+
),
|
|
1960
|
+
"data": data,
|
|
1961
|
+
"execution_time": result.execution_time,
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
@mcp.tool()
|
|
1965
|
+
async def select_feature(input_data: SelectFeatureInput) -> dict[str, Any]:
|
|
1966
|
+
"""Select a named feature, sketch, plane or component.
|
|
1967
|
+
|
|
1968
|
+
Replaces the current selection by default; pass ``append: true`` to
|
|
1969
|
+
add to it (e.g. to select several features in turn). A name that
|
|
1970
|
+
cannot be selected is an error, never a quiet success.
|
|
1971
|
+
|
|
1972
|
+
Args:
|
|
1973
|
+
input_data (SelectFeatureInput): The feature name and append flag.
|
|
1974
|
+
|
|
1975
|
+
Returns:
|
|
1976
|
+
dict[str, Any]: Status, the matched name and how many entities are
|
|
1977
|
+
now selected.
|
|
1978
|
+
|
|
1979
|
+
Example:
|
|
1980
|
+
```python
|
|
1981
|
+
await select_feature({"feature_name": "Boss-Extrude1"})
|
|
1982
|
+
await select_feature({"feature_name": "Sketch1", "append": True})
|
|
1983
|
+
```
|
|
1984
|
+
"""
|
|
1985
|
+
input_data = normalize_input(input_data, SelectFeatureInput)
|
|
1986
|
+
result = await adapter.select_feature(
|
|
1987
|
+
input_data.feature_name, input_data.append
|
|
1988
|
+
)
|
|
1989
|
+
require_success(result)
|
|
1990
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
1991
|
+
return {
|
|
1992
|
+
"status": "success",
|
|
1993
|
+
"message": (
|
|
1994
|
+
f"Selected {data.get('selected_name', input_data.feature_name)} "
|
|
1995
|
+
f"({data.get('selection_count', '?')} selected)"
|
|
1996
|
+
),
|
|
1997
|
+
"data": data,
|
|
1998
|
+
"execution_time": result.execution_time,
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
@mcp.tool()
|
|
2002
|
+
async def select_face_edges(input_data: SelectFaceEdgesInput) -> dict[str, Any]:
|
|
2003
|
+
"""Select every edge of one face of the active part (multi-select).
|
|
2004
|
+
|
|
2005
|
+
Identify the face with exactly one of ``point`` (a point on it, mm),
|
|
2006
|
+
``side`` (``top``/``bottom``/``front``/``back``/``right``/``left`` or
|
|
2007
|
+
``+X``..``-Z``; top=+Y, front=+Z, right=+X as in SolidWorks' default
|
|
2008
|
+
views - the furthest flat face facing that way) or ``plane`` (a
|
|
2009
|
+
reference plane name - the body face lying on it). ``edge_type``
|
|
2010
|
+
narrows to ``linear`` or ``circular`` edges (e.g. hole rims).
|
|
2011
|
+
|
|
2012
|
+
The edges become the live SolidWorks selection, so the next
|
|
2013
|
+
``add_chamfer`` / ``add_fillet`` call with an empty ``edge_names``
|
|
2014
|
+
acts on exactly them. Anything that changes the selection in between
|
|
2015
|
+
(including the user clicking in SolidWorks) changes what is used.
|
|
2016
|
+
|
|
2017
|
+
Args:
|
|
2018
|
+
input_data (SelectFaceEdgesInput): Face identifier, edge filter and
|
|
2019
|
+
append flag.
|
|
2020
|
+
|
|
2021
|
+
Returns:
|
|
2022
|
+
dict[str, Any]: Status plus ``data.face`` (centre, normal, area),
|
|
2023
|
+
``data.edges`` (one entry per selected edge: curve type,
|
|
2024
|
+
endpoints/length or centre/radius) and ``data.selection_count``.
|
|
2025
|
+
|
|
2026
|
+
Example:
|
|
2027
|
+
```python
|
|
2028
|
+
# Chamfer the hole rims on the top face
|
|
2029
|
+
await select_face_edges({"side": "top", "edge_type": "circular"})
|
|
2030
|
+
await add_chamfer({"distance": 0.5, "edge_names": []})
|
|
2031
|
+
```
|
|
2032
|
+
"""
|
|
2033
|
+
input_data = normalize_input(input_data, SelectFaceEdgesInput)
|
|
2034
|
+
result = await adapter.select_face_edges(
|
|
2035
|
+
input_data.point,
|
|
2036
|
+
input_data.side,
|
|
2037
|
+
input_data.plane,
|
|
2038
|
+
input_data.edge_type,
|
|
2039
|
+
input_data.append,
|
|
2040
|
+
)
|
|
2041
|
+
require_success(result)
|
|
2042
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
2043
|
+
kinds = sorted({e.get("curve", "other") for e in data.get("edges", [])})
|
|
2044
|
+
return {
|
|
2045
|
+
"status": "success",
|
|
2046
|
+
"message": (
|
|
2047
|
+
f"Selected {data.get('selected_edges', 0)} edge(s) "
|
|
2048
|
+
f"({', '.join(kinds)}) on the face at "
|
|
2049
|
+
f"{data.get('face', {}).get('center_mm')} mm"
|
|
2050
|
+
),
|
|
2051
|
+
"data": data,
|
|
2052
|
+
"execution_time": result.execution_time,
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
@mcp.tool()
|
|
2056
|
+
async def rebuild_model(
|
|
2057
|
+
input_data: RebuildModelInput | None = None,
|
|
2058
|
+
) -> dict[str, Any]:
|
|
2059
|
+
"""Force a rebuild of the active model and report what is wrong.
|
|
2060
|
+
|
|
2061
|
+
Use after programmatic edits (``set_dimension``, feature edits) to
|
|
2062
|
+
confirm the model rebuilds cleanly. When features fail, the response
|
|
2063
|
+
is an error whose ``data.errors`` names each failing feature with its
|
|
2064
|
+
SolidWorks error code, the features/sketches it depends on, and a
|
|
2065
|
+
concrete ``hint`` for fixing it. Warnings alone still succeed.
|
|
2066
|
+
|
|
2067
|
+
Args:
|
|
2068
|
+
input_data (RebuildModelInput | None): ``top_level_only`` for
|
|
2069
|
+
assemblies; omit for a full rebuild.
|
|
2070
|
+
|
|
2071
|
+
Returns:
|
|
2072
|
+
dict[str, Any]: Status, a one-line summary, and ``data`` with
|
|
2073
|
+
``errors``, ``error_count`` and ``warning_count``.
|
|
2074
|
+
|
|
2075
|
+
Example:
|
|
2076
|
+
```python
|
|
2077
|
+
await rebuild_model()
|
|
2078
|
+
```
|
|
2079
|
+
"""
|
|
2080
|
+
input_data = normalize_input(input_data or {}, RebuildModelInput)
|
|
2081
|
+
result = require_success(
|
|
2082
|
+
await adapter.rebuild_model(input_data.top_level_only), with_data=True
|
|
2083
|
+
)
|
|
2084
|
+
data = result.data if isinstance(result.data, dict) else None
|
|
2085
|
+
response: dict[str, Any] = {
|
|
2086
|
+
"status": "success",
|
|
2087
|
+
"message": (data or {}).get("summary") or "Rebuilt",
|
|
2088
|
+
"execution_time": result.execution_time,
|
|
2089
|
+
}
|
|
2090
|
+
if data is not None:
|
|
2091
|
+
response["data"] = data
|
|
2092
|
+
return response
|
|
2093
|
+
|
|
2094
|
+
@mcp.tool()
|
|
2095
|
+
async def undo(input_data: UndoInput | None = None) -> dict[str, Any]:
|
|
2096
|
+
"""Undo the last operations in the active model.
|
|
2097
|
+
|
|
2098
|
+
SolidWorks accepts an undo with nothing left to undo without
|
|
2099
|
+
complaining, so the response reports whether the feature tree actually
|
|
2100
|
+
changed rather than assuming it did.
|
|
2101
|
+
|
|
2102
|
+
Args:
|
|
2103
|
+
input_data (UndoInput | None): How many steps to undo.
|
|
2104
|
+
|
|
2105
|
+
Returns:
|
|
2106
|
+
dict[str, Any]: Status and whether the tree changed.
|
|
2107
|
+
|
|
2108
|
+
Example:
|
|
2109
|
+
```python
|
|
2110
|
+
await undo({"count": 1})
|
|
2111
|
+
```
|
|
2112
|
+
"""
|
|
2113
|
+
input_data = normalize_input(input_data or UndoInput(), UndoInput)
|
|
2114
|
+
result = await adapter.undo(input_data.count)
|
|
2115
|
+
require_success(result, context="Failed to undo")
|
|
2116
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
2117
|
+
changed = data.get("tree_changed")
|
|
2118
|
+
if changed is False:
|
|
2119
|
+
message = (
|
|
2120
|
+
f"Undo of {input_data.count} step(s) left the feature "
|
|
2121
|
+
"tree unchanged - there was nothing to undo"
|
|
2122
|
+
)
|
|
2123
|
+
else:
|
|
2124
|
+
message = f"Undid {input_data.count} step(s)"
|
|
2125
|
+
return {
|
|
2126
|
+
"status": "success",
|
|
2127
|
+
"message": message,
|
|
2128
|
+
"data": data,
|
|
2129
|
+
"execution_time": result.execution_time,
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
@mcp.tool()
|
|
2133
|
+
async def create_reference_plane(
|
|
2134
|
+
input_data: CreateReferencePlaneInput,
|
|
2135
|
+
) -> dict[str, Any]:
|
|
2136
|
+
"""Create a reference plane offset from an existing plane or planar face.
|
|
2137
|
+
|
|
2138
|
+
Adds a new plane to the feature tree, parallel to and offset from the
|
|
2139
|
+
named reference. The returned plane name can be passed straight to
|
|
2140
|
+
``create_sketch`` to sketch anywhere other than the six built-in
|
|
2141
|
+
planes.
|
|
2142
|
+
|
|
2143
|
+
Angled planes are not supported by this tool: an angled plane needs a
|
|
2144
|
+
second reference (an axis or edge) to rotate about, which this
|
|
2145
|
+
signature cannot take. Use ``offset`` for parallel planes. An
|
|
2146
|
+
``offset`` of zero is rejected — a plane coincident with its
|
|
2147
|
+
reference is not useful.
|
|
2148
|
+
|
|
2149
|
+
Args:
|
|
2150
|
+
input_data (CreateReferencePlaneInput): Reference name, offset,
|
|
2151
|
+
angle and flip.
|
|
2152
|
+
|
|
2153
|
+
Returns:
|
|
2154
|
+
dict[str, Any]: Status and the new plane's details.
|
|
2155
|
+
|
|
2156
|
+
Example:
|
|
2157
|
+
```python
|
|
2158
|
+
plane = await create_reference_plane(
|
|
2159
|
+
{"reference": "Front Plane", "offset": 76.2}
|
|
2160
|
+
)
|
|
2161
|
+
await create_sketch({"plane": plane["plane"]["name"]})
|
|
2162
|
+
```
|
|
2163
|
+
"""
|
|
2164
|
+
input_data = normalize_input(input_data, CreateReferencePlaneInput)
|
|
2165
|
+
result = await adapter.create_reference_plane(
|
|
2166
|
+
input_data.reference,
|
|
2167
|
+
input_data.offset,
|
|
2168
|
+
input_data.angle,
|
|
2169
|
+
input_data.flip,
|
|
2170
|
+
)
|
|
2171
|
+
require_success(result, context="Failed to create reference plane")
|
|
2172
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
2173
|
+
return {
|
|
2174
|
+
"status": "success",
|
|
2175
|
+
"message": f"Created reference plane: {data.get('name', 'Plane')}",
|
|
2176
|
+
"plane": data,
|
|
2177
|
+
"execution_time": result.execution_time,
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
@mcp.tool()
|
|
2181
|
+
async def create_axis(input_data: CreateAxisInput) -> dict[str, Any]:
|
|
2182
|
+
"""Create a reference axis along a principal direction.
|
|
2183
|
+
|
|
2184
|
+
Builds an axis from the intersection of two built-in planes: x from
|
|
2185
|
+
Top + Front, y from Front + Right, z from Top + Right.
|
|
2186
|
+
|
|
2187
|
+
Args:
|
|
2188
|
+
input_data (CreateAxisInput): The principal direction ('x', 'y'
|
|
2189
|
+
or 'z').
|
|
2190
|
+
|
|
2191
|
+
Returns:
|
|
2192
|
+
dict[str, Any]: Status and the axis details (planes used).
|
|
2193
|
+
|
|
2194
|
+
Example:
|
|
2195
|
+
```python
|
|
2196
|
+
result = await create_axis({"reference": "z"})
|
|
2197
|
+
```
|
|
2198
|
+
"""
|
|
2199
|
+
input_data = normalize_input(input_data, CreateAxisInput)
|
|
2200
|
+
result = await adapter.create_axis(input_data.reference)
|
|
2201
|
+
require_success(result, context="Failed to create axis")
|
|
2202
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
2203
|
+
axis_name = data.get("reference", input_data.reference)
|
|
2204
|
+
return {
|
|
2205
|
+
"status": "success",
|
|
2206
|
+
"message": f"Created {axis_name} axis",
|
|
2207
|
+
"axis": data,
|
|
2208
|
+
"execution_time": result.execution_time,
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
@mcp.tool()
|
|
2212
|
+
async def create_reference_point(
|
|
2213
|
+
input_data: CreateReferencePointInput,
|
|
2214
|
+
) -> dict[str, Any]:
|
|
2215
|
+
"""Create a reference point on the active part.
|
|
2216
|
+
|
|
2217
|
+
Two modes:
|
|
2218
|
+
|
|
2219
|
+
- ``along_curve`` — a point on the edge under ``(x, y, z)`` mm, placed
|
|
2220
|
+
``distance`` mm from the edge start, or at ``percent`` (0–100) of
|
|
2221
|
+
its length. Give exactly one of ``distance`` / ``percent``.
|
|
2222
|
+
- ``face_center`` — a point at the centroid of the face under
|
|
2223
|
+
``(x, y, z)`` mm.
|
|
2224
|
+
|
|
2225
|
+
The coordinate must lie on the target edge/face; SolidWorks resolves
|
|
2226
|
+
it with a coordinate pick. ``InsertReferencePoint`` always returns a
|
|
2227
|
+
feature, so success is confirmed by the feature tree growing.
|
|
2228
|
+
|
|
2229
|
+
Args:
|
|
2230
|
+
input_data (CreateReferencePointInput): Mode, coordinate, and the
|
|
2231
|
+
along-curve parameter.
|
|
2232
|
+
|
|
2233
|
+
Returns:
|
|
2234
|
+
dict[str, Any]: Status and the point details (feature counts).
|
|
2235
|
+
|
|
2236
|
+
Example:
|
|
2237
|
+
```python
|
|
2238
|
+
await create_reference_point({
|
|
2239
|
+
"mode": "along_curve", "x": 25, "y": 0, "z": 10, "percent": 50
|
|
2240
|
+
})
|
|
2241
|
+
```
|
|
2242
|
+
"""
|
|
2243
|
+
input_data = normalize_input(input_data, CreateReferencePointInput)
|
|
2244
|
+
result = await adapter.create_reference_point(
|
|
2245
|
+
input_data.mode,
|
|
2246
|
+
input_data.x,
|
|
2247
|
+
input_data.y,
|
|
2248
|
+
input_data.z,
|
|
2249
|
+
input_data.distance,
|
|
2250
|
+
input_data.percent,
|
|
2251
|
+
)
|
|
2252
|
+
require_success(result, context="Failed to create reference point")
|
|
2253
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
2254
|
+
where = (
|
|
2255
|
+
f"{data.get('percent')}% along a curve"
|
|
2256
|
+
if data.get("percent") is not None
|
|
2257
|
+
else (
|
|
2258
|
+
f"{data.get('distance_mm')} mm along a curve"
|
|
2259
|
+
if data.get("distance_mm") is not None
|
|
2260
|
+
else "a face centre"
|
|
2261
|
+
)
|
|
2262
|
+
)
|
|
2263
|
+
return {
|
|
2264
|
+
"status": "success",
|
|
2265
|
+
"message": f"Created a reference point at {where}",
|
|
2266
|
+
"point": data,
|
|
2267
|
+
"execution_time": result.execution_time,
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
@mcp.tool()
|
|
2271
|
+
async def mirror_feature(input_data: MirrorFeatureInput) -> dict[str, Any]:
|
|
2272
|
+
"""Mirror solid bodies or features about a plane.
|
|
2273
|
+
|
|
2274
|
+
Selects the named sources and the mirror plane, then mirrors them.
|
|
2275
|
+
The mirror is verified by comparing model volume before and after:
|
|
2276
|
+
SolidWorks can report a feature even when it mirrored nothing, so a
|
|
2277
|
+
volume that did not grow is reported as an error rather than a
|
|
2278
|
+
false success.
|
|
2279
|
+
|
|
2280
|
+
Args:
|
|
2281
|
+
input_data (MirrorFeatureInput): Source names, mirror plane, and
|
|
2282
|
+
merge/mirror_bodies flags.
|
|
2283
|
+
|
|
2284
|
+
Returns:
|
|
2285
|
+
dict[str, Any]: Status and the mirror feature's details,
|
|
2286
|
+
including volume before/after and the resulting ratio.
|
|
2287
|
+
|
|
2288
|
+
Example:
|
|
2289
|
+
```python
|
|
2290
|
+
result = await mirror_feature(
|
|
2291
|
+
{"features": ["Loft1"], "mirror_plane": "Right Plane"}
|
|
2292
|
+
)
|
|
2293
|
+
```
|
|
2294
|
+
"""
|
|
2295
|
+
input_data = normalize_input(input_data, MirrorFeatureInput)
|
|
2296
|
+
result = await adapter.mirror_feature(
|
|
2297
|
+
input_data.features,
|
|
2298
|
+
input_data.mirror_plane,
|
|
2299
|
+
input_data.merge,
|
|
2300
|
+
input_data.mirror_bodies,
|
|
2301
|
+
)
|
|
2302
|
+
require_success(result, context="Failed to mirror feature")
|
|
2303
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
2304
|
+
return {
|
|
2305
|
+
"status": "success",
|
|
2306
|
+
"message": f"Mirrored {', '.join(input_data.features)} "
|
|
2307
|
+
f"about {input_data.mirror_plane}",
|
|
2308
|
+
"mirror": data,
|
|
2309
|
+
"execution_time": result.execution_time,
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
@mcp.tool()
|
|
2313
|
+
async def pattern_circular(input_data: PatternCircularInput) -> dict[str, Any]:
|
|
2314
|
+
"""Pattern features around an axis.
|
|
2315
|
+
|
|
2316
|
+
Selects the named axis and features, then creates a circular
|
|
2317
|
+
pattern. The pattern is verified by comparing model volume before
|
|
2318
|
+
and after: SolidWorks can report a feature even when it patterned
|
|
2319
|
+
nothing, so a volume that did not grow is reported as an error
|
|
2320
|
+
rather than a false success.
|
|
2321
|
+
|
|
2322
|
+
Args:
|
|
2323
|
+
input_data (PatternCircularInput): Feature names, axis, count,
|
|
2324
|
+
angle, and equal_spacing.
|
|
2325
|
+
|
|
2326
|
+
Returns:
|
|
2327
|
+
dict[str, Any]: Status and the pattern's details, including
|
|
2328
|
+
volume before/after and the resulting ratio.
|
|
2329
|
+
|
|
2330
|
+
Example:
|
|
2331
|
+
```python
|
|
2332
|
+
result = await pattern_circular(
|
|
2333
|
+
{"features": ["Boss-Extrude1"], "axis": "Axis1", "count": 4}
|
|
2334
|
+
)
|
|
2335
|
+
```
|
|
2336
|
+
"""
|
|
2337
|
+
input_data = normalize_input(input_data, PatternCircularInput)
|
|
2338
|
+
result = await adapter.pattern_circular(
|
|
2339
|
+
input_data.features,
|
|
2340
|
+
input_data.axis,
|
|
2341
|
+
input_data.count,
|
|
2342
|
+
input_data.angle,
|
|
2343
|
+
input_data.equal_spacing,
|
|
2344
|
+
)
|
|
2345
|
+
require_success(result, context="Failed to pattern feature")
|
|
2346
|
+
data = result.data if isinstance(result.data, dict) else {}
|
|
2347
|
+
return {
|
|
2348
|
+
"status": "success",
|
|
2349
|
+
"message": f"Patterned {', '.join(input_data.features)} "
|
|
2350
|
+
f"around {input_data.axis} ({input_data.count} instances)",
|
|
2351
|
+
"pattern": data,
|
|
2352
|
+
"execution_time": result.execution_time,
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
# Counted from the registry rather than hand-maintained. The literal that
|
|
2356
|
+
# used to live here had drifted: it read 15 while 16 tools were actually
|
|
2357
|
+
# registered, so the function misreported its own work and the test
|
|
2358
|
+
# enshrined the wrong number.
|
|
2359
|
+
tool_count = len(await mcp.list_tools())
|
|
2360
|
+
return tool_count
|