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,2052 @@
|
|
|
1
|
+
"""Circuit breaker adapter for SolidWorks operations.
|
|
2
|
+
|
|
3
|
+
Implements the circuit breaker pattern to prevent cascading failures when SolidWorks
|
|
4
|
+
operations fail repeatedly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import time
|
|
10
|
+
from collections.abc import Awaitable, Callable
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, TypeVar, cast
|
|
13
|
+
|
|
14
|
+
from loguru import logger
|
|
15
|
+
|
|
16
|
+
from .base import (
|
|
17
|
+
AdapterHealth,
|
|
18
|
+
AdapterResult,
|
|
19
|
+
AdapterResultStatus,
|
|
20
|
+
ExtrusionParameters,
|
|
21
|
+
LoftParameters,
|
|
22
|
+
MassProperties,
|
|
23
|
+
RevolveParameters,
|
|
24
|
+
SolidWorksAdapter,
|
|
25
|
+
SolidWorksFeature,
|
|
26
|
+
SolidWorksModel,
|
|
27
|
+
SweepParameters,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
T = TypeVar("T")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _to_input_dict(params: Any) -> dict[str, Any]:
|
|
34
|
+
"""Convert a Pydantic model or plain dict to a flat dict for SoC logging."""
|
|
35
|
+
if hasattr(params, "model_dump"):
|
|
36
|
+
return cast(dict[str, Any], params.model_dump())
|
|
37
|
+
return cast(dict[str, Any], params) if isinstance(params, dict) else {}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CircuitState(Enum):
|
|
41
|
+
"""Circuit breaker states.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
CLOSED (Any): The closed value.
|
|
45
|
+
HALF_OPEN (Any): The half open value.
|
|
46
|
+
OPEN (Any): The open value.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
CLOSED = "closed" # Normal operation
|
|
50
|
+
OPEN = "open" # Circuit is open, blocking requests
|
|
51
|
+
HALF_OPEN = "half_open" # Testing if service is back
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _BreakerBucket:
|
|
55
|
+
"""Per-operation circuit breaker state.
|
|
56
|
+
|
|
57
|
+
Attributes:
|
|
58
|
+
state (CircuitState): The current state of this bucket.
|
|
59
|
+
failure_count (int): Consecutive failures recorded in ``CLOSED``/``OPEN``.
|
|
60
|
+
last_failure_time (float): ``time.time()`` of the most recent failure.
|
|
61
|
+
half_open_calls (int): Calls let through since entering ``HALF_OPEN``.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
__slots__ = ("state", "failure_count", "last_failure_time", "half_open_calls")
|
|
65
|
+
|
|
66
|
+
def __init__(self) -> None:
|
|
67
|
+
self.state = CircuitState.CLOSED
|
|
68
|
+
self.failure_count = 0
|
|
69
|
+
self.last_failure_time: float = 0.0
|
|
70
|
+
self.half_open_calls = 0
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CircuitBreakerAdapter(SolidWorksAdapter):
|
|
74
|
+
"""Circuit breaker wrapper for SolidWorks adapters.
|
|
75
|
+
|
|
76
|
+
Each named operation (``create_cut_extrude``, ``list_features``, ...) gets
|
|
77
|
+
its own independent breaker bucket, keyed by the ``operation_name`` passed
|
|
78
|
+
to ``_execute_with_circuit_breaker``. A run of failures on one tool (e.g.
|
|
79
|
+
every ``create_cut_extrude`` attempt failing while a wrong argument count
|
|
80
|
+
is being worked out) trips only that tool's breaker — unrelated read-only
|
|
81
|
+
tools like ``list_features`` or ``get_model_info`` keep working. Before
|
|
82
|
+
this, a single shared ``CircuitState`` meant one failing tool blocked
|
|
83
|
+
every other tool on the adapter, including reads, until the shared
|
|
84
|
+
recovery timeout elapsed.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
adapter (SolidWorksAdapter | None): Adapter instance used for the operation.
|
|
88
|
+
Defaults to None.
|
|
89
|
+
failure_threshold (int): The failure threshold value. Defaults to 5.
|
|
90
|
+
recovery_timeout (int): The recovery timeout value. Defaults to 60.
|
|
91
|
+
half_open_max_calls (int): The half open max calls value. Defaults to 3.
|
|
92
|
+
config (dict[str, object] | None): Configuration values for the operation. Defaults
|
|
93
|
+
to None.
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
adapter (Any): The adapter value.
|
|
97
|
+
failure_threshold (Any): The failure threshold value.
|
|
98
|
+
half_open_max_calls (Any): The half open max calls value.
|
|
99
|
+
recovery_timeout (Any): The recovery timeout value.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
adapter: SolidWorksAdapter | None = None,
|
|
105
|
+
failure_threshold: int = 5,
|
|
106
|
+
recovery_timeout: int = 60,
|
|
107
|
+
half_open_max_calls: int = 3,
|
|
108
|
+
config: dict[str, object] | None = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Initialize the circuit breaker adapter.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
adapter (SolidWorksAdapter | None): Adapter instance used for the operation.
|
|
114
|
+
Defaults to None.
|
|
115
|
+
failure_threshold (int): The failure threshold value. Defaults to 5.
|
|
116
|
+
recovery_timeout (int): The recovery timeout value. Defaults to 60.
|
|
117
|
+
half_open_max_calls (int): The half open max calls value. Defaults to 3.
|
|
118
|
+
config (dict[str, object] | None): Configuration values for the operation. Defaults
|
|
119
|
+
to None.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
None: None.
|
|
123
|
+
"""
|
|
124
|
+
if adapter is None:
|
|
125
|
+
from .mock_adapter import MockSolidWorksAdapter
|
|
126
|
+
|
|
127
|
+
adapter = MockSolidWorksAdapter(config or {})
|
|
128
|
+
super().__init__(config)
|
|
129
|
+
self.adapter = adapter
|
|
130
|
+
self.failure_threshold = failure_threshold
|
|
131
|
+
self.recovery_timeout = recovery_timeout
|
|
132
|
+
self.half_open_max_calls = half_open_max_calls
|
|
133
|
+
|
|
134
|
+
# Legacy/default bucket, used by connect() and the generic call() API.
|
|
135
|
+
self.state = CircuitState.CLOSED
|
|
136
|
+
self.failure_count = 0
|
|
137
|
+
self.last_failure_time: float = 0.0
|
|
138
|
+
self.half_open_calls = 0
|
|
139
|
+
|
|
140
|
+
# Per-operation buckets, keyed by operation_name, used by every named
|
|
141
|
+
# method that goes through _execute_with_circuit_breaker.
|
|
142
|
+
self._buckets: dict[str, _BreakerBucket] = {}
|
|
143
|
+
|
|
144
|
+
def _get_bucket(self, operation_name: str) -> _BreakerBucket:
|
|
145
|
+
"""Get (creating if needed) the breaker bucket for one operation name.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
operation_name (str): The operation name value.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
_BreakerBucket: That operation's independent breaker state.
|
|
152
|
+
"""
|
|
153
|
+
bucket = self._buckets.get(operation_name)
|
|
154
|
+
if bucket is None:
|
|
155
|
+
bucket = _BreakerBucket()
|
|
156
|
+
self._buckets[operation_name] = bucket
|
|
157
|
+
return bucket
|
|
158
|
+
|
|
159
|
+
async def _invoke_with_optional_args(
|
|
160
|
+
self,
|
|
161
|
+
method: Callable[..., Awaitable[T]],
|
|
162
|
+
*args: object,
|
|
163
|
+
) -> T:
|
|
164
|
+
"""Invoke adapter method with args, retrying without args on signature mismatch.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
method (Callable[..., Awaitable[T]]): The method value.
|
|
168
|
+
*args (object): Additional positional arguments forwarded to the call.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
T: The result produced by the operation.
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
return await method(*args)
|
|
175
|
+
except TypeError:
|
|
176
|
+
return await method()
|
|
177
|
+
|
|
178
|
+
def _should_allow_request(self, bucket: Any = None) -> bool:
|
|
179
|
+
"""Check if request should be allowed through circuit breaker.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
bucket (Any): The breaker bucket to check. Defaults to ``self``
|
|
183
|
+
(the legacy/default bucket used by ``connect()``/``call()``).
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
bool: True if should allow request, otherwise False.
|
|
187
|
+
"""
|
|
188
|
+
if bucket is None:
|
|
189
|
+
bucket = self
|
|
190
|
+
if bucket.state == CircuitState.CLOSED:
|
|
191
|
+
return True
|
|
192
|
+
elif bucket.state == CircuitState.OPEN:
|
|
193
|
+
# Check if enough time has passed to try again
|
|
194
|
+
if time.time() - bucket.last_failure_time >= self.recovery_timeout:
|
|
195
|
+
bucket.state = CircuitState.HALF_OPEN
|
|
196
|
+
bucket.half_open_calls = 0
|
|
197
|
+
return True
|
|
198
|
+
return False
|
|
199
|
+
elif bucket.state == CircuitState.HALF_OPEN:
|
|
200
|
+
return bucket.half_open_calls < self.half_open_max_calls
|
|
201
|
+
|
|
202
|
+
def _record_success(self, bucket: Any = None) -> None:
|
|
203
|
+
"""Record successful operation.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
bucket (Any): The breaker bucket to update. Defaults to ``self``.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
None: None.
|
|
210
|
+
"""
|
|
211
|
+
if bucket is None:
|
|
212
|
+
bucket = self
|
|
213
|
+
if bucket.state == CircuitState.HALF_OPEN:
|
|
214
|
+
# Reset circuit breaker on success in half-open state
|
|
215
|
+
bucket.state = CircuitState.CLOSED
|
|
216
|
+
bucket.failure_count = 0
|
|
217
|
+
bucket.half_open_calls = 0
|
|
218
|
+
elif bucket.state == CircuitState.CLOSED:
|
|
219
|
+
# Reset failure count on success
|
|
220
|
+
bucket.failure_count = 0
|
|
221
|
+
|
|
222
|
+
def _record_failure(self, bucket: Any = None) -> None:
|
|
223
|
+
"""Record failed operation.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
bucket (Any): The breaker bucket to update. Defaults to ``self``.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
None: None.
|
|
230
|
+
"""
|
|
231
|
+
if bucket is None:
|
|
232
|
+
bucket = self
|
|
233
|
+
bucket.failure_count += 1
|
|
234
|
+
bucket.last_failure_time = time.time()
|
|
235
|
+
|
|
236
|
+
if bucket.state == CircuitState.HALF_OPEN:
|
|
237
|
+
# Go back to open state
|
|
238
|
+
bucket.state = CircuitState.OPEN
|
|
239
|
+
elif (
|
|
240
|
+
bucket.state == CircuitState.CLOSED
|
|
241
|
+
and bucket.failure_count >= self.failure_threshold
|
|
242
|
+
):
|
|
243
|
+
# Open circuit breaker
|
|
244
|
+
bucket.state = CircuitState.OPEN
|
|
245
|
+
logger.warning(
|
|
246
|
+
f"Circuit breaker opened after {bucket.failure_count} failures"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
async def _execute_with_circuit_breaker(
|
|
250
|
+
self,
|
|
251
|
+
operation_name: str,
|
|
252
|
+
operation: Callable[[], Awaitable[AdapterResult[T]]],
|
|
253
|
+
input_dict: dict[str, Any] | None = None,
|
|
254
|
+
error_results_are_failures: bool = True,
|
|
255
|
+
) -> AdapterResult[T]:
|
|
256
|
+
"""Build internal execute with circuit breaker.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
operation_name (str): The operation name value.
|
|
260
|
+
operation (Callable[[], Awaitable[AdapterResult[T]]]): Callable object executed by
|
|
261
|
+
the helper.
|
|
262
|
+
input_dict (dict | None): Input parameters for SoC logging. Defaults to None.
|
|
263
|
+
error_results_are_failures (bool): When False, an ERROR result
|
|
264
|
+
returned by the operation counts as a healthy round trip, not a
|
|
265
|
+
breaker failure - only exceptions trip the breaker. For
|
|
266
|
+
diagnostic/lookup operations whose ERROR is an expected answer
|
|
267
|
+
("the model has rebuild errors", "no such feature"); otherwise
|
|
268
|
+
they would lock themselves out exactly when needed.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
AdapterResult[T]: The result produced by the operation.
|
|
272
|
+
"""
|
|
273
|
+
bucket = self._get_bucket(operation_name)
|
|
274
|
+
if not self._should_allow_request(bucket):
|
|
275
|
+
return AdapterResult(
|
|
276
|
+
status=AdapterResultStatus.ERROR,
|
|
277
|
+
error=f"Circuit breaker is {bucket.state.value} for {operation_name}",
|
|
278
|
+
metadata={"circuit_state": bucket.state.value},
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if bucket.state == CircuitState.HALF_OPEN:
|
|
282
|
+
bucket.half_open_calls += 1
|
|
283
|
+
|
|
284
|
+
t0 = time.time()
|
|
285
|
+
try:
|
|
286
|
+
result = await operation()
|
|
287
|
+
latency_ms = (time.time() - t0) * 1000.0
|
|
288
|
+
if result.is_success or not error_results_are_failures:
|
|
289
|
+
self._record_success(bucket)
|
|
290
|
+
else:
|
|
291
|
+
self._record_failure(bucket)
|
|
292
|
+
self._soc_log(operation_name, input_dict, result, latency_ms)
|
|
293
|
+
return result
|
|
294
|
+
except Exception as e:
|
|
295
|
+
latency_ms = (time.time() - t0) * 1000.0
|
|
296
|
+
self._record_failure(bucket)
|
|
297
|
+
err_result: AdapterResult[T] = AdapterResult(
|
|
298
|
+
status=AdapterResultStatus.ERROR,
|
|
299
|
+
error=f"Circuit breaker caught exception in {operation_name}: {e}",
|
|
300
|
+
metadata={"circuit_state": bucket.state.value},
|
|
301
|
+
)
|
|
302
|
+
self._soc_log(operation_name, input_dict, err_result, latency_ms)
|
|
303
|
+
return err_result
|
|
304
|
+
|
|
305
|
+
def _soc_log(
|
|
306
|
+
self,
|
|
307
|
+
tool_name: str,
|
|
308
|
+
input_dict: dict[str, Any] | None,
|
|
309
|
+
result: AdapterResult[Any],
|
|
310
|
+
latency_ms: float,
|
|
311
|
+
) -> None:
|
|
312
|
+
"""Write a ToolCallRecord if soc_session_id is set."""
|
|
313
|
+
if not self.soc_session_id or input_dict is None:
|
|
314
|
+
return
|
|
315
|
+
try:
|
|
316
|
+
from solidworks_mcp.agents.history_db import insert_tool_call_record
|
|
317
|
+
|
|
318
|
+
output_data: dict[str, Any] = {}
|
|
319
|
+
if result.data is not None:
|
|
320
|
+
output_data = {"data": result.data}
|
|
321
|
+
|
|
322
|
+
insert_tool_call_record(
|
|
323
|
+
session_id=self.soc_session_id,
|
|
324
|
+
tool_name=tool_name,
|
|
325
|
+
input_json=json.dumps(input_dict, default=str),
|
|
326
|
+
output_json=json.dumps(output_data, default=str),
|
|
327
|
+
success=result.is_success,
|
|
328
|
+
latency_ms=latency_ms,
|
|
329
|
+
db_path=self.soc_db_path,
|
|
330
|
+
)
|
|
331
|
+
except Exception as exc:
|
|
332
|
+
logger.debug(
|
|
333
|
+
f"[soc_log] failed to write ToolCallRecord for {tool_name}: {exc}"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Adapter interface implementation
|
|
337
|
+
|
|
338
|
+
async def connect(self) -> None:
|
|
339
|
+
"""Connect through circuit breaker.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
None: None.
|
|
343
|
+
|
|
344
|
+
Raises:
|
|
345
|
+
Exception: If the operation cannot be completed.
|
|
346
|
+
"""
|
|
347
|
+
if not self._should_allow_request():
|
|
348
|
+
raise Exception(f"Circuit breaker is {self.state.value}")
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
await self.adapter.connect()
|
|
352
|
+
self._record_success()
|
|
353
|
+
except Exception:
|
|
354
|
+
self._record_failure()
|
|
355
|
+
raise
|
|
356
|
+
|
|
357
|
+
async def disconnect(self) -> None:
|
|
358
|
+
"""Disconnect - always allowed.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
None: None.
|
|
362
|
+
"""
|
|
363
|
+
await self.adapter.disconnect()
|
|
364
|
+
|
|
365
|
+
def is_connected(self) -> bool:
|
|
366
|
+
"""Check connection status.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
bool: True if connected, otherwise False.
|
|
370
|
+
"""
|
|
371
|
+
return self.adapter.is_connected()
|
|
372
|
+
|
|
373
|
+
async def health_check(self) -> AdapterHealth:
|
|
374
|
+
"""Get health check with circuit breaker status.
|
|
375
|
+
|
|
376
|
+
``circuit_breaker`` reports the legacy/default bucket (used by
|
|
377
|
+
``connect()``), which is what "connection health" means here.
|
|
378
|
+
``circuit_breaker_operations`` reports every per-tool bucket, so a
|
|
379
|
+
tripped tool-specific breaker is visible even while the connection
|
|
380
|
+
itself is healthy.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
AdapterHealth: The result produced by the operation.
|
|
384
|
+
"""
|
|
385
|
+
base_health = await self.adapter.health_check()
|
|
386
|
+
if base_health.metrics is None:
|
|
387
|
+
base_health.metrics = {}
|
|
388
|
+
base_health.metrics["circuit_breaker"] = {
|
|
389
|
+
"state": self.state.value,
|
|
390
|
+
"failure_count": self.failure_count,
|
|
391
|
+
"last_failure_time": self.last_failure_time,
|
|
392
|
+
"half_open_calls": self.half_open_calls,
|
|
393
|
+
}
|
|
394
|
+
base_health.metrics["circuit_breaker_operations"] = {
|
|
395
|
+
name: {
|
|
396
|
+
"state": bucket.state.value,
|
|
397
|
+
"failure_count": bucket.failure_count,
|
|
398
|
+
"last_failure_time": bucket.last_failure_time,
|
|
399
|
+
"half_open_calls": bucket.half_open_calls,
|
|
400
|
+
}
|
|
401
|
+
for name, bucket in self._buckets.items()
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
# Consider circuit as unhealthy if the connection breaker or any
|
|
405
|
+
# per-operation breaker is open.
|
|
406
|
+
open_operations = [
|
|
407
|
+
name
|
|
408
|
+
for name, bucket in self._buckets.items()
|
|
409
|
+
if bucket.state == CircuitState.OPEN
|
|
410
|
+
]
|
|
411
|
+
if self.state == CircuitState.OPEN:
|
|
412
|
+
base_health.healthy = False
|
|
413
|
+
base_health.connection_status = "circuit_breaker_open"
|
|
414
|
+
elif open_operations:
|
|
415
|
+
base_health.healthy = False
|
|
416
|
+
base_health.connection_status = (
|
|
417
|
+
f"circuit_breaker_open_for: {', '.join(sorted(open_operations))}"
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
return base_health
|
|
421
|
+
|
|
422
|
+
# Model operations
|
|
423
|
+
|
|
424
|
+
async def open_model(self, file_path: str) -> AdapterResult[SolidWorksModel]:
|
|
425
|
+
"""Open model through circuit breaker.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
file_path (str): Path to the target file.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
AdapterResult[SolidWorksModel]: The result produced by the operation.
|
|
432
|
+
"""
|
|
433
|
+
return await self._execute_with_circuit_breaker(
|
|
434
|
+
"open_model",
|
|
435
|
+
lambda: self.adapter.open_model(file_path),
|
|
436
|
+
input_dict={"file_path": file_path},
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
async def close_model(self, save: bool = False) -> AdapterResult[None]:
|
|
440
|
+
"""Close model through circuit breaker.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
save (bool): The save value. Defaults to False.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
AdapterResult[None]: The result produced by the operation.
|
|
447
|
+
"""
|
|
448
|
+
return await self._execute_with_circuit_breaker(
|
|
449
|
+
"close_model",
|
|
450
|
+
lambda: self.adapter.close_model(save),
|
|
451
|
+
input_dict={"save": save},
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
async def save_file(self, file_path: str | None = None) -> AdapterResult[None]:
|
|
455
|
+
"""Save model through circuit breaker.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
file_path (str | None): Path to the target file. Defaults to None.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
AdapterResult[None]: The result produced by the operation.
|
|
462
|
+
"""
|
|
463
|
+
return await self._execute_with_circuit_breaker(
|
|
464
|
+
"save_file",
|
|
465
|
+
lambda: self.adapter.save_file(file_path),
|
|
466
|
+
input_dict={"file_path": file_path},
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
async def execute_macro(
|
|
470
|
+
self, params: dict[str, Any]
|
|
471
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
472
|
+
"""Provide execute macro support for the circuit breaker adapter.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
params (dict[str, Any]): The params value.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
479
|
+
"""
|
|
480
|
+
return await self._execute_with_circuit_breaker(
|
|
481
|
+
"execute_macro",
|
|
482
|
+
lambda: self.adapter.execute_macro(params), # type: ignore[attr-defined]
|
|
483
|
+
input_dict=params,
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
async def create_part(
|
|
487
|
+
self,
|
|
488
|
+
name: str | None = None,
|
|
489
|
+
units: str | None = None,
|
|
490
|
+
template: str | None = None,
|
|
491
|
+
) -> AdapterResult[SolidWorksModel]:
|
|
492
|
+
"""Create part through circuit breaker.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
name (str | None): The name value. Defaults to None.
|
|
496
|
+
units (str | None): The units value. Defaults to None.
|
|
497
|
+
template (str | None): Template file path. Defaults to None.
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
AdapterResult[SolidWorksModel]: The result produced by the operation.
|
|
501
|
+
"""
|
|
502
|
+
|
|
503
|
+
async def _op() -> AdapterResult[SolidWorksModel]:
|
|
504
|
+
"""Build internal op.
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
AdapterResult[SolidWorksModel]: The result produced by the operation.
|
|
508
|
+
"""
|
|
509
|
+
if template is not None:
|
|
510
|
+
# No zero-arg retry: dropping the template would silently
|
|
511
|
+
# build the document from a different one.
|
|
512
|
+
return await self.adapter.create_part(name, units, template)
|
|
513
|
+
if name is None and units is None:
|
|
514
|
+
return await self.adapter.create_part()
|
|
515
|
+
return await self._invoke_with_optional_args(
|
|
516
|
+
self.adapter.create_part,
|
|
517
|
+
name,
|
|
518
|
+
units,
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
return await self._execute_with_circuit_breaker(
|
|
522
|
+
"create_part",
|
|
523
|
+
_op,
|
|
524
|
+
input_dict={"name": name, "units": units, "template": template},
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
async def call(self, operation: Callable[[], object | Awaitable[object]]) -> object:
|
|
528
|
+
"""Legacy call API used by tests.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
operation (Callable[[], object | Awaitable[object]]): Callable object executed by
|
|
532
|
+
the helper.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
object: The result produced by the operation.
|
|
536
|
+
|
|
537
|
+
Raises:
|
|
538
|
+
RuntimeError: If the operation cannot be completed.
|
|
539
|
+
Exception: Circuit breaker is open.
|
|
540
|
+
"""
|
|
541
|
+
if not self._should_allow_request():
|
|
542
|
+
raise Exception("Circuit breaker is open")
|
|
543
|
+
try:
|
|
544
|
+
result = operation()
|
|
545
|
+
if asyncio.iscoroutine(result):
|
|
546
|
+
result = await result
|
|
547
|
+
self._record_success()
|
|
548
|
+
return result
|
|
549
|
+
except Exception as exc:
|
|
550
|
+
self._record_failure()
|
|
551
|
+
raise RuntimeError(str(exc)) from exc
|
|
552
|
+
|
|
553
|
+
async def create_assembly(
|
|
554
|
+
self, name: str | None = None, template: str | None = None
|
|
555
|
+
) -> AdapterResult[SolidWorksModel]:
|
|
556
|
+
"""Create assembly through circuit breaker.
|
|
557
|
+
|
|
558
|
+
Args:
|
|
559
|
+
name (str | None): The name value. Defaults to None.
|
|
560
|
+
template (str | None): Template file path. Defaults to None.
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
AdapterResult[SolidWorksModel]: The result produced by the operation.
|
|
564
|
+
"""
|
|
565
|
+
|
|
566
|
+
async def _op() -> AdapterResult[SolidWorksModel]:
|
|
567
|
+
"""Build internal op.
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
AdapterResult[SolidWorksModel]: The result produced by the operation.
|
|
571
|
+
"""
|
|
572
|
+
if template is not None:
|
|
573
|
+
return await self.adapter.create_assembly(name, template)
|
|
574
|
+
if name is None:
|
|
575
|
+
return await self.adapter.create_assembly()
|
|
576
|
+
return await self._invoke_with_optional_args(
|
|
577
|
+
self.adapter.create_assembly,
|
|
578
|
+
name,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
return await self._execute_with_circuit_breaker(
|
|
582
|
+
"create_assembly", _op, input_dict={"name": name, "template": template}
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
async def create_drawing(
|
|
586
|
+
self, name: str | None = None, template: str | None = None
|
|
587
|
+
) -> AdapterResult[SolidWorksModel]:
|
|
588
|
+
"""Create drawing through circuit breaker.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
name (str | None): The name value. Defaults to None.
|
|
592
|
+
template (str | None): Template file path. Defaults to None.
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
AdapterResult[SolidWorksModel]: The result produced by the operation.
|
|
596
|
+
"""
|
|
597
|
+
return await self._execute_with_circuit_breaker(
|
|
598
|
+
"create_drawing",
|
|
599
|
+
lambda: self.adapter.create_drawing(name, template),
|
|
600
|
+
input_dict={"name": name, "template": template},
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
# Feature operations
|
|
604
|
+
|
|
605
|
+
async def create_extrusion(
|
|
606
|
+
self, params: ExtrusionParameters
|
|
607
|
+
) -> AdapterResult[SolidWorksFeature]:
|
|
608
|
+
"""Create extrusion through circuit breaker.
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
params (ExtrusionParameters): The params value.
|
|
612
|
+
|
|
613
|
+
Returns:
|
|
614
|
+
AdapterResult[SolidWorksFeature]: The result produced by the operation.
|
|
615
|
+
"""
|
|
616
|
+
return await self._execute_with_circuit_breaker(
|
|
617
|
+
"create_extrusion",
|
|
618
|
+
lambda: self.adapter.create_extrusion(params),
|
|
619
|
+
input_dict=_to_input_dict(params),
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
async def create_cut_extrude(
|
|
623
|
+
self, params: ExtrusionParameters
|
|
624
|
+
) -> AdapterResult[SolidWorksFeature]:
|
|
625
|
+
"""Create cut-extrude through circuit breaker."""
|
|
626
|
+
return await self._execute_with_circuit_breaker(
|
|
627
|
+
"create_cut_extrude",
|
|
628
|
+
lambda: self.adapter.create_cut_extrude(params),
|
|
629
|
+
input_dict=_to_input_dict(params),
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
async def add_fillet(
|
|
633
|
+
self, radius: float, edge_names: list[str]
|
|
634
|
+
) -> AdapterResult[SolidWorksFeature]:
|
|
635
|
+
"""Add fillet through circuit breaker."""
|
|
636
|
+
return await self._execute_with_circuit_breaker(
|
|
637
|
+
"add_fillet",
|
|
638
|
+
lambda: self.adapter.add_fillet(radius, edge_names),
|
|
639
|
+
input_dict={"radius": radius, "edge_names": edge_names},
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
async def add_chamfer(
|
|
643
|
+
self, distance: float, edge_names: list[str]
|
|
644
|
+
) -> AdapterResult[SolidWorksFeature]:
|
|
645
|
+
"""Add chamfer through circuit breaker."""
|
|
646
|
+
return await self._execute_with_circuit_breaker(
|
|
647
|
+
"add_chamfer",
|
|
648
|
+
lambda: self.adapter.add_chamfer(distance, edge_names),
|
|
649
|
+
input_dict={"distance": distance, "edge_names": edge_names},
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
async def get_selected_entities(self) -> AdapterResult[list[dict[str, Any]]]:
|
|
653
|
+
"""Report the current UI selection through circuit breaker."""
|
|
654
|
+
return await self._execute_with_circuit_breaker(
|
|
655
|
+
"get_selected_entities",
|
|
656
|
+
lambda: self.adapter.get_selected_entities(),
|
|
657
|
+
input_dict={},
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
async def select_feature(
|
|
661
|
+
self, feature_name: str, append: bool = False
|
|
662
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
663
|
+
"""Select a named feature through circuit breaker."""
|
|
664
|
+
return await self._execute_with_circuit_breaker(
|
|
665
|
+
"select_feature",
|
|
666
|
+
lambda: self.adapter.select_feature(feature_name, append),
|
|
667
|
+
input_dict={"feature_name": feature_name, "append": append},
|
|
668
|
+
error_results_are_failures=False,
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
async def select_face_edges(
|
|
672
|
+
self,
|
|
673
|
+
point: list[float] | None = None,
|
|
674
|
+
side: str | None = None,
|
|
675
|
+
plane: str | None = None,
|
|
676
|
+
edge_type: str = "all",
|
|
677
|
+
append: bool = False,
|
|
678
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
679
|
+
"""Multi-select a face's edges through circuit breaker."""
|
|
680
|
+
return await self._execute_with_circuit_breaker(
|
|
681
|
+
"select_face_edges",
|
|
682
|
+
lambda: self.adapter.select_face_edges(
|
|
683
|
+
point, side, plane, edge_type, append
|
|
684
|
+
),
|
|
685
|
+
input_dict={
|
|
686
|
+
"point": point,
|
|
687
|
+
"side": side,
|
|
688
|
+
"plane": plane,
|
|
689
|
+
"edge_type": edge_type,
|
|
690
|
+
"append": append,
|
|
691
|
+
},
|
|
692
|
+
error_results_are_failures=False,
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
async def rebuild_model(
|
|
696
|
+
self, top_level_only: bool = False
|
|
697
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
698
|
+
"""Force a rebuild through circuit breaker."""
|
|
699
|
+
return await self._execute_with_circuit_breaker(
|
|
700
|
+
"rebuild_model",
|
|
701
|
+
lambda: self.adapter.rebuild_model(top_level_only),
|
|
702
|
+
input_dict={"top_level_only": top_level_only},
|
|
703
|
+
error_results_are_failures=False,
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
async def create_reference_plane(
|
|
707
|
+
self,
|
|
708
|
+
reference: str,
|
|
709
|
+
offset: float = 0.0,
|
|
710
|
+
angle: float = 0.0,
|
|
711
|
+
flip: bool = False,
|
|
712
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
713
|
+
"""Create reference plane through circuit breaker."""
|
|
714
|
+
return await self._execute_with_circuit_breaker(
|
|
715
|
+
"create_reference_plane",
|
|
716
|
+
lambda: self.adapter.create_reference_plane(reference, offset, angle, flip),
|
|
717
|
+
input_dict={
|
|
718
|
+
"reference": reference,
|
|
719
|
+
"offset": offset,
|
|
720
|
+
"angle": angle,
|
|
721
|
+
"flip": flip,
|
|
722
|
+
},
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
async def create_axis(self, reference: str) -> AdapterResult[dict[str, Any]]:
|
|
726
|
+
"""Create reference axis through circuit breaker."""
|
|
727
|
+
return await self._execute_with_circuit_breaker(
|
|
728
|
+
"create_axis",
|
|
729
|
+
lambda: self.adapter.create_axis(reference),
|
|
730
|
+
input_dict={"reference": reference},
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
async def create_reference_point(
|
|
734
|
+
self,
|
|
735
|
+
mode: str,
|
|
736
|
+
x: float,
|
|
737
|
+
y: float,
|
|
738
|
+
z: float,
|
|
739
|
+
distance: float | None = None,
|
|
740
|
+
percent: float | None = None,
|
|
741
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
742
|
+
"""Create a reference point through circuit breaker."""
|
|
743
|
+
return await self._execute_with_circuit_breaker(
|
|
744
|
+
"create_reference_point",
|
|
745
|
+
lambda: self.adapter.create_reference_point(
|
|
746
|
+
mode, x, y, z, distance, percent
|
|
747
|
+
),
|
|
748
|
+
input_dict={
|
|
749
|
+
"mode": mode,
|
|
750
|
+
"x": x,
|
|
751
|
+
"y": y,
|
|
752
|
+
"z": z,
|
|
753
|
+
"distance": distance,
|
|
754
|
+
"percent": percent,
|
|
755
|
+
},
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
async def mirror_feature(
|
|
759
|
+
self,
|
|
760
|
+
features: list[str],
|
|
761
|
+
mirror_plane: str,
|
|
762
|
+
merge: bool = True,
|
|
763
|
+
mirror_bodies: bool = True,
|
|
764
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
765
|
+
"""Mirror solid bodies or features through circuit breaker."""
|
|
766
|
+
return await self._execute_with_circuit_breaker(
|
|
767
|
+
"mirror_feature",
|
|
768
|
+
lambda: self.adapter.mirror_feature(
|
|
769
|
+
features, mirror_plane, merge, mirror_bodies
|
|
770
|
+
),
|
|
771
|
+
input_dict={
|
|
772
|
+
"features": features,
|
|
773
|
+
"mirror_plane": mirror_plane,
|
|
774
|
+
"merge": merge,
|
|
775
|
+
"mirror_bodies": mirror_bodies,
|
|
776
|
+
},
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
async def pattern_circular(
|
|
780
|
+
self,
|
|
781
|
+
features: list[str],
|
|
782
|
+
axis: str,
|
|
783
|
+
count: int,
|
|
784
|
+
angle: float = 360.0,
|
|
785
|
+
equal_spacing: bool = True,
|
|
786
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
787
|
+
"""Pattern features around an axis through circuit breaker."""
|
|
788
|
+
return await self._execute_with_circuit_breaker(
|
|
789
|
+
"pattern_circular",
|
|
790
|
+
lambda: self.adapter.pattern_circular(
|
|
791
|
+
features, axis, count, angle, equal_spacing
|
|
792
|
+
),
|
|
793
|
+
input_dict={
|
|
794
|
+
"features": features,
|
|
795
|
+
"axis": axis,
|
|
796
|
+
"count": count,
|
|
797
|
+
"angle": angle,
|
|
798
|
+
"equal_spacing": equal_spacing,
|
|
799
|
+
},
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
async def create_revolve(
|
|
803
|
+
self, params: RevolveParameters
|
|
804
|
+
) -> AdapterResult[SolidWorksFeature]:
|
|
805
|
+
"""Create revolve through circuit breaker.
|
|
806
|
+
|
|
807
|
+
Args:
|
|
808
|
+
params (RevolveParameters): The params value.
|
|
809
|
+
|
|
810
|
+
Returns:
|
|
811
|
+
AdapterResult[SolidWorksFeature]: The result produced by the operation.
|
|
812
|
+
"""
|
|
813
|
+
return await self._execute_with_circuit_breaker(
|
|
814
|
+
"create_revolve",
|
|
815
|
+
lambda: self.adapter.create_revolve(params),
|
|
816
|
+
input_dict=_to_input_dict(params),
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
async def create_sweep(
|
|
820
|
+
self, params: SweepParameters
|
|
821
|
+
) -> AdapterResult[SolidWorksFeature]:
|
|
822
|
+
"""Create sweep through circuit breaker.
|
|
823
|
+
|
|
824
|
+
Args:
|
|
825
|
+
params (SweepParameters): The params value.
|
|
826
|
+
|
|
827
|
+
Returns:
|
|
828
|
+
AdapterResult[SolidWorksFeature]: The result produced by the operation.
|
|
829
|
+
"""
|
|
830
|
+
return await self._execute_with_circuit_breaker(
|
|
831
|
+
"create_sweep",
|
|
832
|
+
lambda: self.adapter.create_sweep(params),
|
|
833
|
+
input_dict=_to_input_dict(params),
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
async def create_loft(
|
|
837
|
+
self, params: LoftParameters
|
|
838
|
+
) -> AdapterResult[SolidWorksFeature]:
|
|
839
|
+
"""Create loft through circuit breaker.
|
|
840
|
+
|
|
841
|
+
Args:
|
|
842
|
+
params (LoftParameters): The params value.
|
|
843
|
+
|
|
844
|
+
Returns:
|
|
845
|
+
AdapterResult[SolidWorksFeature]: The result produced by the operation.
|
|
846
|
+
"""
|
|
847
|
+
return await self._execute_with_circuit_breaker(
|
|
848
|
+
"create_loft",
|
|
849
|
+
lambda: self.adapter.create_loft(params),
|
|
850
|
+
input_dict=_to_input_dict(params),
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
# Sketch operations
|
|
854
|
+
|
|
855
|
+
async def create_sketch(self, plane: str) -> AdapterResult[str]:
|
|
856
|
+
"""Create sketch through circuit breaker.
|
|
857
|
+
|
|
858
|
+
Args:
|
|
859
|
+
plane (str): The plane value.
|
|
860
|
+
|
|
861
|
+
Returns:
|
|
862
|
+
AdapterResult[str]: The result produced by the operation.
|
|
863
|
+
"""
|
|
864
|
+
return await self._execute_with_circuit_breaker(
|
|
865
|
+
"create_sketch",
|
|
866
|
+
lambda: self.adapter.create_sketch(plane),
|
|
867
|
+
input_dict={"plane": plane},
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
async def add_line(
|
|
871
|
+
self, x1: float, y1: float, x2: float, y2: float
|
|
872
|
+
) -> AdapterResult[str]:
|
|
873
|
+
"""Add line through circuit breaker.
|
|
874
|
+
|
|
875
|
+
Args:
|
|
876
|
+
x1 (float): The x1 value.
|
|
877
|
+
y1 (float): The y1 value.
|
|
878
|
+
x2 (float): The x2 value.
|
|
879
|
+
y2 (float): The y2 value.
|
|
880
|
+
|
|
881
|
+
Returns:
|
|
882
|
+
AdapterResult[str]: The result produced by the operation.
|
|
883
|
+
"""
|
|
884
|
+
return await self._execute_with_circuit_breaker(
|
|
885
|
+
"add_line",
|
|
886
|
+
lambda: self.adapter.add_line(x1, y1, x2, y2),
|
|
887
|
+
input_dict={"x1": x1, "y1": y1, "x2": x2, "y2": y2},
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
async def add_centerline(
|
|
891
|
+
self, x1: float, y1: float, x2: float, y2: float
|
|
892
|
+
) -> AdapterResult[str]:
|
|
893
|
+
"""Add centerline through circuit breaker.
|
|
894
|
+
|
|
895
|
+
Args:
|
|
896
|
+
x1 (float): The x1 value.
|
|
897
|
+
y1 (float): The y1 value.
|
|
898
|
+
x2 (float): The x2 value.
|
|
899
|
+
y2 (float): The y2 value.
|
|
900
|
+
|
|
901
|
+
Returns:
|
|
902
|
+
AdapterResult[str]: The result produced by the operation.
|
|
903
|
+
"""
|
|
904
|
+
return await self._execute_with_circuit_breaker(
|
|
905
|
+
"add_centerline",
|
|
906
|
+
lambda: self.adapter.add_centerline(x1, y1, x2, y2),
|
|
907
|
+
input_dict={"x1": x1, "y1": y1, "x2": x2, "y2": y2},
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
async def add_circle(
|
|
911
|
+
self, center_x: float, center_y: float, radius: float
|
|
912
|
+
) -> AdapterResult[str]:
|
|
913
|
+
"""Add circle through circuit breaker.
|
|
914
|
+
|
|
915
|
+
Args:
|
|
916
|
+
center_x (float): The center x value.
|
|
917
|
+
center_y (float): The center y value.
|
|
918
|
+
radius (float): The radius value.
|
|
919
|
+
|
|
920
|
+
Returns:
|
|
921
|
+
AdapterResult[str]: The result produced by the operation.
|
|
922
|
+
"""
|
|
923
|
+
return await self._execute_with_circuit_breaker(
|
|
924
|
+
"add_circle",
|
|
925
|
+
lambda: self.adapter.add_circle(center_x, center_y, radius),
|
|
926
|
+
input_dict={"center_x": center_x, "center_y": center_y, "radius": radius},
|
|
927
|
+
)
|
|
928
|
+
|
|
929
|
+
async def add_rectangle(
|
|
930
|
+
self, x1: float, y1: float, x2: float, y2: float
|
|
931
|
+
) -> AdapterResult[str]:
|
|
932
|
+
"""Add rectangle through circuit breaker.
|
|
933
|
+
|
|
934
|
+
Args:
|
|
935
|
+
x1 (float): The x1 value.
|
|
936
|
+
y1 (float): The y1 value.
|
|
937
|
+
x2 (float): The x2 value.
|
|
938
|
+
y2 (float): The y2 value.
|
|
939
|
+
|
|
940
|
+
Returns:
|
|
941
|
+
AdapterResult[str]: The result produced by the operation.
|
|
942
|
+
"""
|
|
943
|
+
return await self._execute_with_circuit_breaker(
|
|
944
|
+
"add_rectangle",
|
|
945
|
+
lambda: self.adapter.add_rectangle(x1, y1, x2, y2),
|
|
946
|
+
input_dict={"x1": x1, "y1": y1, "x2": x2, "y2": y2},
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
async def add_arc(
|
|
950
|
+
self,
|
|
951
|
+
center_x: float,
|
|
952
|
+
center_y: float,
|
|
953
|
+
start_x: float,
|
|
954
|
+
start_y: float,
|
|
955
|
+
end_x: float,
|
|
956
|
+
end_y: float,
|
|
957
|
+
) -> AdapterResult[str]:
|
|
958
|
+
"""Add arc through circuit breaker.
|
|
959
|
+
|
|
960
|
+
Args:
|
|
961
|
+
center_x (float): Arc center X coordinate.
|
|
962
|
+
center_y (float): Arc center Y coordinate.
|
|
963
|
+
start_x (float): Arc start X coordinate.
|
|
964
|
+
start_y (float): Arc start Y coordinate.
|
|
965
|
+
end_x (float): Arc end X coordinate.
|
|
966
|
+
end_y (float): Arc end Y coordinate.
|
|
967
|
+
|
|
968
|
+
Returns:
|
|
969
|
+
AdapterResult[str]: The result produced by the operation.
|
|
970
|
+
"""
|
|
971
|
+
return await self._execute_with_circuit_breaker(
|
|
972
|
+
"add_arc",
|
|
973
|
+
lambda: self.adapter.add_arc(
|
|
974
|
+
center_x,
|
|
975
|
+
center_y,
|
|
976
|
+
start_x,
|
|
977
|
+
start_y,
|
|
978
|
+
end_x,
|
|
979
|
+
end_y,
|
|
980
|
+
),
|
|
981
|
+
input_dict={
|
|
982
|
+
"center_x": center_x,
|
|
983
|
+
"center_y": center_y,
|
|
984
|
+
"start_x": start_x,
|
|
985
|
+
"start_y": start_y,
|
|
986
|
+
"end_x": end_x,
|
|
987
|
+
"end_y": end_y,
|
|
988
|
+
},
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
async def add_spline(self, points: list[dict[str, float]]) -> AdapterResult[str]:
|
|
992
|
+
"""Add spline through circuit breaker."""
|
|
993
|
+
return await self._execute_with_circuit_breaker(
|
|
994
|
+
"add_spline",
|
|
995
|
+
lambda: self.adapter.add_spline(points),
|
|
996
|
+
input_dict={"points": points},
|
|
997
|
+
)
|
|
998
|
+
|
|
999
|
+
async def add_polygon(
|
|
1000
|
+
self, center_x: float, center_y: float, radius: float, sides: int
|
|
1001
|
+
) -> AdapterResult[str]:
|
|
1002
|
+
"""Add polygon through circuit breaker."""
|
|
1003
|
+
return await self._execute_with_circuit_breaker(
|
|
1004
|
+
"add_polygon",
|
|
1005
|
+
lambda: self.adapter.add_polygon(center_x, center_y, radius, sides),
|
|
1006
|
+
input_dict={
|
|
1007
|
+
"center_x": center_x,
|
|
1008
|
+
"center_y": center_y,
|
|
1009
|
+
"radius": radius,
|
|
1010
|
+
"sides": sides,
|
|
1011
|
+
},
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
async def add_ellipse(
|
|
1015
|
+
self,
|
|
1016
|
+
center_x: float,
|
|
1017
|
+
center_y: float,
|
|
1018
|
+
major_axis: float,
|
|
1019
|
+
minor_axis: float,
|
|
1020
|
+
) -> AdapterResult[str]:
|
|
1021
|
+
"""Add ellipse through circuit breaker."""
|
|
1022
|
+
return await self._execute_with_circuit_breaker(
|
|
1023
|
+
"add_ellipse",
|
|
1024
|
+
lambda: self.adapter.add_ellipse(
|
|
1025
|
+
center_x, center_y, major_axis, minor_axis
|
|
1026
|
+
),
|
|
1027
|
+
input_dict={
|
|
1028
|
+
"center_x": center_x,
|
|
1029
|
+
"center_y": center_y,
|
|
1030
|
+
"major_axis": major_axis,
|
|
1031
|
+
"minor_axis": minor_axis,
|
|
1032
|
+
},
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
async def sketch_linear_pattern(
|
|
1036
|
+
self,
|
|
1037
|
+
entities: list[str],
|
|
1038
|
+
direction_x: float,
|
|
1039
|
+
direction_y: float,
|
|
1040
|
+
spacing: float,
|
|
1041
|
+
count: int,
|
|
1042
|
+
) -> AdapterResult[str]:
|
|
1043
|
+
"""Sketch linear pattern through circuit breaker."""
|
|
1044
|
+
return await self._execute_with_circuit_breaker(
|
|
1045
|
+
"sketch_linear_pattern",
|
|
1046
|
+
lambda: self.adapter.sketch_linear_pattern(
|
|
1047
|
+
entities, direction_x, direction_y, spacing, count
|
|
1048
|
+
),
|
|
1049
|
+
input_dict={
|
|
1050
|
+
"entities": entities,
|
|
1051
|
+
"direction_x": direction_x,
|
|
1052
|
+
"direction_y": direction_y,
|
|
1053
|
+
"spacing": spacing,
|
|
1054
|
+
"count": count,
|
|
1055
|
+
},
|
|
1056
|
+
)
|
|
1057
|
+
|
|
1058
|
+
async def sketch_circular_pattern(
|
|
1059
|
+
self,
|
|
1060
|
+
entities: list[str],
|
|
1061
|
+
angle: float,
|
|
1062
|
+
count: int,
|
|
1063
|
+
) -> AdapterResult[str]:
|
|
1064
|
+
"""Sketch circular pattern through circuit breaker."""
|
|
1065
|
+
return await self._execute_with_circuit_breaker(
|
|
1066
|
+
"sketch_circular_pattern",
|
|
1067
|
+
lambda: self.adapter.sketch_circular_pattern(entities, angle, count),
|
|
1068
|
+
input_dict={"entities": entities, "angle": angle, "count": count},
|
|
1069
|
+
)
|
|
1070
|
+
|
|
1071
|
+
async def sketch_mirror(
|
|
1072
|
+
self, entities: list[str], mirror_line: str
|
|
1073
|
+
) -> AdapterResult[str]:
|
|
1074
|
+
"""Sketch mirror through circuit breaker."""
|
|
1075
|
+
return await self._execute_with_circuit_breaker(
|
|
1076
|
+
"sketch_mirror",
|
|
1077
|
+
lambda: self.adapter.sketch_mirror(entities, mirror_line),
|
|
1078
|
+
input_dict={"entities": entities, "mirror_line": mirror_line},
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
async def sketch_offset(
|
|
1082
|
+
self,
|
|
1083
|
+
entities: list[str],
|
|
1084
|
+
offset_distance: float,
|
|
1085
|
+
reverse_direction: bool,
|
|
1086
|
+
) -> AdapterResult[str]:
|
|
1087
|
+
"""Sketch offset through circuit breaker."""
|
|
1088
|
+
return await self._execute_with_circuit_breaker(
|
|
1089
|
+
"sketch_offset",
|
|
1090
|
+
lambda: self.adapter.sketch_offset(
|
|
1091
|
+
entities, offset_distance, reverse_direction
|
|
1092
|
+
),
|
|
1093
|
+
input_dict={
|
|
1094
|
+
"entities": entities,
|
|
1095
|
+
"offset_distance": offset_distance,
|
|
1096
|
+
"reverse_direction": reverse_direction,
|
|
1097
|
+
},
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
async def add_sketch_constraint(
|
|
1101
|
+
self,
|
|
1102
|
+
entity1: str,
|
|
1103
|
+
entity2: str | None,
|
|
1104
|
+
relation_type: str,
|
|
1105
|
+
entity3: str | None = None,
|
|
1106
|
+
) -> AdapterResult[str]:
|
|
1107
|
+
"""Add sketch constraint through circuit breaker."""
|
|
1108
|
+
return await self._execute_with_circuit_breaker(
|
|
1109
|
+
"add_sketch_constraint",
|
|
1110
|
+
lambda: self.adapter.add_sketch_constraint(
|
|
1111
|
+
entity1,
|
|
1112
|
+
entity2,
|
|
1113
|
+
relation_type,
|
|
1114
|
+
entity3,
|
|
1115
|
+
),
|
|
1116
|
+
input_dict={
|
|
1117
|
+
"entity1": entity1,
|
|
1118
|
+
"entity2": entity2,
|
|
1119
|
+
"relation_type": relation_type,
|
|
1120
|
+
"entity3": entity3,
|
|
1121
|
+
},
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
async def add_sketch_dimension(
|
|
1125
|
+
self,
|
|
1126
|
+
entity1: str,
|
|
1127
|
+
entity2: str | None,
|
|
1128
|
+
dimension_type: str,
|
|
1129
|
+
value: float,
|
|
1130
|
+
) -> AdapterResult[str]:
|
|
1131
|
+
"""Add sketch dimension through circuit breaker."""
|
|
1132
|
+
return await self._execute_with_circuit_breaker(
|
|
1133
|
+
"add_sketch_dimension",
|
|
1134
|
+
lambda: self.adapter.add_sketch_dimension(
|
|
1135
|
+
entity1,
|
|
1136
|
+
entity2,
|
|
1137
|
+
dimension_type,
|
|
1138
|
+
value,
|
|
1139
|
+
),
|
|
1140
|
+
input_dict={
|
|
1141
|
+
"entity1": entity1,
|
|
1142
|
+
"entity2": entity2,
|
|
1143
|
+
"dimension_type": dimension_type,
|
|
1144
|
+
"value": value,
|
|
1145
|
+
},
|
|
1146
|
+
)
|
|
1147
|
+
|
|
1148
|
+
async def check_sketch_fully_defined(
|
|
1149
|
+
self, sketch_name: str | None = None
|
|
1150
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1151
|
+
"""Check sketch definition status through circuit breaker."""
|
|
1152
|
+
return await self._execute_with_circuit_breaker(
|
|
1153
|
+
"check_sketch_fully_defined",
|
|
1154
|
+
lambda: self.adapter.check_sketch_fully_defined(sketch_name),
|
|
1155
|
+
input_dict={"sketch_name": sketch_name},
|
|
1156
|
+
)
|
|
1157
|
+
|
|
1158
|
+
async def exit_sketch(self) -> AdapterResult[None]:
|
|
1159
|
+
"""Exit sketch through circuit breaker.
|
|
1160
|
+
|
|
1161
|
+
Returns:
|
|
1162
|
+
AdapterResult[None]: The result produced by the operation.
|
|
1163
|
+
"""
|
|
1164
|
+
return await self._execute_with_circuit_breaker(
|
|
1165
|
+
"exit_sketch",
|
|
1166
|
+
lambda: self.adapter.exit_sketch(),
|
|
1167
|
+
input_dict={},
|
|
1168
|
+
)
|
|
1169
|
+
|
|
1170
|
+
# Analysis operations
|
|
1171
|
+
|
|
1172
|
+
async def get_mass_properties(self) -> AdapterResult[MassProperties]:
|
|
1173
|
+
"""Get mass properties through circuit breaker.
|
|
1174
|
+
|
|
1175
|
+
Returns:
|
|
1176
|
+
AdapterResult[MassProperties]: The result produced by the operation.
|
|
1177
|
+
"""
|
|
1178
|
+
return await self._execute_with_circuit_breaker(
|
|
1179
|
+
"get_mass_properties",
|
|
1180
|
+
lambda: self.adapter.get_mass_properties(),
|
|
1181
|
+
input_dict={},
|
|
1182
|
+
)
|
|
1183
|
+
|
|
1184
|
+
async def get_model_info(self) -> AdapterResult[dict[str, object]]:
|
|
1185
|
+
"""Get active model metadata through circuit breaker.
|
|
1186
|
+
|
|
1187
|
+
Returns:
|
|
1188
|
+
AdapterResult[dict[str, object]]: The result produced by the operation.
|
|
1189
|
+
"""
|
|
1190
|
+
return await self._execute_with_circuit_breaker(
|
|
1191
|
+
"get_model_info",
|
|
1192
|
+
lambda: self.adapter.get_model_info(),
|
|
1193
|
+
input_dict={},
|
|
1194
|
+
)
|
|
1195
|
+
|
|
1196
|
+
async def list_features(
|
|
1197
|
+
self, include_suppressed: bool = False, max_assembly_depth: int = 2
|
|
1198
|
+
) -> AdapterResult[list[dict[str, object]]]:
|
|
1199
|
+
"""List model features through circuit breaker.
|
|
1200
|
+
|
|
1201
|
+
Args:
|
|
1202
|
+
include_suppressed (bool): The include suppressed value. Defaults to False.
|
|
1203
|
+
max_assembly_depth (int): Sub-assembly recursion depth. Defaults to 2.
|
|
1204
|
+
|
|
1205
|
+
Returns:
|
|
1206
|
+
AdapterResult[list[dict[str, object]]]: The result produced by the operation.
|
|
1207
|
+
"""
|
|
1208
|
+
return await self._execute_with_circuit_breaker(
|
|
1209
|
+
"list_features",
|
|
1210
|
+
lambda: self.adapter.list_features(include_suppressed, max_assembly_depth),
|
|
1211
|
+
input_dict={
|
|
1212
|
+
"include_suppressed": include_suppressed,
|
|
1213
|
+
"max_assembly_depth": max_assembly_depth,
|
|
1214
|
+
},
|
|
1215
|
+
)
|
|
1216
|
+
|
|
1217
|
+
async def list_configurations(self) -> AdapterResult[list[str]]:
|
|
1218
|
+
"""List model configurations through circuit breaker.
|
|
1219
|
+
|
|
1220
|
+
Returns:
|
|
1221
|
+
AdapterResult[list[str]]: The result produced by the operation.
|
|
1222
|
+
"""
|
|
1223
|
+
return await self._execute_with_circuit_breaker(
|
|
1224
|
+
"list_configurations",
|
|
1225
|
+
lambda: self.adapter.list_configurations(),
|
|
1226
|
+
input_dict={},
|
|
1227
|
+
)
|
|
1228
|
+
|
|
1229
|
+
# Export operations
|
|
1230
|
+
|
|
1231
|
+
async def export_image(self, payload: dict) -> AdapterResult[dict]:
|
|
1232
|
+
"""Export viewport image through circuit breaker.
|
|
1233
|
+
|
|
1234
|
+
Args:
|
|
1235
|
+
payload (dict): The payload value.
|
|
1236
|
+
|
|
1237
|
+
Returns:
|
|
1238
|
+
AdapterResult[dict]: The result produced by the operation.
|
|
1239
|
+
"""
|
|
1240
|
+
return await self._execute_with_circuit_breaker(
|
|
1241
|
+
"export_image",
|
|
1242
|
+
lambda: self.adapter.export_image(payload),
|
|
1243
|
+
input_dict=payload,
|
|
1244
|
+
)
|
|
1245
|
+
|
|
1246
|
+
async def export_file(
|
|
1247
|
+
self, file_path: str, format_type: str
|
|
1248
|
+
) -> AdapterResult[None]:
|
|
1249
|
+
"""Export file through circuit breaker.
|
|
1250
|
+
|
|
1251
|
+
Args:
|
|
1252
|
+
file_path (str): Path to the target file.
|
|
1253
|
+
format_type (str): The format type value.
|
|
1254
|
+
|
|
1255
|
+
Returns:
|
|
1256
|
+
AdapterResult[None]: The result produced by the operation.
|
|
1257
|
+
"""
|
|
1258
|
+
return await self._execute_with_circuit_breaker(
|
|
1259
|
+
"export_file",
|
|
1260
|
+
lambda: self.adapter.export_file(file_path, format_type),
|
|
1261
|
+
input_dict={"file_path": file_path, "format_type": format_type},
|
|
1262
|
+
)
|
|
1263
|
+
|
|
1264
|
+
# Dimension operations
|
|
1265
|
+
|
|
1266
|
+
async def get_dimension(self, name: str) -> AdapterResult[float]:
|
|
1267
|
+
"""Get dimension through circuit breaker.
|
|
1268
|
+
|
|
1269
|
+
Args:
|
|
1270
|
+
name (str): The name value.
|
|
1271
|
+
|
|
1272
|
+
Returns:
|
|
1273
|
+
AdapterResult[float]: The result produced by the operation.
|
|
1274
|
+
"""
|
|
1275
|
+
return await self._execute_with_circuit_breaker(
|
|
1276
|
+
"get_dimension",
|
|
1277
|
+
lambda: self.adapter.get_dimension(name),
|
|
1278
|
+
input_dict={"name": name},
|
|
1279
|
+
)
|
|
1280
|
+
|
|
1281
|
+
async def set_dimension(self, name: str, value: float) -> AdapterResult[None]:
|
|
1282
|
+
"""Set dimension through circuit breaker.
|
|
1283
|
+
|
|
1284
|
+
Args:
|
|
1285
|
+
name (str): The name value.
|
|
1286
|
+
value (float): The value value.
|
|
1287
|
+
|
|
1288
|
+
Returns:
|
|
1289
|
+
AdapterResult[None]: The result produced by the operation.
|
|
1290
|
+
"""
|
|
1291
|
+
return await self._execute_with_circuit_breaker(
|
|
1292
|
+
"set_dimension",
|
|
1293
|
+
lambda: self.adapter.set_dimension(name, value),
|
|
1294
|
+
input_dict={"name": name, "value": value},
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1297
|
+
async def set_units(self, unit_system: str) -> AdapterResult[dict[str, Any]]:
|
|
1298
|
+
"""Set document units through circuit breaker.
|
|
1299
|
+
|
|
1300
|
+
Args:
|
|
1301
|
+
unit_system (str): One of ``mm``, ``cm``, ``m``, ``in``, ``ft``.
|
|
1302
|
+
|
|
1303
|
+
Returns:
|
|
1304
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1305
|
+
"""
|
|
1306
|
+
return await self._execute_with_circuit_breaker(
|
|
1307
|
+
"set_units",
|
|
1308
|
+
lambda: self.adapter.set_units(unit_system),
|
|
1309
|
+
input_dict={"unit_system": unit_system},
|
|
1310
|
+
)
|
|
1311
|
+
|
|
1312
|
+
async def list_open_documents(self) -> AdapterResult[list[dict[str, Any]]]:
|
|
1313
|
+
"""List open documents through circuit breaker.
|
|
1314
|
+
|
|
1315
|
+
Returns:
|
|
1316
|
+
AdapterResult[list[dict[str, Any]]]: The result produced by the
|
|
1317
|
+
operation.
|
|
1318
|
+
"""
|
|
1319
|
+
return await self._execute_with_circuit_breaker(
|
|
1320
|
+
"list_open_documents",
|
|
1321
|
+
lambda: self.adapter.list_open_documents(),
|
|
1322
|
+
input_dict={},
|
|
1323
|
+
)
|
|
1324
|
+
|
|
1325
|
+
async def activate_document(
|
|
1326
|
+
self, title_or_path: str
|
|
1327
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1328
|
+
"""Activate an open document through circuit breaker.
|
|
1329
|
+
|
|
1330
|
+
Args:
|
|
1331
|
+
title_or_path (str): Title, full path, or file name of an open
|
|
1332
|
+
document.
|
|
1333
|
+
|
|
1334
|
+
Returns:
|
|
1335
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1336
|
+
"""
|
|
1337
|
+
return await self._execute_with_circuit_breaker(
|
|
1338
|
+
"activate_document",
|
|
1339
|
+
lambda: self.adapter.activate_document(title_or_path),
|
|
1340
|
+
input_dict={"title_or_path": title_or_path},
|
|
1341
|
+
)
|
|
1342
|
+
|
|
1343
|
+
# Assembly operations
|
|
1344
|
+
|
|
1345
|
+
async def insert_component(
|
|
1346
|
+
self, file_path: str, x: float = 0.0, y: float = 0.0, z: float = 0.0
|
|
1347
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1348
|
+
"""Insert component through circuit breaker.
|
|
1349
|
+
|
|
1350
|
+
Args:
|
|
1351
|
+
file_path (str): Path to the target file.
|
|
1352
|
+
x (float): X position in millimetres. Defaults to 0.0.
|
|
1353
|
+
y (float): Y position in millimetres. Defaults to 0.0.
|
|
1354
|
+
z (float): Z position in millimetres. Defaults to 0.0.
|
|
1355
|
+
|
|
1356
|
+
Returns:
|
|
1357
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1358
|
+
"""
|
|
1359
|
+
return await self._execute_with_circuit_breaker(
|
|
1360
|
+
"insert_component",
|
|
1361
|
+
lambda: self.adapter.insert_component(file_path, x, y, z),
|
|
1362
|
+
input_dict={"file_path": file_path, "x": x, "y": y, "z": z},
|
|
1363
|
+
)
|
|
1364
|
+
|
|
1365
|
+
async def list_components(self) -> AdapterResult[list[str]]:
|
|
1366
|
+
"""List components through circuit breaker.
|
|
1367
|
+
|
|
1368
|
+
Returns:
|
|
1369
|
+
AdapterResult[list[str]]: The result produced by the operation.
|
|
1370
|
+
"""
|
|
1371
|
+
return await self._execute_with_circuit_breaker(
|
|
1372
|
+
"list_components",
|
|
1373
|
+
lambda: self.adapter.list_components(),
|
|
1374
|
+
input_dict={},
|
|
1375
|
+
)
|
|
1376
|
+
|
|
1377
|
+
async def get_model_geometry_summary(self) -> AdapterResult[dict[str, Any]]:
|
|
1378
|
+
"""Summarise the active document through circuit breaker.
|
|
1379
|
+
|
|
1380
|
+
Returns:
|
|
1381
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1382
|
+
"""
|
|
1383
|
+
return await self._execute_with_circuit_breaker(
|
|
1384
|
+
"get_model_geometry_summary",
|
|
1385
|
+
lambda: self.adapter.get_model_geometry_summary(),
|
|
1386
|
+
input_dict={},
|
|
1387
|
+
)
|
|
1388
|
+
|
|
1389
|
+
async def create_drawing_view(
|
|
1390
|
+
self, payload: Any = None
|
|
1391
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1392
|
+
"""Create a drawing view through circuit breaker.
|
|
1393
|
+
|
|
1394
|
+
Args:
|
|
1395
|
+
payload (Any): Tool payload for the drawing view.
|
|
1396
|
+
|
|
1397
|
+
Returns:
|
|
1398
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1399
|
+
"""
|
|
1400
|
+
return await self._execute_with_circuit_breaker(
|
|
1401
|
+
"create_drawing_view",
|
|
1402
|
+
lambda: self.adapter.create_drawing_view(payload),
|
|
1403
|
+
input_dict=payload if isinstance(payload, dict) else {},
|
|
1404
|
+
)
|
|
1405
|
+
|
|
1406
|
+
async def add_drawing_view(
|
|
1407
|
+
self, payload: Any = None
|
|
1408
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1409
|
+
"""Add a drawing view through circuit breaker.
|
|
1410
|
+
|
|
1411
|
+
Args:
|
|
1412
|
+
payload (Any): Tool payload for the drawing view.
|
|
1413
|
+
|
|
1414
|
+
Returns:
|
|
1415
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1416
|
+
"""
|
|
1417
|
+
return await self._execute_with_circuit_breaker(
|
|
1418
|
+
"add_drawing_view",
|
|
1419
|
+
lambda: self.adapter.add_drawing_view(payload),
|
|
1420
|
+
input_dict=payload if isinstance(payload, dict) else {},
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
async def create_technical_drawing(
|
|
1424
|
+
self, payload: Any = None
|
|
1425
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1426
|
+
"""Create standard views through circuit breaker.
|
|
1427
|
+
|
|
1428
|
+
Args:
|
|
1429
|
+
payload (Any): Tool payload for the technical drawing.
|
|
1430
|
+
|
|
1431
|
+
Returns:
|
|
1432
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1433
|
+
"""
|
|
1434
|
+
return await self._execute_with_circuit_breaker(
|
|
1435
|
+
"create_technical_drawing",
|
|
1436
|
+
lambda: self.adapter.create_technical_drawing(payload),
|
|
1437
|
+
input_dict=payload if isinstance(payload, dict) else {},
|
|
1438
|
+
)
|
|
1439
|
+
|
|
1440
|
+
async def add_note(self, payload: Any = None) -> AdapterResult[dict[str, Any]]:
|
|
1441
|
+
"""Add a drawing note through circuit breaker.
|
|
1442
|
+
|
|
1443
|
+
Args:
|
|
1444
|
+
payload (Any): Tool payload for the note.
|
|
1445
|
+
|
|
1446
|
+
Returns:
|
|
1447
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1448
|
+
"""
|
|
1449
|
+
return await self._execute_with_circuit_breaker(
|
|
1450
|
+
"add_note",
|
|
1451
|
+
lambda: self.adapter.add_note(payload),
|
|
1452
|
+
input_dict=payload if isinstance(payload, dict) else {},
|
|
1453
|
+
)
|
|
1454
|
+
|
|
1455
|
+
async def list_drawing_views(self) -> AdapterResult[list[str]]:
|
|
1456
|
+
"""List drawing views through circuit breaker.
|
|
1457
|
+
|
|
1458
|
+
Returns:
|
|
1459
|
+
AdapterResult[list[str]]: The result produced by the operation.
|
|
1460
|
+
"""
|
|
1461
|
+
return await self._execute_with_circuit_breaker(
|
|
1462
|
+
"list_drawing_views",
|
|
1463
|
+
lambda: self.adapter.list_drawing_views(),
|
|
1464
|
+
input_dict={},
|
|
1465
|
+
)
|
|
1466
|
+
|
|
1467
|
+
async def auto_center_marks(
|
|
1468
|
+
self,
|
|
1469
|
+
view_name: str,
|
|
1470
|
+
mark_holes: bool = True,
|
|
1471
|
+
mark_fillets: bool = False,
|
|
1472
|
+
mark_slots: bool = True,
|
|
1473
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1474
|
+
"""Auto-insert drawing centre marks through circuit breaker."""
|
|
1475
|
+
return await self._execute_with_circuit_breaker(
|
|
1476
|
+
"auto_center_marks",
|
|
1477
|
+
lambda: self.adapter.auto_center_marks(
|
|
1478
|
+
view_name, mark_holes, mark_fillets, mark_slots
|
|
1479
|
+
),
|
|
1480
|
+
input_dict={
|
|
1481
|
+
"view_name": view_name,
|
|
1482
|
+
"mark_holes": mark_holes,
|
|
1483
|
+
"mark_fillets": mark_fillets,
|
|
1484
|
+
"mark_slots": mark_slots,
|
|
1485
|
+
},
|
|
1486
|
+
)
|
|
1487
|
+
|
|
1488
|
+
async def save_body_as_part(
|
|
1489
|
+
self, body_name: str, file_path: str
|
|
1490
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1491
|
+
"""Save a body to a new part through circuit breaker."""
|
|
1492
|
+
return await self._execute_with_circuit_breaker(
|
|
1493
|
+
"save_body_as_part",
|
|
1494
|
+
lambda: self.adapter.save_body_as_part(body_name, file_path),
|
|
1495
|
+
input_dict={"body_name": body_name, "file_path": file_path},
|
|
1496
|
+
)
|
|
1497
|
+
|
|
1498
|
+
async def check_interference(
|
|
1499
|
+
self, params: Any = None
|
|
1500
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1501
|
+
"""Check interference through circuit breaker.
|
|
1502
|
+
|
|
1503
|
+
Args:
|
|
1504
|
+
params (Any): Optional interference-detection settings.
|
|
1505
|
+
|
|
1506
|
+
Returns:
|
|
1507
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1508
|
+
"""
|
|
1509
|
+
return await self._execute_with_circuit_breaker(
|
|
1510
|
+
"check_interference",
|
|
1511
|
+
lambda: self.adapter.check_interference(params),
|
|
1512
|
+
input_dict=params if isinstance(params, dict) else {},
|
|
1513
|
+
)
|
|
1514
|
+
|
|
1515
|
+
async def delete_feature(self, name: str) -> AdapterResult[dict[str, Any]]:
|
|
1516
|
+
"""Delete a feature through circuit breaker.
|
|
1517
|
+
|
|
1518
|
+
Args:
|
|
1519
|
+
name (str): Feature or sketch name.
|
|
1520
|
+
|
|
1521
|
+
Returns:
|
|
1522
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1523
|
+
"""
|
|
1524
|
+
return await self._execute_with_circuit_breaker(
|
|
1525
|
+
"delete_feature",
|
|
1526
|
+
lambda: self.adapter.delete_feature(name),
|
|
1527
|
+
input_dict={"name": name},
|
|
1528
|
+
)
|
|
1529
|
+
|
|
1530
|
+
async def suppress_feature(
|
|
1531
|
+
self, name: str, suppress: bool = True
|
|
1532
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1533
|
+
"""Suppress or unsuppress a feature through circuit breaker.
|
|
1534
|
+
|
|
1535
|
+
Args:
|
|
1536
|
+
name (str): Feature name.
|
|
1537
|
+
suppress (bool): True to suppress, False to unsuppress.
|
|
1538
|
+
|
|
1539
|
+
Returns:
|
|
1540
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1541
|
+
"""
|
|
1542
|
+
return await self._execute_with_circuit_breaker(
|
|
1543
|
+
"suppress_feature",
|
|
1544
|
+
lambda: self.adapter.suppress_feature(name, suppress),
|
|
1545
|
+
input_dict={"name": name, "suppress": suppress},
|
|
1546
|
+
)
|
|
1547
|
+
|
|
1548
|
+
async def rename_feature(
|
|
1549
|
+
self, old_name: str, new_name: str
|
|
1550
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1551
|
+
"""Rename a feature through circuit breaker.
|
|
1552
|
+
|
|
1553
|
+
Args:
|
|
1554
|
+
old_name (str): Current feature name.
|
|
1555
|
+
new_name (str): New feature name.
|
|
1556
|
+
|
|
1557
|
+
Returns:
|
|
1558
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1559
|
+
"""
|
|
1560
|
+
return await self._execute_with_circuit_breaker(
|
|
1561
|
+
"rename_feature",
|
|
1562
|
+
lambda: self.adapter.rename_feature(old_name, new_name),
|
|
1563
|
+
input_dict={"old_name": old_name, "new_name": new_name},
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1566
|
+
async def undo(self, count: int = 1) -> AdapterResult[dict[str, Any]]:
|
|
1567
|
+
"""Undo through circuit breaker.
|
|
1568
|
+
|
|
1569
|
+
Args:
|
|
1570
|
+
count (int): Number of steps to undo.
|
|
1571
|
+
|
|
1572
|
+
Returns:
|
|
1573
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1574
|
+
"""
|
|
1575
|
+
return await self._execute_with_circuit_breaker(
|
|
1576
|
+
"undo",
|
|
1577
|
+
lambda: self.adapter.undo(count),
|
|
1578
|
+
input_dict={"count": count},
|
|
1579
|
+
)
|
|
1580
|
+
|
|
1581
|
+
async def add_mate(
|
|
1582
|
+
self,
|
|
1583
|
+
component_a: str,
|
|
1584
|
+
component_b: str,
|
|
1585
|
+
entity_a: str = "Front Plane",
|
|
1586
|
+
entity_b: str = "Front Plane",
|
|
1587
|
+
mate_type: str = "coincident",
|
|
1588
|
+
alignment: str = "aligned",
|
|
1589
|
+
distance: float = 0.0,
|
|
1590
|
+
angle: float = 0.0,
|
|
1591
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1592
|
+
"""Add mate through circuit breaker.
|
|
1593
|
+
|
|
1594
|
+
Args:
|
|
1595
|
+
component_a (str): First component instance name.
|
|
1596
|
+
component_b (str): Second component instance name.
|
|
1597
|
+
entity_a (str): Named feature on the first component. Defaults to
|
|
1598
|
+
"Front Plane".
|
|
1599
|
+
entity_b (str): Named feature on the second component. Defaults to
|
|
1600
|
+
"Front Plane".
|
|
1601
|
+
mate_type (str): Mate type. Defaults to "coincident".
|
|
1602
|
+
alignment (str): Mate alignment. Defaults to "aligned".
|
|
1603
|
+
distance (float): Distance in millimetres. Defaults to 0.0.
|
|
1604
|
+
angle (float): Angle in degrees. Defaults to 0.0.
|
|
1605
|
+
|
|
1606
|
+
Returns:
|
|
1607
|
+
AdapterResult[dict[str, Any]]: The result produced by the operation.
|
|
1608
|
+
"""
|
|
1609
|
+
return await self._execute_with_circuit_breaker(
|
|
1610
|
+
"add_mate",
|
|
1611
|
+
lambda: self.adapter.add_mate(
|
|
1612
|
+
component_a,
|
|
1613
|
+
component_b,
|
|
1614
|
+
entity_a,
|
|
1615
|
+
entity_b,
|
|
1616
|
+
mate_type,
|
|
1617
|
+
alignment,
|
|
1618
|
+
distance,
|
|
1619
|
+
angle,
|
|
1620
|
+
),
|
|
1621
|
+
input_dict={
|
|
1622
|
+
"component_a": component_a,
|
|
1623
|
+
"component_b": component_b,
|
|
1624
|
+
"entity_a": entity_a,
|
|
1625
|
+
"entity_b": entity_b,
|
|
1626
|
+
"mate_type": mate_type,
|
|
1627
|
+
"alignment": alignment,
|
|
1628
|
+
"distance": distance,
|
|
1629
|
+
"angle": angle,
|
|
1630
|
+
},
|
|
1631
|
+
)
|
|
1632
|
+
|
|
1633
|
+
# SolidWorks-as-Code checkpoint
|
|
1634
|
+
|
|
1635
|
+
async def soc_create_checkpoint(
|
|
1636
|
+
self,
|
|
1637
|
+
label: str,
|
|
1638
|
+
file_path: str,
|
|
1639
|
+
*,
|
|
1640
|
+
feature_tree: list[dict[str, Any]] | None = None,
|
|
1641
|
+
) -> int | None:
|
|
1642
|
+
"""Create a named SoC checkpoint after saving the model.
|
|
1643
|
+
|
|
1644
|
+
Records a SoCCheckpoint row and a ModelStateSnapshot. Call this
|
|
1645
|
+
immediately after ``save_file`` to mark a stable rollback point.
|
|
1646
|
+
|
|
1647
|
+
Args:
|
|
1648
|
+
label (str): Short human name (e.g. "base-extrude").
|
|
1649
|
+
file_path (str): Path of the .sldprt that was just saved.
|
|
1650
|
+
feature_tree (list | None): Optional feature list from list_features().
|
|
1651
|
+
|
|
1652
|
+
Returns:
|
|
1653
|
+
int | None: The new SoCCheckpoint.id, or None if soc_session_id is unset.
|
|
1654
|
+
"""
|
|
1655
|
+
if not self.soc_session_id:
|
|
1656
|
+
return None
|
|
1657
|
+
try:
|
|
1658
|
+
from solidworks_mcp.agents.history_db import (
|
|
1659
|
+
create_soc_checkpoint,
|
|
1660
|
+
insert_model_state_snapshot,
|
|
1661
|
+
list_tool_call_records,
|
|
1662
|
+
)
|
|
1663
|
+
|
|
1664
|
+
records = list_tool_call_records(
|
|
1665
|
+
self.soc_session_id, db_path=self.soc_db_path
|
|
1666
|
+
)
|
|
1667
|
+
last_id = records[-1]["id"] if records else None
|
|
1668
|
+
|
|
1669
|
+
snapshot_id: int | None = None
|
|
1670
|
+
if feature_tree is not None:
|
|
1671
|
+
import json as _json
|
|
1672
|
+
|
|
1673
|
+
insert_model_state_snapshot(
|
|
1674
|
+
session_id=self.soc_session_id,
|
|
1675
|
+
model_path=file_path,
|
|
1676
|
+
feature_tree_json=_json.dumps(feature_tree, default=str),
|
|
1677
|
+
db_path=self.soc_db_path,
|
|
1678
|
+
)
|
|
1679
|
+
from solidworks_mcp.agents.history_db import list_model_state_snapshots
|
|
1680
|
+
|
|
1681
|
+
snaps = list_model_state_snapshots(
|
|
1682
|
+
self.soc_session_id, db_path=self.soc_db_path
|
|
1683
|
+
)
|
|
1684
|
+
if snaps:
|
|
1685
|
+
snapshot_id = snaps[0]["id"]
|
|
1686
|
+
|
|
1687
|
+
return create_soc_checkpoint(
|
|
1688
|
+
session_id=self.soc_session_id,
|
|
1689
|
+
label=label,
|
|
1690
|
+
file_path=file_path,
|
|
1691
|
+
last_record_id=last_id,
|
|
1692
|
+
snapshot_id=snapshot_id,
|
|
1693
|
+
db_path=self.soc_db_path,
|
|
1694
|
+
)
|
|
1695
|
+
except Exception as exc:
|
|
1696
|
+
logger.debug(
|
|
1697
|
+
f"[soc_checkpoint] failed to create checkpoint {label!r}: {exc}"
|
|
1698
|
+
)
|
|
1699
|
+
return None
|
|
1700
|
+
|
|
1701
|
+
# --- Engineering-data and drawing-documentation methods ---
|
|
1702
|
+
|
|
1703
|
+
async def list_model_dimensions(
|
|
1704
|
+
self,
|
|
1705
|
+
feature_name: str | None = None,
|
|
1706
|
+
) -> AdapterResult[list[dict[str, Any]]]:
|
|
1707
|
+
"""List every dimension in the active part or assembly through circuit breaker."""
|
|
1708
|
+
return await self._execute_with_circuit_breaker(
|
|
1709
|
+
"list_model_dimensions",
|
|
1710
|
+
lambda: self.adapter.list_model_dimensions(feature_name),
|
|
1711
|
+
input_dict={"feature_name": feature_name},
|
|
1712
|
+
error_results_are_failures=False,
|
|
1713
|
+
)
|
|
1714
|
+
|
|
1715
|
+
async def measure_entities(
|
|
1716
|
+
self,
|
|
1717
|
+
entities: list[str],
|
|
1718
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1719
|
+
"""Measure one or two selected entities through circuit breaker."""
|
|
1720
|
+
return await self._execute_with_circuit_breaker(
|
|
1721
|
+
"measure_entities",
|
|
1722
|
+
lambda: self.adapter.measure_entities(entities),
|
|
1723
|
+
input_dict={"entities": entities},
|
|
1724
|
+
error_results_are_failures=False,
|
|
1725
|
+
)
|
|
1726
|
+
|
|
1727
|
+
async def get_material(
|
|
1728
|
+
self,
|
|
1729
|
+
configuration: str | None = None,
|
|
1730
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1731
|
+
"""Read the material assigned to the active part through circuit breaker."""
|
|
1732
|
+
return await self._execute_with_circuit_breaker(
|
|
1733
|
+
"get_material",
|
|
1734
|
+
lambda: self.adapter.get_material(configuration),
|
|
1735
|
+
input_dict={"configuration": configuration},
|
|
1736
|
+
error_results_are_failures=False,
|
|
1737
|
+
)
|
|
1738
|
+
|
|
1739
|
+
async def set_material(
|
|
1740
|
+
self,
|
|
1741
|
+
material_name: str,
|
|
1742
|
+
database: str | None = None,
|
|
1743
|
+
configuration: str | None = None,
|
|
1744
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1745
|
+
"""Assign a material to the active part through circuit breaker."""
|
|
1746
|
+
return await self._execute_with_circuit_breaker(
|
|
1747
|
+
"set_material",
|
|
1748
|
+
lambda: self.adapter.set_material(material_name, database, configuration),
|
|
1749
|
+
input_dict={
|
|
1750
|
+
"material_name": material_name,
|
|
1751
|
+
"database": database,
|
|
1752
|
+
"configuration": configuration,
|
|
1753
|
+
},
|
|
1754
|
+
)
|
|
1755
|
+
|
|
1756
|
+
async def list_available_materials(
|
|
1757
|
+
self,
|
|
1758
|
+
search: str | None = None,
|
|
1759
|
+
) -> AdapterResult[list[dict[str, str]]]:
|
|
1760
|
+
"""List materials from the configured material libraries through circuit breaker."""
|
|
1761
|
+
return await self._execute_with_circuit_breaker(
|
|
1762
|
+
"list_available_materials",
|
|
1763
|
+
lambda: self.adapter.list_available_materials(search),
|
|
1764
|
+
input_dict={"search": search},
|
|
1765
|
+
error_results_are_failures=False,
|
|
1766
|
+
)
|
|
1767
|
+
|
|
1768
|
+
async def get_custom_properties(
|
|
1769
|
+
self,
|
|
1770
|
+
configuration: str | None = None,
|
|
1771
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1772
|
+
"""Read the custom properties of the active document through circuit breaker."""
|
|
1773
|
+
return await self._execute_with_circuit_breaker(
|
|
1774
|
+
"get_custom_properties",
|
|
1775
|
+
lambda: self.adapter.get_custom_properties(configuration),
|
|
1776
|
+
input_dict={"configuration": configuration},
|
|
1777
|
+
error_results_are_failures=False,
|
|
1778
|
+
)
|
|
1779
|
+
|
|
1780
|
+
async def set_custom_property(
|
|
1781
|
+
self,
|
|
1782
|
+
name: str,
|
|
1783
|
+
value: str,
|
|
1784
|
+
configuration: str | None = None,
|
|
1785
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1786
|
+
"""Create or overwrite one custom property through circuit breaker."""
|
|
1787
|
+
return await self._execute_with_circuit_breaker(
|
|
1788
|
+
"set_custom_property",
|
|
1789
|
+
lambda: self.adapter.set_custom_property(name, value, configuration),
|
|
1790
|
+
input_dict={"name": name, "value": value, "configuration": configuration},
|
|
1791
|
+
)
|
|
1792
|
+
|
|
1793
|
+
async def export_flat_pattern(
|
|
1794
|
+
self,
|
|
1795
|
+
file_path: str,
|
|
1796
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1797
|
+
"""Export the active sheet-metal part's flat pattern through circuit breaker."""
|
|
1798
|
+
return await self._execute_with_circuit_breaker(
|
|
1799
|
+
"export_flat_pattern",
|
|
1800
|
+
lambda: self.adapter.export_flat_pattern(file_path),
|
|
1801
|
+
input_dict={"file_path": file_path},
|
|
1802
|
+
)
|
|
1803
|
+
|
|
1804
|
+
async def insert_model_dimensions(
|
|
1805
|
+
self,
|
|
1806
|
+
view_name: str | None = None,
|
|
1807
|
+
include_hole_wizard: bool = True,
|
|
1808
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1809
|
+
"""Bring model dimensions into drawing views through circuit breaker."""
|
|
1810
|
+
return await self._execute_with_circuit_breaker(
|
|
1811
|
+
"insert_model_dimensions",
|
|
1812
|
+
lambda: self.adapter.insert_model_dimensions(
|
|
1813
|
+
view_name, include_hole_wizard
|
|
1814
|
+
),
|
|
1815
|
+
input_dict={
|
|
1816
|
+
"view_name": view_name,
|
|
1817
|
+
"include_hole_wizard": include_hole_wizard,
|
|
1818
|
+
},
|
|
1819
|
+
)
|
|
1820
|
+
|
|
1821
|
+
async def arrange_dimensions(
|
|
1822
|
+
self,
|
|
1823
|
+
view_name: str | None = None,
|
|
1824
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1825
|
+
"""Move drawing dimensions so none overlaps, then verify through circuit breaker."""
|
|
1826
|
+
return await self._execute_with_circuit_breaker(
|
|
1827
|
+
"arrange_dimensions",
|
|
1828
|
+
lambda: self.adapter.arrange_dimensions(view_name),
|
|
1829
|
+
input_dict={"view_name": view_name},
|
|
1830
|
+
)
|
|
1831
|
+
|
|
1832
|
+
async def insert_bom_table(
|
|
1833
|
+
self,
|
|
1834
|
+
view_name: str,
|
|
1835
|
+
template_path: str | None = None,
|
|
1836
|
+
bom_type: str = "top_level_only",
|
|
1837
|
+
x_mm: float | None = None,
|
|
1838
|
+
y_mm: float | None = None,
|
|
1839
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1840
|
+
"""Insert a BOM table anchored to an assembly drawing view through circuit breaker."""
|
|
1841
|
+
return await self._execute_with_circuit_breaker(
|
|
1842
|
+
"insert_bom_table",
|
|
1843
|
+
lambda: self.adapter.insert_bom_table(
|
|
1844
|
+
view_name, template_path, bom_type, x_mm, y_mm
|
|
1845
|
+
),
|
|
1846
|
+
input_dict={
|
|
1847
|
+
"view_name": view_name,
|
|
1848
|
+
"template_path": template_path,
|
|
1849
|
+
"bom_type": bom_type,
|
|
1850
|
+
"x_mm": x_mm,
|
|
1851
|
+
"y_mm": y_mm,
|
|
1852
|
+
},
|
|
1853
|
+
)
|
|
1854
|
+
|
|
1855
|
+
async def set_sheet_scale(
|
|
1856
|
+
self,
|
|
1857
|
+
numerator: float,
|
|
1858
|
+
denominator: float,
|
|
1859
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1860
|
+
"""Set the active drawing sheet's scale through circuit breaker."""
|
|
1861
|
+
return await self._execute_with_circuit_breaker(
|
|
1862
|
+
"set_sheet_scale",
|
|
1863
|
+
lambda: self.adapter.set_sheet_scale(numerator, denominator),
|
|
1864
|
+
input_dict={"numerator": numerator, "denominator": denominator},
|
|
1865
|
+
)
|
|
1866
|
+
|
|
1867
|
+
async def add_gtol(
|
|
1868
|
+
self,
|
|
1869
|
+
characteristic: str,
|
|
1870
|
+
tolerance: float,
|
|
1871
|
+
x_mm: float,
|
|
1872
|
+
y_mm: float,
|
|
1873
|
+
diameter_zone: bool = False,
|
|
1874
|
+
material_condition: str = "none",
|
|
1875
|
+
datums: list[str] | None = None,
|
|
1876
|
+
attach_at_mm: tuple[float, float] | None = None,
|
|
1877
|
+
dimension: tuple[str, str] | None = None,
|
|
1878
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1879
|
+
"""Add a GD&T feature control frame through circuit breaker."""
|
|
1880
|
+
return await self._execute_with_circuit_breaker(
|
|
1881
|
+
"add_gtol",
|
|
1882
|
+
lambda: self.adapter.add_gtol(
|
|
1883
|
+
characteristic,
|
|
1884
|
+
tolerance,
|
|
1885
|
+
x_mm,
|
|
1886
|
+
y_mm,
|
|
1887
|
+
diameter_zone,
|
|
1888
|
+
material_condition,
|
|
1889
|
+
datums,
|
|
1890
|
+
attach_at_mm,
|
|
1891
|
+
dimension,
|
|
1892
|
+
),
|
|
1893
|
+
input_dict={
|
|
1894
|
+
"characteristic": characteristic,
|
|
1895
|
+
"tolerance": tolerance,
|
|
1896
|
+
"x_mm": x_mm,
|
|
1897
|
+
"y_mm": y_mm,
|
|
1898
|
+
"diameter_zone": diameter_zone,
|
|
1899
|
+
"material_condition": material_condition,
|
|
1900
|
+
"datums": datums,
|
|
1901
|
+
"attach_at_mm": attach_at_mm,
|
|
1902
|
+
"dimension": dimension,
|
|
1903
|
+
},
|
|
1904
|
+
)
|
|
1905
|
+
|
|
1906
|
+
async def add_datum_tag(
|
|
1907
|
+
self,
|
|
1908
|
+
label: str,
|
|
1909
|
+
attach_at_mm: tuple[float, float],
|
|
1910
|
+
x_mm: float,
|
|
1911
|
+
y_mm: float,
|
|
1912
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1913
|
+
"""Add a datum feature symbol through circuit breaker."""
|
|
1914
|
+
return await self._execute_with_circuit_breaker(
|
|
1915
|
+
"add_datum_tag",
|
|
1916
|
+
lambda: self.adapter.add_datum_tag(label, attach_at_mm, x_mm, y_mm),
|
|
1917
|
+
input_dict={
|
|
1918
|
+
"label": label,
|
|
1919
|
+
"attach_at_mm": attach_at_mm,
|
|
1920
|
+
"x_mm": x_mm,
|
|
1921
|
+
"y_mm": y_mm,
|
|
1922
|
+
},
|
|
1923
|
+
)
|
|
1924
|
+
|
|
1925
|
+
async def create_standard_drawing(
|
|
1926
|
+
self,
|
|
1927
|
+
model_path: str,
|
|
1928
|
+
output_dir: str | None = None,
|
|
1929
|
+
title: str | None = None,
|
|
1930
|
+
subtitle: str | None = None,
|
|
1931
|
+
drawing_number: str | None = None,
|
|
1932
|
+
author: str = "",
|
|
1933
|
+
revision: str = "A",
|
|
1934
|
+
company: str = "",
|
|
1935
|
+
drawn_date: str | None = None,
|
|
1936
|
+
notes: list[str] | None = None,
|
|
1937
|
+
include_isometric: bool = True,
|
|
1938
|
+
write_model_properties: bool = True,
|
|
1939
|
+
title_block_text_height_mm: float | None = None,
|
|
1940
|
+
include_bom: bool = False,
|
|
1941
|
+
) -> AdapterResult[dict[str, Any]]:
|
|
1942
|
+
"""Create a complete standard A3 drawing through circuit breaker."""
|
|
1943
|
+
return await self._execute_with_circuit_breaker(
|
|
1944
|
+
"create_standard_drawing",
|
|
1945
|
+
lambda: self.adapter.create_standard_drawing(
|
|
1946
|
+
model_path,
|
|
1947
|
+
output_dir,
|
|
1948
|
+
title,
|
|
1949
|
+
subtitle,
|
|
1950
|
+
drawing_number,
|
|
1951
|
+
author,
|
|
1952
|
+
revision,
|
|
1953
|
+
company,
|
|
1954
|
+
drawn_date,
|
|
1955
|
+
notes,
|
|
1956
|
+
include_isometric,
|
|
1957
|
+
write_model_properties,
|
|
1958
|
+
title_block_text_height_mm,
|
|
1959
|
+
include_bom,
|
|
1960
|
+
),
|
|
1961
|
+
input_dict={
|
|
1962
|
+
"model_path": model_path,
|
|
1963
|
+
"output_dir": output_dir,
|
|
1964
|
+
"title": title,
|
|
1965
|
+
"subtitle": subtitle,
|
|
1966
|
+
"drawing_number": drawing_number,
|
|
1967
|
+
"author": author,
|
|
1968
|
+
"revision": revision,
|
|
1969
|
+
"company": company,
|
|
1970
|
+
"drawn_date": drawn_date,
|
|
1971
|
+
"notes": notes,
|
|
1972
|
+
"include_isometric": include_isometric,
|
|
1973
|
+
"write_model_properties": write_model_properties,
|
|
1974
|
+
"title_block_text_height_mm": title_block_text_height_mm,
|
|
1975
|
+
"include_bom": include_bom,
|
|
1976
|
+
},
|
|
1977
|
+
)
|
|
1978
|
+
|
|
1979
|
+
|
|
1980
|
+
class CircuitBreaker:
|
|
1981
|
+
"""Legacy standalone circuit breaker class expected by tests.
|
|
1982
|
+
|
|
1983
|
+
Args:
|
|
1984
|
+
failure_threshold (int): The failure threshold value. Defaults to 5.
|
|
1985
|
+
recovery_timeout (float): The recovery timeout value. Defaults to 60.0.
|
|
1986
|
+
expected_exception (type[Exception]): The expected exception value. Defaults to
|
|
1987
|
+
Exception.
|
|
1988
|
+
|
|
1989
|
+
Attributes:
|
|
1990
|
+
expected_exception (Any): The expected exception value.
|
|
1991
|
+
failure_count (Any): The failure count value.
|
|
1992
|
+
failure_threshold (Any): The failure threshold value.
|
|
1993
|
+
last_failure_time (Any): The last failure time value.
|
|
1994
|
+
recovery_timeout (Any): The recovery timeout value.
|
|
1995
|
+
state (Any): The state value.
|
|
1996
|
+
"""
|
|
1997
|
+
|
|
1998
|
+
def __init__(
|
|
1999
|
+
self,
|
|
2000
|
+
failure_threshold: int = 5,
|
|
2001
|
+
recovery_timeout: float = 60.0,
|
|
2002
|
+
expected_exception: type[Exception] = Exception,
|
|
2003
|
+
) -> None:
|
|
2004
|
+
"""Initialize the circuit breaker.
|
|
2005
|
+
|
|
2006
|
+
Args:
|
|
2007
|
+
failure_threshold (int): The failure threshold value. Defaults to 5.
|
|
2008
|
+
recovery_timeout (float): The recovery timeout value. Defaults to 60.0.
|
|
2009
|
+
expected_exception (type[Exception]): The expected exception value. Defaults to
|
|
2010
|
+
Exception.
|
|
2011
|
+
|
|
2012
|
+
Returns:
|
|
2013
|
+
None: None.
|
|
2014
|
+
"""
|
|
2015
|
+
self.failure_threshold = failure_threshold
|
|
2016
|
+
self.recovery_timeout = recovery_timeout
|
|
2017
|
+
self.expected_exception = expected_exception
|
|
2018
|
+
self.state = CircuitState.CLOSED
|
|
2019
|
+
self.failure_count = 0
|
|
2020
|
+
self.last_failure_time = 0.0
|
|
2021
|
+
|
|
2022
|
+
async def call(self, operation: Callable[[], object | Awaitable[object]]) -> object:
|
|
2023
|
+
"""Provide call support for the circuit breaker.
|
|
2024
|
+
|
|
2025
|
+
Args:
|
|
2026
|
+
operation (Callable[[], object | Awaitable[object]]): Callable object executed by
|
|
2027
|
+
the helper.
|
|
2028
|
+
|
|
2029
|
+
Returns:
|
|
2030
|
+
object: The result produced by the operation.
|
|
2031
|
+
|
|
2032
|
+
Raises:
|
|
2033
|
+
Exception: Circuit breaker is open.
|
|
2034
|
+
"""
|
|
2035
|
+
if self.state == CircuitState.OPEN:
|
|
2036
|
+
if time.time() - self.last_failure_time < self.recovery_timeout:
|
|
2037
|
+
raise Exception("Circuit breaker is open")
|
|
2038
|
+
self.state = CircuitState.HALF_OPEN
|
|
2039
|
+
|
|
2040
|
+
try:
|
|
2041
|
+
result = operation()
|
|
2042
|
+
if asyncio.iscoroutine(result):
|
|
2043
|
+
result = await result
|
|
2044
|
+
self.state = CircuitState.CLOSED
|
|
2045
|
+
self.failure_count = 0
|
|
2046
|
+
return result
|
|
2047
|
+
except self.expected_exception:
|
|
2048
|
+
self.failure_count += 1
|
|
2049
|
+
self.last_failure_time = time.time()
|
|
2050
|
+
if self.failure_count >= self.failure_threshold:
|
|
2051
|
+
self.state = CircuitState.OPEN
|
|
2052
|
+
raise
|