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,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
|
+
)
|