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,66 @@
1
+ """SolidWorks ``swFeatureError_e`` codes (GENERATED - do not edit).
2
+
3
+ Generated by ``scripts/gen_sw_feature_errors.py`` from ``swconst.tlb``
4
+ (SOLIDWORKS 3DEXPERIENCE R2026x). Regenerate after a SolidWorks upgrade.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ SW_FEATURE_ERRORS: dict[int, str] = {
10
+ 0: "swFeatureErrorNone",
11
+ 1: "swFeatureErrorUnknown",
12
+ 10: "swFeatureErrorFilletNoLoop",
13
+ 11: "swFeatureErrorFilletNoFace",
14
+ 12: "swFeatureErrorFilletInvalidRadius",
15
+ 13: "swFeatureErrorFilletNoEdge",
16
+ 14: "swFeatureErrorFilletModelGeometry",
17
+ 15: "swFeatureErrorFilletRadiusTooSmall",
18
+ 16: "swFeatureErrorFilletCannotExtend",
19
+ 17: "swFeatureErrorFilletRadiusEliminateElement",
20
+ 18: "swFeatureErrorFilletRadiusTooBig",
21
+ 19: "swFeatureErrorFilletRadiusTooBig2",
22
+ 30: "swFeatureErrorExtrusionDisjoint",
23
+ 31: "swFeatureErrorExtrusionNoEndFound",
24
+ 32: "swFeatureErrorExtrusionBadGeometricConditions",
25
+ 33: "swFeatureErrorExtrusionCutContourOpenAndClosed",
26
+ 34: "swFeatureErrorExtrusionCutContourInvalid",
27
+ 35: "swFeatureErrorExtrusionOpenCutContourInvalid",
28
+ 36: "swFeatureErrorExtrusionBossContourOpenAndClosed",
29
+ 37: "swFeatureErrorExtrusionBossContourInvalid",
30
+ 38: "swFeatureErrorMateInvalidEdge",
31
+ 39: "swFeatureErrorMateInvalidFace",
32
+ 40: "swFeatureErrorMateFailedCreatingSurface",
33
+ 41: "swFeatureErrorMateInvalidEntity",
34
+ 42: "swFeatureErrorMateUnknownTangent",
35
+ 43: "swFeatureErrorMateDanglingGeometry",
36
+ 44: "swFeatureErrorMateEntityNotLinear",
37
+ 45: "swFeatureErrorMateEntityFailed",
38
+ 46: "swFeatureErrorMateOverdefined",
39
+ 47: "swFeatureErrorMateIlldefined",
40
+ 48: "swFeatureErrorMateBroken",
41
+ 49: "swFeatureErrorFeatureDeprecated",
42
+ 50: "swFeatureErrorFeatureObsolete",
43
+ 51: "swSketchErrorExtRefFail",
44
+ 52: "swFeatureErrorPartialEdgeFilletNoIntersection",
45
+ 53: "swFeatureErrorPartialEdgeFilletUpdateFailed",
46
+ 54: "swFeatureErrorPartialEdgeFilletNoPropagateEdges",
47
+ 55: "swFeatureErrorPartialEdgeFilletNoStartEdge",
48
+ 56: "swFeatureErrorPartialEdgeFilletNoEndEdge",
49
+ 57: "swFeatureErrorPartialEdgeFilletNoMainEdge",
50
+ 58: "swFeatureErrorPartialEdgeFilletOffsetTooBig",
51
+ 59: "swFeatureErrorPartialEdgeFilletNoReferenceEntity",
52
+ 60: "swFeatureErrorPartialEdgeFilletTooManyRefEntities",
53
+ 61: "swFeatureErrorPartialEdgeFilletInvalidOffsetValue",
54
+ 62: "swFeatureErrorPartialEdgeFilletCrossOverEndCondition",
55
+ 63: "swFeatureErrorPartialEdgeFilletMissingReferenceEntity",
56
+ 64: "swFeatureErrorPartialEdgeFilletInvalidReferenceEntity",
57
+ 65: "swFeatureErrorPartialEdgeFilletNotSupported",
58
+ 66: "swFeatureErrorPartialEdgeFilletNotSupportedForClosedLoop",
59
+ 67: "swFeatureErrorPartialEdgeFilletMultiProjectionPoint",
60
+ 68: "swFeatureErrorPartialEdgeFilletFailedToRepair",
61
+ 69: "swFeatureErrorSweptFlangeInvalidProfileOrPath",
62
+ 70: "swFeatureErrorSweptFlangeSelfIntersectingGeometry",
63
+ 71: "swFeatureErrorCutNotIntersectModel",
64
+ 72: "swFeatureErrorSketchContainsSelfIntersectingContour",
65
+ 73: "swFeatureErrorMissingItemsInFeature",
66
+ }
@@ -0,0 +1,317 @@
1
+ """
2
+ SolidWorks type-library introspection for robust COM method resolution.
3
+
4
+ Background: pywin32's late-binding (``CDispatch``) sometimes resolves SW
5
+ zero-argument methods (``GetType``, ``GetTitle``, ``GetPathName``,
6
+ ``RevisionNumber`` …) as *properties* instead of methods. Calling them raises
7
+ ``TypeError: 'int'/'str' object is not callable`` because the property getter
8
+ returns the value, and Python tries to call the value.
9
+
10
+ Fix: call ``CDispatch._FlagAsMethod(name)`` for each method name **that actually
11
+ belongs to the object's COM interface**. The calls tell pywin32 to resolve
12
+ ``name`` via method invocation (IDispatch ``Invoke``), not property access.
13
+
14
+ This module:
15
+
16
+ 1. Loads the makepy-generated wrapper for ``sldworks.tlb`` (``gen_py``).
17
+ 2. Builds per-interface sets of method names (``ISldWorks``, ``IModelDoc2``,
18
+ ``IAssemblyDoc``, ``IPartDoc``, ``IDrawingDoc`` …).
19
+ 3. Exposes ``flag_methods(obj, *interfaces)`` to flag a dispatch in one shot.
20
+
21
+ Why per-interface rather than flagging everything: flagging an unknown name
22
+ triggers a COM ``GetIDsOfNames`` round-trip that fails with ``Unknown name.``.
23
+ Those round-trips are ~5 ms each; the full SW TLB has ~6 000 method names
24
+ across 482 interfaces, so a naive flag-everything approach costs ~30 s per
25
+ object. Per-interface flagging is ~1-3 s.
26
+
27
+ Fallback: if the gen_py wrapper is missing (e.g. fresh install on a new box),
28
+ we attempt lazy generation via ``gencache.EnsureModule``. If that also fails,
29
+ ``flag_methods`` silently becomes a no-op; callers will fall back to the
30
+ original ``TypeError`` symptom on affected methods, but everything else still
31
+ works.
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import inspect
37
+ import weakref
38
+ from typing import Any
39
+
40
+ from loguru import logger
41
+
42
+ try:
43
+ import win32com.client # noqa: F401 — optional Windows dep
44
+ from win32com.client import DispatchBaseClass, gencache
45
+
46
+ PYWIN32_AVAILABLE = True
47
+ except ImportError:
48
+ PYWIN32_AVAILABLE = False
49
+
50
+
51
+ # SolidWorks type library IID (stable across SW versions).
52
+ SW_TLB_IID = "{83A33D31-27C5-11CE-BFD4-00400513BB57}"
53
+
54
+ # Module state: populated by _load_wrapper() the first time it's needed.
55
+ _wrapper_module: Any | None = None
56
+ _interface_methods: dict[str, frozenset[str]] = {}
57
+ # Per-object record of which interfaces have already been flagged. Keyed by
58
+ # id(obj) so ``flag_methods(doc, 'IModelDoc2')`` followed by
59
+ # ``flag_methods(doc, 'IAssemblyDoc')`` does incremental work, not a no-op.
60
+ # Value is (weak reference to the object, interfaces already flagged). The
61
+ # weakref is what makes the id() key safe to trust — see _flagged_interfaces.
62
+ _flag_cache: dict[int, tuple[weakref.ref[Any] | None, set[str]]] = {}
63
+
64
+
65
+ def _load_wrapper() -> None:
66
+ """Load the gen_py wrapper and extract per-interface method names.
67
+
68
+ Tries ``GetModuleForTypelib`` first (fast path, no COM work), falls back
69
+ to ``EnsureModule`` (may trigger makepy generation), then gives up and
70
+ logs a warning. Probes common SW major versions (33..30) because the
71
+ minor/major numbers change per SW year.
72
+ """
73
+ global _wrapper_module, _interface_methods
74
+
75
+ # Try version numbers from newest to oldest. SW 3DEXPERIENCE R2026x = 34,
76
+ # SW 2025 = 33, SW 2024 = 32, SW 2023 = 31, SW 2022 = 30.
77
+ for major in (35, 34, 33, 32, 31, 30):
78
+ try:
79
+ mod = gencache.GetModuleForTypelib(SW_TLB_IID, 0, major, 0)
80
+ except Exception:
81
+ mod = None
82
+ if mod is not None:
83
+ _wrapper_module = mod
84
+ break
85
+
86
+ if _wrapper_module is None:
87
+ # Gen_py wrapper not generated yet — try to generate now.
88
+ for major in (35, 34, 33, 32, 31, 30):
89
+ try:
90
+ gencache.EnsureModule(SW_TLB_IID, 0, major, 0)
91
+ _wrapper_module = gencache.GetModuleForTypelib(SW_TLB_IID, 0, major, 0)
92
+ if _wrapper_module is not None:
93
+ break
94
+ except Exception:
95
+ continue
96
+
97
+ if _wrapper_module is None:
98
+ logger.warning(
99
+ "SolidWorks gen_py wrapper not available; method flagging "
100
+ "disabled. Zero-arg SW methods may raise TypeError. To fix, "
101
+ "run: python -m win32com.client.makepy "
102
+ '"C:\\Program Files\\SOLIDWORKS Corp\\SOLIDWORKS\\sldworks.tlb"'
103
+ )
104
+ return
105
+
106
+ # Build per-interface method sets by inspecting each DispatchBaseClass
107
+ # subclass in the wrapper module.
108
+ for name in dir(_wrapper_module):
109
+ cls = getattr(_wrapper_module, name, None)
110
+ if not (inspect.isclass(cls) and issubclass(cls, DispatchBaseClass)):
111
+ continue
112
+ method_names: set[str] = set()
113
+ for attr_name, attr in vars(cls).items():
114
+ if attr_name.startswith("_"):
115
+ continue
116
+ if callable(attr):
117
+ method_names.add(attr_name)
118
+ if method_names:
119
+ _interface_methods[name] = frozenset(method_names)
120
+
121
+ logger.info(
122
+ f"SolidWorks type info loaded: {len(_interface_methods)} interfaces, "
123
+ f"wrapper={_wrapper_module.__name__}"
124
+ )
125
+
126
+
127
+ def _ensure_loaded() -> None:
128
+ """Lazy-load the wrapper on first use."""
129
+ if _wrapper_module is None and PYWIN32_AVAILABLE:
130
+ _load_wrapper()
131
+
132
+
133
+ def interface_method_names(interface: str) -> frozenset[str]:
134
+ """Return the set of method names declared by the given SW interface.
135
+
136
+ Args:
137
+ interface: Interface name as it appears in the type library
138
+ (e.g. ``"ISldWorks"``, ``"IModelDoc2"``).
139
+
140
+ Returns:
141
+ Immutable set of method names, or an empty set if the interface is
142
+ unknown or the wrapper isn't loaded.
143
+ """
144
+ _ensure_loaded()
145
+ return _interface_methods.get(interface, frozenset())
146
+
147
+
148
+ # Interface sets most tool operations need to flag. When acquiring a doc
149
+ # dispatch, always flag IModelDoc2 (base) plus the subclass that matches the
150
+ # doc type. For the app root, flag ISldWorks.
151
+ DOC_TYPE_TO_INTERFACES: dict[int, tuple[str, ...]] = {
152
+ # Values match swDocumentTypes_e: 1=Part, 2=Assembly, 3=Drawing
153
+ 1: ("IModelDoc2", "IPartDoc"),
154
+ 2: ("IModelDoc2", "IAssemblyDoc"),
155
+ 3: ("IModelDoc2", "IDrawingDoc"),
156
+ }
157
+
158
+
159
+ def _flagged_interfaces(obj: Any) -> set[str]:
160
+ """Return the set of interfaces already flagged on ``obj``.
161
+
162
+ The entry is keyed by ``id(obj)`` for speed, but is only trusted when the
163
+ stored weak reference still resolves to *this same object*. Without that
164
+ check the cache is unsound: ids are only unique among live objects, and
165
+ CPython reuses addresses eagerly. A fresh dispatch landing on a dead one's
166
+ address was judged "already flagged", so ``flag_methods`` returned without
167
+ flagging anything and its members then resolved as properties — surfacing
168
+ from SolidWorks as "Member not found".
169
+
170
+ Objects that do not support weak references (some test doubles) simply are
171
+ not cached; they are flagged every time, which is correct if slower.
172
+ """
173
+ key = id(obj)
174
+ entry = _flag_cache.get(key)
175
+ if entry is not None:
176
+ ref, names = entry
177
+ if ref is None or ref() is obj:
178
+ return names
179
+ # Stale: the object this entry described is gone and its address has
180
+ # been recycled. Drop it and start fresh for the new occupant.
181
+ del _flag_cache[key]
182
+
183
+ names = set()
184
+ try:
185
+ _flag_cache[key] = (weakref.ref(obj), names)
186
+ except TypeError:
187
+ # Not weak-referenceable, so we cannot detect a later address reuse.
188
+ # Skip caching rather than risk the stale-entry bug.
189
+ pass
190
+ return names
191
+
192
+
193
+ def flag_methods(obj: Any, *interfaces: str) -> int:
194
+ """Flag SW methods on ``obj`` so pywin32 dispatches them as methods,
195
+ not properties.
196
+
197
+ Safe to call repeatedly on the same object — results are cached by
198
+ ``id(obj)``. Unknown method names are silently skipped (those are
199
+ the ones that don't belong to the object's real interface).
200
+
201
+ Args:
202
+ obj: A pywin32 ``CDispatch`` wrapping a SolidWorks COM object.
203
+ *interfaces: One or more interface names whose methods to flag.
204
+ Common values: ``"ISldWorks"``, ``"IModelDoc2"``,
205
+ ``"IAssemblyDoc"``, ``"IPartDoc"``, ``"IDrawingDoc"``.
206
+
207
+ Returns:
208
+ Number of methods successfully flagged.
209
+ """
210
+ _ensure_loaded()
211
+
212
+ if not _interface_methods or obj is None:
213
+ return 0
214
+
215
+ already = _flagged_interfaces(obj)
216
+
217
+ # Only flag methods from interfaces we haven't already processed for
218
+ # this object. Repeats are a no-op; novel interfaces add incrementally.
219
+ new_interfaces = [i for i in interfaces if i not in already]
220
+ if not new_interfaces:
221
+ return 0
222
+
223
+ names: set[str] = set()
224
+ for iface in new_interfaces:
225
+ names.update(_interface_methods.get(iface, ()))
226
+
227
+ flagged = 0
228
+ for name in names:
229
+ try:
230
+ obj._FlagAsMethod(name)
231
+ flagged += 1
232
+ except Exception:
233
+ # Name not on this dispatch's real interface — skip silently.
234
+ pass
235
+
236
+ already.update(new_interfaces)
237
+ return flagged
238
+
239
+
240
+ def flag_members(obj: Any, *names: str) -> int:
241
+ """Flag a specific handful of method names on ``obj``.
242
+
243
+ ``flag_methods`` flags *every* method of an interface — around 100 names
244
+ for ``IFeature`` — and its cache is keyed by ``id(obj)``, so a loop over
245
+ many short-lived dispatches (walking a feature tree, iterating components)
246
+ pays that cost once per object and never hits the cache.
247
+
248
+ When the caller knows exactly which members it is about to read, flagging
249
+ just those is far cheaper and behaves identically for them.
250
+
251
+ Args:
252
+ obj: A pywin32 ``CDispatch`` wrapping a SolidWorks COM object.
253
+ *names: Method names to flag.
254
+
255
+ Returns:
256
+ int: Number of names flagged.
257
+ """
258
+ if obj is None:
259
+ return 0
260
+
261
+ flagged_count = 0
262
+ for name in names:
263
+ try:
264
+ obj._FlagAsMethod(name)
265
+ flagged_count += 1
266
+ except Exception:
267
+ # Not on this dispatch's real interface — skip silently.
268
+ pass
269
+ return flagged_count
270
+
271
+
272
+ def flagged(obj: Any, *interfaces: str) -> Any:
273
+ """Flag ``obj``'s methods then return ``obj`` — call-chain friendly.
274
+
275
+ Useful for inline flagging of short-lived dispatches::
276
+
277
+ config = sw_type_info.flagged(
278
+ model.GetActiveConfiguration(), "IConfiguration"
279
+ )
280
+ name = config.GetName()
281
+
282
+ If ``obj`` is ``None`` (e.g. SW returned Nothing), passes through
283
+ unchanged.
284
+ """
285
+ if obj is not None:
286
+ flag_methods(obj, *interfaces)
287
+ return obj
288
+
289
+
290
+ def flag_doc(obj: Any, doc_type: int) -> int:
291
+ """Flag methods for a SolidWorks document dispatch given its type.
292
+
293
+ Convenience wrapper around ``flag_methods`` that looks up the correct
294
+ interface list for the document type.
295
+
296
+ Args:
297
+ obj: ``CDispatch`` wrapping the document.
298
+ doc_type: Value returned by ``swDoc.GetType()`` — 1=Part, 2=Assembly,
299
+ 3=Drawing.
300
+
301
+ Returns:
302
+ Number of methods flagged.
303
+ """
304
+ interfaces = DOC_TYPE_TO_INTERFACES.get(doc_type, ("IModelDoc2",))
305
+ return flag_methods(obj, *interfaces)
306
+
307
+
308
+ def invalidate_flag_cache(obj: Any | None = None) -> None:
309
+ """Forget that ``obj`` has been flagged, or clear the cache entirely.
310
+
311
+ Needed when a dispatch is closed / re-acquired; the new object at the
312
+ same address would otherwise be treated as already-flagged.
313
+ """
314
+ if obj is None:
315
+ _flag_cache.clear()
316
+ else:
317
+ _flag_cache.pop(id(obj), None)
@@ -0,0 +1,318 @@
1
+ """VBA adapter path for complex operations with generated macro metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+ from .base import (
9
+ AdapterResult,
10
+ ExtrusionParameters,
11
+ LoftParameters,
12
+ RevolveParameters,
13
+ SweepParameters,
14
+ )
15
+ from .vba_macro_executor import MacroExecutionRequest, VbaMacroExecutor
16
+
17
+
18
+ class VbaGeneratorAdapter:
19
+ """Adapter that executes complex operations through VBA-oriented flow.
20
+
21
+ This adapter currently uses the wrapped COM adapter for final execution, but annotates
22
+ responses as VBA-routed and can be extended to execute generated macros directly in
23
+ future iterations.
24
+
25
+ Args:
26
+ backing_adapter (Any): The backing adapter value.
27
+ macro_executor (VbaMacroExecutor | None): The macro executor value. Defaults to
28
+ None.
29
+
30
+ Attributes:
31
+ _backing_adapter (Any): The backing adapter value.
32
+ _macro_executor (Any): The macro executor value.
33
+ config (Any): The config value.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ backing_adapter: Any,
39
+ macro_executor: VbaMacroExecutor | None = None,
40
+ ) -> None:
41
+ """Initialize the vba generator adapter.
42
+
43
+ Args:
44
+ backing_adapter (Any): The backing adapter value.
45
+ macro_executor (VbaMacroExecutor | None): The macro executor value. Defaults to
46
+ None.
47
+
48
+ Returns:
49
+ None: None.
50
+ """
51
+ self._backing_adapter = backing_adapter
52
+ self._macro_executor = macro_executor or VbaMacroExecutor()
53
+ self.config = getattr(backing_adapter, "config", None)
54
+
55
+ def __getattr__(self, item: str) -> Any:
56
+ """Delegate unknown members to backing adapter.
57
+
58
+ Args:
59
+ item (str): The item value.
60
+
61
+ Returns:
62
+ Any: The result produced by the operation.
63
+ """
64
+ return getattr(self._backing_adapter, item)
65
+
66
+ async def connect(self) -> None:
67
+ """Connect to SolidWorks using wrapped adapter.
68
+
69
+ Returns:
70
+ None: None.
71
+ """
72
+ await self._backing_adapter.connect()
73
+
74
+ async def disconnect(self) -> None:
75
+ """Disconnect wrapped adapter.
76
+
77
+ Returns:
78
+ None: None.
79
+ """
80
+ await self._backing_adapter.disconnect()
81
+
82
+ def is_connected(self) -> bool:
83
+ """Return wrapped adapter connection state.
84
+
85
+ Returns:
86
+ bool: True if connected, otherwise False.
87
+ """
88
+ return bool(self._backing_adapter.is_connected())
89
+
90
+ async def health_check(self) -> Any:
91
+ """Return wrapped adapter health with VBA route marker.
92
+
93
+ Returns:
94
+ Any: The result produced by the operation.
95
+ """
96
+ health = await self._backing_adapter.health_check()
97
+ if hasattr(health, "metrics"):
98
+ metrics = dict(health.metrics or {})
99
+ metrics["route"] = "vba"
100
+ health.metrics = metrics
101
+ return health
102
+
103
+ async def create_extrusion(
104
+ self,
105
+ params: ExtrusionParameters,
106
+ ) -> AdapterResult[Any]:
107
+ """Create the extrusion.
108
+
109
+ Args:
110
+ params (ExtrusionParameters): The params value.
111
+
112
+ Returns:
113
+ AdapterResult[Any]: The result produced by the operation.
114
+ """
115
+ return await self._run_with_vba_metadata(
116
+ operation="create_extrusion",
117
+ payload=params,
118
+ com_call=self._backing_adapter.create_extrusion,
119
+ vba_code=self._generate_extrusion_vba(params),
120
+ )
121
+
122
+ async def create_revolve(
123
+ self,
124
+ params: RevolveParameters,
125
+ ) -> AdapterResult[Any]:
126
+ """Create the revolve.
127
+
128
+ Args:
129
+ params (RevolveParameters): The params value.
130
+
131
+ Returns:
132
+ AdapterResult[Any]: The result produced by the operation.
133
+ """
134
+ return await self._run_with_vba_metadata(
135
+ operation="create_revolve",
136
+ payload=params,
137
+ com_call=self._backing_adapter.create_revolve,
138
+ vba_code=self._generate_revolve_vba(params),
139
+ )
140
+
141
+ async def create_sweep(
142
+ self,
143
+ params: SweepParameters,
144
+ ) -> AdapterResult[Any]:
145
+ """Create the sweep.
146
+
147
+ Args:
148
+ params (SweepParameters): The params value.
149
+
150
+ Returns:
151
+ AdapterResult[Any]: The result produced by the operation.
152
+ """
153
+ return await self._run_with_vba_metadata(
154
+ operation="create_sweep",
155
+ payload=params,
156
+ com_call=self._backing_adapter.create_sweep,
157
+ vba_code=self._generate_sweep_vba(params),
158
+ )
159
+
160
+ async def create_loft(
161
+ self,
162
+ params: LoftParameters,
163
+ ) -> AdapterResult[Any]:
164
+ """Create the loft.
165
+
166
+ Args:
167
+ params (LoftParameters): The params value.
168
+
169
+ Returns:
170
+ AdapterResult[Any]: The result produced by the operation.
171
+ """
172
+ return await self._run_with_vba_metadata(
173
+ operation="create_loft",
174
+ payload=params,
175
+ com_call=self._backing_adapter.create_loft,
176
+ vba_code=self._generate_loft_vba(params),
177
+ )
178
+
179
+ async def _run_with_vba_metadata(
180
+ self,
181
+ operation: str,
182
+ payload: Any,
183
+ com_call: Any,
184
+ vba_code: str,
185
+ ) -> AdapterResult[Any]:
186
+ """Build internal run with vba metadata.
187
+
188
+ Args:
189
+ operation (str): Callable object executed by the helper.
190
+ payload (Any): The payload value.
191
+ com_call (Any): The com call value.
192
+ vba_code (str): The vba code value.
193
+
194
+ Returns:
195
+ AdapterResult[Any]: The result produced by the operation.
196
+ """
197
+ result: AdapterResult[Any] = await com_call(payload)
198
+ metadata = dict(result.metadata or {})
199
+ metadata.update(
200
+ {
201
+ "route": "vba",
202
+ "operation": operation,
203
+ "vba_code": vba_code,
204
+ "generated_at": datetime.utcnow().isoformat(),
205
+ }
206
+ )
207
+ result.metadata = metadata
208
+ return result
209
+
210
+ def _generate_extrusion_vba(self, params: ExtrusionParameters) -> str:
211
+ """Generate simple VBA snippet for extrusion operation.
212
+
213
+ Args:
214
+ params (ExtrusionParameters): The params value.
215
+
216
+ Returns:
217
+ str: The resulting text value.
218
+ """
219
+ return (
220
+ "Sub CreateExtrusion()\n"
221
+ " ' Auto-generated VBA fallback snippet\n"
222
+ f" ' Depth: {params.depth}\n"
223
+ "End Sub"
224
+ )
225
+
226
+ def _generate_revolve_vba(self, params: RevolveParameters) -> str:
227
+ """Generate simple VBA snippet for revolve operation.
228
+
229
+ Args:
230
+ params (RevolveParameters): The params value.
231
+
232
+ Returns:
233
+ str: The resulting text value.
234
+ """
235
+ return (
236
+ "Sub CreateRevolve()\n"
237
+ " ' Auto-generated VBA fallback snippet\n"
238
+ f" ' Angle: {params.angle}\n"
239
+ "End Sub"
240
+ )
241
+
242
+ def _generate_sweep_vba(self, params: SweepParameters) -> str:
243
+ """Generate simple VBA snippet for sweep operation.
244
+
245
+ Args:
246
+ params (SweepParameters): The params value.
247
+
248
+ Returns:
249
+ str: The resulting text value.
250
+ """
251
+ return (
252
+ "Sub CreateSweep()\n"
253
+ " ' Auto-generated VBA fallback snippet\n"
254
+ f" ' Path: {params.path}\n"
255
+ "End Sub"
256
+ )
257
+
258
+ async def execute_macro(
259
+ self,
260
+ macro_code: str,
261
+ macro_name: str = "GeneratedMacro",
262
+ subroutine: str = "Main",
263
+ ) -> AdapterResult[Any]:
264
+ """Provide execute macro support for the vba generator adapter.
265
+
266
+ Args:
267
+ macro_code (str): The macro code value.
268
+ macro_name (str): The macro name value. Defaults to "GeneratedMacro".
269
+ subroutine (str): The subroutine value. Defaults to "Main".
270
+
271
+ Returns:
272
+ AdapterResult[Any]: The result produced by the operation.
273
+ """
274
+ request = MacroExecutionRequest(
275
+ macro_code=macro_code,
276
+ macro_name=macro_name,
277
+ subroutine=subroutine,
278
+ )
279
+ return await self._macro_executor.execute_macro(
280
+ request=request,
281
+ backing_adapter=self._backing_adapter,
282
+ )
283
+
284
+ def get_macro_execution_history(
285
+ self, macro_name: str | None = None
286
+ ) -> dict[
287
+ str,
288
+ Any,
289
+ ]:
290
+ """Retrieve VBA macro execution history.
291
+
292
+ Args:
293
+ macro_name (str | None): The macro name value. Defaults to None.
294
+
295
+ Returns:
296
+ dict[
297
+ str,
298
+ Any,
299
+ ]: A dictionary containing the resulting values.
300
+ """
301
+ history = self._macro_executor.get_execution_history(macro_name)
302
+ return {key: value.__dict__ for key, value in history.items()}
303
+
304
+ def _generate_loft_vba(self, params: LoftParameters) -> str:
305
+ """Generate simple VBA snippet for loft operation.
306
+
307
+ Args:
308
+ params (LoftParameters): The params value.
309
+
310
+ Returns:
311
+ str: The resulting text value.
312
+ """
313
+ return (
314
+ "Sub CreateLoft()\n"
315
+ " ' Auto-generated VBA fallback snippet\n"
316
+ f" ' Profiles: {len(params.profiles)}\n"
317
+ "End Sub"
318
+ )