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.
Files changed (115) hide show
  1. solidworks_mcp/__init__.py +57 -0
  2. solidworks_mcp/adapters/__init__.py +40 -0
  3. solidworks_mcp/adapters/base.py +1882 -0
  4. solidworks_mcp/adapters/circuit_breaker.py +2052 -0
  5. solidworks_mcp/adapters/com_executor.py +252 -0
  6. solidworks_mcp/adapters/complexity_analyzer.py +272 -0
  7. solidworks_mcp/adapters/connection_pool.py +1884 -0
  8. solidworks_mcp/adapters/factory.py +366 -0
  9. solidworks_mcp/adapters/intelligent_router.py +218 -0
  10. solidworks_mcp/adapters/mock_adapter.py +4016 -0
  11. solidworks_mcp/adapters/pywin32_adapter.py +2842 -0
  12. solidworks_mcp/adapters/solidworks/__init__.py +17 -0
  13. solidworks_mcp/adapters/solidworks/bitmap.py +180 -0
  14. solidworks_mcp/adapters/solidworks/com_utils.py +56 -0
  15. solidworks_mcp/adapters/solidworks/dimension_layout.py +753 -0
  16. solidworks_mcp/adapters/solidworks/documentation.py +1476 -0
  17. solidworks_mcp/adapters/solidworks/drawing_standard.py +1993 -0
  18. solidworks_mcp/adapters/solidworks/face_selection.py +392 -0
  19. solidworks_mcp/adapters/solidworks/features.py +3362 -0
  20. solidworks_mcp/adapters/solidworks/io.py +3649 -0
  21. solidworks_mcp/adapters/solidworks/rebuild_diagnostics.py +269 -0
  22. solidworks_mcp/adapters/solidworks/rebuild_inspector.py +146 -0
  23. solidworks_mcp/adapters/solidworks/selection.py +308 -0
  24. solidworks_mcp/adapters/solidworks/sketch.py +2545 -0
  25. solidworks_mcp/adapters/sw_feature_errors.py +66 -0
  26. solidworks_mcp/adapters/sw_type_info.py +317 -0
  27. solidworks_mcp/adapters/vba_adapter.py +318 -0
  28. solidworks_mcp/adapters/vba_macro_executor.py +215 -0
  29. solidworks_mcp/agents/__init__.py +68 -0
  30. solidworks_mcp/agents/design_knowledge/assembly_patterns.md +127 -0
  31. solidworks_mcp/agents/design_knowledge/hinges.md +118 -0
  32. solidworks_mcp/agents/design_knowledge/snap_fits.md +93 -0
  33. solidworks_mcp/agents/design_knowledge/solidworks_basics.md +3 -0
  34. solidworks_mcp/agents/design_knowledge/tolerancing.md +128 -0
  35. solidworks_mcp/agents/harness.py +216 -0
  36. solidworks_mcp/agents/history_db.py +1508 -0
  37. solidworks_mcp/agents/retrieval_index.py +164 -0
  38. solidworks_mcp/agents/schemas.py +165 -0
  39. solidworks_mcp/agents/smoke_test.py +285 -0
  40. solidworks_mcp/agents/soc_exporter.py +650 -0
  41. solidworks_mcp/agents/soc_pickup.py +332 -0
  42. solidworks_mcp/agents/soc_rewind.py +221 -0
  43. solidworks_mcp/agents/vector_rag.py +665 -0
  44. solidworks_mcp/cache/__init__.py +5 -0
  45. solidworks_mcp/cache/response_cache.py +164 -0
  46. solidworks_mcp/config.py +729 -0
  47. solidworks_mcp/exceptions.py +37 -0
  48. solidworks_mcp/security/__init__.py +68 -0
  49. solidworks_mcp/security/auth.py +128 -0
  50. solidworks_mcp/security/cors.py +27 -0
  51. solidworks_mcp/security/rate_limiting.py +90 -0
  52. solidworks_mcp/security/runtime.py +155 -0
  53. solidworks_mcp/server.py +777 -0
  54. solidworks_mcp/server_cli_fixed.py +120 -0
  55. solidworks_mcp/setup/__init__.py +1 -0
  56. solidworks_mcp/setup/cli.py +399 -0
  57. solidworks_mcp/setup/clients.py +157 -0
  58. solidworks_mcp/setup/config_file.py +230 -0
  59. solidworks_mcp/setup/handshake.py +202 -0
  60. solidworks_mcp/setup/launcher.py +102 -0
  61. solidworks_mcp/setup/legacy.py +100 -0
  62. solidworks_mcp/setup/registration.py +193 -0
  63. solidworks_mcp/setup/solidworks.py +49 -0
  64. solidworks_mcp/tools/__init__.py +64 -0
  65. solidworks_mcp/tools/analysis.py +723 -0
  66. solidworks_mcp/tools/automation.py +578 -0
  67. solidworks_mcp/tools/docs_discovery.py +1476 -0
  68. solidworks_mcp/tools/drawing.py +1015 -0
  69. solidworks_mcp/tools/drawing_analysis.py +456 -0
  70. solidworks_mcp/tools/drawing_automation.py +627 -0
  71. solidworks_mcp/tools/engineering_data.py +466 -0
  72. solidworks_mcp/tools/error_classifier.py +232 -0
  73. solidworks_mcp/tools/export.py +833 -0
  74. solidworks_mcp/tools/file_facts.py +40 -0
  75. solidworks_mcp/tools/file_management.py +1473 -0
  76. solidworks_mcp/tools/input_compat.py +17 -0
  77. solidworks_mcp/tools/macro_recording.py +464 -0
  78. solidworks_mcp/tools/modeling.py +2360 -0
  79. solidworks_mcp/tools/sketching.py +1422 -0
  80. solidworks_mcp/tools/template_management.py +367 -0
  81. solidworks_mcp/tools/tool_boundary.py +280 -0
  82. solidworks_mcp/tools/vba_generation.py +1145 -0
  83. solidworks_mcp/ui/__init__.py +9 -0
  84. solidworks_mcp/ui/local_llm.py +725 -0
  85. solidworks_mcp/ui/routers/__init__.py +10 -0
  86. solidworks_mcp/ui/routers/checkpoint.py +24 -0
  87. solidworks_mcp/ui/routers/docs.py +47 -0
  88. solidworks_mcp/ui/routers/llm.py +83 -0
  89. solidworks_mcp/ui/routers/local_model.py +69 -0
  90. solidworks_mcp/ui/routers/model.py +75 -0
  91. solidworks_mcp/ui/routers/preview.py +38 -0
  92. solidworks_mcp/ui/routers/session.py +161 -0
  93. solidworks_mcp/ui/routers/viewer.py +154 -0
  94. solidworks_mcp/ui/schemas.py +502 -0
  95. solidworks_mcp/ui/server.py +298 -0
  96. solidworks_mcp/ui/service.py +481 -0
  97. solidworks_mcp/ui/services/__init__.py +136 -0
  98. solidworks_mcp/ui/services/_utils.py +958 -0
  99. solidworks_mcp/ui/services/checkpoint_service.py +1110 -0
  100. solidworks_mcp/ui/services/docs_service.py +282 -0
  101. solidworks_mcp/ui/services/llm_service.py +956 -0
  102. solidworks_mcp/ui/services/model_service.py +459 -0
  103. solidworks_mcp/ui/services/parameter_repair_service.py +314 -0
  104. solidworks_mcp/ui/services/preview_service.py +489 -0
  105. solidworks_mcp/ui/services/session_service.py +1389 -0
  106. solidworks_mcp/utils/__init__.py +9 -0
  107. solidworks_mcp/utils/feature_tree_classifier.py +342 -0
  108. solidworks_mcp/utils/logging.py +76 -0
  109. solidworks_mcp/utils/validation.py +77 -0
  110. solidworks_mcp/version.py +8 -0
  111. solidworks_mcp_python-1.5.0.dist-info/METADATA +410 -0
  112. solidworks_mcp_python-1.5.0.dist-info/RECORD +115 -0
  113. solidworks_mcp_python-1.5.0.dist-info/WHEEL +4 -0
  114. solidworks_mcp_python-1.5.0.dist-info/entry_points.txt +3 -0
  115. solidworks_mcp_python-1.5.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,308 @@
1
+ """Feature selection mixin for PyWin32 SolidWorks operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any, cast
6
+
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Callable
9
+
10
+ from .. import sw_type_info as _sw_type_info
11
+ from ..base import AdapterResult, AdapterResultStatus
12
+ from .com_utils import selection_count, strip_op_prefix
13
+ from .face_selection import FaceEdgeSelector, describe_edge, validate_face_query
14
+
15
+ # swSelectType_e values actually observed on this SW install for the entity
16
+ # kinds this method cares about (EDGE=1, FACE=2 confirmed live 2026-09-18 by
17
+ # selecting each kind and checking which methods resolve on the returned
18
+ # object -- IEdge.GetStartVertex for EDGE, IFace2.GetBox for FACE. VERTEX=3
19
+ # is the documented swSelectType_e value but has not been live-verified the
20
+ # same way; treat it as a good guess, not a fact, if it ever misbehaves.
21
+ _SELECTED_ENTITY_TYPE_NAMES = {1: "EDGE", 2: "FACE", 3: "VERTEX"}
22
+
23
+
24
+ class SolidWorksSelectionMixin:
25
+ """Expose feature-selection and feature-list methods through a mixin."""
26
+
27
+ if TYPE_CHECKING:
28
+ # Mixin protocol: declare attributes that must exist on the parent class
29
+ currentModel: Any
30
+ swApp: Any
31
+ _feature_selector: Any
32
+
33
+ def _handle_com_operation(
34
+ self, operation_name: str, operation: Callable[..., Any], *args: Any
35
+ ) -> AdapterResult[Any]: ...
36
+
37
+ def _attempt(
38
+ self, operation: Callable[[], Any], *, default: Any = None
39
+ ) -> Any: ...
40
+
41
+ async def list_features(
42
+ self, include_suppressed: bool = False, max_assembly_depth: int = 2
43
+ ) -> AdapterResult[list[dict[str, Any]]]:
44
+ sw_app = getattr(self, "swApp", None)
45
+ active_model = getattr(sw_app, "ActiveDoc", None) if sw_app else None
46
+ if active_model is not None:
47
+ self.currentModel = active_model
48
+ if not self.currentModel:
49
+ return AdapterResult(
50
+ status=AdapterResultStatus.ERROR,
51
+ error="No active model",
52
+ )
53
+ return self._handle_com_operation(
54
+ "list_features",
55
+ self._feature_selector.list_features,
56
+ include_suppressed,
57
+ max_assembly_depth,
58
+ )
59
+
60
+ @staticmethod
61
+ def _normalize_feature_name(raw_name: str | None) -> str:
62
+ return str(raw_name or "").strip().strip('"').casefold()
63
+
64
+ def _build_feature_candidate_names(
65
+ self, feature_name: str, target_doc: Any
66
+ ) -> list[str]:
67
+ return cast(
68
+ list[str],
69
+ self._feature_selector.build_feature_candidate_names(
70
+ feature_name, target_doc
71
+ ),
72
+ )
73
+
74
+ def _try_select_by_extension(
75
+ self,
76
+ target_doc: Any,
77
+ candidate_names: list[str],
78
+ feature_name: str,
79
+ ) -> dict[str, Any] | None:
80
+ return cast(
81
+ "dict[str, Any] | None",
82
+ self._feature_selector.try_select_by_extension(
83
+ target_doc, candidate_names, feature_name
84
+ ),
85
+ )
86
+
87
+ def _try_select_by_component(
88
+ self,
89
+ target_doc: Any,
90
+ candidate_names: list[str],
91
+ feature_name: str,
92
+ ) -> dict[str, Any] | None:
93
+ return cast(
94
+ "dict[str, Any] | None",
95
+ self._feature_selector.try_select_by_component(
96
+ target_doc, candidate_names, feature_name
97
+ ),
98
+ )
99
+
100
+ def _try_select_by_feature_tree(
101
+ self,
102
+ target_doc: Any,
103
+ feature_name: str,
104
+ candidate_names: list[str],
105
+ ) -> dict[str, Any] | None:
106
+ return cast(
107
+ "dict[str, Any] | None",
108
+ self._feature_selector.try_select_by_feature_tree(
109
+ target_doc, feature_name, candidate_names
110
+ ),
111
+ )
112
+
113
+ async def select_feature(
114
+ self, feature_name: str, append: bool = False
115
+ ) -> AdapterResult[dict[str, Any]]:
116
+ """Select a named feature, sketch, plane or component.
117
+
118
+ Args:
119
+ feature_name: Feature name, optionally ``@document`` qualified.
120
+ append: Add to the current selection instead of replacing it.
121
+
122
+ Returns:
123
+ AdapterResult[dict[str, Any]]: The selector payload plus
124
+ ``selection_count``. ERROR - never SUCCESS with
125
+ ``selected: False`` - when no strategy could select the name.
126
+ """
127
+ if not self.currentModel:
128
+ return AdapterResult(
129
+ status=AdapterResultStatus.ERROR, error="No active model"
130
+ )
131
+
132
+ def _select() -> dict[str, Any]:
133
+ payload = self._feature_selector.select_feature(feature_name, append)
134
+ if not payload or not payload.get("selected"):
135
+ raise LookupError(
136
+ f"Feature not found or could not be selected: {feature_name}"
137
+ )
138
+ payload["selection_count"] = selection_count(self, self.currentModel)
139
+ return payload
140
+
141
+ return self._run_clean("select_feature", _select)
142
+
143
+ async def select_face_edges(
144
+ self,
145
+ point: list[float] | None = None,
146
+ side: str | None = None,
147
+ plane: str | None = None,
148
+ edge_type: str = "all",
149
+ append: bool = False,
150
+ ) -> AdapterResult[dict[str, Any]]:
151
+ """Multi-select every edge bounding one face of the active part.
152
+
153
+ See ``SolidWorksAdapter.select_face_edges`` for the contract; the face
154
+ resolution and selection live in ``face_selection.FaceEdgeSelector``.
155
+ """
156
+ problem = validate_face_query(point, side, plane, edge_type)
157
+ if problem:
158
+ return AdapterResult(status=AdapterResultStatus.ERROR, error=problem)
159
+ if not self.currentModel:
160
+ return AdapterResult(
161
+ status=AdapterResultStatus.ERROR, error="No active model"
162
+ )
163
+ from .io import _doc_type
164
+
165
+ doc_type = _doc_type(self)
166
+ if doc_type is not None and doc_type != 1:
167
+ return AdapterResult(
168
+ status=AdapterResultStatus.ERROR,
169
+ error=(
170
+ "select_face_edges requires an active part document "
171
+ f"(active document type is {doc_type})."
172
+ ),
173
+ )
174
+
175
+ def _select_edges() -> dict[str, Any]:
176
+ selector = FaceEdgeSelector(self)
177
+ faces = selector.resolve(point, side, plane)
178
+ edges = selector.edges(faces, edge_type)
179
+ if not edges:
180
+ kind = "" if edge_type == "all" else f"{edge_type} "
181
+ raise LookupError(
182
+ f"The selected face has no {kind}edges "
183
+ f"(face centre {faces[0].describe()['center_mm']} mm)."
184
+ )
185
+ count = selector.select(edges, append)
186
+ described = [f.describe() for f in faces]
187
+ return {
188
+ "face": described[0],
189
+ "faces": described,
190
+ "edge_type": edge_type,
191
+ "edges": [info for _edge, info in edges],
192
+ "selected_edges": len(edges),
193
+ "selection_count": count,
194
+ }
195
+
196
+ return self._run_clean("select_face_edges", _select_edges)
197
+
198
+ def _run_clean(
199
+ self, operation_name: str, operation: Callable[[], Any]
200
+ ) -> AdapterResult[dict[str, Any]]:
201
+ """Run ``operation`` via ``_handle_com_operation`` with a clean error.
202
+
203
+ Strips the generic ``"Error in <op>: "`` prefix so expected failures
204
+ (not found, nothing to select) read as plain sentences for the LLM.
205
+ """
206
+ result = self._handle_com_operation(operation_name, operation)
207
+ if not result.is_success:
208
+ result.error = strip_op_prefix(result.error, operation_name)
209
+ return cast("AdapterResult[dict[str, Any]]", result)
210
+
211
+ async def get_selected_entities(self) -> AdapterResult[list[dict[str, Any]]]:
212
+ """Report the faces/edges/vertices currently highlighted in the live
213
+ SolidWorks UI, so an agent can see exactly what the user clicked on
214
+ instead of guessing from a screenshot (see com-api-pitfalls.md #21 for
215
+ why a screenshot alone is not reliable proof of document/view state).
216
+
217
+ Refreshes ``currentModel`` from ``swApp.ActiveDoc`` first, the same
218
+ way ``list_features`` does, since the user's selection lives on
219
+ whatever document is actually active in the UI right now -- which
220
+ may not be the document this adapter last created or touched.
221
+
222
+ Returns:
223
+ AdapterResult[list[dict[str, Any]]]: One entry per selected
224
+ entity, each with at least ``index`` and ``type``
225
+ (``"FACE"``/``"EDGE"``/``"VERTEX"``/``"UNKNOWN(<n>)"``). Faces
226
+ additionally carry ``bounding_box_mm``, ``center_mm``,
227
+ ``area_mm2`` and ``normal`` (when planar). Edges carry
228
+ ``start_mm``/``end_mm`` and a straight-line ``length_mm``
229
+ (the true length of a curved edge will be longer). Vertices
230
+ carry ``point_mm``. Any field that fails to resolve is simply
231
+ omitted rather than failing the whole call -- a partial
232
+ description is more useful than none.
233
+ """
234
+ sw_app = getattr(self, "swApp", None)
235
+ active_model = getattr(sw_app, "ActiveDoc", None) if sw_app else None
236
+ if active_model is not None:
237
+ self.currentModel = active_model
238
+ if not self.currentModel:
239
+ return AdapterResult(
240
+ status=AdapterResultStatus.ERROR, error="No active model"
241
+ )
242
+
243
+ def _describe() -> list[dict[str, Any]]:
244
+ model = self.currentModel
245
+ sel_mgr = model.SelectionManager
246
+ self._attempt(
247
+ lambda: _sw_type_info.flag_methods(sel_mgr, "ISelectionMgr"),
248
+ default=0,
249
+ )
250
+ count = (
251
+ self._attempt(lambda: sel_mgr.GetSelectedObjectCount2(-1), default=0)
252
+ or 0
253
+ )
254
+
255
+ results: list[dict[str, Any]] = []
256
+ for i in range(1, int(count) + 1):
257
+ raw_type = self._attempt(
258
+ lambda i=i: sel_mgr.GetSelectedObjectType3(i, -1), default=None
259
+ )
260
+ obj = self._attempt(
261
+ lambda i=i: sel_mgr.GetSelectedObject6(i, -1), default=None
262
+ )
263
+ if obj is None:
264
+ continue
265
+ self._attempt(
266
+ lambda o=obj: _sw_type_info.flag_methods(
267
+ o, "IFace2", "IEdge", "IVertex"
268
+ ),
269
+ default=0,
270
+ )
271
+
272
+ type_name = _SELECTED_ENTITY_TYPE_NAMES.get(
273
+ int(raw_type) if isinstance(raw_type, (int, float)) else -1,
274
+ f"UNKNOWN({raw_type})",
275
+ )
276
+ entry: dict[str, Any] = {"index": i, "type": type_name}
277
+
278
+ if type_name == "FACE":
279
+ box = self._attempt(lambda o=obj: o.GetBox(), default=None)
280
+ if box and len(box) == 6:
281
+ entry["bounding_box_mm"] = {
282
+ "min": [c * 1000 for c in box[0:3]],
283
+ "max": [c * 1000 for c in box[3:6]],
284
+ }
285
+ entry["center_mm"] = [
286
+ (box[j] + box[j + 3]) / 2 * 1000 for j in range(3)
287
+ ]
288
+ area = self._attempt(lambda o=obj: o.GetArea(), default=None)
289
+ if area is not None:
290
+ entry["area_mm2"] = area * 1e6
291
+ normal = self._attempt(lambda o=obj: o.Normal, default=None)
292
+ if normal:
293
+ entry["normal"] = [round(float(n), 6) for n in normal]
294
+
295
+ elif type_name == "EDGE":
296
+ # Shared with select_face_edges: start/end/length plus
297
+ # curve kind and, for circles, centre/axis/radius.
298
+ entry.update(describe_edge(self, obj))
299
+
300
+ elif type_name == "VERTEX":
301
+ point = self._attempt(lambda o=obj: o.GetPoint(), default=None)
302
+ if point:
303
+ entry["point_mm"] = [c * 1000 for c in point]
304
+
305
+ results.append(entry)
306
+ return results
307
+
308
+ return self._handle_com_operation("get_selected_entities", _describe)