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,17 @@
|
|
|
1
|
+
"""Grouped SolidWorks mixins for the PyWin32 adapter."""
|
|
2
|
+
|
|
3
|
+
from .documentation import SolidWorksDocumentationMixin
|
|
4
|
+
from .drawing_standard import SolidWorksDrawingStandardMixin
|
|
5
|
+
from .features import SolidWorksFeaturesMixin
|
|
6
|
+
from .io import SolidWorksIOMixin
|
|
7
|
+
from .selection import SolidWorksSelectionMixin
|
|
8
|
+
from .sketch import SolidWorksSketchMixin
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"SolidWorksDocumentationMixin",
|
|
12
|
+
"SolidWorksDrawingStandardMixin",
|
|
13
|
+
"SolidWorksFeaturesMixin",
|
|
14
|
+
"SolidWorksIOMixin",
|
|
15
|
+
"SolidWorksSelectionMixin",
|
|
16
|
+
"SolidWorksSketchMixin",
|
|
17
|
+
]
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Turn ``IModelDoc2.SaveBMP`` output into PNG/JPEG and read image sizes.
|
|
2
|
+
|
|
3
|
+
``SaveBMP(path, width, height)`` is the only SolidWorks call that renders the
|
|
4
|
+
view at a caller-chosen pixel size; ``SaveAs3`` to ``.png`` always writes the
|
|
5
|
+
window's size. It only writes BMP, so a PNG is produced here with the
|
|
6
|
+
standard library (``zlib``) - Pillow is an optional extra and absent from the
|
|
7
|
+
server's environment. JPEG needs Pillow and reports ``ImportError`` without
|
|
8
|
+
it, so the caller can fall back.
|
|
9
|
+
|
|
10
|
+
``SaveBMP`` keeps the window's framing, so an image whose aspect differs
|
|
11
|
+
from the window's crops the zoomed-to-fit model. The caller therefore
|
|
12
|
+
renders at the window's aspect and :func:`pad` extends the picture to the
|
|
13
|
+
requested size by repeating its edge pixels (the background).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import struct
|
|
19
|
+
import zlib
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def read_bmp(path: str | Path) -> tuple[int, int, list[bytes]]:
|
|
24
|
+
"""Read an uncompressed 24/32-bit BMP.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
tuple[int, int, list[bytes]]: Width, height and the RGB rows, top
|
|
28
|
+
row first.
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
ValueError: When the file is not an uncompressed 24/32-bit BMP.
|
|
32
|
+
"""
|
|
33
|
+
data = Path(path).read_bytes()
|
|
34
|
+
if len(data) < 54 or data[:2] != b"BM":
|
|
35
|
+
raise ValueError(f"{path} is not a BMP file")
|
|
36
|
+
offset = struct.unpack_from("<I", data, 10)[0]
|
|
37
|
+
width, height = struct.unpack_from("<ii", data, 18)
|
|
38
|
+
bits, compression = struct.unpack_from("<HI", data, 28)
|
|
39
|
+
# BI_RGB (0), or BI_BITFIELDS (3) with the standard BGRA masks at 32 bpp.
|
|
40
|
+
if bits not in (24, 32) or compression not in (0, 3) or width <= 0:
|
|
41
|
+
raise ValueError(
|
|
42
|
+
f"{path}: unsupported BMP ({bits} bpp, compression {compression})"
|
|
43
|
+
)
|
|
44
|
+
step = bits // 8
|
|
45
|
+
stride = (width * step + 3) & ~3
|
|
46
|
+
top_down = height < 0
|
|
47
|
+
height = abs(height)
|
|
48
|
+
rows = []
|
|
49
|
+
for index in range(height):
|
|
50
|
+
start = offset + index * stride
|
|
51
|
+
bgr = data[start : start + width * step]
|
|
52
|
+
rgb = bytearray(width * 3)
|
|
53
|
+
rgb[0::3] = bgr[2::step]
|
|
54
|
+
rgb[1::3] = bgr[1::step]
|
|
55
|
+
rgb[2::3] = bgr[0::step]
|
|
56
|
+
rows.append(bytes(rgb))
|
|
57
|
+
if not top_down:
|
|
58
|
+
rows.reverse()
|
|
59
|
+
return width, height, rows
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def fit_aspect(width: int, height: int, aspect: float) -> tuple[int, int]:
|
|
63
|
+
"""Largest ``aspect`` (width / height) size that fits in ``width`` x ``height``."""
|
|
64
|
+
if width / height > aspect:
|
|
65
|
+
return max(1, round(height * aspect)), height
|
|
66
|
+
return width, max(1, round(width / aspect))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def pad(
|
|
70
|
+
width: int, height: int, rows: list[bytes], target: tuple[int, int]
|
|
71
|
+
) -> list[bytes]:
|
|
72
|
+
"""Centre RGB ``rows`` in a ``target`` canvas, repeating the edge pixels.
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
ValueError: When the image is larger than ``target``.
|
|
76
|
+
"""
|
|
77
|
+
target_w, target_h = target
|
|
78
|
+
if width > target_w or height > target_h:
|
|
79
|
+
raise ValueError(f"{width}x{height} does not fit in {target_w}x{target_h}")
|
|
80
|
+
left = (target_w - width) // 2
|
|
81
|
+
right = target_w - width - left
|
|
82
|
+
widened = [row[:3] * left + row + row[-3:] * right for row in rows]
|
|
83
|
+
top = (target_h - height) // 2
|
|
84
|
+
bottom = target_h - height - top
|
|
85
|
+
return [widened[0]] * top + widened + [widened[-1]] * bottom
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def write_bmp(path: str | Path, width: int, height: int, rows: list[bytes]) -> None:
|
|
89
|
+
"""Write RGB rows (top first) as a bottom-up 24-bit BMP."""
|
|
90
|
+
stride = (width * 3 + 3) & ~3
|
|
91
|
+
body = bytearray()
|
|
92
|
+
for row in reversed(rows):
|
|
93
|
+
bgr = bytearray(width * 3)
|
|
94
|
+
bgr[0::3] = row[2::3]
|
|
95
|
+
bgr[1::3] = row[1::3]
|
|
96
|
+
bgr[2::3] = row[0::3]
|
|
97
|
+
body += bgr + b"\x00" * (stride - width * 3)
|
|
98
|
+
header = struct.pack(
|
|
99
|
+
"<2sIHHIIiiHHIIiiII",
|
|
100
|
+
*(b"BM", 54 + len(body), 0, 0, 54, 40, width, height, 1, 24, 0),
|
|
101
|
+
*(len(body), 2835, 2835, 0, 0),
|
|
102
|
+
)
|
|
103
|
+
Path(path).write_bytes(header + bytes(body))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def write_png(path: str | Path, width: int, height: int, rows: list[bytes]) -> None:
|
|
107
|
+
"""Write 8-bit RGB rows (top first) as a PNG."""
|
|
108
|
+
|
|
109
|
+
def chunk(kind: bytes, body: bytes) -> bytes:
|
|
110
|
+
return (
|
|
111
|
+
struct.pack(">I", len(body))
|
|
112
|
+
+ kind
|
|
113
|
+
+ body
|
|
114
|
+
+ struct.pack(">I", zlib.crc32(kind + body) & 0xFFFFFFFF)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
raw = b"".join(b"\x00" + row for row in rows)
|
|
118
|
+
Path(path).write_bytes(
|
|
119
|
+
b"\x89PNG\r\n\x1a\n"
|
|
120
|
+
+ chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0))
|
|
121
|
+
+ chunk(b"IDAT", zlib.compress(raw, 6))
|
|
122
|
+
+ chunk(b"IEND", b"")
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def convert_bmp(
|
|
127
|
+
source: str | Path, target: str | Path, size: tuple[int, int] | None = None
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Convert a BMP to the format named by ``target``'s extension.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
source: The BMP ``SaveBMP`` wrote.
|
|
133
|
+
target: Output path; ``.png``, ``.bmp``, ``.jpg`` or ``.jpeg``.
|
|
134
|
+
size: Pad the picture to this ``(width, height)`` (see :func:`pad`).
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
ImportError: For JPEG when Pillow is not installed.
|
|
138
|
+
ValueError: For any other extension, a BMP that :func:`read_bmp`
|
|
139
|
+
cannot read, or one larger than ``size``.
|
|
140
|
+
"""
|
|
141
|
+
suffix = Path(target).suffix.lower()
|
|
142
|
+
if suffix not in (".png", ".bmp", ".jpg", ".jpeg"):
|
|
143
|
+
raise ValueError(f"Cannot convert a BMP to {suffix!r}")
|
|
144
|
+
width, height, rows = read_bmp(source)
|
|
145
|
+
if size is not None:
|
|
146
|
+
rows = pad(width, height, rows, size)
|
|
147
|
+
width, height = size
|
|
148
|
+
if suffix == ".png":
|
|
149
|
+
write_png(target, width, height, rows)
|
|
150
|
+
elif suffix == ".bmp":
|
|
151
|
+
write_bmp(target, width, height, rows)
|
|
152
|
+
else:
|
|
153
|
+
from PIL import Image
|
|
154
|
+
|
|
155
|
+
image = Image.frombytes("RGB", (width, height), b"".join(rows))
|
|
156
|
+
image.save(target, "JPEG", quality=92)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def image_size(path: str | Path) -> tuple[int, int] | None:
|
|
160
|
+
"""Return ``(width, height)`` of a PNG, BMP or JPEG, or ``None``."""
|
|
161
|
+
try:
|
|
162
|
+
data = Path(path).read_bytes()
|
|
163
|
+
except OSError:
|
|
164
|
+
return None
|
|
165
|
+
if data[:8] == b"\x89PNG\r\n\x1a\n" and len(data) >= 24:
|
|
166
|
+
return struct.unpack_from(">II", data, 16)
|
|
167
|
+
if data[:2] == b"BM" and len(data) >= 26:
|
|
168
|
+
width, height = struct.unpack_from("<ii", data, 18)
|
|
169
|
+
return width, abs(height)
|
|
170
|
+
if data[:2] == b"\xff\xd8":
|
|
171
|
+
index = 2
|
|
172
|
+
while index + 9 <= len(data) and data[index] == 0xFF:
|
|
173
|
+
marker = data[index + 1]
|
|
174
|
+
length = struct.unpack_from(">H", data, index + 2)[0]
|
|
175
|
+
# SOF0..SOF15 carry the frame size; C4/C8/CC are not frames.
|
|
176
|
+
if 0xC0 <= marker <= 0xCF and marker not in (0xC4, 0xC8, 0xCC):
|
|
177
|
+
height, width = struct.unpack_from(">HH", data, index + 5)
|
|
178
|
+
return width, height
|
|
179
|
+
index += 2 + length
|
|
180
|
+
return None
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Small COM helpers shared by the selection / face / rebuild modules.
|
|
2
|
+
|
|
3
|
+
One copy of each, so error-handling and flagging behaviour cannot drift
|
|
4
|
+
between modules.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .. import sw_type_info as _sw_type_info
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def attempt(adapter: Any, fn: Any, default: Any = None) -> Any:
|
|
15
|
+
"""Run ``fn`` through the adapter's ``_attempt`` (exceptions -> default)."""
|
|
16
|
+
return adapter._attempt(fn, default=default)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def flag(adapter: Any, obj: Any, *names: str) -> None:
|
|
20
|
+
"""Flag just ``names`` as methods on ``obj`` (no-op for ``None``/fakes).
|
|
21
|
+
|
|
22
|
+
Flagging a whole interface costs ~27 ms per fresh dispatch (see
|
|
23
|
+
``_WALK_MEMBERS`` in pywin32_adapter); SolidWorks hands back a fresh
|
|
24
|
+
dispatch on most calls, so flag only what is about to be read.
|
|
25
|
+
"""
|
|
26
|
+
attempt(adapter, lambda: _sw_type_info.flag_members(obj, *names), 0)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def as_list(value: Any) -> list[Any]:
|
|
30
|
+
"""Normalise a COM array/scalar/None return into a list."""
|
|
31
|
+
if value is None:
|
|
32
|
+
return []
|
|
33
|
+
if isinstance(value, (list, tuple)):
|
|
34
|
+
return list(value)
|
|
35
|
+
return [value]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def selection_count(adapter: Any, model: Any) -> int | None:
|
|
39
|
+
"""Number of entities currently selected in ``model``, or None."""
|
|
40
|
+
sel_mgr = attempt(adapter, lambda: model.SelectionManager)
|
|
41
|
+
if sel_mgr is None:
|
|
42
|
+
return None
|
|
43
|
+
flag(adapter, sel_mgr, "GetSelectedObjectCount2")
|
|
44
|
+
count = attempt(adapter, lambda: sel_mgr.GetSelectedObjectCount2(-1))
|
|
45
|
+
return int(count) if isinstance(count, (int, float)) else None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def strip_op_prefix(error: str | None, operation_name: str) -> str | None:
|
|
49
|
+
"""Drop ``_handle_com_operation``'s generic ``"Error in <op>: "`` prefix.
|
|
50
|
+
|
|
51
|
+
Expected failures (not found, nothing to select, rebuild errors) then read
|
|
52
|
+
as plain sentences for the LLM caller.
|
|
53
|
+
"""
|
|
54
|
+
if not error:
|
|
55
|
+
return error
|
|
56
|
+
return error.removeprefix(f"Error in {operation_name}: ")
|