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,342 @@
|
|
|
1
|
+
"""Helpers for classifying SolidWorks model families from feature-tree snapshots."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
_SHEET_METAL_TOKENS = (
|
|
9
|
+
"sheet-metal",
|
|
10
|
+
"sheet metal",
|
|
11
|
+
"base-flange",
|
|
12
|
+
"edge-flange",
|
|
13
|
+
"sketched bend",
|
|
14
|
+
"miter flange",
|
|
15
|
+
"hem",
|
|
16
|
+
"jog",
|
|
17
|
+
"unfold",
|
|
18
|
+
"fold",
|
|
19
|
+
"flatten",
|
|
20
|
+
"flat-pattern",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
_ADVANCED_SOLID_TOKENS = (
|
|
24
|
+
"loft",
|
|
25
|
+
"boundary",
|
|
26
|
+
"sweep",
|
|
27
|
+
"shell",
|
|
28
|
+
"surface",
|
|
29
|
+
"split",
|
|
30
|
+
"combine",
|
|
31
|
+
"rib",
|
|
32
|
+
"dome",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
_REVOLVE_TOKENS = (
|
|
36
|
+
"revolve",
|
|
37
|
+
"bossrevolve",
|
|
38
|
+
"boss-revolve",
|
|
39
|
+
"cutrevolve",
|
|
40
|
+
"cut-revolve",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
_EXTRUDE_TOKENS = (
|
|
44
|
+
"extrude",
|
|
45
|
+
"boss-extrude",
|
|
46
|
+
"cut-extrude",
|
|
47
|
+
"bosscutextrude",
|
|
48
|
+
"featureextrusion",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
_ASSEMBLY_TOKENS = ("mate", "component", "subassembly")
|
|
52
|
+
_DRAWING_TOKENS = (
|
|
53
|
+
"sheet format",
|
|
54
|
+
"drawing view",
|
|
55
|
+
"section view",
|
|
56
|
+
"detail view",
|
|
57
|
+
"model view",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _as_lower_text(value: Any) -> str:
|
|
62
|
+
"""Build internal as lower text.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
value (Any): The value value.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
str: The resulting text value.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
return str(value or "").strip().lower()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _feature_text(feature: Mapping[str, Any]) -> str:
|
|
75
|
+
"""Build internal feature text.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
feature (Mapping[str, Any]): The feature value.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
str: The resulting text value.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
name = _as_lower_text(feature.get("name"))
|
|
85
|
+
feature_type = _as_lower_text(feature.get("type"))
|
|
86
|
+
return f"{name} {feature_type}".strip()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _has_any(texts: list[str], tokens: tuple[str, ...]) -> bool:
|
|
90
|
+
"""Build internal has any.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
texts (list[str]): The texts value.
|
|
94
|
+
tokens (tuple[str, ...]): The tokens value.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
bool: True if any, otherwise False.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
return any(token in text for token in tokens for text in texts)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _match_examples(
|
|
104
|
+
texts: list[str], tokens: tuple[str, ...], limit: int = 4
|
|
105
|
+
) -> list[str]:
|
|
106
|
+
"""Build internal match examples.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
texts (list[str]): The texts value.
|
|
110
|
+
tokens (tuple[str, ...]): The tokens value.
|
|
111
|
+
limit (int): The limit value. Defaults to 4.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
list[str]: A list containing the resulting items.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
matches: list[str] = []
|
|
118
|
+
for text in texts:
|
|
119
|
+
if any(token in text for token in tokens):
|
|
120
|
+
matches.append(text)
|
|
121
|
+
if len(matches) >= limit:
|
|
122
|
+
break
|
|
123
|
+
return matches
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def classify_feature_tree_snapshot(
|
|
127
|
+
model_info: Mapping[str, Any] | None,
|
|
128
|
+
features: list[Mapping[str, Any]] | None,
|
|
129
|
+
) -> dict[str, Any]:
|
|
130
|
+
"""Classify a model family from model-info and feature-tree snapshots.
|
|
131
|
+
|
|
132
|
+
The output is intentionally simple and explainable so agents can use it as a planning
|
|
133
|
+
primitive rather than as a black-box prediction.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
model_info (Mapping[str, Any] | None): The model info value.
|
|
137
|
+
features (list[Mapping[str, Any]] | None): The features value.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
dict[str, Any]: A dictionary containing the resulting values.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
feature_list = list(features or [])
|
|
144
|
+
feature_texts = [_feature_text(feature) for feature in feature_list]
|
|
145
|
+
document_type = _as_lower_text((model_info or {}).get("type")) or "unknown"
|
|
146
|
+
|
|
147
|
+
evidence: list[str] = []
|
|
148
|
+
warnings: list[str] = []
|
|
149
|
+
next_actions: list[str] = []
|
|
150
|
+
|
|
151
|
+
family = "unknown"
|
|
152
|
+
workflow = "inspect-more"
|
|
153
|
+
confidence = "low"
|
|
154
|
+
needs_vba = False
|
|
155
|
+
|
|
156
|
+
if document_type == "assembly" or _has_any(feature_texts, _ASSEMBLY_TOKENS):
|
|
157
|
+
family = "assembly"
|
|
158
|
+
workflow = "assembly-planning"
|
|
159
|
+
confidence = "high" if document_type == "assembly" else "medium"
|
|
160
|
+
evidence.append("Assembly document or mate/component evidence detected")
|
|
161
|
+
next_actions.extend(
|
|
162
|
+
[
|
|
163
|
+
"List components and mates before planning inserts or edits",
|
|
164
|
+
"Delegate part-level reconstruction for each component separately",
|
|
165
|
+
]
|
|
166
|
+
)
|
|
167
|
+
elif document_type == "drawing" or _has_any(feature_texts, _DRAWING_TOKENS):
|
|
168
|
+
family = "drawing"
|
|
169
|
+
workflow = "drawing-review"
|
|
170
|
+
confidence = "high" if document_type == "drawing" else "medium"
|
|
171
|
+
evidence.append("Drawing document or drawing-view evidence detected")
|
|
172
|
+
next_actions.append("Use drawing tools instead of part-modeling tools")
|
|
173
|
+
elif _has_any(feature_texts, _SHEET_METAL_TOKENS):
|
|
174
|
+
family = "sheet_metal"
|
|
175
|
+
workflow = "vba-sheet-metal"
|
|
176
|
+
confidence = "high"
|
|
177
|
+
needs_vba = True
|
|
178
|
+
evidence.extend(
|
|
179
|
+
_match_examples(feature_texts, _SHEET_METAL_TOKENS)
|
|
180
|
+
or ["Sheet metal features detected in the tree"]
|
|
181
|
+
)
|
|
182
|
+
next_actions.extend(
|
|
183
|
+
[
|
|
184
|
+
"Preserve base-flange and bend dependency order",
|
|
185
|
+
"If cuts appear between Unfold and Fold, keep them in flat-pattern state",
|
|
186
|
+
"Prefer a VBA-aware reconstruction plan until direct sheet metal tools exist",
|
|
187
|
+
]
|
|
188
|
+
)
|
|
189
|
+
elif _has_any(feature_texts, _ADVANCED_SOLID_TOKENS):
|
|
190
|
+
family = "advanced_solid"
|
|
191
|
+
workflow = "vba-advanced-solid"
|
|
192
|
+
confidence = "medium"
|
|
193
|
+
needs_vba = True
|
|
194
|
+
evidence.extend(
|
|
195
|
+
_match_examples(feature_texts, _ADVANCED_SOLID_TOKENS)
|
|
196
|
+
or ["Advanced solid or surface features detected"]
|
|
197
|
+
)
|
|
198
|
+
next_actions.extend(
|
|
199
|
+
[
|
|
200
|
+
"Plan around loft/sweep/shell boundaries before issuing build commands",
|
|
201
|
+
"Prefer macro-backed execution for unsupported direct-MCP features",
|
|
202
|
+
]
|
|
203
|
+
)
|
|
204
|
+
elif _has_any(feature_texts, _REVOLVE_TOKENS):
|
|
205
|
+
family = "revolve"
|
|
206
|
+
workflow = "direct-mcp-revolve"
|
|
207
|
+
confidence = "high"
|
|
208
|
+
evidence.extend(
|
|
209
|
+
_match_examples(feature_texts, _REVOLVE_TOKENS)
|
|
210
|
+
or ["Revolve features detected"]
|
|
211
|
+
)
|
|
212
|
+
next_actions.extend(
|
|
213
|
+
[
|
|
214
|
+
"Locate the axis sketch or centerline before recreating the profile",
|
|
215
|
+
"Verify the half-profile closes on the revolve axis",
|
|
216
|
+
]
|
|
217
|
+
)
|
|
218
|
+
elif _has_any(feature_texts, _EXTRUDE_TOKENS):
|
|
219
|
+
family = "extrude"
|
|
220
|
+
workflow = "direct-mcp-extrude"
|
|
221
|
+
confidence = "high"
|
|
222
|
+
evidence.extend(
|
|
223
|
+
_match_examples(feature_texts, _EXTRUDE_TOKENS)
|
|
224
|
+
or ["Extrude features detected"]
|
|
225
|
+
)
|
|
226
|
+
next_actions.extend(
|
|
227
|
+
[
|
|
228
|
+
"Read the driving sketch before recreating downstream cuts or fillets",
|
|
229
|
+
"Verify closed-loop profiles before extrusion",
|
|
230
|
+
]
|
|
231
|
+
)
|
|
232
|
+
else:
|
|
233
|
+
non_reference_count = 0
|
|
234
|
+
sketch_like_count = 0
|
|
235
|
+
for feature in feature_list:
|
|
236
|
+
feature_type = _as_lower_text(feature.get("type"))
|
|
237
|
+
if feature_type and feature_type not in {
|
|
238
|
+
"refplane",
|
|
239
|
+
"originprofilefeature",
|
|
240
|
+
}:
|
|
241
|
+
non_reference_count += 1
|
|
242
|
+
if feature_type in {"profilefeature", "3dprofilefeature", "sketch"}:
|
|
243
|
+
sketch_like_count += 1
|
|
244
|
+
|
|
245
|
+
if sketch_like_count > 0 and sketch_like_count == non_reference_count:
|
|
246
|
+
family = "sketch_only"
|
|
247
|
+
workflow = "inspect-more"
|
|
248
|
+
confidence = "low"
|
|
249
|
+
evidence.append("Only sketch-like features were found in the tree snapshot")
|
|
250
|
+
warnings.append(
|
|
251
|
+
"Feature tree may be incomplete for this adapter/runtime; do not infer the 3D family from sketches alone"
|
|
252
|
+
)
|
|
253
|
+
next_actions.extend(
|
|
254
|
+
[
|
|
255
|
+
"Combine feature-tree output with mass properties and exported images",
|
|
256
|
+
"Prefer reading the original file in SolidWorks before planning a rebuild",
|
|
257
|
+
]
|
|
258
|
+
)
|
|
259
|
+
else:
|
|
260
|
+
warnings.append(
|
|
261
|
+
"No strong feature-family evidence found; keep planning provisional"
|
|
262
|
+
)
|
|
263
|
+
next_actions.extend(
|
|
264
|
+
[
|
|
265
|
+
"Inspect more of the model state before building",
|
|
266
|
+
"Avoid committing to direct-MCP or VBA-only paths until evidence improves",
|
|
267
|
+
]
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
"document_type": document_type or "unknown",
|
|
272
|
+
"family": family,
|
|
273
|
+
"recommended_workflow": workflow,
|
|
274
|
+
"confidence": confidence,
|
|
275
|
+
"needs_vba": needs_vba,
|
|
276
|
+
"evidence": evidence,
|
|
277
|
+
"warnings": warnings,
|
|
278
|
+
"next_actions": next_actions,
|
|
279
|
+
"feature_count": len(feature_list),
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def build_component_tree(features: list[Mapping[str, Any]] | None) -> dict[str, Any]:
|
|
284
|
+
"""Reconstruct a nested component/sub-component tree from a flat
|
|
285
|
+
``list_features`` result.
|
|
286
|
+
|
|
287
|
+
``list_features`` returns a single flat list — see
|
|
288
|
+
``openspec/changes/archive/2026-10-30-assembly-aware-list-features/design.md`` for why:
|
|
289
|
+
several existing callers (feature-tree diffing, feature-family
|
|
290
|
+
classification, the MCP tool's ``count`` field) assume
|
|
291
|
+
``AdapterResult.data`` is a flat ``list[dict]`` unconditionally, so the
|
|
292
|
+
adapter contract stays flat. Each descriptor carries ``component``
|
|
293
|
+
(owning component, or ``None`` for the document's own features) and
|
|
294
|
+
``component_parent`` (that component's immediate parent component, or
|
|
295
|
+
``None`` for a top-level component), which is enough to rebuild the
|
|
296
|
+
tree a caller actually wants to *look at* without changing what the
|
|
297
|
+
adapter returns.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
features: The flat list returned in an ``AdapterResult.data`` from
|
|
301
|
+
``list_features``. Rows without a ``component_parent`` key
|
|
302
|
+
(older data, or non-component rows) are treated as top-level.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
dict[str, Any]: ``{"features": [...], "components": {name: {"path":
|
|
306
|
+
..., "features": [...], "components": {...nested...}}}}``. For a
|
|
307
|
+
Part document (no rows carry a ``component``), this is just
|
|
308
|
+
``{"features": [...all rows...], "components": {}}``.
|
|
309
|
+
"""
|
|
310
|
+
rows = list(features or [])
|
|
311
|
+
|
|
312
|
+
own_features = [dict(r) for r in rows if r.get("component") is None]
|
|
313
|
+
root: dict[str, Any] = {"features": own_features, "components": {}}
|
|
314
|
+
|
|
315
|
+
nodes: dict[str, dict[str, Any]] = {}
|
|
316
|
+
parent_of: dict[str, str | None] = {}
|
|
317
|
+
|
|
318
|
+
for row in rows:
|
|
319
|
+
component = row.get("component")
|
|
320
|
+
if component is None:
|
|
321
|
+
continue
|
|
322
|
+
node = nodes.setdefault(
|
|
323
|
+
component,
|
|
324
|
+
{"path": row.get("component_path"), "features": [], "components": {}},
|
|
325
|
+
)
|
|
326
|
+
if row.get("type") not in ("UnresolvedComponent", "Component"):
|
|
327
|
+
node["features"].append(dict(row))
|
|
328
|
+
if row.get("component_path") and not node.get("path"):
|
|
329
|
+
node["path"] = row["component_path"]
|
|
330
|
+
parent = row.get("component_parent")
|
|
331
|
+
if parent is not None or component not in parent_of:
|
|
332
|
+
parent_of[component] = parent
|
|
333
|
+
|
|
334
|
+
for name, node in nodes.items():
|
|
335
|
+
parent = parent_of.get(name)
|
|
336
|
+
parent_node = nodes.get(parent) if parent else None
|
|
337
|
+
target = (
|
|
338
|
+
parent_node["components"] if parent_node is not None else root["components"]
|
|
339
|
+
)
|
|
340
|
+
target[name] = node
|
|
341
|
+
|
|
342
|
+
return root
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Logging configuration for SolidWorks MCP Server."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from loguru import logger
|
|
8
|
+
|
|
9
|
+
from ..config import SolidWorksMCPConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def setup_logging(config: SolidWorksMCPConfig) -> None:
|
|
13
|
+
"""Configure application and audit logging sinks.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
config (SolidWorksMCPConfig): Configuration values for the operation.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
None: None.
|
|
20
|
+
"""
|
|
21
|
+
# Remove default handler
|
|
22
|
+
logger.remove()
|
|
23
|
+
|
|
24
|
+
# Console logging
|
|
25
|
+
logger.add(
|
|
26
|
+
sys.stderr,
|
|
27
|
+
level=config.log_level,
|
|
28
|
+
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
|
|
29
|
+
"<level>{level: <8}</level> | "
|
|
30
|
+
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
|
|
31
|
+
"<level>{message}</level>",
|
|
32
|
+
colorize=True,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# File logging
|
|
36
|
+
if config.log_file:
|
|
37
|
+
# Ensure log directory exists
|
|
38
|
+
config.log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
|
|
40
|
+
logger.add(
|
|
41
|
+
config.log_file,
|
|
42
|
+
level=config.log_level,
|
|
43
|
+
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} | {message}",
|
|
44
|
+
rotation="10 MB",
|
|
45
|
+
retention="1 week",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Audit logging for security
|
|
49
|
+
if config.enable_audit_logging:
|
|
50
|
+
audit_log = (
|
|
51
|
+
config.log_file.parent / "audit.log"
|
|
52
|
+
if config.log_file
|
|
53
|
+
else Path("audit.log")
|
|
54
|
+
)
|
|
55
|
+
audit_log.parent.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
|
|
57
|
+
logger.add(
|
|
58
|
+
audit_log,
|
|
59
|
+
level="INFO",
|
|
60
|
+
format="{time:YYYY-MM-DD HH:mm:ss} | AUDIT | {extra[audit_type]} | {message}",
|
|
61
|
+
filter=lambda record: "audit" in record["extra"],
|
|
62
|
+
rotation="1 day",
|
|
63
|
+
retention="30 days",
|
|
64
|
+
compression="gz",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
logger.info("Logging configured")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_audit_logger() -> Any:
|
|
71
|
+
"""Create a logger bound to audit metadata.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Any: The result produced by the operation.
|
|
75
|
+
"""
|
|
76
|
+
return logger.bind(audit=True)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Environment validation for SolidWorks MCP Server."""
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from ..config import SolidWorksMCPConfig
|
|
9
|
+
from ..exceptions import SolidWorksMCPError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def validate_environment(config: SolidWorksMCPConfig) -> None:
|
|
13
|
+
"""Validate runtime prerequisites for the server.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
config (SolidWorksMCPConfig): Configuration values for the operation.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
None: None.
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
SolidWorksMCPError: If the operation cannot be completed.
|
|
23
|
+
"""
|
|
24
|
+
logger.info("Validating environment...")
|
|
25
|
+
|
|
26
|
+
# Check platform
|
|
27
|
+
if not config.can_use_solidworks and not config.mock_solidworks:
|
|
28
|
+
if platform.system() != "Windows":
|
|
29
|
+
logger.warning("SolidWorks requires Windows platform. Using mock adapter.")
|
|
30
|
+
else:
|
|
31
|
+
logger.warning("SolidWorks not available. Using mock adapter.")
|
|
32
|
+
|
|
33
|
+
# Check SolidWorks installation if on Windows
|
|
34
|
+
if (
|
|
35
|
+
config.enable_windows_validation
|
|
36
|
+
and platform.system() == "Windows"
|
|
37
|
+
and not config.mock_solidworks
|
|
38
|
+
):
|
|
39
|
+
await _validate_solidworks_installation(config)
|
|
40
|
+
|
|
41
|
+
# Check Python version
|
|
42
|
+
import sys
|
|
43
|
+
|
|
44
|
+
python_version = sys.version_info
|
|
45
|
+
if python_version < (3, 13):
|
|
46
|
+
raise SolidWorksMCPError(
|
|
47
|
+
f"Python 3.13+ required, but running {python_version.major}.{python_version.minor}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
logger.info("Environment validation complete")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _validate_solidworks_installation(config: SolidWorksMCPConfig) -> None:
|
|
54
|
+
"""Validate SolidWorks availability on Windows hosts.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
config (SolidWorksMCPConfig): Configuration values for the operation.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
None: None.
|
|
61
|
+
"""
|
|
62
|
+
# Check if SolidWorks executable exists
|
|
63
|
+
if config.solidworks_path:
|
|
64
|
+
if not shutil.which(config.solidworks_path):
|
|
65
|
+
logger.warning(
|
|
66
|
+
f"SolidWorks executable not found at: {config.solidworks_path}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Check COM registration only. Resolving the ProgID reads the registry;
|
|
70
|
+
# creating the object (Dispatch) would start SolidWorks.
|
|
71
|
+
try:
|
|
72
|
+
import pywintypes
|
|
73
|
+
|
|
74
|
+
pywintypes.IID("SldWorks.Application")
|
|
75
|
+
logger.info("SolidWorks COM interface is available")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.warning(f"SolidWorks COM interface issue: {e}")
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Version information for SolidWorks MCP Server."""
|
|
2
|
+
|
|
3
|
+
__version__ = "1.5.0"
|
|
4
|
+
__author__ = "Andrew Bartels"
|
|
5
|
+
__email__ = "andrewbartels1@gmail.com"
|
|
6
|
+
__description__ = "SolidWorks MCP Server with FastMCP and PydanticAI"
|
|
7
|
+
__original_author__ = "Vinnie Esposito"
|
|
8
|
+
__license__ = "MIT"
|