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,252 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Single-threaded executor for SolidWorks COM calls.
|
|
3
|
+
|
|
4
|
+
Background: SolidWorks COM is STA (single-threaded apartment). An
|
|
5
|
+
``IDispatch`` proxy obtained on thread A cannot be invoked from thread B —
|
|
6
|
+
pywin32's late-binding surfaces this as
|
|
7
|
+
``AttributeError: SldWorks.Application.<method>``. FastMCP dispatches async
|
|
8
|
+
tool handlers on worker threads that are *not* the thread where
|
|
9
|
+
``connect()`` ran, so any cached ``swApp`` reference breaks.
|
|
10
|
+
|
|
11
|
+
This module provides a single dedicated STA worker thread (``ComExecutor``).
|
|
12
|
+
All COM work is submitted to it as callables and awaited via ``Future``.
|
|
13
|
+
Because exactly one thread ever touches COM:
|
|
14
|
+
|
|
15
|
+
1. ``CoInitialize()`` is called once at thread startup.
|
|
16
|
+
2. ``self.swApp`` and ``self.currentModel`` can be shared instance attributes
|
|
17
|
+
without marshalling — no thread-local trickery.
|
|
18
|
+
3. STA constraints are satisfied (SW is happy).
|
|
19
|
+
4. ``_FlagAsMethod`` results accumulate on the same object lifetime.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
|
|
23
|
+
executor = ComExecutor()
|
|
24
|
+
executor.start()
|
|
25
|
+
try:
|
|
26
|
+
result = executor.submit(lambda: sw.ActiveDoc.GetTitle())
|
|
27
|
+
finally:
|
|
28
|
+
executor.stop()
|
|
29
|
+
|
|
30
|
+
or with the synchronous helper:
|
|
31
|
+
|
|
32
|
+
with ComExecutor() as ex:
|
|
33
|
+
title = ex.run(lambda: sw.ActiveDoc.GetTitle())
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import queue
|
|
39
|
+
import threading
|
|
40
|
+
from collections.abc import Callable
|
|
41
|
+
from concurrent.futures import Future
|
|
42
|
+
from typing import Any, TypeVar
|
|
43
|
+
|
|
44
|
+
from loguru import logger
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
import pythoncom
|
|
48
|
+
|
|
49
|
+
PYWIN32_AVAILABLE = True
|
|
50
|
+
except ImportError:
|
|
51
|
+
PYWIN32_AVAILABLE = False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
T = TypeVar("T")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Sentinel object used to signal the worker thread to exit.
|
|
58
|
+
_SHUTDOWN = object()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ComExecutor:
|
|
62
|
+
"""Single-threaded STA executor for SolidWorks COM calls.
|
|
63
|
+
|
|
64
|
+
Thread-safe: ``submit()`` / ``run()`` may be called from any thread.
|
|
65
|
+
|
|
66
|
+
Lifecycle:
|
|
67
|
+
- Construct: creates the executor (no thread yet).
|
|
68
|
+
- ``start()``: launches the worker thread and waits for it to
|
|
69
|
+
CoInitialize.
|
|
70
|
+
- ``submit(fn)``: schedules ``fn`` on the worker; returns a Future.
|
|
71
|
+
- ``run(fn)``: convenience wrapper around submit+result.
|
|
72
|
+
- ``stop()``: signals the worker to exit, joins the thread, then
|
|
73
|
+
CoUninitializes.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, name: str = "SolidWorks-COM") -> None:
|
|
77
|
+
"""Create the executor (thread not yet running).
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
name: Thread name for debugging / logs.
|
|
81
|
+
"""
|
|
82
|
+
self._name = name
|
|
83
|
+
self._queue: queue.Queue[Any] = queue.Queue()
|
|
84
|
+
self._thread: threading.Thread | None = None
|
|
85
|
+
# Set by worker thread once CoInitialize succeeds, allowing submit()
|
|
86
|
+
# to block until the executor is truly ready.
|
|
87
|
+
self._ready = threading.Event()
|
|
88
|
+
self._stopped = threading.Event()
|
|
89
|
+
# Set by worker thread when CoInitialize fails so start() can join
|
|
90
|
+
# the thread synchronously and callers see is_alive() == False.
|
|
91
|
+
self._init_failed = threading.Event()
|
|
92
|
+
|
|
93
|
+
# ---- Public API ----
|
|
94
|
+
|
|
95
|
+
def start(self, timeout: float = 10.0) -> None:
|
|
96
|
+
"""Launch the worker thread and wait until it has CoInitialized.
|
|
97
|
+
|
|
98
|
+
Idempotent: calling ``start()`` on a running executor is a no-op.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
timeout: Seconds to wait for CoInitialize to complete. If it
|
|
102
|
+
doesn't fire in time, raises RuntimeError.
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
RuntimeError: Worker didn't become ready in time.
|
|
106
|
+
"""
|
|
107
|
+
if self._thread is not None and self._thread.is_alive():
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
if not PYWIN32_AVAILABLE:
|
|
111
|
+
raise RuntimeError("pywin32 is required for ComExecutor; not available")
|
|
112
|
+
|
|
113
|
+
self._ready.clear()
|
|
114
|
+
self._stopped.clear()
|
|
115
|
+
self._init_failed.clear()
|
|
116
|
+
self._thread = threading.Thread(
|
|
117
|
+
target=self._worker, name=self._name, daemon=True
|
|
118
|
+
)
|
|
119
|
+
self._thread.start()
|
|
120
|
+
|
|
121
|
+
if not self._ready.wait(timeout):
|
|
122
|
+
raise RuntimeError(
|
|
123
|
+
f"ComExecutor worker '{self._name}' did not "
|
|
124
|
+
f"initialize within {timeout}s"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# If CoInitialize failed the worker exits immediately after setting
|
|
128
|
+
# _ready. Join here so callers see is_alive() == False synchronously.
|
|
129
|
+
if self._init_failed.is_set():
|
|
130
|
+
self._thread.join(timeout)
|
|
131
|
+
|
|
132
|
+
def stop(self, timeout: float = 5.0) -> None:
|
|
133
|
+
"""Signal the worker to exit and wait for it to join.
|
|
134
|
+
|
|
135
|
+
After ``stop()`` returns, no further ``submit()`` calls will be
|
|
136
|
+
serviced. Idempotent.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
timeout: Seconds to wait for the worker to exit cleanly before
|
|
140
|
+
abandoning the join.
|
|
141
|
+
"""
|
|
142
|
+
if self._thread is None or not self._thread.is_alive():
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
self._queue.put(_SHUTDOWN)
|
|
146
|
+
self._thread.join(timeout)
|
|
147
|
+
if self._thread.is_alive():
|
|
148
|
+
logger.warning(
|
|
149
|
+
f"ComExecutor worker '{self._name}' did not exit "
|
|
150
|
+
f"within {timeout}s; abandoning"
|
|
151
|
+
)
|
|
152
|
+
self._thread = None
|
|
153
|
+
self._stopped.set()
|
|
154
|
+
|
|
155
|
+
def submit(self, fn: Callable[[], T]) -> Future[T]:
|
|
156
|
+
"""Schedule ``fn`` to run on the worker thread.
|
|
157
|
+
|
|
158
|
+
The callable receives no arguments — close over any state needed
|
|
159
|
+
via the enclosing scope. The return value (or exception) is
|
|
160
|
+
propagated through the returned ``Future``.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
fn: Zero-argument callable to run on the COM thread.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Future that will hold the result or exception.
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
RuntimeError: Executor isn't running.
|
|
170
|
+
"""
|
|
171
|
+
if self._thread is None or not self._thread.is_alive():
|
|
172
|
+
raise RuntimeError(
|
|
173
|
+
f"ComExecutor '{self._name}' is not running; call start() first"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
fut: Future[T] = Future()
|
|
177
|
+
self._queue.put((fn, fut))
|
|
178
|
+
return fut
|
|
179
|
+
|
|
180
|
+
def run(self, fn: Callable[[], T], timeout: float | None = None) -> T:
|
|
181
|
+
"""Run ``fn`` on the worker and block until the result is ready.
|
|
182
|
+
|
|
183
|
+
Convenience wrapper around ``submit()`` + ``Future.result()``.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
fn: Zero-argument callable to run on the COM thread.
|
|
187
|
+
timeout: Seconds to wait before raising TimeoutError.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Whatever ``fn`` returned.
|
|
191
|
+
|
|
192
|
+
Raises:
|
|
193
|
+
Exception: Any exception raised by ``fn``, re-raised in the caller.
|
|
194
|
+
TimeoutError: If ``timeout`` elapses before ``fn`` finishes.
|
|
195
|
+
"""
|
|
196
|
+
return self.submit(fn).result(timeout=timeout)
|
|
197
|
+
|
|
198
|
+
# ---- Context manager ----
|
|
199
|
+
|
|
200
|
+
def __enter__(self) -> ComExecutor:
|
|
201
|
+
self.start()
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
def __exit__(self, exc_type: object, exc: object, tb: object) -> None:
|
|
205
|
+
self.stop()
|
|
206
|
+
|
|
207
|
+
# ---- Worker ----
|
|
208
|
+
|
|
209
|
+
def _worker(self) -> None:
|
|
210
|
+
"""Run on the dedicated thread; initialize COM, process work, cleanup.
|
|
211
|
+
|
|
212
|
+
Loop exits when ``_SHUTDOWN`` appears in the queue. Each work item
|
|
213
|
+
is a ``(callable, Future)`` tuple; the callable runs, and the
|
|
214
|
+
Future is set with either the result or the raised exception.
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
pythoncom.CoInitialize()
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.error(
|
|
220
|
+
f"CoInitialize failed in ComExecutor worker '{self._name}': {e!r}"
|
|
221
|
+
)
|
|
222
|
+
# Signal init failure then unblock start() so it can join this
|
|
223
|
+
# thread synchronously before returning.
|
|
224
|
+
self._init_failed.set()
|
|
225
|
+
self._ready.set()
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
self._ready.set()
|
|
229
|
+
logger.info(f"ComExecutor '{self._name}' ready")
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
while True:
|
|
233
|
+
item = self._queue.get()
|
|
234
|
+
if item is _SHUTDOWN:
|
|
235
|
+
break
|
|
236
|
+
|
|
237
|
+
fn, fut = item
|
|
238
|
+
if not fut.set_running_or_notify_cancel():
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
result = fn()
|
|
243
|
+
except BaseException as e: # noqa: BLE001 — propagate everything
|
|
244
|
+
fut.set_exception(e)
|
|
245
|
+
else:
|
|
246
|
+
fut.set_result(result)
|
|
247
|
+
finally:
|
|
248
|
+
try:
|
|
249
|
+
pythoncom.CoUninitialize()
|
|
250
|
+
except Exception:
|
|
251
|
+
pass
|
|
252
|
+
logger.info(f"ComExecutor '{self._name}' stopped")
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Complexity analysis for routing operations between COM and VBA paths."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class OperationProfile:
|
|
13
|
+
"""Complexity profile metadata for an operation.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
base_complexity (float): The base complexity value.
|
|
17
|
+
name (str): The name value.
|
|
18
|
+
vba_preferred (bool): The vba preferred value.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
name: str
|
|
22
|
+
base_complexity: float
|
|
23
|
+
vba_preferred: bool = False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RoutingDecision(BaseModel):
|
|
27
|
+
"""Complexity-based routing decision.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
complexity_score (float): The complexity score value.
|
|
31
|
+
operation (str): The operation value.
|
|
32
|
+
parameter_count (int): The parameter count value.
|
|
33
|
+
prefer_vba (bool): The prefer vba value.
|
|
34
|
+
reason (str): The reason value.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
operation: str
|
|
38
|
+
parameter_count: int
|
|
39
|
+
complexity_score: float
|
|
40
|
+
prefer_vba: bool
|
|
41
|
+
reason: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ComplexityAnalyzer:
|
|
45
|
+
"""Analyze operation complexity and recommend COM or VBA execution path.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
parameter_threshold (int): The parameter threshold value. Defaults to 12.
|
|
49
|
+
score_threshold (float): The score threshold value. Defaults to 0.6.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
_parameter_threshold (Any): The parameter threshold value.
|
|
53
|
+
_score_threshold (Any): The score threshold value.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
parameter_threshold: int = 12,
|
|
59
|
+
score_threshold: float = 0.6,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Initialize analyzer state.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
parameter_threshold (int): The parameter threshold value. Defaults to 12.
|
|
65
|
+
score_threshold (float): The score threshold value. Defaults to 0.6.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
None: None.
|
|
69
|
+
"""
|
|
70
|
+
self._parameter_threshold = max(parameter_threshold, 1)
|
|
71
|
+
self._score_threshold = max(min(score_threshold, 1.0), 0.1)
|
|
72
|
+
self._profiles: dict[str, OperationProfile] = self._default_profiles()
|
|
73
|
+
self._history: dict[str, dict[str, int]] = {}
|
|
74
|
+
|
|
75
|
+
def analyze(self, operation: str, payload: object) -> RoutingDecision:
|
|
76
|
+
"""Produce a routing recommendation for an operation call.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
operation (str): Callable object executed by the helper.
|
|
80
|
+
payload (object): The payload value.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
RoutingDecision: The result produced by the operation.
|
|
84
|
+
"""
|
|
85
|
+
profile = self._profiles.get(
|
|
86
|
+
operation,
|
|
87
|
+
OperationProfile(name=operation, base_complexity=0.2, vba_preferred=False),
|
|
88
|
+
)
|
|
89
|
+
parameter_count = self._count_parameters(payload)
|
|
90
|
+
|
|
91
|
+
parameter_component = min(
|
|
92
|
+
parameter_count / float(self._parameter_threshold), 1.0
|
|
93
|
+
)
|
|
94
|
+
history_component = self._history_bias(operation)
|
|
95
|
+
complexity_score = min(
|
|
96
|
+
1.0,
|
|
97
|
+
(parameter_component * 0.45)
|
|
98
|
+
+ (profile.base_complexity * 0.40)
|
|
99
|
+
+ (history_component * 0.15),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
prefer_vba = (
|
|
103
|
+
parameter_count > self._parameter_threshold
|
|
104
|
+
or profile.vba_preferred
|
|
105
|
+
or complexity_score >= self._score_threshold
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
reason = (
|
|
109
|
+
"parameter threshold exceeded"
|
|
110
|
+
if parameter_count > self._parameter_threshold
|
|
111
|
+
else "profile prefers VBA"
|
|
112
|
+
if profile.vba_preferred
|
|
113
|
+
else "complexity score exceeded threshold"
|
|
114
|
+
if complexity_score >= self._score_threshold
|
|
115
|
+
else "COM path preferred"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return RoutingDecision(
|
|
119
|
+
operation=operation,
|
|
120
|
+
parameter_count=parameter_count,
|
|
121
|
+
complexity_score=complexity_score,
|
|
122
|
+
prefer_vba=prefer_vba,
|
|
123
|
+
reason=reason,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def record_result(self, operation: str, route: str, success: bool) -> None:
|
|
127
|
+
"""Record operation outcome for future routing influence.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
operation (str): Callable object executed by the helper.
|
|
131
|
+
route (str): The route value.
|
|
132
|
+
success (bool): The success value.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
None: None.
|
|
136
|
+
"""
|
|
137
|
+
history = self._history.setdefault(
|
|
138
|
+
operation,
|
|
139
|
+
{
|
|
140
|
+
"com_success": 0,
|
|
141
|
+
"com_failure": 0,
|
|
142
|
+
"vba_success": 0,
|
|
143
|
+
"vba_failure": 0,
|
|
144
|
+
},
|
|
145
|
+
)
|
|
146
|
+
key = f"{route}_{'success' if success else 'failure'}"
|
|
147
|
+
if key in history:
|
|
148
|
+
history[key] += 1
|
|
149
|
+
|
|
150
|
+
def _history_bias(self, operation: str) -> float:
|
|
151
|
+
"""Compute historical bias toward VBA based on COM reliability.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
operation (str): Callable object executed by the helper.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
float: The computed numeric result.
|
|
158
|
+
"""
|
|
159
|
+
history = self._history.get(operation)
|
|
160
|
+
if history is None:
|
|
161
|
+
return 0.0
|
|
162
|
+
|
|
163
|
+
com_success = history.get("com_success", 0)
|
|
164
|
+
com_failure = history.get("com_failure", 0)
|
|
165
|
+
com_total = com_success + com_failure
|
|
166
|
+
if com_total == 0:
|
|
167
|
+
return 0.0
|
|
168
|
+
|
|
169
|
+
failure_ratio = com_failure / float(com_total)
|
|
170
|
+
return max(min(failure_ratio, 1.0), 0.0)
|
|
171
|
+
|
|
172
|
+
def _count_parameters(self, payload: object) -> int:
|
|
173
|
+
"""Count meaningful parameters in payload recursively.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
payload (object): The payload value.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
int: The computed numeric result.
|
|
180
|
+
"""
|
|
181
|
+
if payload is None:
|
|
182
|
+
return 0
|
|
183
|
+
|
|
184
|
+
if isinstance(payload, BaseModel):
|
|
185
|
+
return self._count_parameters(payload.model_dump(exclude_none=True))
|
|
186
|
+
|
|
187
|
+
if isinstance(payload, Mapping):
|
|
188
|
+
total = 0
|
|
189
|
+
for value in payload.values():
|
|
190
|
+
total += self._count_parameters(value)
|
|
191
|
+
return max(total, len(payload))
|
|
192
|
+
|
|
193
|
+
if isinstance(payload, (list, tuple, set)):
|
|
194
|
+
return sum(self._count_parameters(item) for item in payload)
|
|
195
|
+
|
|
196
|
+
return 1
|
|
197
|
+
|
|
198
|
+
def _default_profiles(self) -> dict[str, OperationProfile]:
|
|
199
|
+
"""Return default complexity profiles for high-impact operations.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
dict[str, OperationProfile]: A dictionary containing the resulting values.
|
|
203
|
+
"""
|
|
204
|
+
return {
|
|
205
|
+
# Modeling operations - likely to benefit from VBA routing
|
|
206
|
+
"create_extrusion": OperationProfile(
|
|
207
|
+
name="create_extrusion", base_complexity=0.55, vba_preferred=False
|
|
208
|
+
),
|
|
209
|
+
"create_revolve": OperationProfile(
|
|
210
|
+
name="create_revolve", base_complexity=0.50, vba_preferred=False
|
|
211
|
+
),
|
|
212
|
+
"create_sweep": OperationProfile(
|
|
213
|
+
name="create_sweep", base_complexity=0.65, vba_preferred=True
|
|
214
|
+
),
|
|
215
|
+
"create_loft": OperationProfile(
|
|
216
|
+
name="create_loft", base_complexity=0.70, vba_preferred=True
|
|
217
|
+
),
|
|
218
|
+
"create_assembly": OperationProfile(
|
|
219
|
+
name="create_assembly", base_complexity=0.45, vba_preferred=False
|
|
220
|
+
),
|
|
221
|
+
"create_part": OperationProfile(
|
|
222
|
+
name="create_part", base_complexity=0.30, vba_preferred=False
|
|
223
|
+
),
|
|
224
|
+
"create_drawing": OperationProfile(
|
|
225
|
+
name="create_drawing", base_complexity=0.50, vba_preferred=False
|
|
226
|
+
),
|
|
227
|
+
# Sketching operations - moderate complexity
|
|
228
|
+
"create_sketch": OperationProfile(
|
|
229
|
+
name="create_sketch", base_complexity=0.25, vba_preferred=False
|
|
230
|
+
),
|
|
231
|
+
"add_circle": OperationProfile(
|
|
232
|
+
name="add_circle", base_complexity=0.20, vba_preferred=False
|
|
233
|
+
),
|
|
234
|
+
"add_rectangle": OperationProfile(
|
|
235
|
+
name="add_rectangle", base_complexity=0.20, vba_preferred=False
|
|
236
|
+
),
|
|
237
|
+
"add_line": OperationProfile(
|
|
238
|
+
name="add_line", base_complexity=0.15, vba_preferred=False
|
|
239
|
+
),
|
|
240
|
+
"add_arc": OperationProfile(
|
|
241
|
+
name="add_arc", base_complexity=0.35, vba_preferred=False
|
|
242
|
+
),
|
|
243
|
+
"add_spline": OperationProfile(
|
|
244
|
+
name="add_spline", base_complexity=0.50, vba_preferred=True
|
|
245
|
+
),
|
|
246
|
+
"add_polygon": OperationProfile(
|
|
247
|
+
name="add_polygon", base_complexity=0.30, vba_preferred=False
|
|
248
|
+
),
|
|
249
|
+
"add_sketch_constraint": OperationProfile(
|
|
250
|
+
name="add_sketch_constraint", base_complexity=0.40, vba_preferred=False
|
|
251
|
+
),
|
|
252
|
+
"add_sketch_dimension": OperationProfile(
|
|
253
|
+
name="add_sketch_dimension", base_complexity=0.30, vba_preferred=False
|
|
254
|
+
),
|
|
255
|
+
# Assembly operations
|
|
256
|
+
"insert_component": OperationProfile(
|
|
257
|
+
name="insert_component", base_complexity=0.40, vba_preferred=False
|
|
258
|
+
),
|
|
259
|
+
"add_mate": OperationProfile(
|
|
260
|
+
name="add_mate", base_complexity=0.55, vba_preferred=False
|
|
261
|
+
),
|
|
262
|
+
# Drawing operations
|
|
263
|
+
"create_drawing_view": OperationProfile(
|
|
264
|
+
name="create_drawing_view", base_complexity=0.45, vba_preferred=False
|
|
265
|
+
),
|
|
266
|
+
"add_drawing_annotation": OperationProfile(
|
|
267
|
+
name="add_drawing_annotation", base_complexity=0.35, vba_preferred=False
|
|
268
|
+
),
|
|
269
|
+
"add_dimension": OperationProfile(
|
|
270
|
+
name="add_dimension", base_complexity=0.40, vba_preferred=False
|
|
271
|
+
),
|
|
272
|
+
}
|