Skip to content

solidworks_mcp.adapters.pywin32_adapter

solidworks_mcp.adapters.pywin32_adapter

PyWin32 SolidWorks adapter for Windows COM integration.

This adapter uses pywin32 to communicate with SolidWorks via COM, providing real SolidWorks automation capabilities on Windows platforms.

Attributes

PYWIN32_AVAILABLE module-attribute

PYWIN32_AVAILABLE = True

T module-attribute

T = TypeVar('T')

_DEFAULT_COM_SETTLE_SECONDS module-attribute

_DEFAULT_COM_SETTLE_SECONDS = 0.25

_DEFAULT_DOC_SETTLE_SECONDS module-attribute

_DEFAULT_DOC_SETTLE_SECONDS = 1.0

_DOCUMENT_OPERATIONS module-attribute

_DOCUMENT_OPERATIONS = frozenset({'activate_document', 'close_model', 'create_assembly', 'create_drawing', 'create_part', 'create_technical_drawing', 'open_model', 'pack_and_go_assembly', 'save_body_as_part', 'standard_drawing_views'})

_OLE2_MAGIC module-attribute

_OLE2_MAGIC = b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1'

_SW_RUN_MACRO_ERROR module-attribute

_SW_RUN_MACRO_ERROR = {0: 'NoError', 1: 'InvalidArg', 2: 'MacrosAreDisabled', 3: 'NotInDesignMode', 4: 'OnlyCodeModules', 5: 'OutOfMemory', 6: 'InvalidProcname', 7: 'InvalidPropertyType', 8: 'SuborfuncExpected', 9: 'BadParmCount', 10: 'BadVarType', 11: 'UserInterrupt', 12: 'Exception', 13: 'Overflow', 14: 'TypeMismatch', 15: 'ParmNotOptional', 16: 'UnknownLcid', 17: 'Busy', 18: 'ConnectionTerminated', 19: 'CallRejected', 20: 'CallFailed', 21: 'Zombied', 22: 'Invalidindex', 23: 'NoPermission', 24: 'Reverted', 25: 'TooManyOpenFiles', 26: 'DiskError', 27: 'CantSave', 28: 'OpenFileFailed'}

_WALK_MEMBERS module-attribute

_WALK_MEMBERS = ('GetNextFeature', 'GetTypeName2', 'IsSuppressed', 'IsSuppressed2')

Classes

AdapterHealth

Bases: BaseModel

Health status information for adapters.

Attributes:

Name Type Description
average_response_time float

The average response time value.

connection_status str

The connection status value.

error_count int

The error count value.

healthy bool

The healthy value.

last_check datetime

The last check value.

metrics dict[str, Any] | None

The metrics value.

success_count int

The success count value.

Methods:
__contains__
__contains__(key: str) -> bool

Build internal contains.

Parameters:

Name Type Description Default
key str

The key value.

required

Returns:

Name Type Description
bool bool

True if contains, otherwise False.

Source code in src/solidworks_mcp/adapters/base.py
def __contains__(self, key: str) -> bool:
    """Build internal contains.

    Args:
        key (str): The key value.

    Returns:
        bool: True if contains, otherwise False.
    """
    legacy_keys = {"status", "connected", "adapter_type", "version", "uptime"}
    if key in legacy_keys:
        return True
    return key in self.model_dump()
__getitem__
__getitem__(key: str) -> Any

Build internal getitem.

Parameters:

Name Type Description Default
key str

The key value.

required

Returns:

Name Type Description
Any Any

The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
def __getitem__(self, key: str) -> Any:
    """Build internal getitem.

    Args:
        key (str): The key value.

    Returns:
        Any: The result produced by the operation.
    """
    if key == "status":
        return "healthy" if self.healthy else "unhealthy"
    if key == "connected":
        return self.connection_status == "connected"
    if key == "adapter_type":
        return (self.metrics or {}).get("adapter_type")
    if key == "version":
        return (self.metrics or {}).get("version", "mock-1.0")
    if key == "uptime":
        return (self.metrics or {}).get("uptime", 0.0)
    return self.model_dump().get(key)

AdapterResult dataclass

AdapterResult(status: AdapterResultStatus, data: T | None = None, error: str | None = None, execution_time: float | None = None, metadata: dict[str, Any] | None = None)

Result wrapper for adapter operations.

Attributes:

Name Type Description
data T | None

The data value.

error str | None

The error value.

execution_time float | None

The execution time value.

metadata dict[str, Any] | None

The metadata value.

status AdapterResultStatus

The status value.

Attributes
is_error property
is_error: bool

Check if operation had an error.

Returns:

Name Type Description
bool bool

True if error, otherwise False.

is_success property
is_success: bool

Check if operation was successful.

Returns:

Name Type Description
bool bool

True if success, otherwise False.

AdapterResultStatus

Bases: StrEnum

Result status for adapter operations.

Attributes:

Name Type Description
ERROR Any

The error value.

SUCCESS Any

The success value.

TIMEOUT Any

The timeout value.

WARNING Any

The warning value.

PyWin32Adapter

PyWin32Adapter(config: dict[str, Any] | None = None)

Bases: SolidWorksDrawingStandardMixin, SolidWorksDocumentationMixin, SolidWorksSketchMixin, SolidWorksFeaturesMixin, SolidWorksIOMixin, SolidWorksSelectionMixin, SolidWorksAdapter

SolidWorks adapter using pywin32 COM integration.

This adapter provides direct COM integration with SolidWorks using pywin32, enabling real-time automation and control of SolidWorks applications on Windows.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Configuration values for the operation. Defaults to None.

None

Raises:

Type Description
SolidWorksMCPError

PyWin32Adapter requires Windows platform.

Attributes:

Name Type Description
constants Any

The constants value.

Example
adapter = PyWin32Adapter({'timeout': 30})
result = await adapter.connect()
if result.status == AdapterResultStatus.SUCCESS:
    print("Connected to SolidWorks successfully")

Initialize PyWin32Adapter with configuration.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Configuration values for the operation. Defaults to None.

None

Returns:

Name Type Description
None None

None.

Raises:

Type Description
SolidWorksMCPError

PyWin32Adapter requires Windows platform.

Example
config = {
    "timeout": 30,
    "auto_connect": True,
    "startup_timeout": 60
}
adapter = PyWin32Adapter(config)
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def __init__(self, config: dict[str, Any] | None = None) -> None:
    """Initialize PyWin32Adapter with configuration.

    Args:
        config (dict[str, Any] | None): Configuration values for the operation. Defaults to
                                        None.

    Returns:
        None: None.

    Raises:
        SolidWorksMCPError: PyWin32Adapter requires Windows platform.

    Example:
                        ```python
                        config = {
                            "timeout": 30,
                            "auto_connect": True,
                            "startup_timeout": 60
                        }
                        adapter = PyWin32Adapter(config)
                        ```
    """
    if not PYWIN32_AVAILABLE:
        raise SolidWorksMCPError(
            "pywin32 is not available. Install with: pip install pywin32"
        )

    if platform.system() != "Windows":
        raise SolidWorksMCPError("PyWin32Adapter requires Windows platform")

    super().__init__(config)

    self.swApp: Any | None = None
    self.currentModel: Any | None = None
    self.currentSketch: Any | None = None
    self.currentSketchManager: Any | None = None
    self._last_sketch_name: str | None = None
    self._sketch_count: int = 0  # incremented each time a sketch is created
    self._sketch_entities: dict[str, Any] = {}
    # Cached (center_x_mm, center_y_mm) for entities whose center can't be
    # recovered via ``GetCenterPoint`` — currently polygons, which register
    # as a SAFEARRAY of segment handles (no single dispatch to read from).
    # ``sketch_circular_pattern`` reads this to derive the seed-to-axis
    # offset for polygon seeds.
    self._sketch_entity_centers: dict[str, tuple[float, float]] = {}
    self._sketch_entity_counter = 0
    self._com_initialized = False
    # Thread that initialised the COM apartment (see _handle_com_operation).
    self._com_thread_id: int | None = None

    # COM constants (equivalent to SolidWorks API constants)
    self.constants = {
        # Document types
        "swDocPART": 1,
        "swDocASSEMBLY": 2,
        "swDocDRAWING": 3,
        # Selection types
        "swSelFACES": 1,
        "swSelEDGES": 2,
        "swSelVERTICES": 3,
        "swSelSKETCHSEGS": 4,
        "swSelSKETCHPOINTS": 5,
        "swSelDATUMPLANES": 6,
        # Feature end conditions
        "swEndCondBlind": 0,
        "swEndCondThroughAll": 1,
        "swEndCondUpToNext": 2,
        "swEndCondUpToSurface": 3,
        "swEndCondOffset": 4,
        "swEndCondUpToVertex": 5,
        "swEndCondMidPlane": 6,
        "swEndCondThroughAllBoth": 9,
        # Dimension preferences / directions
        "swInputDimValOnCreate": 10,
        "swSketchAcceptNumericInput": 372,
        "swSketchCreateDimensionOnlyWhenEntered": 520,
        "swScaleSketchOnFirstDimension": 642,
        "swSmartDimensionDirectionRight": 0,
        "swSmartDimensionDirectionUp": 1,
        "swSmartDimensionDirectionLeft": 2,
        "swSmartDimensionDirectionDown": 3,
    }

    self._session_coordinator = _ComSessionCoordinator(self)
    self._sketch_geometry = _SketchGeometryService(self)
    self._document_routing = _DocumentRoutingService(self)
    self._feature_selector = _FeatureSelectionService(self)
Methods:
connect async
connect() -> None

Connect to SolidWorks COM and prepare automation-safe session state.

Raises:

Type Description
SolidWorksMCPError

If connection or readiness checks fail.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
async def connect(self) -> None:
    """Connect to SolidWorks COM and prepare automation-safe session state.

    Raises:
        SolidWorksMCPError: If connection or readiness checks fail.
    """
    await self._session_coordinator.connect()
disconnect async
disconnect() -> None

Disconnect from SolidWorks application.

Properly disconnects from SolidWorks COM interface and cleans up resources. This method should always be called when finished to prevent memory leaks.

Note: - Clears references to current model and application - Uninitialize COM apartment - Does not close SolidWorks application itself

Returns:

Name Type Description
None None

None.

Example
try:
    await adapter.connect()
    # ... do work ...
finally:
    await adapter.disconnect()
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
async def disconnect(self) -> None:
    """Disconnect from SolidWorks application.

    Properly disconnects from SolidWorks COM interface and cleans up resources. This method
    should always be called when finished to prevent memory leaks.

    Note: - Clears references to current model and application - Uninitialize COM apartment
    - Does not close SolidWorks application itself

    Returns:
        None: None.

    Example:
                        ```python
                        try:
                            await adapter.connect()
                            # ... do work ...
                        finally:
                            await adapter.disconnect()
                        ```
    """
    await self._session_coordinator.disconnect()
execute_macro async
execute_macro(params: dict[str, Any]) -> AdapterResult[dict[str, Any]]

Provide execute macro support for the py win32 adapter.

Parameters:

Name Type Description Default
params dict[str, Any]

The params value.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The result produced by the operation.

Raises:

Type Description
SolidWorksMCPError

If the operation cannot be completed.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
async def execute_macro(
    self, params: dict[str, Any]
) -> AdapterResult[dict[str, Any]]:
    """Provide execute macro support for the py win32 adapter.

    Args:
        params (dict[str, Any]): The params value.

    Returns:
        AdapterResult[dict[str, Any]]: The result produced by the operation.

    Raises:
        SolidWorksMCPError: If the operation cannot be completed.
    """
    macro_path = params.get("macro_path") or params.get("macro_file") or ""
    if not macro_path:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No macro_path provided"
        )
    if not os.path.isfile(macro_path):
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=f"Macro file not found: {macro_path}",
        )

    def _run() -> dict[str, Any]:
        """Resolve module/proc names and delegate to _invoke_run_macro2.

        Returns:
            dict[str, Any]: A dictionary containing the resulting values.

        Raises:
            SolidWorksMCPError: If the operation cannot be completed.
        """
        module_name = _parse_vb_module_name(macro_path)
        proc_name = params.get("proc_name", "main")
        return self._invoke_run_macro2(macro_path, module_name, proc_name)

    return self._handle_com_operation("execute_macro", _run)
export_file async
export_file(file_path: str, format_type: str) -> AdapterResult[None]

Export the current model to a file.

Parameters:

Name Type Description Default
file_path str

Path to the target file.

required
format_type str

The format type value.

required

Returns:

Type Description
AdapterResult[None]

AdapterResult[None]: The result produced by the operation.

Raises:

Type Description
Exception

If the operation cannot be completed.

RuntimeError

No active SolidWorks document for export.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
async def export_file(
    self, file_path: str, format_type: str
) -> AdapterResult[None]:
    """Export the current model to a file.

    Args:
        file_path (str): Path to the target file.
        format_type (str): The format type value.

    Returns:
        AdapterResult[None]: The result produced by the operation.

    Raises:
        Exception: If the operation cannot be completed.
        RuntimeError: No active SolidWorks document for export.
    """
    if not self.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    def _export_operation() -> None:
        """Build internal export operation.

        Returns:
            None: None.

        Raises:
            Exception: If the operation cannot be completed.
            RuntimeError: No active SolidWorks document for export.
        """
        format_map = {
            "step": 0,  # swSaveAsSTEP
            "iges": 1,  # swSaveAsIGS
            "stl": 2,  # swSaveAsSTL
            "pdf": 3,  # swSaveAsPDF
            "dwg": 4,  # swSaveAsDWG
            "jpg": 5,  # swSaveAsJPEG
            "glb": 41,  # swSaveAsGLTF (binary GLTF, SW 2023+)
            "gltf": 41,  # same enum value, text GLTF
        }

        format_lower = format_type.lower()
        if format_lower not in format_map:
            raise Exception(f"Unsupported export format: {format_type}")

        resolved_path = os.path.abspath(file_path)
        os.makedirs(os.path.dirname(resolved_path), exist_ok=True)

        if os.path.exists(resolved_path):
            self._attempt(lambda: os.remove(resolved_path))

        # Prefer swApp.ActiveDoc — more reliably typed than the late-bound
        # IDispatch reference stored in self.currentModel after OpenDoc6.
        # Use getattr so tests can pass a SimpleNamespace without ActiveDoc.
        target_doc = (
            getattr(self.swApp, "ActiveDoc", None) if self.swApp else None
        ) or self.currentModel

        # ----------------------------------------------------------------
        # STL export: use Extension.SaveAs2 + ISTLExportData
        # for both parts AND assemblies.  SaveAs3 with format=2 works for
        # parts but is unreliable for assemblies (only exports first body).
        # ----------------------------------------------------------------
        if format_lower == "stl":
            # For assemblies, resolve lightweight components first so all
            # geometry is available for the mesh export.
            self._attempt(lambda: target_doc.ResolveAllLightweightComponents(True))  # type: ignore[union-attr]

            ext = getattr(target_doc, "Extension", None)
            if ext is None:
                raise RuntimeError("No Extension object available for STL export")

            stl_data = self._prepare_stl_export_data()
            if not self._save_stl_with_extension(ext, stl_data, resolved_path):
                # SaveAs2 didn't produce file — try SaveAs3 fallback
                self._save_stl_with_fallback(target_doc, resolved_path)

            return None

        # ----------------------------------------------------------------
        # All other formats — classic SaveAs3 path.
        # SaveAs3 signature: SaveAs3(FileName, Version, Options)
        # Version = 0 means "current version" (swSaveAsCurrentVersion).
        # SolidWorks infers the export format from the file extension, so
        # we must NOT pass the format-enum value as the Version argument.
        # ----------------------------------------------------------------
        _ = format_map[format_lower]  # validate format is known; value unused
        logger.debug(
            "[pywin32.export_file] SaveAs3 {} (version=0, options=Silent)",
            resolved_path,
        )
        success = target_doc.SaveAs3(  # type: ignore[union-attr]
            resolved_path,
            0,  # swSaveAsCurrentVersion — format inferred from file extension
            2,  # swSaveAsOptions_Silent
        )

        if not success and not os.path.exists(resolved_path):
            raise Exception(
                f"SaveAs3 returned False and no file produced: {resolved_path}"
            )

        return None

    return self._handle_com_operation("export_file", _export_operation)
export_image async
export_image(payload: dict) -> AdapterResult[dict]

Export a screenshot of the current model to a PNG/JPG file.

Payload keys (matching ExportImageInput): file_path (str): Output path including extension. format_type (str): "png" or "jpg". Default "png". width (int): Pixel width. Default 1280. height (int): Pixel height. Default 720. view_orientation (str): "front" | "top" | "right" | "isometric" | "current".

Parameters:

Name Type Description Default
payload dict

The payload value.

required

Returns:

Type Description
AdapterResult[dict]

AdapterResult[dict]: The result produced by the operation.

Raises:

Type Description
RuntimeError

If the operation cannot be completed.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
async def export_image(self, payload: dict) -> AdapterResult[dict]:
    """Export a screenshot of the current model to a PNG/JPG file.

    Payload keys (matching ExportImageInput): file_path (str): Output path including
    extension. format_type (str): "png" or "jpg". Default "png". width (int): Pixel width.
    Default 1280. height (int): Pixel height. Default 720. view_orientation (str): "front" |
    "top" | "right" | "isometric" | "current".

    Args:
        payload (dict): The payload value.

    Returns:
        AdapterResult[dict]: The result produced by the operation.

    Raises:
        RuntimeError: If the operation cannot be completed.
    """
    if not self.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )
    if not self.swApp:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="SolidWorks not connected"
        )

    orientation = str(payload.get("view_orientation", "current")).lower()
    file_path = payload.get("file_path", "")
    width = int(payload.get("width", 1280))
    height = int(payload.get("height", 720))

    # Map orientation names to SolidWorks swStandardViews_e constants
    _VIEW_CONSTANTS = {
        "front": 1,  # swFrontView
        "back": 2,  # swBackView
        "left": 3,  # swLeftView
        "right": 4,  # swRightView
        "top": 5,  # swTopView
        "bottom": 6,  # swBottomView
        "isometric": 7,  # swIsometricView
        "dimetric": 8,  # swDimetricView
        "trimetric": 9,  # swTriMetricView
    }

    def _screenshot_operation() -> dict:
        """Build internal screenshot operation.

        Returns:
            dict: A dictionary containing the resulting values.

        Raises:
            RuntimeError: If the operation cannot be completed.
        """

        import os as _os

        resolved = _os.path.abspath(file_path)
        _os.makedirs(_os.path.dirname(resolved), exist_ok=True)

        target_doc = self._resolve_export_target_doc()

        # Activate target_doc's own MDI window before capturing. Without
        # this, SaveBitmapWithVariableSize renders whatever document
        # window is currently frontmost in the SW frame -- which can be
        # a completely different open document if the user has clicked
        # around in SolidWorks since -- even though it's called on the
        # correct target_doc COM object. See com-api-pitfalls.md #21.
        _, target_title = self._document_identity(target_doc)
        if target_title:
            self._attempt(
                lambda: self.swApp.ActivateDoc3(  # type: ignore[union-attr]
                    target_title, False, 0, _byref_int()
                )
            )

        # Ensure SolidWorks window is focused so the viewport is rendered.
        # Required for both view changes and bitmap capture.
        self._attempt(lambda: self.swApp.Frame.SetFocus())  # type: ignore[union-attr]

        # Set view orientation if requested
        if orientation != "current" and orientation in _VIEW_CONSTANTS:
            view_const = _VIEW_CONSTANTS[orientation]
            self._set_view_orientation(target_doc, orientation, view_const)

        # Zoom to fit so the model fills the viewport before capture
        self._zoom_to_fit(target_doc)

        # Remove any pre-existing file so SolidWorks never shows the
        # "already exists — replace?" confirmation dialog.
        if _os.path.exists(resolved):
            _os.remove(resolved)

        # SaveBMP at the requested size; SaveAs3 (window size) otherwise.
        saved = self._save_screenshot_sized(target_doc, resolved, width, height)
        if not saved:
            self._save_screenshot_with_saveas3(target_doc, resolved)
            saved = _os.path.exists(resolved)

        if not saved:
            raise RuntimeError(
                f"All screenshot methods produced no output for {resolved}"
            )

        # Report the size actually written, not the one asked for: the
        # SaveAs3 fallback ignores width/height.
        size = bitmap.image_size(resolved)
        return {
            "file_path": resolved,
            "format": _os.path.splitext(resolved)[1].lstrip(".").upper() or "PNG",
            "dimensions": f"{size[0]}x{size[1]}" if size else "unknown",
            "requested_dimensions": f"{width}x{height}",
            "view": orientation,
        }

    return self._handle_com_operation("export_image", _screenshot_operation)
health_check async
health_check() -> AdapterHealth

Get adapter health status.

Performs comprehensive health check including connection status, operation metrics, and SolidWorks application responsiveness.

Returns:

Name Type Description
AdapterHealth AdapterHealth

The result produced by the operation.

Example
health = await adapter.health_check()
if health.healthy:
    print(f"Adapter healthy, {health.success_count} operations completed")
else:
    print(f"Adapter unhealthy: {health.error_count} errors")
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
async def health_check(self) -> AdapterHealth:
    """Get adapter health status.

    Performs comprehensive health check including connection status, operation metrics, and
    SolidWorks application responsiveness.

    Returns:
        AdapterHealth: The result produced by the operation.

    Example:
                        ```python
                        health = await adapter.health_check()
                        if health.healthy:
                            print(f"Adapter healthy, {health.success_count} operations completed")
                        else:
                            print(f"Adapter unhealthy: {health.error_count} errors")
                        ```
    """
    healthy = self.is_connected()

    # Support both callable COM method and property-style RevisionNumber.
    sw_version: str | None = None
    if self.swApp:
        sw_version = self._attempt(
            lambda: self._get_attr_or_call(self.swApp, "RevisionNumber")
        )

    # Try a simple operation to verify connection
    if healthy:
        healthy = sw_version is not None

    return AdapterHealth(
        healthy=healthy,
        last_check=datetime.now(),
        error_count=int(self._metrics["errors_count"]),
        success_count=int(
            self._metrics["operations_count"] - self._metrics["errors_count"]
        ),
        average_response_time=self._metrics["average_response_time"],
        connection_status="connected" if healthy else "disconnected",
        metrics={
            "adapter_type": "pywin32",
            "sw_version": sw_version or "Unknown",
            "current_model": self.currentModel.GetTitle()
            if self.currentModel
            else None,
        },
    )
is_connected
is_connected() -> bool

Check if connected to SolidWorks.

Returns:

Name Type Description
bool bool

True if connected, otherwise False.

Example
if adapter.is_connected():
    print("Ready to automate SolidWorks")
else:
    await adapter.connect()
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def is_connected(self) -> bool:
    """Check if connected to SolidWorks.

    Returns:
        bool: True if connected, otherwise False.

    Example:
                        ```python
                        if adapter.is_connected():
                            print("Ready to automate SolidWorks")
                        else:
                            await adapter.connect()
                        ```
    """
    return self.swApp is not None

SolidWorksAdapter

SolidWorksAdapter(config: object | None = None)

Bases: ABC

Base adapter interface for SolidWorks integration.

Parameters:

Name Type Description Default
config object | None

Configuration values for the operation. Defaults to None.

None

Attributes:

Name Type Description
_metrics Any

The metrics value.

config Any

The config value.

config_dict Any

The config dict value.

Initialize adapter with configuration.

Parameters:

Name Type Description Default
config object | None

Configuration values for the operation. Defaults to None.

None
Source code in src/solidworks_mcp/adapters/base.py
def __init__(self, config: object | None = None):
    """Initialize adapter with configuration.

    Args:
        config (object | None): Configuration values for the operation. Defaults to None.
    """
    if config is None:
        normalized_config: dict[str, Any] = {}
    elif isinstance(config, Mapping):
        normalized_config = dict(config)
    elif hasattr(config, "model_dump"):
        normalized_config = dict(config.model_dump())
    else:
        normalized_config = {}

    # Preserve original config object for compatibility with tests and
    # call sites that compare object identity/equality.
    self.config = config
    # Keep a normalized mapping for adapter internals.
    self.config_dict = normalized_config
    self._metrics = {
        "operations_count": 0,
        "errors_count": 0,
        "average_response_time": 0.0,
    }
    # SolidWorks-as-Code session logging. Set soc_session_id to enable
    # automatic ToolCallRecord writes for every adapter operation.
    self.soc_session_id: str | None = None
    self.soc_db_path: Path | None = None
Methods:
activate_document async
activate_document(title_or_path: str) -> AdapterResult[dict[str, Any]]

Make an already-open document the active one.

Parameters:

Name Type Description Default
title_or_path str

Title, full path, or file name of an open document.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: What was activated, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def activate_document(
    self, title_or_path: str
) -> AdapterResult[dict[str, Any]]:
    """Make an already-open document the active one.

    Args:
        title_or_path (str): Title, full path, or file name of an open
            document.

    Returns:
        AdapterResult[dict[str, Any]]: What was activated, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="activate_document is not implemented by this adapter",
    )
add_arc async
add_arc(center_x: float, center_y: float, start_x: float, start_y: float, end_x: float, end_y: float) -> AdapterResult[str]

Add an arc to the current sketch.

Parameters:

Name Type Description Default
center_x float

The center x value.

required
center_y float

The center y value.

required
start_x float

The start x value.

required
start_y float

The start y value.

required
end_x float

The end x value.

required
end_y float

The end y value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def add_arc(
    self,
    center_x: float,
    center_y: float,
    start_x: float,
    start_y: float,
    end_x: float,
    end_y: float,
) -> AdapterResult[str]:
    """Add an arc to the current sketch.

    Args:
        center_x (float): The center x value.
        center_y (float): The center y value.
        start_x (float): The start x value.
        start_y (float): The start y value.
        end_x (float): The end x value.
        end_y (float): The end y value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_arc is not implemented by this adapter",
    )
add_centerline async
add_centerline(x1: float, y1: float, x2: float, y2: float) -> AdapterResult[str]

Add a centerline to the current sketch.

Parameters:

Name Type Description Default
x1 float

The x1 value.

required
y1 float

The y1 value.

required
x2 float

The x2 value.

required
y2 float

The y2 value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def add_centerline(
    self, x1: float, y1: float, x2: float, y2: float
) -> AdapterResult[str]:
    """Add a centerline to the current sketch.

    Args:
        x1 (float): The x1 value.
        y1 (float): The y1 value.
        x2 (float): The x2 value.
        y2 (float): The y2 value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_centerline is not implemented by this adapter",
    )
add_chamfer async
add_chamfer(distance: float, edge_names: list[str], angle: float = 45.0, flip: bool = False) -> AdapterResult[Any]

Add an angle-distance chamfer to the named or selected edges.

At the default 45 degrees both legs equal distance.

Parameters:

Name Type Description Default
distance float

Chamfer distance in millimeters, along one face.

required
edge_names list[str]

List of edge names to chamfer.

required
angle float

Chamfer angle in degrees. Defaults to 45.

45.0
flip bool

Measure distance along the other face.

False

Returns:

Name Type Description
AdapterResult AdapterResult[Any]

Feature result or error.

Source code in src/solidworks_mcp/adapters/base.py
async def add_chamfer(
    self,
    distance: float,
    edge_names: list[str],
    angle: float = 45.0,
    flip: bool = False,
) -> AdapterResult[Any]:
    """Add an angle-distance chamfer to the named or selected edges.

    At the default 45 degrees both legs equal ``distance``.

    Args:
        distance (float): Chamfer distance in millimeters, along one face.
        edge_names (list[str]): List of edge names to chamfer.
        angle (float): Chamfer angle in degrees. Defaults to 45.
        flip (bool): Measure ``distance`` along the other face.

    Returns:
        AdapterResult: Feature result or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_chamfer is not implemented by this adapter",
    )
add_circle abstractmethod async
add_circle(center_x: float, center_y: float, radius: float) -> AdapterResult[str]

Add a circle to the current sketch.

Parameters:

Name Type Description Default
center_x float

The center x value.

required
center_y float

The center y value.

required
radius float

The radius value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def add_circle(
    self, center_x: float, center_y: float, radius: float
) -> AdapterResult[str]:
    """Add a circle to the current sketch.

    Args:
        center_x (float): The center x value.
        center_y (float): The center y value.
        radius (float): The radius value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    pass
add_datum_tag async
add_datum_tag(label: str, attach_at_mm: tuple[float, float], x_mm: float, y_mm: float) -> AdapterResult[dict[str, Any]]

Add a datum feature symbol on a drawing edge.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def add_datum_tag(
    self,
    label: str,
    attach_at_mm: tuple[float, float],
    x_mm: float,
    y_mm: float,
) -> AdapterResult[dict[str, Any]]:
    """Add a datum feature symbol on a drawing edge.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_datum_tag is not implemented by this adapter",
    )
add_drawing_view async
add_drawing_view(payload: Any = None) -> AdapterResult[dict[str, Any]]

Add a view of a model to the active drawing sheet.

Parameters:

Name Type Description Default
payload Any

Tool payload carrying the model path, orientation, position in millimetres and optional view scale.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The new view's details, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def add_drawing_view(
    self, payload: Any = None
) -> AdapterResult[dict[str, Any]]:
    """Add a view of a model to the active drawing sheet.

    Args:
        payload (Any): Tool payload carrying the model path, orientation,
            position in millimetres and optional view scale.

    Returns:
        AdapterResult[dict[str, Any]]: The new view's details, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_drawing_view is not implemented by this adapter",
    )
add_ellipse async
add_ellipse(center_x: float, center_y: float, major_axis: float, minor_axis: float) -> AdapterResult[str]

Add an ellipse to the current sketch.

Parameters:

Name Type Description Default
center_x float

The center x value.

required
center_y float

The center y value.

required
major_axis float

The major axis value.

required
minor_axis float

The minor axis value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def add_ellipse(
    self,
    center_x: float,
    center_y: float,
    major_axis: float,
    minor_axis: float,
) -> AdapterResult[str]:
    """Add an ellipse to the current sketch.

    Args:
        center_x (float): The center x value.
        center_y (float): The center y value.
        major_axis (float): The major axis value.
        minor_axis (float): The minor axis value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_ellipse is not implemented by this adapter",
    )
add_fillet async
add_fillet(radius: float, edge_names: list[str]) -> AdapterResult[Any]

Add a fillet feature to selected edges.

Rounds the selected edges of the current solid body with the given radius.

Parameters:

Name Type Description Default
radius float

Fillet radius in millimeters.

required
edge_names list[str]

List of edge names to fillet.

required

Returns:

Name Type Description
AdapterResult AdapterResult[Any]

Feature result or error.

Source code in src/solidworks_mcp/adapters/base.py
async def add_fillet(
    self, radius: float, edge_names: list[str]
) -> AdapterResult[Any]:
    """Add a fillet feature to selected edges.

    Rounds the selected edges of the current solid body with the given radius.

    Args:
        radius (float): Fillet radius in millimeters.
        edge_names (list[str]): List of edge names to fillet.

    Returns:
        AdapterResult: Feature result or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_fillet is not implemented by this adapter",
    )
add_gtol async
add_gtol(characteristic: str, tolerance: float, x_mm: float, y_mm: float, diameter_zone: bool = False, material_condition: str = 'none', datums: list[str] | None = None, attach_at_mm: tuple[float, float] | None = None, dimension: tuple[str, str] | None = None) -> AdapterResult[dict[str, Any]]

Add a GD&T feature control frame to the active drawing.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def add_gtol(
    self,
    characteristic: str,
    tolerance: float,
    x_mm: float,
    y_mm: float,
    diameter_zone: bool = False,
    material_condition: str = "none",
    datums: list[str] | None = None,
    attach_at_mm: tuple[float, float] | None = None,
    dimension: tuple[str, str] | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Add a GD&T feature control frame to the active drawing.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_gtol is not implemented by this adapter",
    )
add_line abstractmethod async
add_line(x1: float, y1: float, x2: float, y2: float) -> AdapterResult[str]

Add a line to the current sketch.

Parameters:

Name Type Description Default
x1 float

The x1 value.

required
y1 float

The y1 value.

required
x2 float

The x2 value.

required
y2 float

The y2 value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def add_line(
    self, x1: float, y1: float, x2: float, y2: float
) -> AdapterResult[str]:
    """Add a line to the current sketch.

    Args:
        x1 (float): The x1 value.
        y1 (float): The y1 value.
        x2 (float): The x2 value.
        y2 (float): The y2 value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    pass
add_mate async
add_mate(component_a: str, component_b: str, entity_a: str = 'Front Plane', entity_b: str = 'Front Plane', mate_type: str = 'coincident', alignment: str = 'aligned', distance: float = 0.0, angle: float = 0.0, face_a: list[float] | None = None, face_b: list[float] | None = None) -> AdapterResult[dict[str, Any]]

Mate two components together in the active assembly.

Parameters:

Name Type Description Default
component_a str

First component instance name.

required
component_b str

Second component instance name.

required
entity_a str

Named feature on the first component. Defaults to "Front Plane".

'Front Plane'
entity_b str

Named feature on the second component. Defaults to "Front Plane".

'Front Plane'
mate_type str

Mate type, e.g. coincident. Defaults to "coincident".

'coincident'
alignment str

aligned, anti_aligned or closest. Defaults to "aligned".

'aligned'
distance float

Distance in millimetres, for a distance mate. Defaults to 0.0.

0.0
angle float

Angle in degrees, for an angle mate. Defaults to 0.0.

0.0
face_a list[float] | None

A point on a face of the first component, in its part coordinates (mm), used instead of entity_a.

None
face_b list[float] | None

The same for the second component.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Mate details, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def add_mate(
    self,
    component_a: str,
    component_b: str,
    entity_a: str = "Front Plane",
    entity_b: str = "Front Plane",
    mate_type: str = "coincident",
    alignment: str = "aligned",
    distance: float = 0.0,
    angle: float = 0.0,
    face_a: list[float] | None = None,
    face_b: list[float] | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Mate two components together in the active assembly.

    Args:
        component_a (str): First component instance name.
        component_b (str): Second component instance name.
        entity_a (str): Named feature on the first component. Defaults to
            "Front Plane".
        entity_b (str): Named feature on the second component. Defaults to
            "Front Plane".
        mate_type (str): Mate type, e.g. ``coincident``. Defaults to
            "coincident".
        alignment (str): ``aligned``, ``anti_aligned`` or ``closest``.
            Defaults to "aligned".
        distance (float): Distance in millimetres, for a distance mate.
            Defaults to 0.0.
        angle (float): Angle in degrees, for an angle mate. Defaults to 0.0.
        face_a (list[float] | None): A point on a face of the first
            component, in its part coordinates (mm), used instead of
            ``entity_a``.
        face_b (list[float] | None): The same for the second component.

    Returns:
        AdapterResult[dict[str, Any]]: Mate details, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_mate is not implemented by this adapter",
    )
add_note async
add_note(payload: Any = None) -> AdapterResult[dict[str, Any]]

Place a text note on the active drawing sheet.

Parameters:

Name Type Description Default
payload Any

Tool payload carrying the note text, its position in millimetres and an optional font size in points.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The note's details, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def add_note(self, payload: Any = None) -> AdapterResult[dict[str, Any]]:
    """Place a text note on the active drawing sheet.

    Args:
        payload (Any): Tool payload carrying the note text, its position in
            millimetres and an optional font size in points.

    Returns:
        AdapterResult[dict[str, Any]]: The note's details, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_note is not implemented by this adapter",
    )
add_polygon async
add_polygon(center_x: float, center_y: float, radius: float, sides: int) -> AdapterResult[str]

Add a regular polygon to the current sketch.

Parameters:

Name Type Description Default
center_x float

The center x value.

required
center_y float

The center y value.

required
radius float

The radius value.

required
sides int

The sides value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def add_polygon(
    self, center_x: float, center_y: float, radius: float, sides: int
) -> AdapterResult[str]:
    """Add a regular polygon to the current sketch.

    Args:
        center_x (float): The center x value.
        center_y (float): The center y value.
        radius (float): The radius value.
        sides (int): The sides value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_polygon is not implemented by this adapter",
    )
add_rectangle abstractmethod async
add_rectangle(x1: float, y1: float, x2: float, y2: float) -> AdapterResult[str]

Add a rectangle to the current sketch.

Parameters:

Name Type Description Default
x1 float

The x1 value.

required
y1 float

The y1 value.

required
x2 float

The x2 value.

required
y2 float

The y2 value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def add_rectangle(
    self, x1: float, y1: float, x2: float, y2: float
) -> AdapterResult[str]:
    """Add a rectangle to the current sketch.

    Args:
        x1 (float): The x1 value.
        y1 (float): The y1 value.
        x2 (float): The x2 value.
        y2 (float): The y2 value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    pass
add_sketch_circle async
add_sketch_circle(center_x: float, center_y: float, radius: float, construction: bool = False) -> AdapterResult[str]

Alias for add_circle used by some tool flows.

Parameters:

Name Type Description Default
center_x float

The center x value.

required
center_y float

The center y value.

required
radius float

The radius value.

required
construction bool

The construction value. Defaults to False.

False

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def add_sketch_circle(
    self,
    center_x: float,
    center_y: float,
    radius: float,
    construction: bool = False,
) -> AdapterResult[str]:
    """Alias for add_circle used by some tool flows.

    Args:
        center_x (float): The center x value.
        center_y (float): The center y value.
        radius (float): The radius value.
        construction (bool): The construction value. Defaults to False.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return await self.add_circle(center_x, center_y, radius)
add_sketch_constraint async
add_sketch_constraint(entity1: str, entity2: str | None, relation_type: str, entity3: str | None = None) -> AdapterResult[str]

Apply a geometric constraint between sketch entities.

Parameters:

Name Type Description Default
entity1 str

The entity1 value.

required
entity2 str | None

The entity2 value.

required
relation_type str

The relation type value.

required
entity3 str | None

Third entity ID — only used by the symmetric relation (the centerline of symmetry). All other relation types reject a non-null entity3.

None

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def add_sketch_constraint(
    self,
    entity1: str,
    entity2: str | None,
    relation_type: str,
    entity3: str | None = None,
) -> AdapterResult[str]:
    """Apply a geometric constraint between sketch entities.

    Args:
        entity1 (str): The entity1 value.
        entity2 (str | None): The entity2 value.
        relation_type (str): The relation type value.
        entity3 (str | None): Third entity ID — only used by the
            ``symmetric`` relation (the centerline of symmetry). All
            other relation types reject a non-null ``entity3``.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_sketch_constraint is not implemented by this adapter",
    )
add_sketch_dimension async
add_sketch_dimension(entity1: str, entity2: str | None, dimension_type: str, value: float) -> AdapterResult[str]

Add a sketch dimension.

Parameters:

Name Type Description Default
entity1 str

The entity1 value.

required
entity2 str | None

The entity2 value.

required
dimension_type str

The dimension type value.

required
value float

The value value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def add_sketch_dimension(
    self,
    entity1: str,
    entity2: str | None,
    dimension_type: str,
    value: float,
) -> AdapterResult[str]:
    """Add a sketch dimension.

    Args:
        entity1 (str): The entity1 value.
        entity2 (str | None): The entity2 value.
        dimension_type (str): The dimension type value.
        value (float): The value value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_sketch_dimension is not implemented by this adapter",
    )
add_spline async
add_spline(points: list[dict[str, float]]) -> AdapterResult[str]

Add a spline through the provided points.

Parameters:

Name Type Description Default
points list[dict[str, float]]

The points value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def add_spline(self, points: list[dict[str, float]]) -> AdapterResult[str]:
    """Add a spline through the provided points.

    Args:
        points (list[dict[str, float]]): The points value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="add_spline is not implemented by this adapter",
    )
arrange_dimensions async
arrange_dimensions(view_name: str | None = None) -> AdapterResult[dict[str, Any]]

Move drawing dimensions so none overlaps, then verify.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def arrange_dimensions(
    self,
    view_name: str | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Move drawing dimensions so none overlaps, then verify.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="arrange_dimensions is not implemented by this adapter",
    )
auto_center_marks async
auto_center_marks(view_name: str, mark_holes: bool = True, mark_fillets: bool = False, mark_slots: bool = True) -> AdapterResult[dict[str, Any]]

Auto-insert centre marks on circular features in a drawing view.

Parameters:

Name Type Description Default
view_name str

Name of a view on the active drawing.

required
mark_holes bool

Mark holes / bores.

True
mark_fillets bool

Mark fillets.

False
mark_slots bool

Mark slots.

True

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The view and before/after centre-mark

AdapterResult[dict[str, Any]]

counts, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def auto_center_marks(
    self,
    view_name: str,
    mark_holes: bool = True,
    mark_fillets: bool = False,
    mark_slots: bool = True,
) -> AdapterResult[dict[str, Any]]:
    """Auto-insert centre marks on circular features in a drawing view.

    Args:
        view_name (str): Name of a view on the active drawing.
        mark_holes (bool): Mark holes / bores.
        mark_fillets (bool): Mark fillets.
        mark_slots (bool): Mark slots.

    Returns:
        AdapterResult[dict[str, Any]]: The view and before/after centre-mark
        counts, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="auto_center_marks is not implemented by this adapter",
    )
check_interference async
check_interference(params: Any = None) -> AdapterResult[dict[str, Any]]

Check the active assembly for interfering components.

Parameters:

Name Type Description Default
params Any

Optional settings; coincident, components.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Interference details, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def check_interference(
    self, params: Any = None
) -> AdapterResult[dict[str, Any]]:
    """Check the active assembly for interfering components.

    Args:
        params (Any): Optional settings; ``coincident``, ``components``.

    Returns:
        AdapterResult[dict[str, Any]]: Interference details, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="check_interference is not implemented by this adapter",
    )
check_sketch_fully_defined async
check_sketch_fully_defined(sketch_name: str | None = None) -> AdapterResult[dict[str, Any]]

Check whether a sketch is fully defined.

Parameters:

Name Type Description Default
sketch_name str | None

Optional sketch name to inspect. Defaults to None.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Definition status payload.

Source code in src/solidworks_mcp/adapters/base.py
async def check_sketch_fully_defined(
    self, sketch_name: str | None = None
) -> AdapterResult[dict[str, Any]]:
    """Check whether a sketch is fully defined.

    Args:
        sketch_name (str | None): Optional sketch name to inspect. Defaults to None.

    Returns:
        AdapterResult[dict[str, Any]]: Definition status payload.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="check_sketch_fully_defined is not implemented by this adapter",
    )
close_model abstractmethod async
close_model(save: bool = False) -> AdapterResult[None]

Close the current model.

Parameters:

Name Type Description Default
save bool

The save value. Defaults to False.

False

Returns:

Type Description
AdapterResult[None]

AdapterResult[None]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def close_model(self, save: bool = False) -> AdapterResult[None]:
    """Close the current model.

    Args:
        save (bool): The save value. Defaults to False.

    Returns:
        AdapterResult[None]: The result produced by the operation.
    """
    pass
connect abstractmethod async
connect() -> None

Connect to SolidWorks application.

Returns:

Name Type Description
None None

None.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def connect(self) -> None:
    """Connect to SolidWorks application.

    Returns:
        None: None.
    """
    pass
create_assembly abstractmethod async
create_assembly(name: str | None = None, template: str | None = None) -> AdapterResult[SolidWorksModel]

Create a new assembly document.

Parameters:

Name Type Description Default
name str | None

The name value. Defaults to None.

None
template str | None

Template file path. Defaults to None.

None

Returns:

Type Description
AdapterResult[SolidWorksModel]

AdapterResult[SolidWorksModel]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def create_assembly(
    self, name: str | None = None, template: str | None = None
) -> AdapterResult[SolidWorksModel]:
    """Create a new assembly document.

    Args:
        name (str | None): The name value. Defaults to None.
        template (str | None): Template file path. Defaults to None.

    Returns:
        AdapterResult[SolidWorksModel]: The result produced by the operation.
    """
    pass
create_axis async
create_axis(reference: str) -> AdapterResult[dict[str, Any]]

Create a reference axis along a principal direction.

Parameters:

Name Type Description Default
reference str

"x", "y" or "z" (case-insensitive, leading ± stripped).

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The plane pair used and feature counts, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def create_axis(self, reference: str) -> AdapterResult[dict[str, Any]]:
    """Create a reference axis along a principal direction.

    Args:
        reference (str): "x", "y" or "z" (case-insensitive, leading +/- stripped).

    Returns:
        AdapterResult[dict[str, Any]]: The plane pair used and feature counts, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="create_axis is not implemented by this adapter",
    )
create_cut async
create_cut(sketch_name: str, depth: float) -> AdapterResult[str]

Create a cut feature from an existing sketch.

Parameters:

Name Type Description Default
sketch_name str

The sketch name value.

required
depth float

The depth value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def create_cut(self, sketch_name: str, depth: float) -> AdapterResult[str]:
    """Create a cut feature from an existing sketch.

    Args:
        sketch_name (str): The sketch name value.
        depth (float): The depth value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="create_cut is not implemented by this adapter",
    )
create_cut_extrude async
create_cut_extrude(params: ExtrusionParameters) -> AdapterResult[Any]

Create a cut-extrude feature from the active sketch.

Cuts material from the current solid body using the active sketch profile. Equivalent to SolidWorks Insert > Cut > Extrude.

Parameters:

Name Type Description Default
params ExtrusionParameters

Depth and direction parameters.

required

Returns:

Name Type Description
AdapterResult AdapterResult[Any]

Feature result or error.

Source code in src/solidworks_mcp/adapters/base.py
async def create_cut_extrude(
    self, params: ExtrusionParameters
) -> AdapterResult[Any]:
    """Create a cut-extrude feature from the active sketch.

    Cuts material from the current solid body using the active sketch profile.
    Equivalent to SolidWorks Insert > Cut > Extrude.

    Args:
        params (ExtrusionParameters): Depth and direction parameters.

    Returns:
        AdapterResult: Feature result or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="create_cut_extrude is not implemented by this adapter",
    )
create_drawing abstractmethod async
create_drawing(name: str | None = None, template: str | None = None) -> AdapterResult[SolidWorksModel]

Create a new drawing document.

Parameters:

Name Type Description Default
name str | None

The name value. Defaults to None.

None
template str | None

Template file path. Defaults to None.

None

Returns:

Type Description
AdapterResult[SolidWorksModel]

AdapterResult[SolidWorksModel]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def create_drawing(
    self, name: str | None = None, template: str | None = None
) -> AdapterResult[SolidWorksModel]:
    """Create a new drawing document.

    Args:
        name (str | None): The name value. Defaults to None.
        template (str | None): Template file path. Defaults to None.

    Returns:
        AdapterResult[SolidWorksModel]: The result produced by the operation.
    """
    pass
create_drawing_view async
create_drawing_view(payload: Any = None) -> AdapterResult[dict[str, Any]]

Place a view of a model on the active drawing sheet.

Parameters:

Name Type Description Default
payload Any

Tool payload carrying the model path, orientation, position in millimetres and optional view scale.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The new view's details, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def create_drawing_view(
    self, payload: Any = None
) -> AdapterResult[dict[str, Any]]:
    """Place a view of a model on the active drawing sheet.

    Args:
        payload (Any): Tool payload carrying the model path, orientation,
            position in millimetres and optional view scale.

    Returns:
        AdapterResult[dict[str, Any]]: The new view's details, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="create_drawing_view is not implemented by this adapter",
    )
create_extrusion abstractmethod async
create_extrusion(params: ExtrusionParameters) -> AdapterResult[SolidWorksFeature]

Create an extrusion feature.

Parameters:

Name Type Description Default
params ExtrusionParameters

The params value.

required

Returns:

Type Description
AdapterResult[SolidWorksFeature]

AdapterResult[SolidWorksFeature]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def create_extrusion(
    self, params: ExtrusionParameters
) -> AdapterResult[SolidWorksFeature]:
    """Create an extrusion feature.

    Args:
        params (ExtrusionParameters): The params value.

    Returns:
        AdapterResult[SolidWorksFeature]: The result produced by the operation.
    """
    pass
create_loft abstractmethod async
create_loft(params: LoftParameters) -> AdapterResult[SolidWorksFeature]

Create a loft feature.

Parameters:

Name Type Description Default
params LoftParameters

The params value.

required

Returns:

Type Description
AdapterResult[SolidWorksFeature]

AdapterResult[SolidWorksFeature]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def create_loft(
    self, params: LoftParameters
) -> AdapterResult[SolidWorksFeature]:
    """Create a loft feature.

    Args:
        params (LoftParameters): The params value.

    Returns:
        AdapterResult[SolidWorksFeature]: The result produced by the operation.
    """
    pass
create_part abstractmethod async
create_part(name: str | None = None, units: str | None = None, template: str | None = None) -> AdapterResult[SolidWorksModel]

Create a new part document.

Parameters:

Name Type Description Default
name str | None

The name value. Defaults to None.

None
units str | None

The units value. Defaults to None.

None
template str | None

Template file path. Defaults to None.

None

Returns:

Type Description
AdapterResult[SolidWorksModel]

AdapterResult[SolidWorksModel]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def create_part(
    self,
    name: str | None = None,
    units: str | None = None,
    template: str | None = None,
) -> AdapterResult[SolidWorksModel]:
    """Create a new part document.

    Args:
        name (str | None): The name value. Defaults to None.
        units (str | None): The units value. Defaults to None.
        template (str | None): Template file path. Defaults to None.

    Returns:
        AdapterResult[SolidWorksModel]: The result produced by the operation.
    """
    pass
create_reference_plane async
create_reference_plane(reference: str, offset: float = 0.0, angle: float = 0.0, flip: bool = False, axis: str | None = None, face_point: list[float] | None = None) -> AdapterResult[dict[str, Any]]

Create a reference plane offset from, angled to, or on a plane or face.

Parameters:

Name Type Description Default
reference str

Name of the reference plane.

required
offset float

Offset distance in millimetres. Defaults to 0.0.

0.0
angle float

Angle in degrees about axis. Defaults to 0.0.

0.0
flip bool

Reverse the offset or angle direction. Defaults to False.

False
axis str | None

Named axis to rotate about; needed with angle.

None
face_point list[float] | None

[x, y, z] mm on a flat face to use as the reference instead of reference.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The new plane's name and parameters, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def create_reference_plane(
    self,
    reference: str,
    offset: float = 0.0,
    angle: float = 0.0,
    flip: bool = False,
    axis: str | None = None,
    face_point: list[float] | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Create a reference plane offset from, angled to, or on a plane or face.

    Args:
        reference (str): Name of the reference plane.
        offset (float): Offset distance in millimetres. Defaults to 0.0.
        angle (float): Angle in degrees about ``axis``. Defaults to 0.0.
        flip (bool): Reverse the offset or angle direction. Defaults to False.
        axis (str | None): Named axis to rotate about; needed with ``angle``.
        face_point (list[float] | None): ``[x, y, z]`` mm on a flat face to
            use as the reference instead of ``reference``.

    Returns:
        AdapterResult[dict[str, Any]]: The new plane's name and parameters, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="create_reference_plane is not implemented by this adapter",
    )
create_reference_point async
create_reference_point(mode: str, x: float, y: float, z: float, distance: float | None = None, percent: float | None = None) -> AdapterResult[dict[str, Any]]

Create a reference point on the active part.

Parameters:

Name Type Description Default
mode str

"along_curve" or "face_center".

required
x float

X of a point on the target edge/face, in millimetres.

required
y float

Y of a point on the target edge/face, in millimetres.

required
z float

Z of a point on the target edge/face, in millimetres.

required
distance float | None

For "along_curve", offset from the edge start in millimetres.

None
percent float | None

For "along_curve", position as 0-100 of the edge length.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The mode and feature counts, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def create_reference_point(
    self,
    mode: str,
    x: float,
    y: float,
    z: float,
    distance: float | None = None,
    percent: float | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Create a reference point on the active part.

    Args:
        mode (str): "along_curve" or "face_center".
        x (float): X of a point on the target edge/face, in millimetres.
        y (float): Y of a point on the target edge/face, in millimetres.
        z (float): Z of a point on the target edge/face, in millimetres.
        distance (float | None): For "along_curve", offset from the edge
            start in millimetres.
        percent (float | None): For "along_curve", position as 0-100 of
            the edge length.

    Returns:
        AdapterResult[dict[str, Any]]: The mode and feature counts, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="create_reference_point is not implemented by this adapter",
    )
create_revolve abstractmethod async
create_revolve(params: RevolveParameters) -> AdapterResult[SolidWorksFeature]

Create a revolve feature.

Parameters:

Name Type Description Default
params RevolveParameters

The params value.

required

Returns:

Type Description
AdapterResult[SolidWorksFeature]

AdapterResult[SolidWorksFeature]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def create_revolve(
    self, params: RevolveParameters
) -> AdapterResult[SolidWorksFeature]:
    """Create a revolve feature.

    Args:
        params (RevolveParameters): The params value.

    Returns:
        AdapterResult[SolidWorksFeature]: The result produced by the operation.
    """
    pass
create_sketch abstractmethod async
create_sketch(plane: str) -> AdapterResult[str]

Create a new sketch on the specified plane.

Parameters:

Name Type Description Default
plane str

The plane value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def create_sketch(self, plane: str) -> AdapterResult[str]:
    """Create a new sketch on the specified plane.

    Args:
        plane (str): The plane value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    pass
create_standard_drawing async
create_standard_drawing(model_path: str, output_dir: str | None = None, title: str | None = None, subtitle: str | None = None, drawing_number: str | None = None, author: str = '', revision: str = 'A', company: str = '', drawn_date: str | None = None, notes: list[str] | None = None, include_isometric: bool = True, write_model_properties: bool = True, title_block_text_height_mm: float | None = None, include_bom: bool = False) -> AdapterResult[dict[str, Any]]

Create a complete standard A3 drawing.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def create_standard_drawing(
    self,
    model_path: str,
    output_dir: str | None = None,
    title: str | None = None,
    subtitle: str | None = None,
    drawing_number: str | None = None,
    author: str = "",
    revision: str = "A",
    company: str = "",
    drawn_date: str | None = None,
    notes: list[str] | None = None,
    include_isometric: bool = True,
    write_model_properties: bool = True,
    title_block_text_height_mm: float | None = None,
    include_bom: bool = False,
) -> AdapterResult[dict[str, Any]]:
    """Create a complete standard A3 drawing.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="create_standard_drawing is not implemented by this adapter",
    )
create_sweep abstractmethod async
create_sweep(params: SweepParameters) -> AdapterResult[SolidWorksFeature]

Create a sweep feature.

Parameters:

Name Type Description Default
params SweepParameters

The params value.

required

Returns:

Type Description
AdapterResult[SolidWorksFeature]

AdapterResult[SolidWorksFeature]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def create_sweep(
    self, params: SweepParameters
) -> AdapterResult[SolidWorksFeature]:
    """Create a sweep feature.

    Args:
        params (SweepParameters): The params value.

    Returns:
        AdapterResult[SolidWorksFeature]: The result produced by the operation.
    """
    pass
create_technical_drawing async
create_technical_drawing(payload: Any = None) -> AdapterResult[dict[str, Any]]

Lay out the standard views of a model on the active sheet.

Parameters:

Name Type Description Default
payload Any

Tool payload carrying the model path and the projection angle.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The view names created, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def create_technical_drawing(
    self, payload: Any = None
) -> AdapterResult[dict[str, Any]]:
    """Lay out the standard views of a model on the active sheet.

    Args:
        payload (Any): Tool payload carrying the model path and the
            projection angle.

    Returns:
        AdapterResult[dict[str, Any]]: The view names created, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="create_technical_drawing is not implemented by this adapter",
    )
delete_feature async
delete_feature(name: str) -> AdapterResult[dict[str, Any]]

Delete a named feature or sketch from the active model.

Parameters:

Name Type Description Default
name str

Feature or sketch name.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: What was deleted, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def delete_feature(self, name: str) -> AdapterResult[dict[str, Any]]:
    """Delete a named feature or sketch from the active model.

    Args:
        name (str): Feature or sketch name.

    Returns:
        AdapterResult[dict[str, Any]]: What was deleted, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="delete_feature is not implemented by this adapter",
    )
disconnect abstractmethod async
disconnect() -> None

Disconnect from SolidWorks application.

Returns:

Name Type Description
None None

None.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def disconnect(self) -> None:
    """Disconnect from SolidWorks application.

    Returns:
        None: None.
    """
    pass
exit_sketch abstractmethod async
exit_sketch() -> AdapterResult[None]

Exit sketch editing mode.

Returns:

Type Description
AdapterResult[None]

AdapterResult[None]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def exit_sketch(self) -> AdapterResult[None]:
    """Exit sketch editing mode.

    Returns:
        AdapterResult[None]: The result produced by the operation.
    """
    pass
export_file abstractmethod async
export_file(file_path: str, format_type: str) -> AdapterResult[None]

Export the current model to a file.

Parameters:

Name Type Description Default
file_path str

Path to the target file.

required
format_type str

The format type value.

required

Returns:

Type Description
AdapterResult[None]

AdapterResult[None]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def export_file(
    self, file_path: str, format_type: str
) -> AdapterResult[None]:
    """Export the current model to a file.

    Args:
        file_path (str): Path to the target file.
        format_type (str): The format type value.

    Returns:
        AdapterResult[None]: The result produced by the operation.
    """
    pass
export_flat_pattern async
export_flat_pattern(file_path: str) -> AdapterResult[dict[str, Any]]

Export the active sheet-metal part's flat pattern.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def export_flat_pattern(
    self,
    file_path: str,
) -> AdapterResult[dict[str, Any]]:
    """Export the active sheet-metal part's flat pattern.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="export_flat_pattern is not implemented by this adapter",
    )
export_image abstractmethod async
export_image(payload: dict) -> AdapterResult[dict]

Export a viewport screenshot (PNG/JPG) of the current model.

Payload keys: file_path (str): Absolute output path. width (int): Image width in pixels. height (int): Image height in pixels. view_orientation (str): One of "isometric", "front", "top", "right", "back", "bottom", "current".

Parameters:

Name Type Description Default
payload dict

The payload value.

required

Returns:

Type Description
AdapterResult[dict]

AdapterResult[dict]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def export_image(self, payload: dict) -> AdapterResult[dict]:
    """Export a viewport screenshot (PNG/JPG) of the current model.

    Payload keys: file_path (str): Absolute output path. width (int): Image width in pixels.
    height (int): Image height in pixels. view_orientation (str): One of "isometric",
    "front", "top", "right", "back", "bottom", "current".

    Args:
        payload (dict): The payload value.

    Returns:
        AdapterResult[dict]: The result produced by the operation.
    """
    pass
get_application_info async
get_application_info() -> AdapterResult[dict[str, Any]]

Describe the connected SolidWorks without changing anything.

Only file names leave the adapter, never full paths, so the result can be pasted into a public report. A value that cannot be read is None, never guessed.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: revision (e.g. "34.4.0"),

AdapterResult[dict[str, Any]]

language (e.g. "english") and templates: for each of

AdapterResult[dict[str, Any]]

part, assembly and drawing, the default template's

AdapterResult[dict[str, Any]]

file_name and whether the file exists.

Source code in src/solidworks_mcp/adapters/base.py
async def get_application_info(self) -> AdapterResult[dict[str, Any]]:
    """Describe the connected SolidWorks without changing anything.

    Only file names leave the adapter, never full paths, so the result
    can be pasted into a public report. A value that cannot be read is
    ``None``, never guessed.

    Returns:
        AdapterResult[dict[str, Any]]: ``revision`` (e.g. ``"34.4.0"``),
        ``language`` (e.g. ``"english"``) and ``templates``: for each of
        ``part``, ``assembly`` and ``drawing``, the default template's
        ``file_name`` and whether the file ``exists``.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="get_application_info is not implemented by this adapter",
    )
get_custom_properties async
get_custom_properties(configuration: str | None = None) -> AdapterResult[dict[str, Any]]

Read the custom properties of the active document.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def get_custom_properties(
    self,
    configuration: str | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Read the custom properties of the active document.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="get_custom_properties is not implemented by this adapter",
    )
get_dimension abstractmethod async
get_dimension(name: str) -> AdapterResult[dict[str, Any]]

Get the value of a dimension.

Parameters:

Name Type Description Default
name str

The name value.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: value and units (mm, deg or None).

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def get_dimension(self, name: str) -> AdapterResult[dict[str, Any]]:
    """Get the value of a dimension.

    Args:
        name (str): The name value.

    Returns:
        AdapterResult[dict[str, Any]]: ``value`` and ``units`` (mm, deg or None).
    """
    pass
get_mass_properties abstractmethod async
get_mass_properties() -> AdapterResult[MassProperties]

Get mass properties of the current model.

Returns:

Type Description
AdapterResult[MassProperties]

AdapterResult[MassProperties]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def get_mass_properties(self) -> AdapterResult[MassProperties]:
    """Get mass properties of the current model.

    Returns:
        AdapterResult[MassProperties]: The result produced by the operation.
    """
    pass
get_material async
get_material(configuration: str | None = None) -> AdapterResult[dict[str, Any]]

Read the material assigned to the active part.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def get_material(
    self,
    configuration: str | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Read the material assigned to the active part.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="get_material is not implemented by this adapter",
    )
get_metrics
get_metrics() -> dict[str, Any]

Get adapter metrics.

Returns:

Type Description
dict[str, Any]

dict[str, Any]: A dictionary containing the resulting values.

Source code in src/solidworks_mcp/adapters/base.py
def get_metrics(self) -> dict[str, Any]:
    """Get adapter metrics.

    Returns:
        dict[str, Any]: A dictionary containing the resulting values.
    """
    return self._metrics.copy()
get_model_geometry_summary async
get_model_geometry_summary() -> AdapterResult[dict[str, Any]]

Summarise the active part or assembly without changing it.

Must not rebuild the model, modify it, or clear the selection, so callers can read it around a feature call. Values the adapter cannot determine are None, never guessed.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: document_type ("part" or

AdapterResult[dict[str, Any]]

"assembly"), volume_mm3, bounding_box_mm (min,

AdapterResult[dict[str, Any]]

max and size as [x, y, z]), bounding_box_method,

AdapterResult[dict[str, Any]]

solid_body_count, and for assemblies component_count and

AdapterResult[dict[str, Any]]

mate_count. Error when there is no active part or assembly.

Source code in src/solidworks_mcp/adapters/base.py
async def get_model_geometry_summary(self) -> AdapterResult[dict[str, Any]]:
    """Summarise the active part or assembly without changing it.

    Must not rebuild the model, modify it, or clear the selection, so
    callers can read it around a feature call. Values the adapter cannot
    determine are ``None``, never guessed.

    Returns:
        AdapterResult[dict[str, Any]]: ``document_type`` (``"part"`` or
        ``"assembly"``), ``volume_mm3``, ``bounding_box_mm`` (``min``,
        ``max`` and ``size`` as ``[x, y, z]``), ``bounding_box_method``,
        ``solid_body_count``, and for assemblies ``component_count`` and
        ``mate_count``. Error when there is no active part or assembly.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="get_model_geometry_summary is not implemented by this adapter",
    )
get_model_info abstractmethod async
get_model_info() -> AdapterResult[dict[str, Any]]

Get metadata for the active model.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def get_model_info(self) -> AdapterResult[dict[str, Any]]:
    """Get metadata for the active model.

    Returns:
        AdapterResult[dict[str, Any]]: The result produced by the operation.
    """
    pass
get_selected_entities async
get_selected_entities() -> AdapterResult[list[dict[str, Any]]]

Report the faces/edges/vertices currently selected in the live UI.

Lets an agent see exactly what the user has clicked on in SolidWorks (e.g. "chamfer THIS face") instead of inferring it from a screenshot.

Returns:

Type Description
AdapterResult[list[dict[str, Any]]]

AdapterResult[list[dict[str, Any]]]: One descriptor per selected

AdapterResult[list[dict[str, Any]]]

entity. See the PyWin32 adapter's implementation for the field

AdapterResult[list[dict[str, Any]]]

set (varies by entity type).

Source code in src/solidworks_mcp/adapters/base.py
async def get_selected_entities(self) -> AdapterResult[list[dict[str, Any]]]:
    """Report the faces/edges/vertices currently selected in the live UI.

    Lets an agent see exactly what the user has clicked on in SolidWorks
    (e.g. "chamfer THIS face") instead of inferring it from a screenshot.

    Returns:
        AdapterResult[list[dict[str, Any]]]: One descriptor per selected
        entity. See the PyWin32 adapter's implementation for the field
        set (varies by entity type).
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="get_selected_entities is not implemented by this adapter",
    )
health_check abstractmethod async
health_check() -> AdapterHealth

Get adapter health status.

Returns:

Name Type Description
AdapterHealth AdapterHealth

The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def health_check(self) -> AdapterHealth:
    """Get adapter health status.

    Returns:
        AdapterHealth: The result produced by the operation.
    """
    pass
insert_bom_table async
insert_bom_table(view_name: str, template_path: str | None = None, bom_type: str = 'top_level_only', x_mm: float | None = None, y_mm: float | None = None) -> AdapterResult[dict[str, Any]]

Insert a BOM table anchored to an assembly drawing view.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def insert_bom_table(
    self,
    view_name: str,
    template_path: str | None = None,
    bom_type: str = "top_level_only",
    x_mm: float | None = None,
    y_mm: float | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Insert a BOM table anchored to an assembly drawing view.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="insert_bom_table is not implemented by this adapter",
    )
insert_component async
insert_component(file_path: str, x: float = 0.0, y: float = 0.0, z: float = 0.0) -> AdapterResult[dict[str, Any]]

Insert a part or sub-assembly into the active assembly.

Parameters:

Name Type Description Default
file_path str

Absolute path to the .sldprt or .sldasm.

required
x float

X position in millimetres. Defaults to 0.0.

0.0
y float

Y position in millimetres. Defaults to 0.0.

0.0
z float

Z position in millimetres. Defaults to 0.0.

0.0

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Component name and counts, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def insert_component(
    self, file_path: str, x: float = 0.0, y: float = 0.0, z: float = 0.0
) -> AdapterResult[dict[str, Any]]:
    """Insert a part or sub-assembly into the active assembly.

    Args:
        file_path (str): Absolute path to the ``.sldprt`` or ``.sldasm``.
        x (float): X position in millimetres. Defaults to 0.0.
        y (float): Y position in millimetres. Defaults to 0.0.
        z (float): Z position in millimetres. Defaults to 0.0.

    Returns:
        AdapterResult[dict[str, Any]]: Component name and counts, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="insert_component is not implemented by this adapter",
    )
insert_model_dimensions async
insert_model_dimensions(view_name: str | None = None, include_hole_wizard: bool = True) -> AdapterResult[dict[str, Any]]

Bring model dimensions into drawing views.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def insert_model_dimensions(
    self,
    view_name: str | None = None,
    include_hole_wizard: bool = True,
) -> AdapterResult[dict[str, Any]]:
    """Bring model dimensions into drawing views.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="insert_model_dimensions is not implemented by this adapter",
    )
is_connected abstractmethod
is_connected() -> bool

Check if connected to SolidWorks.

Returns:

Name Type Description
bool bool

True if connected, otherwise False.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
def is_connected(self) -> bool:
    """Check if connected to SolidWorks.

    Returns:
        bool: True if connected, otherwise False.
    """
    pass
list_available_materials async
list_available_materials(search: str | None = None) -> AdapterResult[list[dict[str, str]]]

List materials from the configured material libraries.

Returns:

Type Description
AdapterResult[list[dict[str, str]]]

AdapterResult[list[dict[str, str]]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def list_available_materials(
    self,
    search: str | None = None,
) -> AdapterResult[list[dict[str, str]]]:
    """List materials from the configured material libraries.

    Returns:
        AdapterResult[list[dict[str, str]]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="list_available_materials is not implemented by this adapter",
    )
list_components async
list_components() -> AdapterResult[list[str]]

List the top-level components of the active assembly.

Returns:

Type Description
AdapterResult[list[str]]

AdapterResult[list[str]]: Component instance names, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def list_components(self) -> AdapterResult[list[str]]:
    """List the top-level components of the active assembly.

    Returns:
        AdapterResult[list[str]]: Component instance names, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="list_components is not implemented by this adapter",
    )
list_configurations abstractmethod async
list_configurations() -> AdapterResult[list[str]]

List configuration names for the active model.

Returns:

Type Description
AdapterResult[list[str]]

AdapterResult[list[str]]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def list_configurations(self) -> AdapterResult[list[str]]:
    """List configuration names for the active model.

    Returns:
        AdapterResult[list[str]]: The result produced by the operation.
    """
    pass
list_drawing_views async
list_drawing_views() -> AdapterResult[list[str]]

List the views on the active drawing.

Returns:

Type Description
AdapterResult[list[str]]

AdapterResult[list[str]]: View names, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def list_drawing_views(self) -> AdapterResult[list[str]]:
    """List the views on the active drawing.

    Returns:
        AdapterResult[list[str]]: View names, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="list_drawing_views is not implemented by this adapter",
    )
list_features abstractmethod async
list_features(include_suppressed: bool = False, max_assembly_depth: int = 2) -> AdapterResult[list[dict[str, Any]]]

List model features from the feature tree.

For a Part document, returns that document's own feature list, unchanged from prior behavior. For an Assembly document, returns a single flat list containing the assembly's own top-level features plus every resolved component's features, recursing into sub-assemblies up to max_assembly_depth. Every descriptor gains component, component_path, and component_parent keys: None for a document's own features (Part features, or an Assembly's top-level features), or the owning component's name/path for a component-derived feature. component_parent is the immediate parent component name when the component is nested inside a sub-assembly, otherwise None. A component that cannot be resolved (suppressed, lightweight-and-unloaded, missing file) is represented by one descriptor with type: "UnresolvedComponent" instead of being silently dropped.

Parameters:

Name Type Description Default
include_suppressed bool

The include suppressed value. Defaults to False. Applied independently to the assembly's own features and to each component's features.

False
max_assembly_depth int

How many levels of sub-assembly to recurse into. Defaults to 2. Ignored for Part documents. A component beyond the configured depth still appears in the result as a single descriptor (name + path), not expanded.

2

Returns:

Type Description
AdapterResult[list[dict[str, Any]]]

AdapterResult[list[dict[str, Any]]]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def list_features(
    self, include_suppressed: bool = False, max_assembly_depth: int = 2
) -> AdapterResult[list[dict[str, Any]]]:
    """List model features from the feature tree.

    For a Part document, returns that document's own feature list,
    unchanged from prior behavior. For an Assembly document, returns a
    single flat list containing the assembly's own top-level features
    plus every resolved component's features, recursing into
    sub-assemblies up to ``max_assembly_depth``. Every descriptor gains
    ``component``, ``component_path``, and ``component_parent`` keys:
    ``None`` for a document's own features (Part features, or an Assembly's
    top-level features), or the owning component's name/path for a
    component-derived feature. ``component_parent`` is the immediate parent
    component name when the component is nested inside a sub-assembly,
    otherwise ``None``. A component that cannot be resolved (suppressed,
    lightweight-and-unloaded, missing file) is represented by one descriptor
    with ``type: "UnresolvedComponent"`` instead of being silently dropped.

    Args:
        include_suppressed (bool): The include suppressed value. Defaults to False.
            Applied independently to the assembly's own features and to
            each component's features.
        max_assembly_depth (int): How many levels of sub-assembly to
            recurse into. Defaults to 2. Ignored for Part documents. A
            component beyond the configured depth still appears in the
            result as a single descriptor (name + path), not expanded.

    Returns:
        AdapterResult[list[dict[str, Any]]]: The result produced by the operation.
    """
    pass
list_model_dimensions async
list_model_dimensions(feature_name: str | None = None) -> AdapterResult[list[dict[str, Any]]]

List every dimension in the active part or assembly.

Returns:

Type Description
AdapterResult[list[dict[str, Any]]]

AdapterResult[list[dict[str, Any]]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def list_model_dimensions(
    self,
    feature_name: str | None = None,
) -> AdapterResult[list[dict[str, Any]]]:
    """List every dimension in the active part or assembly.

    Returns:
        AdapterResult[list[dict[str, Any]]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="list_model_dimensions is not implemented by this adapter",
    )
list_open_documents async
list_open_documents() -> AdapterResult[list[dict[str, Any]]]

Enumerate every document currently open in SolidWorks.

Returns:

Type Description
AdapterResult[list[dict[str, Any]]]

AdapterResult[list[dict[str, Any]]]: One entry per open document

AdapterResult[list[dict[str, Any]]]

(title, path, type, is_active), or error.

Source code in src/solidworks_mcp/adapters/base.py
async def list_open_documents(self) -> AdapterResult[list[dict[str, Any]]]:
    """Enumerate every document currently open in SolidWorks.

    Returns:
        AdapterResult[list[dict[str, Any]]]: One entry per open document
        (``title``, ``path``, ``type``, ``is_active``), or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="list_open_documents is not implemented by this adapter",
    )
measure_entities async
measure_entities(entities: list[str]) -> AdapterResult[dict[str, Any]]

Measure one or two selected entities.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def measure_entities(
    self,
    entities: list[str],
) -> AdapterResult[dict[str, Any]]:
    """Measure one or two selected entities.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="measure_entities is not implemented by this adapter",
    )
mirror_feature async
mirror_feature(features: list[str], mirror_plane: str, merge: bool = True, mirror_bodies: bool = True) -> AdapterResult[dict[str, Any]]

Mirror solid bodies or features about a plane.

Parameters:

Name Type Description Default
features list[str]

Body or feature names to mirror.

required
mirror_plane str

Plane name to mirror about.

required
merge bool

Merge the mirrored result with the original. Defaults to True.

True
mirror_bodies bool

Mirror whole bodies rather than features. Defaults to True.

True

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The mirror feature's name, inputs and volume

AdapterResult[dict[str, Any]]

before/after, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def mirror_feature(
    self,
    features: list[str],
    mirror_plane: str,
    merge: bool = True,
    mirror_bodies: bool = True,
) -> AdapterResult[dict[str, Any]]:
    """Mirror solid bodies or features about a plane.

    Args:
        features (list[str]): Body or feature names to mirror.
        mirror_plane (str): Plane name to mirror about.
        merge (bool): Merge the mirrored result with the original. Defaults to True.
        mirror_bodies (bool): Mirror whole bodies rather than features. Defaults to True.

    Returns:
        AdapterResult[dict[str, Any]]: The mirror feature's name, inputs and volume
        before/after, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="mirror_feature is not implemented by this adapter",
    )
open_model abstractmethod async
open_model(file_path: str) -> AdapterResult[SolidWorksModel]

Open a SolidWorks model (part, assembly, or drawing).

Parameters:

Name Type Description Default
file_path str

Path to the target file.

required

Returns:

Type Description
AdapterResult[SolidWorksModel]

AdapterResult[SolidWorksModel]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def open_model(self, file_path: str) -> AdapterResult[SolidWorksModel]:
    """Open a SolidWorks model (part, assembly, or drawing).

    Args:
        file_path (str): Path to the target file.

    Returns:
        AdapterResult[SolidWorksModel]: The result produced by the operation.
    """
    pass
pattern_circular async
pattern_circular(features: list[str], axis: str, count: int, angle: float = 360.0, equal_spacing: bool = True) -> AdapterResult[dict[str, Any]]

Pattern features around an axis.

Parameters:

Name Type Description Default
features list[str]

Names of the features to pattern.

required
axis str

Name of the axis to rotate about.

required
count int

Total number of instances including the original.

required
angle float

Total angle in degrees to spread them over. Defaults to 360.0.

360.0
equal_spacing bool

Space instances evenly across angle. Defaults to True.

True

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: What was patterned and the volume

AdapterResult[dict[str, Any]]

before/after, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def pattern_circular(
    self,
    features: list[str],
    axis: str,
    count: int,
    angle: float = 360.0,
    equal_spacing: bool = True,
) -> AdapterResult[dict[str, Any]]:
    """Pattern features around an axis.

    Args:
        features (list[str]): Names of the features to pattern.
        axis (str): Name of the axis to rotate about.
        count (int): Total number of instances including the original.
        angle (float): Total angle in degrees to spread them over. Defaults to 360.0.
        equal_spacing (bool): Space instances evenly across ``angle``. Defaults to True.

    Returns:
        AdapterResult[dict[str, Any]]: What was patterned and the volume
        before/after, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="pattern_circular is not implemented by this adapter",
    )
rebuild_model async
rebuild_model(top_level_only: bool = False) -> AdapterResult[dict[str, Any]]

Force a rebuild and report per-feature rebuild errors.

Parameters:

Name Type Description Default
top_level_only bool

Rebuild only the top level of an assembly. Defaults to False (full rebuild).

False

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: rebuilt, top_level_only,

AdapterResult[dict[str, Any]]

errors (one entry per failing feature with feature,

AdapterResult[dict[str, Any]]

type, code, code_name, severity, description,

AdapterResult[dict[str, Any]]

parents, hint), error_count and warning_count.

AdapterResult[dict[str, Any]]

ERROR (still carrying data) when any error-severity entry

AdapterResult[dict[str, Any]]

remains.

Source code in src/solidworks_mcp/adapters/base.py
async def rebuild_model(
    self, top_level_only: bool = False
) -> AdapterResult[dict[str, Any]]:
    """Force a rebuild and report per-feature rebuild errors.

    Args:
        top_level_only (bool): Rebuild only the top level of an assembly.
            Defaults to False (full rebuild).

    Returns:
        AdapterResult[dict[str, Any]]: ``rebuilt``, ``top_level_only``,
        ``errors`` (one entry per failing feature with ``feature``,
        ``type``, ``code``, ``code_name``, ``severity``, ``description``,
        ``parents``, ``hint``), ``error_count`` and ``warning_count``.
        ERROR (still carrying ``data``) when any error-severity entry
        remains.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="rebuild_model is not implemented by this adapter",
    )
rename_feature async
rename_feature(old_name: str, new_name: str) -> AdapterResult[dict[str, Any]]

Rename an existing feature on the feature tree.

Parameters:

Name Type Description Default
old_name str

Current feature name.

required
new_name str

New feature name.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The old and new names, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def rename_feature(
    self, old_name: str, new_name: str
) -> AdapterResult[dict[str, Any]]:
    """Rename an existing feature on the feature tree.

    Args:
        old_name (str): Current feature name.
        new_name (str): New feature name.

    Returns:
        AdapterResult[dict[str, Any]]: The old and new names, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="rename_feature is not implemented by this adapter",
    )
reset_circuit_breakers async
reset_circuit_breakers(operation: str | None = None) -> AdapterResult[dict[str, list[str]]]

Re-open operations a circuit breaker paused.

An adapter without a circuit breaker has nothing paused; the breaker and the connection pool override this.

Parameters:

Name Type Description Default
operation str | None

The operation to reset; None resets every one.

None

Returns:

Type Description
AdapterResult[dict[str, list[str]]]

AdapterResult[dict[str, list[str]]]: reset, the operations that were paused.

Source code in src/solidworks_mcp/adapters/base.py
async def reset_circuit_breakers(
    self, operation: str | None = None
) -> AdapterResult[dict[str, list[str]]]:
    """Re-open operations a circuit breaker paused.

    An adapter without a circuit breaker has nothing paused; the breaker
    and the connection pool override this.

    Args:
        operation: The operation to reset; ``None`` resets every one.

    Returns:
        AdapterResult[dict[str, list[str]]]: ``reset``, the operations that were paused.
    """
    return AdapterResult(status=AdapterResultStatus.SUCCESS, data={"reset": []})
save_body_as_part async
save_body_as_part(body_name: str, file_path: str) -> AdapterResult[dict[str, Any]]

Extract one solid body from the active multibody part to a new file.

Parameters:

Name Type Description Default
body_name str

Name of a solid body in the active part.

required
file_path str

Absolute path for the new .sldprt.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The body, the written path, and the

AdapterResult[dict[str, Any]]

solid-body names found, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def save_body_as_part(
    self, body_name: str, file_path: str
) -> AdapterResult[dict[str, Any]]:
    """Extract one solid body from the active multibody part to a new file.

    Args:
        body_name (str): Name of a solid body in the active part.
        file_path (str): Absolute path for the new ``.sldprt``.

    Returns:
        AdapterResult[dict[str, Any]]: The body, the written path, and the
        solid-body names found, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="save_body_as_part is not implemented by this adapter",
    )
save_file async
save_file(file_path: str | None = None) -> AdapterResult[Any]

Save the active model to the existing path or the provided path.

Parameters:

Name Type Description Default
file_path str | None

Path to the target file. Defaults to None.

None

Returns:

Type Description
AdapterResult[Any]

AdapterResult[Any]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def save_file(self, file_path: str | None = None) -> AdapterResult[Any]:
    """Save the active model to the existing path or the provided path.

    Args:
        file_path (str | None): Path to the target file. Defaults to None.

    Returns:
        AdapterResult[Any]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="save_file is not implemented by this adapter",
    )
select_face_edges async
select_face_edges(point: list[float] | None = None, side: str | None = None, plane: str | None = None, edge_type: str = 'all', append: bool = False) -> AdapterResult[dict[str, Any]]

Multi-select every edge bounding one face of the active part.

Exactly one of point/side/plane identifies the face.

Parameters:

Name Type Description Default
point list[float] | None

[x, y, z] in mm on the face.

None
side str | None

+X..-Z or top/bottom/front/back/ right/left; the furthest planar face facing that way.

None
plane str | None

Reference plane name; the body face(s) lying on it.

None
edge_type str

all, linear or circular.

'all'
append bool

Add to the current selection. Defaults to False.

False

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: face (or faces), edges

AdapterResult[dict[str, Any]]

(one descriptor per selected edge) and selection_count.

Source code in src/solidworks_mcp/adapters/base.py
async def select_face_edges(
    self,
    point: list[float] | None = None,
    side: str | None = None,
    plane: str | None = None,
    edge_type: str = "all",
    append: bool = False,
) -> AdapterResult[dict[str, Any]]:
    """Multi-select every edge bounding one face of the active part.

    Exactly one of ``point``/``side``/``plane`` identifies the face.

    Args:
        point (list[float] | None): ``[x, y, z]`` in mm on the face.
        side (str | None): ``+X``..``-Z`` or ``top/bottom/front/back/
            right/left``; the furthest planar face facing that way.
        plane (str | None): Reference plane name; the body face(s) lying
            on it.
        edge_type (str): ``all``, ``linear`` or ``circular``.
        append (bool): Add to the current selection. Defaults to False.

    Returns:
        AdapterResult[dict[str, Any]]: ``face`` (or ``faces``), ``edges``
        (one descriptor per selected edge) and ``selection_count``.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="select_face_edges is not implemented by this adapter",
    )
select_feature async
select_feature(feature_name: str, append: bool = False) -> AdapterResult[dict[str, Any]]

Select a named feature, sketch, plane or component.

Parameters:

Name Type Description Default
feature_name str

The feature name, optionally @document qualified.

required
append bool

Add to the current selection instead of replacing it. Defaults to False.

False

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: On success selected (True),

AdapterResult[dict[str, Any]]

feature_name, selected_name, entity_type and

AdapterResult[dict[str, Any]]

selection_count. ERROR when nothing could be selected - never

AdapterResult[dict[str, Any]]

SUCCESS with selected: False.

Source code in src/solidworks_mcp/adapters/base.py
async def select_feature(
    self, feature_name: str, append: bool = False
) -> AdapterResult[dict[str, Any]]:
    """Select a named feature, sketch, plane or component.

    Args:
        feature_name (str): The feature name, optionally ``@document``
            qualified.
        append (bool): Add to the current selection instead of replacing
            it. Defaults to False.

    Returns:
        AdapterResult[dict[str, Any]]: On success ``selected`` (True),
        ``feature_name``, ``selected_name``, ``entity_type`` and
        ``selection_count``. ERROR when nothing could be selected - never
        SUCCESS with ``selected: False``.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="select_feature is not implemented by this adapter",
    )
set_component_state async
set_component_state(component: str, fixed: bool | None = None, flexible: bool | None = None) -> AdapterResult[dict[str, Any]]

Fix or float a component, and make a sub-assembly rigid or flexible.

Parameters:

Name Type Description Default
component str

Instance name, as list_components reports it.

required
fixed bool | None

True fixes it, False floats it.

None
flexible bool | None

True flexible, False rigid.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The state before and after, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def set_component_state(
    self,
    component: str,
    fixed: bool | None = None,
    flexible: bool | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Fix or float a component, and make a sub-assembly rigid or flexible.

    Args:
        component (str): Instance name, as ``list_components`` reports it.
        fixed (bool | None): True fixes it, False floats it.
        flexible (bool | None): True flexible, False rigid.

    Returns:
        AdapterResult[dict[str, Any]]: The state before and after, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="set_component_state is not implemented by this adapter",
    )
set_custom_property async
set_custom_property(name: str, value: str, configuration: str | None = None) -> AdapterResult[dict[str, Any]]

Create or overwrite one custom property.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def set_custom_property(
    self,
    name: str,
    value: str,
    configuration: str | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Create or overwrite one custom property.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="set_custom_property is not implemented by this adapter",
    )
set_dimension abstractmethod async
set_dimension(name: str, value: float) -> AdapterResult[None]

Set the value of a dimension.

Parameters:

Name Type Description Default
name str

The name value.

required
value float

The value value.

required

Returns:

Type Description
AdapterResult[None]

AdapterResult[None]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
@abstractmethod
async def set_dimension(self, name: str, value: float) -> AdapterResult[None]:
    """Set the value of a dimension.

    Args:
        name (str): The name value.
        value (float): The value value.

    Returns:
        AdapterResult[None]: The result produced by the operation.
    """
    pass
set_material async
set_material(material_name: str, database: str | None = None, configuration: str | None = None) -> AdapterResult[dict[str, Any]]

Assign a material to the active part.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def set_material(
    self,
    material_name: str,
    database: str | None = None,
    configuration: str | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Assign a material to the active part.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="set_material is not implemented by this adapter",
    )
set_sheet_scale async
set_sheet_scale(numerator: float, denominator: float) -> AdapterResult[dict[str, Any]]

Set the active drawing sheet's scale.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Error unless the adapter implements it.

Source code in src/solidworks_mcp/adapters/base.py
async def set_sheet_scale(
    self,
    numerator: float,
    denominator: float,
) -> AdapterResult[dict[str, Any]]:
    """Set the active drawing sheet's scale.

    Returns:
        AdapterResult[dict[str, Any]]: Error unless the adapter implements it.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="set_sheet_scale is not implemented by this adapter",
    )
set_units async
set_units(unit_system: str) -> AdapterResult[dict[str, Any]]

Set the active document's linear unit system.

Parameters:

Name Type Description Default
unit_system str

One of mm, cm, m, in, ft.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: What was applied, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def set_units(self, unit_system: str) -> AdapterResult[dict[str, Any]]:
    """Set the active document's linear unit system.

    Args:
        unit_system (str): One of ``mm``, ``cm``, ``m``, ``in``, ``ft``.

    Returns:
        AdapterResult[dict[str, Any]]: What was applied, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="set_units is not implemented by this adapter",
    )
sketch_circular_pattern async
sketch_circular_pattern(entities: list[str], angle: float, count: int) -> AdapterResult[str]

Create a circular pattern of sketch entities around the sketch origin.

The rotation axis is always the sketch origin — SW's CreateCircularSketchStepAndRepeat has no pattern-centre parameter and derives the axis from the seed's geometry. Place the seed entity at the desired radius from the origin.

Parameters:

Name Type Description Default
entities list[str]

The entities value.

required
angle float

The angle value.

required
count int

The count value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def sketch_circular_pattern(
    self,
    entities: list[str],
    angle: float,
    count: int,
) -> AdapterResult[str]:
    """Create a circular pattern of sketch entities around the sketch origin.

    The rotation axis is always the sketch origin — SW's
    ``CreateCircularSketchStepAndRepeat`` has no pattern-centre
    parameter and derives the axis from the seed's geometry. Place
    the seed entity at the desired radius from the origin.

    Args:
        entities (list[str]): The entities value.
        angle (float): The angle value.
        count (int): The count value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="sketch_circular_pattern is not implemented by this adapter",
    )
sketch_linear_pattern async
sketch_linear_pattern(entities: list[str], direction_x: float, direction_y: float, spacing: float, count: int) -> AdapterResult[str]

Create a linear pattern of sketch entities.

Parameters:

Name Type Description Default
entities list[str]

The entities value.

required
direction_x float

The direction x value.

required
direction_y float

The direction y value.

required
spacing float

The spacing value.

required
count int

The count value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def sketch_linear_pattern(
    self,
    entities: list[str],
    direction_x: float,
    direction_y: float,
    spacing: float,
    count: int,
) -> AdapterResult[str]:
    """Create a linear pattern of sketch entities.

    Args:
        entities (list[str]): The entities value.
        direction_x (float): The direction x value.
        direction_y (float): The direction y value.
        spacing (float): The spacing value.
        count (int): The count value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="sketch_linear_pattern is not implemented by this adapter",
    )
sketch_mirror async
sketch_mirror(entities: list[str], mirror_line: str) -> AdapterResult[str]

Mirror sketch entities about a mirror line.

Parameters:

Name Type Description Default
entities list[str]

The entities value.

required
mirror_line str

The mirror line value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def sketch_mirror(
    self, entities: list[str], mirror_line: str
) -> AdapterResult[str]:
    """Mirror sketch entities about a mirror line.

    Args:
        entities (list[str]): The entities value.
        mirror_line (str): The mirror line value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="sketch_mirror is not implemented by this adapter",
    )
sketch_offset async
sketch_offset(entities: list[str], offset_distance: float, reverse_direction: bool) -> AdapterResult[str]

Offset sketch entities.

Parameters:

Name Type Description Default
entities list[str]

The entities value.

required
offset_distance float

The offset distance value.

required
reverse_direction bool

The reverse direction value.

required

Returns:

Type Description
AdapterResult[str]

AdapterResult[str]: The result produced by the operation.

Source code in src/solidworks_mcp/adapters/base.py
async def sketch_offset(
    self,
    entities: list[str],
    offset_distance: float,
    reverse_direction: bool,
) -> AdapterResult[str]:
    """Offset sketch entities.

    Args:
        entities (list[str]): The entities value.
        offset_distance (float): The offset distance value.
        reverse_direction (bool): The reverse direction value.

    Returns:
        AdapterResult[str]: The result produced by the operation.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="sketch_offset is not implemented by this adapter",
    )
suppress_feature async
suppress_feature(name: str, suppress: bool = True) -> AdapterResult[dict[str, Any]]

Suppress or unsuppress a named feature.

Parameters:

Name Type Description Default
name str

Feature name.

required
suppress bool

True to suppress, False to unsuppress.

True

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The resulting state, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def suppress_feature(
    self, name: str, suppress: bool = True
) -> AdapterResult[dict[str, Any]]:
    """Suppress or unsuppress a named feature.

    Args:
        name (str): Feature name.
        suppress (bool): True to suppress, False to unsuppress.

    Returns:
        AdapterResult[dict[str, Any]]: The resulting state, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="suppress_feature is not implemented by this adapter",
    )
undo async
undo(count: int = 1) -> AdapterResult[dict[str, Any]]

Undo the last operations in the active model.

Parameters:

Name Type Description Default
count int

Number of steps to undo.

1

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Whether the tree changed, or error.

Source code in src/solidworks_mcp/adapters/base.py
async def undo(self, count: int = 1) -> AdapterResult[dict[str, Any]]:
    """Undo the last operations in the active model.

    Args:
        count (int): Number of steps to undo.

    Returns:
        AdapterResult[dict[str, Any]]: Whether the tree changed, or error.
    """
    return AdapterResult(
        status=AdapterResultStatus.ERROR,
        error="undo is not implemented by this adapter",
    )
update_metrics
update_metrics(operation_time: float, success: bool) -> None

Update adapter metrics.

Parameters:

Name Type Description Default
operation_time float

The operation time value.

required
success bool

The success value.

required

Returns:

Name Type Description
None None

None.

Source code in src/solidworks_mcp/adapters/base.py
def update_metrics(self, operation_time: float, success: bool) -> None:
    """Update adapter metrics.

    Args:
        operation_time (float): The operation time value.
        success (bool): The success value.

    Returns:
        None: None.
    """
    self._metrics["operations_count"] += 1
    if not success:
        self._metrics["errors_count"] += 1

    # Update average response time
    current_avg = self._metrics["average_response_time"]
    count = self._metrics["operations_count"]
    self._metrics["average_response_time"] = (
        current_avg * (count - 1) + operation_time
    ) / count

SolidWorksDocumentationMixin

Expose model-data and drawing-documentation methods through a mixin.

Methods:
arrange_dimensions async
arrange_dimensions(view_name: str | None = None) -> AdapterResult[dict[str, Any]]

Move drawing dimensions so none overlaps another, then verify.

Reads every dimension's text box and lines, plans positions outside the views with :func:dimension_layout.plan_layout, applies them with IAnnotation::SetPosition, rebuilds, then re-reads the real geometry and checks the rule with :func:dimension_layout.find_overlaps. The check covers every view on the sheet, even when only one view is rearranged, because neighbouring views' dimensions can collide.

Parameters:

Name Type Description Default
view_name str | None

Only move this view's dimensions; all views when omitted.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: overlaps_before, overlaps_after

AdapterResult[dict[str, Any]]

(each violation listed), moved and unplaced. ERROR when any

AdapterResult[dict[str, Any]]

overlap remains - the rule is not met - with the same data, so the

AdapterResult[dict[str, Any]]

caller can see which dimensions still collide.

Source code in src/solidworks_mcp/adapters/solidworks/documentation.py
async def arrange_dimensions(
    self, view_name: str | None = None
) -> AdapterResult[dict[str, Any]]:
    """Move drawing dimensions so none overlaps another, then verify.

    Reads every dimension's text box and lines, plans positions outside
    the views with :func:`dimension_layout.plan_layout`, applies them with
    ``IAnnotation::SetPosition``, rebuilds, then **re-reads the real
    geometry** and checks the rule with :func:`dimension_layout.find_overlaps`.
    The check covers every view on the sheet, even when only one view is
    rearranged, because neighbouring views' dimensions can collide.

    Args:
        view_name: Only move this view's dimensions; all views when omitted.

    Returns:
        AdapterResult[dict[str, Any]]: ``overlaps_before``, ``overlaps_after``
        (each violation listed), ``moved`` and ``unplaced``. ERROR when any
        overlap remains - the rule is not met - with the same data, so the
        caller can see which dimensions still collide.
    """
    guard = self._require_drawing()
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)

    def _arrange() -> dict[str, Any]:
        return self._arrange_layout(view_name)

    result = cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation("arrange_dimensions", _arrange),
    )
    if result.is_success and result.data and result.data["overlaps_after"]:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=_overlap_summary(result.data),
            data=result.data,
            execution_time=result.execution_time,
        )
    return result
export_flat_pattern async
export_flat_pattern(file_path: str) -> AdapterResult[dict[str, Any]]

Export the active sheet-metal part's flat pattern to DXF or DWG.

Wraps IPartDoc::ExportToDWG2 with swExportToDWG_ExportSheetMetal. SheetMetalOptions 1 exports the flat-pattern geometry only (no bend lines or sketches).

Parameters:

Name Type Description Default
file_path str

Output .dxf or .dwg path.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The written path and its size.

Source code in src/solidworks_mcp/adapters/solidworks/documentation.py
async def export_flat_pattern(
    self, file_path: str
) -> AdapterResult[dict[str, Any]]:
    """Export the active sheet-metal part's flat pattern to DXF or DWG.

    Wraps ``IPartDoc::ExportToDWG2`` with ``swExportToDWG_ExportSheetMetal``.
    SheetMetalOptions ``1`` exports the flat-pattern geometry only (no bend
    lines or sketches).

    Args:
        file_path: Output ``.dxf`` or ``.dwg`` path.

    Returns:
        AdapterResult[dict[str, Any]]: The written path and its size.
    """
    guard = self._require_doc_type({1}, "export_flat_pattern")
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)
    out = os.path.abspath(file_path)
    if os.path.splitext(out)[1].lower() not in {".dxf", ".dwg"}:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="file_path must end in .dxf or .dwg",
        )

    def _export() -> dict[str, Any]:
        model = self.currentModel
        part = _sw_type_info.flagged(model, "IPartDoc")
        bodies = _sequence(
            self._attempt(
                lambda: part.GetBodies2(_SW_SOLID_BODY, True), default=None
            )
        )
        if not any(
            self._attempt(
                lambda b=b: _as_com(self, b, "IBody2").IsSheetMetal(), default=False
            )
            for b in bodies
        ):
            raise LookupError(
                "The active part has no sheet-metal body, so it has no flat "
                "pattern to export"
            )
        os.makedirs(os.path.dirname(out) or ".", exist_ok=True)
        model_path = str(
            self._attempt(lambda: model.GetPathName(), default="") or ""
        )
        ok = part.ExportToDWG2(
            out,
            model_path,
            _SW_EXPORT_SHEET_METAL,
            True,
            None,
            False,
            False,
            1,
            None,
        )
        if not ok or not os.path.exists(out):
            raise Exception(f"ExportToDWG2 did not write {out} (returned {ok!r})")
        return {"file_path": out, "size_bytes": os.path.getsize(out)}

    return cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation("export_flat_pattern", _export),
    )
get_custom_properties async
get_custom_properties(configuration: str | None = None) -> AdapterResult[dict[str, Any]]

Read the custom properties of the active document.

Wraps IModelDocExtension::CustomPropertyManager(config) with GetNames + Get6. "" reads the file-level properties a drawing title block usually links to.

Parameters:

Name Type Description Default
configuration str | None

Configuration name; file-level when omitted.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: properties mapping name to

AdapterResult[dict[str, Any]]

{"value", "resolved"}.

Source code in src/solidworks_mcp/adapters/solidworks/documentation.py
async def get_custom_properties(
    self, configuration: str | None = None
) -> AdapterResult[dict[str, Any]]:
    """Read the custom properties of the active document.

    Wraps ``IModelDocExtension::CustomPropertyManager(config)`` with
    ``GetNames`` + ``Get6``. ``""`` reads the file-level properties a
    drawing title block usually links to.

    Args:
        configuration: Configuration name; file-level when omitted.

    Returns:
        AdapterResult[dict[str, Any]]: ``properties`` mapping name to
        ``{"value", "resolved"}``.
    """
    if not self.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    def _get() -> dict[str, Any]:
        manager = _sw_type_info.flagged(
            self.currentModel.Extension.CustomPropertyManager(configuration or ""),
            "ICustomPropertyManager",
        )
        props: dict[str, dict[str, str]] = {}
        names = self._attempt(
            lambda: self._get_attr_or_call(manager, "GetNames"), default=None
        )
        for name in _sequence(names):
            value, resolved = _read_custom_property(self, manager, str(name))
            props[str(name)] = {"value": value, "resolved": resolved}
        return {"configuration": configuration or "", "properties": props}

    return cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation("get_custom_properties", _get),
    )
get_material async
get_material(configuration: str | None = None) -> AdapterResult[dict[str, Any]]

Read the material assigned to the active part.

Wraps IPartDoc::GetMaterialPropertyName2; see :func:_read_material for its Database out-parameter.

Parameters:

Name Type Description Default
configuration str | None

Configuration name; the active one when omitted.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: material (None when unset),

AdapterResult[dict[str, Any]]

database and density_kg_m3, read from the material's entry

AdapterResult[dict[str, Any]]

in its .sldmat library (None when unset or not found).

Source code in src/solidworks_mcp/adapters/solidworks/documentation.py
async def get_material(
    self, configuration: str | None = None
) -> AdapterResult[dict[str, Any]]:
    """Read the material assigned to the active part.

    Wraps ``IPartDoc::GetMaterialPropertyName2``; see
    :func:`_read_material` for its ``Database`` out-parameter.

    Args:
        configuration: Configuration name; the active one when omitted.

    Returns:
        AdapterResult[dict[str, Any]]: ``material`` (``None`` when unset),
        ``database`` and ``density_kg_m3``, read from the material's entry
        in its ``.sldmat`` library (``None`` when unset or not found).
    """
    guard = self._require_doc_type({1}, "get_material")
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)

    def _get() -> dict[str, Any]:
        part = _sw_type_info.flagged(self.currentModel, "IPartDoc")
        name, database = _read_material(part, configuration)
        app = getattr(self, "swApp", None)
        density = (
            _material_density(
                _library_files(self._material_folders(app)), name, database
            )
            if name and app is not None
            else None
        )
        return {
            "material": name or None,
            "database": database or None,
            "density_kg_m3": density,
            "configuration": configuration,
        }

    return cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation("get_material", _get),
    )
insert_model_dimensions async
insert_model_dimensions(view_name: str | None = None, include_hole_wizard: bool = True) -> AdapterResult[dict[str, Any]]

Bring model dimensions into drawing views, each dimension shown once.

Imports the model's "marked for drawing" dimensions view by view with IDrawingDoc::InsertModelAnnotations3 (UsePlacementInSketch off, so SolidWorks places them rather than copying sketch positions). Drafting rules applied:

  • pictorial views (isometric, dimetric, trimetric) are not dimensioned unless named explicitly;
  • a model dimension already shown in an earlier view is deleted from later views, so each dimension appears exactly once on the sheet.

Falls back to IDrawingDoc::AutoDimension only when nothing at all could be imported. Placement is then made overlap-free by :meth:arrange_dimensions.

Parameters:

Name Type Description Default
view_name str | None

One view to dimension; every orthographic view when omitted.

None
include_hole_wizard bool

Also import Hole Wizard location dimensions.

True

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Per-view counts, duplicates removed,

AdapterResult[dict[str, Any]]

skipped pictorial views and the method used.

Source code in src/solidworks_mcp/adapters/solidworks/documentation.py
async def insert_model_dimensions(
    self, view_name: str | None = None, include_hole_wizard: bool = True
) -> AdapterResult[dict[str, Any]]:
    """Bring model dimensions into drawing views, each dimension shown once.

    Imports the model's "marked for drawing" dimensions view by view with
    ``IDrawingDoc::InsertModelAnnotations3`` (``UsePlacementInSketch`` off,
    so SolidWorks places them rather than copying sketch positions).
    Drafting rules applied:

    * pictorial views (isometric, dimetric, trimetric) are not dimensioned
      unless named explicitly;
    * a model dimension already shown in an earlier view is deleted from
      later views, so each dimension appears exactly once on the sheet.

    Falls back to ``IDrawingDoc::AutoDimension`` only when nothing at all
    could be imported. Placement is then made overlap-free by
    :meth:`arrange_dimensions`.

    Args:
        view_name: One view to dimension; every orthographic view when omitted.
        include_hole_wizard: Also import Hole Wizard location dimensions.

    Returns:
        AdapterResult[dict[str, Any]]: Per-view counts, duplicates removed,
        skipped pictorial views and the ``method`` used.
    """
    guard = self._require_drawing()
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)

    def _insert() -> dict[str, Any]:
        return self._import_dimensions(view_name, include_hole_wizard)

    return cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation("insert_model_dimensions", _insert),
    )
list_available_materials async
list_available_materials(search: str | None = None) -> AdapterResult[list[dict[str, str]]]

List materials from the configured SolidWorks material libraries.

Reads the custom library folders from swFileLocationsMaterialDatabases (a ;-separated list) plus the install's built-in sldmaterials folder, and parses every .sldmat file in them.

Parameters:

Name Type Description Default
search str | None

Case-insensitive substring filter on name or classification.

None

Returns:

Type Description
AdapterResult[list[dict[str, str]]]

AdapterResult[list[dict[str, str]]]: name, classification

AdapterResult[list[dict[str, str]]]

and database per material. metadata gives the folders

AdapterResult[list[dict[str, str]]]

read and the total before filtering, so an empty search can be

AdapterResult[list[dict[str, str]]]

told apart from an unexpected library location.

Source code in src/solidworks_mcp/adapters/solidworks/documentation.py
async def list_available_materials(
    self, search: str | None = None
) -> AdapterResult[list[dict[str, str]]]:
    """List materials from the configured SolidWorks material libraries.

    Reads the custom library folders from
    ``swFileLocationsMaterialDatabases`` (a ``;``-separated list) plus the
    install's built-in ``sldmaterials`` folder, and parses every
    ``.sldmat`` file in them.

    Args:
        search: Case-insensitive substring filter on name or classification.

    Returns:
        AdapterResult[list[dict[str, str]]]: ``name``, ``classification``
        and ``database`` per material. ``metadata`` gives the ``folders``
        read and the ``total`` before filtering, so an empty search can be
        told apart from an unexpected library location.
    """
    app = getattr(self, "swApp", None)
    if app is None:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="Not connected to SolidWorks"
        )
    needle = (search or "").strip().lower()
    searched: dict[str, Any] = {}

    def _list() -> list[dict[str, str]]:
        folder_list = self._material_folders(app)
        folders = ";".join(folder_list)
        materials: list[dict[str, str]] = []
        for path in _library_files(folder_list):
            materials.extend(_parse_material_library(path))
        searched["folders"] = folder_list
        searched["total"] = len(materials)
        if not materials:
            raise LookupError(f"No .sldmat libraries found in: {folders}")
        if needle:
            materials = [
                m
                for m in materials
                if needle in m["name"].lower()
                or needle in m["classification"].lower()
            ]
        return materials

    result = cast(
        "AdapterResult[list[dict[str, str]]]",
        self._handle_com_operation("list_available_materials", _list),
    )
    if result.is_success:
        result.metadata = dict(searched)
    return result
list_model_dimensions async
list_model_dimensions(feature_name: str | None = None) -> AdapterResult[list[dict[str, Any]]]

List every dimension in the active part or assembly.

Walks IModelDoc2::FirstFeature / IFeature::GetNextFeature and each feature's GetFirstDisplayDimension chain. full_name (e.g. "D1@Sketch1") is the name set_dimension accepts, so the pair gives edit-by-name access to every driving parameter in the model.

Parameters:

Name Type Description Default
feature_name str | None

Only list dimensions of this feature (case-insensitive).

None

Returns:

Type Description
AdapterResult[list[dict[str, Any]]]

AdapterResult[list[dict[str, Any]]]: full_name, feature,

AdapterResult[list[dict[str, Any]]]

value (document units), system_value (metres / radians)

AdapterResult[list[dict[str, Any]]]

and read_only per dimension.

Source code in src/solidworks_mcp/adapters/solidworks/documentation.py
async def list_model_dimensions(
    self, feature_name: str | None = None
) -> AdapterResult[list[dict[str, Any]]]:
    """List every dimension in the active part or assembly.

    Walks ``IModelDoc2::FirstFeature`` / ``IFeature::GetNextFeature`` and
    each feature's ``GetFirstDisplayDimension`` chain. ``full_name`` (e.g.
    ``"D1@Sketch1"``) is the name ``set_dimension`` accepts, so the pair
    gives edit-by-name access to every driving parameter in the model.

    Args:
        feature_name: Only list dimensions of this feature (case-insensitive).

    Returns:
        AdapterResult[list[dict[str, Any]]]: ``full_name``, ``feature``,
        ``value`` (document units), ``system_value`` (metres / radians)
        and ``read_only`` per dimension.
    """
    guard = self._require_doc_type({1, 2}, "list_model_dimensions")
    if guard is not None:
        return cast("AdapterResult[list[dict[str, Any]]]", guard)
    target = (feature_name or "").strip().lower()

    def _list() -> list[dict[str, Any]]:
        model = self.currentModel
        dims: list[dict[str, Any]] = []
        feature = self._attempt(lambda: model.FirstFeature(), default=None)
        guard_count = 0
        while feature is not None and guard_count < 20000:
            guard_count += 1
            feat = _sw_type_info.flagged(feature, "IFeature")
            name = str(self._attempt(lambda f=feat: f.Name, default="") or "")
            if not target or name.lower() == target:
                display = self._attempt(
                    lambda f=feat: f.GetFirstDisplayDimension(), default=None
                )
                while display is not None:
                    disp = _sw_type_info.flagged(display, "IDisplayDimension")
                    dim = self._attempt(
                        lambda d=disp: d.GetDimension2(0), default=None
                    )
                    if dim is not None:
                        dim = _sw_type_info.flagged(dim, "IDimension")
                        dims.append(
                            {
                                "full_name": _strip_document_suffix(
                                    self._attempt(
                                        lambda d=dim: d.FullName, default=None
                                    )
                                ),
                                "feature": name,
                                "value": self._attempt(
                                    lambda d=dim: d.Value, default=None
                                ),
                                "system_value": self._attempt(
                                    lambda d=dim: d.SystemValue, default=None
                                ),
                                "read_only": bool(
                                    self._attempt(
                                        lambda d=dim: d.ReadOnly, default=False
                                    )
                                ),
                            }
                        )
                    display = self._attempt(
                        lambda f=feat, d=display: f.GetNextDisplayDimension(d),
                        default=None,
                    )
            feature = self._attempt(lambda f=feat: f.GetNextFeature(), default=None)
        if target and not dims:
            raise LookupError(
                f"No dimensions found on feature {feature_name!r}; list "
                "the feature tree with list_features for exact names"
            )
        return dims

    return cast(
        "AdapterResult[list[dict[str, Any]]]",
        self._handle_com_operation("list_model_dimensions", _list),
    )
measure_entities async
measure_entities(entities: list[str]) -> AdapterResult[dict[str, Any]]

Measure one or more entities with SolidWorks' Measure tool.

Selects each entity by spec (see :func:_parse_measure_spec), then runs IModelDocExtension::CreateMeasure + IMeasure::Calculate(None), which measures the current selection. Values SolidWorks reports as -1 (not applicable to the selection) are dropped.

Parameters:

Name Type Description Default
entities list[str]

One or two entity specs, e.g. ["face:0,0.01,0"] for a face area, or two edge/face specs for distance and angle.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Measured values in metres, square

AdapterResult[dict[str, Any]]

metres and radians, plus selected; is_parallel only for

AdapterResult[dict[str, Any]]

two entities.

Source code in src/solidworks_mcp/adapters/solidworks/documentation.py
async def measure_entities(
    self, entities: list[str]
) -> AdapterResult[dict[str, Any]]:
    """Measure one or more entities with SolidWorks' Measure tool.

    Selects each entity by spec (see :func:`_parse_measure_spec`), then runs
    ``IModelDocExtension::CreateMeasure`` + ``IMeasure::Calculate(None)``,
    which measures the current selection. Values SolidWorks reports as
    ``-1`` (not applicable to the selection) are dropped.

    Args:
        entities: One or two entity specs, e.g. ``["face:0,0.01,0"]`` for a
            face area, or two edge/face specs for distance and angle.

    Returns:
        AdapterResult[dict[str, Any]]: Measured values in metres, square
        metres and radians, plus ``selected``; ``is_parallel`` only for
        two entities.
    """
    guard = self._require_doc_type({1, 2}, "measure_entities")
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)
    if not entities or len(entities) > 2:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="measure_entities takes one or two entity specs",
        )

    def _measure() -> dict[str, Any]:
        model = self.currentModel
        self._attempt(lambda: model.ClearSelection2(True), default=None)
        for index, spec in enumerate(entities):
            name, x, y, z, kind = _parse_measure_spec(spec)
            append = index > 0
            if name:
                ok = bool(
                    self._attempt(
                        lambda n=name, k=kind, a=append: (
                            model.Extension.SelectByID2(
                                n, k, 0.0, 0.0, 0.0, a, 0, _null_callout(), 0
                            )
                        ),
                        default=False,
                    )
                )
            else:
                ok = _select_edge_by_coord(self, x, y, z, append, 0, kind)
            if not ok:
                raise LookupError(
                    f"No entity found for {spec!r}; check the name or the "
                    "coordinates (metres)"
                )

        # CreateMeasure is a zero-arg method; unflagged, late binding asks
        # for it as a property and SW 2026 answers "Member not found".
        # Not wrapped in _attempt: a COM error surfaces with its message.
        extension = _sw_type_info.flagged(model.Extension, "IModelDocExtension")
        measure = extension.CreateMeasure()
        if measure is None:
            raise Exception("CreateMeasure returned nothing")
        measure = _sw_type_info.flagged(measure, "IMeasure")
        if not measure.Calculate(None):
            raise Exception("IMeasure.Calculate failed for this selection")

        result: dict[str, Any] = {"selected": list(entities)}
        for prop, key in (
            ("Distance", "distance_m"),
            ("CenterDistance", "center_distance_m"),
            ("Angle", "angle_rad"),
            ("Radius", "radius_m"),
            ("Diameter", "diameter_m"),
            ("Length", "length_m"),
            ("ArcLength", "arc_length_m"),
            ("Area", "area_m2"),
            ("Perimeter", "perimeter_m"),
            ("DeltaX", "delta_x_m"),
            ("DeltaY", "delta_y_m"),
            ("DeltaZ", "delta_z_m"),
        ):
            value = self._attempt(lambda p=prop: getattr(measure, p), default=None)
            if isinstance(value, (int, float)) and value != -1:
                result[key] = float(value)
        if len(entities) == 2:
            # Parallelism only means something between two entities.
            parallel = self._attempt(lambda: measure.IsParallel, default=None)
            if isinstance(parallel, bool):
                result["is_parallel"] = parallel
        self._attempt(lambda: model.ClearSelection2(True), default=None)
        return result

    return cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation("measure_entities", _measure),
    )
set_custom_property async
set_custom_property(name: str, value: str, configuration: str | None = None) -> AdapterResult[dict[str, Any]]

Create or overwrite one text custom property on the active document.

Wraps ICustomPropertyManager::Add3(name, swCustomInfoText, value, swCustomPropertyReplaceValue) and reads the value back.

Parameters:

Name Type Description Default
name str

Property name, e.g. "PartNo" or "DrawnBy".

required
value str

Text value; may be a $PRP:"..." link.

required
configuration str | None

Configuration name; file-level when omitted.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The stored and resolved value.

Source code in src/solidworks_mcp/adapters/solidworks/documentation.py
async def set_custom_property(
    self, name: str, value: str, configuration: str | None = None
) -> AdapterResult[dict[str, Any]]:
    """Create or overwrite one text custom property on the active document.

    Wraps ``ICustomPropertyManager::Add3(name, swCustomInfoText, value,
    swCustomPropertyReplaceValue)`` and reads the value back.

    Args:
        name: Property name, e.g. ``"PartNo"`` or ``"DrawnBy"``.
        value: Text value; may be a ``$PRP:"..."`` link.
        configuration: Configuration name; file-level when omitted.

    Returns:
        AdapterResult[dict[str, Any]]: The stored and resolved value.
    """
    if not self.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )
    if not name.strip():
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="name is required"
        )

    def _set() -> dict[str, Any]:
        manager = _sw_type_info.flagged(
            self.currentModel.Extension.CustomPropertyManager(configuration or ""),
            "ICustomPropertyManager",
        )
        code = manager.Add3(
            name,
            _SW_CUSTOM_INFO_TEXT,
            str(value),
            _SW_CUSTOM_PROPERTY_REPLACE_VALUE,
        )
        stored, resolved = _read_custom_property(self, manager, name)
        if stored != str(value):
            raise Exception(
                f"Custom property {name!r} was not stored (Add3 returned {code!r}, "
                f"read back {stored!r})."
            )
        return {
            "name": name,
            "value": stored,
            "resolved": resolved,
            "configuration": configuration or "",
        }

    return cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation("set_custom_property", _set),
    )
set_material async
set_material(material_name: str, database: str | None = None, configuration: str | None = None) -> AdapterResult[dict[str, Any]]

Assign a material to the active part.

Wraps IPartDoc::SetMaterialPropertyName2 (a void call), then reads the material back - the read-back is the only proof it applied. A name the library does not hold leaves the part unchanged and is reported as not found.

Parameters:

Name Type Description Default
material_name str

Material name exactly as in the library, e.g. "AISI 1020" or "6061-T6 (SS)".

required
database str | None

Library name; "SOLIDWORKS Materials" when omitted.

None
configuration str | None

Configuration name; "" (all) when omitted.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Requested and applied material.

Source code in src/solidworks_mcp/adapters/solidworks/documentation.py
async def set_material(
    self,
    material_name: str,
    database: str | None = None,
    configuration: str | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Assign a material to the active part.

    Wraps ``IPartDoc::SetMaterialPropertyName2`` (a ``void`` call), then
    reads the material back - the read-back is the only proof it applied.
    A name the library does not hold leaves the part unchanged and is
    reported as not found.

    Args:
        material_name: Material name exactly as in the library, e.g.
            ``"AISI 1020"`` or ``"6061-T6 (SS)"``.
        database: Library name; ``"SOLIDWORKS Materials"`` when omitted.
        configuration: Configuration name; ``""`` (all) when omitted.

    Returns:
        AdapterResult[dict[str, Any]]: Requested and applied material.
    """
    guard = self._require_doc_type({1}, "set_material")
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)
    if not material_name.strip():
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="material_name is required"
        )
    library = database or _DEFAULT_MATERIAL_DATABASE

    def _set() -> dict[str, Any]:
        part = _sw_type_info.flagged(self.currentModel, "IPartDoc")
        part.SetMaterialPropertyName2(configuration or "", library, material_name)
        self._attempt(lambda: self.currentModel.EditRebuild3(), default=None)
        applied, _library = _read_material(part, configuration)
        if applied.lower() != material_name.lower():
            # SolidWorks ignores a name its library does not hold.
            raise LookupError(
                f"Material {material_name!r} not found in {library!r}; the "
                f"part still reports {applied or 'no material'}. Use "
                "list_available_materials for the exact name and library."
            )
        return {
            "material": applied,
            "database": library,
            "configuration": configuration,
        }

    return cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation("set_material", _set),
    )

SolidWorksDrawingStandardMixin

Create complete standard A3 drawings, BOM tables and the sheet scale.

Methods:
add_datum_tag async
add_datum_tag(label: str, attach_at_mm: tuple[float, float], x_mm: float, y_mm: float) -> AdapterResult[dict[str, Any]]

Add a datum feature symbol (a datum tag) on a drawing edge.

IModelDoc2::InsertDatumTag2 attaches the tag to the edge picked at attach_at_mm; IDatumTag::SetLabel names it, and the label is read back with GetLabel.

Parameters:

Name Type Description Default
label str

The datum letter, e.g. A.

required
attach_at_mm tuple[float, float]

A sheet point on the edge or face line it marks.

required
x_mm float

Where the tag goes on the sheet.

required
y_mm float

Where the tag goes on the sheet.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The label as read back.

Source code in src/solidworks_mcp/adapters/solidworks/drawing_standard.py
async def add_datum_tag(
    self,
    label: str,
    attach_at_mm: tuple[float, float],
    x_mm: float,
    y_mm: float,
) -> AdapterResult[dict[str, Any]]:
    """Add a datum feature symbol (a datum tag) on a drawing edge.

    ``IModelDoc2::InsertDatumTag2`` attaches the tag to the edge picked at
    ``attach_at_mm``; ``IDatumTag::SetLabel`` names it, and the label is
    read back with ``GetLabel``.

    Args:
        label: The datum letter, e.g. ``A``.
        attach_at_mm: A sheet point on the edge or face line it marks.
        x_mm: Where the tag goes on the sheet.
        y_mm: Where the tag goes on the sheet.

    Returns:
        AdapterResult[dict[str, Any]]: The label as read back.
    """
    guard = self._require_drawing()
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)
    letter = label.strip().upper()
    if len(letter) != 1 or not letter.isalpha():
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="label must be a single letter, e.g. 'A'",
        )

    def _insert() -> dict[str, Any]:
        model = self.currentModel
        if not self._select_edge_at(attach_at_mm):
            raise LookupError(f"No drawing edge found at {list(attach_at_mm)} mm")
        tag = self._get_attr_or_call(model, "InsertDatumTag2")
        self._attempt(lambda: model.ClearSelection2(True), default=None)
        if tag is None:
            raise Exception("InsertDatumTag2 returned nothing; no tag was added")
        tag = _sw_type_info.flagged(tag, "IDatumTag")
        tag.SetLabel(letter)
        self._place_annotation(tag, x_mm, y_mm)
        read = str(
            self._attempt(lambda: self._get_attr_or_call(tag, "GetLabel")) or ""
        )
        if read != letter:
            raise Exception(f"The datum tag reads {read!r}, not {letter!r}")
        return {"label": letter, "position_mm": [x_mm, y_mm]}

    return cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation("add_datum_tag", _insert),
    )
add_gtol async
add_gtol(characteristic: str, tolerance: float, x_mm: float, y_mm: float, diameter_zone: bool = False, material_condition: str = 'none', datums: list[str] | None = None, attach_at_mm: tuple[float, float] | None = None, dimension: tuple[str, str] | None = None) -> AdapterResult[dict[str, Any]]

Add a feature control frame (GD&T) to the active drawing.

IModelDoc2::InsertGtol creates the frame on whatever is selected: a drawing dimension (dimension; the frame sits under its text and moves with it, the usual place for a hole's position tolerance), the edge picked at attach_at_mm (with a leader), or nothing (free-standing); IGtol::SetFrameSymbols2 sets the characteristic, the Ø zone and the material condition, and IGtol::SetFrameValues the tolerance and datums. The values are read back with GetFrameValues, so a frame SolidWorks did not fill in is an error, not a success.

Parameters:

Name Type Description Default
characteristic str

position, flatness, perpendicularity, parallelism, profile_surface or concentricity.

required
tolerance float

The tolerance value, in drawing units.

required
x_mm float

Where the frame goes on the sheet.

required
y_mm float

Where the frame goes on the sheet.

required
diameter_zone bool

Prefix the tolerance with Ø (a cylindrical zone).

False
material_condition str

none, mmc (Ⓜ) or lmc (Ⓛ).

'none'
datums list[str] | None

Up to three datum letters, primary first.

None
attach_at_mm tuple[float, float] | None

A sheet point on the edge the leader points to.

None
dimension tuple[str, str] | None

(view name, model dimension name), e.g. ("Drawing View2", "D3@Sketch1"): attach the frame to that drawing dimension. x_mm/y_mm are then not used.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The frame as read back.

Source code in src/solidworks_mcp/adapters/solidworks/drawing_standard.py
async def add_gtol(
    self,
    characteristic: str,
    tolerance: float,
    x_mm: float,
    y_mm: float,
    diameter_zone: bool = False,
    material_condition: str = "none",
    datums: list[str] | None = None,
    attach_at_mm: tuple[float, float] | None = None,
    dimension: tuple[str, str] | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Add a feature control frame (GD&T) to the active drawing.

    ``IModelDoc2::InsertGtol`` creates the frame on whatever is selected:
    a drawing dimension (``dimension``; the frame sits under its text and
    moves with it, the usual place for a hole's position tolerance), the
    edge picked at ``attach_at_mm`` (with a leader), or nothing
    (free-standing);
    ``IGtol::SetFrameSymbols2`` sets the characteristic, the Ø zone and
    the material condition, and ``IGtol::SetFrameValues`` the tolerance
    and datums. The values are read back with ``GetFrameValues``, so a
    frame SolidWorks did not fill in is an error, not a success.

    Args:
        characteristic: ``position``, ``flatness``, ``perpendicularity``,
            ``parallelism``, ``profile_surface`` or ``concentricity``.
        tolerance: The tolerance value, in drawing units.
        x_mm: Where the frame goes on the sheet.
        y_mm: Where the frame goes on the sheet.
        diameter_zone: Prefix the tolerance with Ø (a cylindrical zone).
        material_condition: ``none``, ``mmc`` (Ⓜ) or ``lmc`` (Ⓛ).
        datums: Up to three datum letters, primary first.
        attach_at_mm: A sheet point on the edge the leader points to.
        dimension: ``(view name, model dimension name)``, e.g.
            ``("Drawing View2", "D3@Sketch1")``: attach the frame to that
            drawing dimension. ``x_mm``/``y_mm`` are then not used.

    Returns:
        AdapterResult[dict[str, Any]]: The frame as read back.
    """
    guard = self._require_drawing()
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)
    problem = gtol_problem(characteristic, tolerance, material_condition, datums)
    if problem is not None:
        return AdapterResult(status=AdapterResultStatus.ERROR, error=problem)
    symbol = _GTOL_SYMBOLS[characteristic]
    modifier = _GTOL_MODIFIERS[material_condition]
    letters = [d.strip().upper() for d in datums or []]

    def _insert() -> dict[str, Any]:
        model = self.currentModel
        target = None
        if dimension is not None:
            target = self._drawing_dimension(*dimension)
            if target is None or not self._select_display_dimension(model, target):
                raise LookupError(
                    f"No dimension {dimension[1]!r} in view {dimension[0]!r}"
                )
            attached: Any = dimension[1]
        elif attach_at_mm is not None:
            if not self._select_edge_at(attach_at_mm):
                raise LookupError(
                    f"No drawing edge found at {list(attach_at_mm)} mm"
                )
            attached = True
        else:
            attached = False
        gtol = self._get_attr_or_call(model, "InsertGtol")
        self._attempt(lambda: model.ClearSelection2(True), default=None)
        if gtol is None:
            raise Exception("InsertGtol returned nothing; no frame was added")
        gtol = _sw_type_info.flagged(gtol, "IGtol")
        padded = [*letters, "", "", ""][:3]
        text = f"{tolerance:g}"
        gtol.SetFrameSymbols2(
            1, symbol, diameter_zone, modifier, False, "", "", "", ""
        )
        gtol.SetFrameValues(1, text, "", *padded)
        if dimension is None:
            self._place_annotation(gtol, x_mm, y_mm)
        self._attempt(lambda: model.GraphicsRedraw2(), default=None)
        values = self._attempt(lambda: gtol.GetFrameValues(1), default=None)
        read = [str(v) for v in values] if isinstance(values, (list, tuple)) else []
        if not read or read[0] != text or read[2:5] != padded:
            raise Exception(
                f"The frame did not take its values: read back {read or None}, "
                f"expected tolerance {text!r} and datums {padded}"
            )
        return {
            "characteristic": characteristic,
            "tolerance": text,
            "diameter_zone": diameter_zone,
            "material_condition": material_condition,
            "datums": letters,
            "attached": attached,
            "position_mm": None if dimension is not None else [x_mm, y_mm],
            "layout": (
                None
                if dimension is None
                else self._clear_attached_frame(dimension[0], target, gtol)
            ),
        }

    result = cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation("add_gtol", _insert),
    )
    layout = (result.data or {}).get("layout") if result.is_success else None
    if layout and layout["overlaps_after"]:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                f"The frame was added but {layout['overlaps_after']} overlap(s) "
                "remain on the sheet - the no-overlap rule is not met; see layout"
            ),
            data=result.data,
            execution_time=result.execution_time,
        )
    return result
create_standard_drawing async
create_standard_drawing(model_path: str, output_dir: str | None = None, title: str | None = None, subtitle: str | None = None, drawing_number: str | None = None, author: str = '', revision: str = 'A', company: str = '', drawn_date: str | None = None, notes: list[str] | None = None, include_isometric: bool = True, write_model_properties: bool = True, title_block_text_height_mm: float | None = None, include_bom: bool = False) -> AdapterResult[dict[str, Any]]

Create a complete A3 drawing that follows the drawing standard.

Opens the model (or uses its open copy), writes the title-block properties into it, makes a drawing on the A3 ISO format, builds the sheet in separate COM steps, writes the drawing properties, saves <drawing_number>.SLDDRW and exports a PDF.

Parameters:

Name Type Description Default
model_path str

Part (.SLDPRT) or assembly (.SLDASM) to draw.

required
output_dir str | None

Folder for the .SLDDRW and PDF; the model's folder by default.

None
title str | None

Title block TITLE; the model file name by default.

None
subtitle str | None

Company field when company is empty.

None
drawing_number str | None

DWG NO.; also the drawing file name. Default <MODEL>-Rev<revision>-01.

None
author str

DRAWN name.

''
revision str

Revision letter.

'A'
company str

Company name field.

''
drawn_date str | None

DRAWN date; today (e.g. "17 Oct 2026") by default.

None
notes list[str] | None

Extra note lines (at most seven lines in total are shown).

None
include_isometric bool

Add the isometric view in its cell.

True
write_model_properties bool

Write Description/Material/Weight custom properties into the model (the format reads them from there) and save the model.

True
title_block_text_height_mm float | None

Largest text height for the DWG NO. and TITLE fields, e.g. 3 for smaller text; they are shrunk further when they still do not fit.

None
include_bom bool

Assemblies only: a top-level BOM in the top of the isometric's cell, the isometric fitted below it.

False

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Files, scale, views, dimension

AdapterResult[dict[str, Any]]

counts, the overlap check, every property written and each step's

AdapterResult[dict[str, Any]]

outcome. ERROR, with that data, when a step fails, the drawing or

AdapterResult[dict[str, Any]]

PDF could not be saved, or any overlap remains.

Source code in src/solidworks_mcp/adapters/solidworks/drawing_standard.py
async def create_standard_drawing(
    self,
    model_path: str,
    output_dir: str | None = None,
    title: str | None = None,
    subtitle: str | None = None,
    drawing_number: str | None = None,
    author: str = "",
    revision: str = "A",
    company: str = "",
    drawn_date: str | None = None,
    notes: list[str] | None = None,
    include_isometric: bool = True,
    write_model_properties: bool = True,
    title_block_text_height_mm: float | None = None,
    include_bom: bool = False,
) -> AdapterResult[dict[str, Any]]:
    """Create a complete A3 drawing that follows the drawing standard.

    Opens the model (or uses its open copy), writes the title-block
    properties into it, makes a drawing on the A3 ISO format, builds the
    sheet in separate COM steps, writes the drawing properties, saves
    ``<drawing_number>.SLDDRW`` and exports a PDF.

    Args:
        model_path: Part (.SLDPRT) or assembly (.SLDASM) to draw.
        output_dir: Folder for the .SLDDRW and PDF; the model's folder by default.
        title: Title block TITLE; the model file name by default.
        subtitle: Company field when ``company`` is empty.
        drawing_number: DWG NO.; also the drawing file name. Default
            ``<MODEL>-Rev<revision>-01``.
        author: DRAWN name.
        revision: Revision letter.
        company: Company name field.
        drawn_date: DRAWN date; today (e.g. "17 Oct 2026") by default.
        notes: Extra note lines (at most seven lines in total are shown).
        include_isometric: Add the isometric view in its cell.
        write_model_properties: Write Description/Material/Weight custom
            properties into the model (the format reads them from there)
            and save the model.
        title_block_text_height_mm: Largest text height for the DWG NO.
            and TITLE fields, e.g. 3 for smaller text; they are shrunk
            further when they still do not fit.
        include_bom: Assemblies only: a top-level BOM in the top of the
            isometric's cell, the isometric fitted below it.

    Returns:
        AdapterResult[dict[str, Any]]: Files, scale, views, dimension
        counts, the overlap check, every property written and each step's
        outcome. ERROR, with that data, when a step fails, the drawing or
        PDF could not be saved, or any overlap remains.
    """
    path = os.path.abspath(model_path)
    if not os.path.isfile(path):
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error=f"Model file not found: {path}"
        )
    ext = os.path.splitext(path)[1].lower()
    if ext not in {".sldprt", ".sldasm"}:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=f"model_path must be a .SLDPRT or .SLDASM, not {ext or 'no extension'}",
        )
    if include_bom and ext != ".sldasm":
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="include_bom needs an assembly (.SLDASM); a part has no BOM",
        )
    stem = os.path.splitext(os.path.basename(path))[0]
    rev = (revision or "A").strip()
    number = drawing_number or f"{stem.upper().replace(' ', '-')}-Rev{rev}-01"
    out_dir = os.path.abspath(output_dir or os.path.dirname(path))
    drawing_path = os.path.join(out_dir, f"{number}.SLDDRW")
    pdf_path = os.path.join(out_dir, f"{number}.pdf")
    steps: list[dict[str, Any]] = []
    written: dict[str, dict[str, str]] = {}
    data: dict[str, Any] = {"drawing_number": number, "steps": steps}

    def failed(error: str) -> AdapterResult[dict[str, Any]]:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error=error, data=data
        )

    # 1. The model: open it, read its material, write the title-block properties.
    active = self._handle_com_operation(
        "activate_document", lambda: self._use_open_document(path)
    )
    if not (active.is_success and active.data):
        opened = await self.open_model(path)
        if not opened.is_success:
            return failed(
                f"Could not open model: {opened.error}. If a different file "
                "with the same name is open in SolidWorks, close it first."
            )
    material = None
    if ext == ".sldprt":
        mat = await self.get_material()
        material = (mat.data or {}).get("material") if mat.is_success else None
    model_props, drawing_props = title_block_properties(
        model_file=path,
        title=title or stem,
        subtitle=subtitle,
        author=author,
        drawn_date=drawn_date or _date.today().strftime("%d %b %Y"),
        revision=rev,
        company=company,
    )
    if ext == ".sldasm":
        # An assembly has no material of its own: a link to it shows
        # "<not specified>". Its own Material property is left as set.
        model_props.pop("Material")
    if write_model_properties:
        for name, value in model_props.items():
            _step(
                steps,
                f"model_property:{name}",
                await self.set_custom_property(name, value),
            )
        _step(steps, "save_model", await self.save_file())
        written["model"] = model_props

    # 2. A drawing on the A3 ISO format, without the Sheet Format/Size prompt.
    found = self._handle_com_operation(
        "standard_drawing_format", lambda: find_sheet_format(self)
    )
    if not (found.is_success and found.data):
        return failed(
            "Sheet format 'a3 - iso.slddrt' not found in SolidWorks' sheet "
            "format folders or under C:\\ProgramData\\SOLIDWORKS"
        )
    prompt = self._handle_com_operation(
        "standard_drawing_dialog", self._new_drawing_without_dialog
    )
    try:
        created = await self.create_drawing()
    finally:
        self._handle_com_operation(
            "standard_drawing_dialog",
            lambda: self._restore_dialog(
                prompt.data if prompt.is_success else None
            ),
        )
    if not created.is_success:
        return failed(f"Could not create drawing: {created.error}")
    _step(steps, "drawing_units_mm", await self.set_units("mm"))

    # 3. The sheet, one COM step at a time.
    s = _Sheet(
        path=path,
        sheet_format=str(found.data),
        include_isometric=include_isometric,
        include_bom=include_bom,
        material=material,
        extra_notes=list(notes or []),
        field_height=(
            title_block_text_height_mm / 1000.0
            if title_block_text_height_mm
            else None
        ),
    )
    for name, build in (
        ("sheet", self._setup_a3_sheet),
        ("views", self._create_views),
        ("layout", self._fit_views),
        ("bom", self._bom_above_iso),
        ("dimensions", self._dimension_views),
        ("notes", self._annotate),
        ("check", self._check_sheet),
    ):
        done = self._handle_com_operation(
            f"standard_drawing_{name}", lambda build=build: build(s)
        )
        _step(steps, f"build_{name}", done)
        if not done.is_success:
            return failed(f"Drawing step '{name}' failed: {done.error}")
        if name == "dimensions":
            data["dimensions"] = done.data
        if name == "bom":
            data["bom"] = done.data
        if name == "views":
            data["hidden_lines_visible"] = done.data["hidden_lines_visible"]
        if name == "check":
            data["overlap_check"] = done.data
    data.update(
        {
            "scale": scale_label(s.scale),
            "isometric_scale": scale_label(s.iso_scale) if s.iso_scale else None,
            "views": {
                n: {"role": s.role.get(n), "caption": s.captions.get(n)}
                for n in s.views
            },
            "sheet_format": s.sheet_format,
            "notes": s.note_lines,
        }
    )

    # 4. Drawing properties, then the files.
    for name, value in drawing_props.items():
        _step(
            steps,
            f"drawing_property:{name}",
            await self.set_custom_property(name, value),
        )
    written["drawing"] = drawing_props
    data["properties_written"] = written
    os.makedirs(out_dir, exist_ok=True)
    saved = await self.save_file(drawing_path)
    _step(steps, "save_drawing", saved)
    data["drawing_path"] = drawing_path if saved.is_success else None
    if not saved.is_success:
        return failed(
            f"Drawing built but not saved to {drawing_path}: {saved.error}"
        )

    # 5. The title block, now that the DWG NO. shows the saved file name.
    fitted = self._handle_com_operation(
        "standard_drawing_title_block", lambda: self._fit_title_block(s)
    )
    _step(steps, "fit_title_block", fitted)
    data["title_block"] = fitted.data if fitted.is_success else None
    data["format_links"] = s.format_links
    check = data["overlap_check"]
    for name, field_fit in (data["title_block"] or {}).items():
        if field_fit.get("fits") is False:
            check["annotation_overlaps"].append(
                {
                    "dimension": f"title block {name}",
                    "conflicts_with": "the next title-block field",
                    "kind": "field_overflow",
                }
            )
            check["overlaps_after"] = check.get("overlaps_after", 0) + 1
    _step(steps, "save_drawing_again", await self.save_file(drawing_path))
    pdf = await self.export_file(pdf_path, "pdf")
    _step(steps, "export_pdf", pdf)
    data["pdf_path"] = pdf_path if pdf.is_success else None
    if not pdf.is_success:
        return failed(
            f"Drawing saved to {drawing_path} but the PDF export failed: {pdf.error}"
        )

    overlaps = data["overlap_check"].get("overlaps_after", 0)
    overflowing = any(
        f.get("fits") is False for f in (data.get("title_block") or {}).values()
    )
    if overlaps:
        check = data["overlap_check"]
        found = [
            *check.get("remaining_overlaps", []),
            *check.get("annotation_overlaps", []),
        ]
        named = "; ".join(
            f"{o['dimension']} vs {o['conflicts_with']} ({o['kind']})"
            for o in found[:5]
        )
        return failed(
            f"Drawing saved but {overlaps} overlap(s) remain - the no-overlap "
            "rule is not met; see overlap_check"
            + (f": {named}" if named else "")
            + (f". For a title-block field, {FIELD_HINT}" if overflowing else "")
        )
    return AdapterResult(status=AdapterResultStatus.SUCCESS, data=data)
insert_bom_table async
insert_bom_table(view_name: str, template_path: str | None = None, bom_type: str = 'top_level_only', x_mm: float | None = None, y_mm: float | None = None) -> AdapterResult[dict[str, Any]]

Insert a bill-of-materials table anchored to an assembly drawing view.

Wraps IView::InsertBomTable4. With no position the table snaps to the sheet format's BOM anchor, or to the sheet's bottom-left corner when the sheet has no format. Its stationary corner is the bottom-left, so the table grows up onto the sheet; with the top-left it hung below the sheet (measured live on SW 2026). The table's box is read back and must lie on the sheet. An anchor near the top of the sheet (the ISO A3 format's) makes it grow off the top, so a table placed by the anchor is slid back onto the sheet; one placed at a given x_mm/y_mm is never moved. With no template the library default bom-standard.sldbomtbt from the BOM templates folder is used.

Parameters:

Name Type Description Default
view_name str

A view of an assembly on the active drawing.

required
template_path str | None

.sldbomtbt table template.

None
bom_type str

top_level_only, parts_only or indented.

'top_level_only'
x_mm float | None

Optional table position on the sheet.

None
y_mm float | None

Optional table position on the sheet.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The view, BOM type, template, row

AdapterResult[dict[str, Any]]

count, table_box_mm and moved_onto_sheet; an error naming

AdapterResult[dict[str, Any]]

the box when the table still runs off the sheet.

Source code in src/solidworks_mcp/adapters/solidworks/drawing_standard.py
async def insert_bom_table(
    self,
    view_name: str,
    template_path: str | None = None,
    bom_type: str = "top_level_only",
    x_mm: float | None = None,
    y_mm: float | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Insert a bill-of-materials table anchored to an assembly drawing view.

    Wraps ``IView::InsertBomTable4``. With no position the table snaps to
    the sheet format's BOM anchor, or to the sheet's bottom-left corner
    when the sheet has no format. Its stationary corner is the
    bottom-left, so the table grows up onto the sheet; with the top-left
    it hung below the sheet (measured live on SW 2026). The table's box is
    read back and must lie on the sheet. An anchor near the top of the
    sheet (the ISO A3 format's) makes it grow off the top, so a table
    placed by the anchor is slid back onto the sheet; one placed at a
    given ``x_mm``/``y_mm`` is never moved. With no template the library
    default ``bom-standard.sldbomtbt`` from the BOM templates folder is
    used.

    Args:
        view_name: A view of an assembly on the active drawing.
        template_path: ``.sldbomtbt`` table template.
        bom_type: ``top_level_only``, ``parts_only`` or ``indented``.
        x_mm: Optional table position on the sheet.
        y_mm: Optional table position on the sheet.

    Returns:
        AdapterResult[dict[str, Any]]: The view, BOM type, template, row
        count, ``table_box_mm`` and ``moved_onto_sheet``; an error naming
        the box when the table still runs off the sheet.
    """
    guard = self._require_drawing()
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)
    if bom_type not in _SW_BOM_TYPES:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="bom_type must be top_level_only, parts_only or indented",
        )

    return cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation(
            "insert_bom_table",
            lambda: self._insert_bom(
                view_name, template_path, bom_type, x_mm, y_mm
            )[1],
        ),
    )
set_sheet_scale async
set_sheet_scale(numerator: float, denominator: float) -> AdapterResult[dict[str, Any]]

Set the active drawing sheet's scale, e.g. 1:2 (ISheet::SetScale).

Parameters:

Name Type Description Default
numerator float

Scale numerator.

required
denominator float

Scale denominator.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The sheet name, the scale read

AdapterResult[dict[str, Any]]

back from the sheet and the requested one.

Source code in src/solidworks_mcp/adapters/solidworks/drawing_standard.py
async def set_sheet_scale(
    self, numerator: float, denominator: float
) -> AdapterResult[dict[str, Any]]:
    """Set the active drawing sheet's scale, e.g. 1:2 (``ISheet::SetScale``).

    Args:
        numerator: Scale numerator.
        denominator: Scale denominator.

    Returns:
        AdapterResult[dict[str, Any]]: The sheet name, the ``scale`` read
        back from the sheet and the ``requested`` one.
    """
    guard = self._require_drawing()
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)
    if numerator <= 0 or denominator <= 0:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="Scale values must be positive"
        )

    def _scale() -> dict[str, Any]:
        drawing = _sw_type_info.flagged(self.currentModel, "IDrawingDoc")
        sheet = _sw_type_info.flagged(drawing.GetCurrentSheet(), "ISheet")
        if not sheet.SetScale(float(numerator), float(denominator), True, False):
            raise Exception("ISheet.SetScale returned False")
        self._attempt(lambda: self.currentModel.EditRebuild3(), default=None)
        name = self._attempt(lambda: sheet.GetName(), default=None)
        # GetProperties2: paper size, template, scale num, scale den, ...
        props = self._attempt(lambda: sheet.GetProperties2(), default=None)
        if not (isinstance(props, (list, tuple)) and len(props) >= 4):
            raise Exception("The sheet's scale could not be read back")
        return {
            "sheet": name,
            "scale": f"{float(props[2]):g}:{float(props[3]):g}",
            "requested": f"{numerator:g}:{denominator:g}",
        }

    return cast(
        "AdapterResult[dict[str, Any]]",
        self._handle_com_operation("set_sheet_scale", _scale),
    )

SolidWorksFeaturesMixin

Expose SolidWorks feature methods via mixin-local implementation helpers.

Methods:
add_fillet async
add_fillet(radius: float, edge_names: list[str]) -> AdapterResult[SolidWorksFeature]

Fillet edges, then confirm the model's volume actually changed.

IModelDoc2.FeatureFillet3 can report success without building anything, so besides the feature-count check inside :func:_add_fillet_impl the volume is compared before and after, here in the async layer for the same reason as mirror_feature. An unreadable volume is not treated as failure.

Parameters:

Name Type Description Default
radius float

Fillet radius in millimetres.

required
edge_names list[str]

Edge names, "x,y,z" points or "face:x,y,z" specs; see :func:_add_fillet_impl.

required

Returns:

Type Description
AdapterResult[SolidWorksFeature]

AdapterResult[SolidWorksFeature]: The fillet, with

AdapterResult[SolidWorksFeature]

volume_before / volume_after in its properties when they

AdapterResult[SolidWorksFeature]

could be read. ERROR when the volume did not change.

Source code in src/solidworks_mcp/adapters/solidworks/features.py
async def add_fillet(
    self, radius: float, edge_names: list[str]
) -> AdapterResult[SolidWorksFeature]:
    """Fillet edges, then confirm the model's volume actually changed.

    ``IModelDoc2.FeatureFillet3`` can report success without building
    anything, so besides the feature-count check inside
    :func:`_add_fillet_impl` the volume is compared before and after,
    here in the async layer for the same reason as ``mirror_feature``.
    An unreadable volume is not treated as failure.

    Args:
        radius: Fillet radius in millimetres.
        edge_names: Edge names, ``"x,y,z"`` points or ``"face:x,y,z"``
            specs; see :func:`_add_fillet_impl`.

    Returns:
        AdapterResult[SolidWorksFeature]: The fillet, with
        ``volume_before`` / ``volume_after`` in its properties when they
        could be read. ``ERROR`` when the volume did not change.
    """
    before = await self.get_mass_properties()
    volume_before = (
        before.data.volume if before.is_success and before.data else None
    )

    result = _add_fillet_impl(self, radius, edge_names)
    if not result.is_success or result.data is None or volume_before is None:
        return result

    after = await self.get_mass_properties()
    volume_after = after.data.volume if after.is_success and after.data else None
    if volume_after is None:
        return result
    if abs(volume_after - volume_before) <= 1e-7 * max(volume_before, 1.0):
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                f"The fillet changed nothing (volume stayed at "
                f"{volume_before:.3f} mm3). SolidWorks accepted the call "
                "but filleted no edge; check that the specs name edges or "
                "faces of the body."
            ),
        )
    result.data.properties["volume_before"] = volume_before
    result.data.properties["volume_after"] = volume_after
    return result
create_reference_point async
create_reference_point(mode: str, x: float, y: float, z: float, distance: float | None = None, percent: float | None = None) -> AdapterResult[dict[str, Any]]

Create a reference point on the active part.

Parameters:

Name Type Description Default
mode str

"along_curve" or "face_center".

required
x float

X of a point on the target edge/face, in millimetres.

required
y float

Y of a point on the target edge/face, in millimetres.

required
z float

Z of a point on the target edge/face, in millimetres.

required
distance float | None

For along_curve, offset from the edge start in mm.

None
percent float | None

For along_curve, position as 0-100 of edge length.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Delegates to

AdapterResult[dict[str, Any]]

func:_create_reference_point_impl; see it for the payload shape.

Source code in src/solidworks_mcp/adapters/solidworks/features.py
async def create_reference_point(
    self,
    mode: str,
    x: float,
    y: float,
    z: float,
    distance: float | None = None,
    percent: float | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Create a reference point on the active part.

    Args:
        mode: ``"along_curve"`` or ``"face_center"``.
        x: X of a point on the target edge/face, in millimetres.
        y: Y of a point on the target edge/face, in millimetres.
        z: Z of a point on the target edge/face, in millimetres.
        distance: For ``along_curve``, offset from the edge start in mm.
        percent: For ``along_curve``, position as 0-100 of edge length.

    Returns:
        AdapterResult[dict[str, Any]]: Delegates to
        :func:`_create_reference_point_impl`; see it for the payload shape.
    """
    return _create_reference_point_impl(self, mode, x, y, z, distance, percent)
delete_feature async
delete_feature(name: str) -> AdapterResult[dict[str, Any]]

Delete a feature and report every feature that went with it.

The tree is listed before and after with list_features, the read path that works on SolidWorks, and the names that disappeared become removed_features: the named feature and its dependents.

Parameters:

Name Type Description Default
name str

The feature or sketch to delete.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The delete's data plus

AdapterResult[dict[str, Any]]

removed_features in tree order (just name when the tree

AdapterResult[dict[str, Any]]

can't be listed).

Source code in src/solidworks_mcp/adapters/solidworks/features.py
async def delete_feature(self, name: str) -> AdapterResult[dict[str, Any]]:
    """Delete a feature and report every feature that went with it.

    The tree is listed before and after with ``list_features``, the read
    path that works on SolidWorks, and the names that disappeared become
    ``removed_features``: the named feature and its dependents.

    Args:
        name: The feature or sketch to delete.

    Returns:
        AdapterResult[dict[str, Any]]: The delete's data plus
        ``removed_features`` in tree order (just ``name`` when the tree
        can't be listed).
    """
    before = await self.list_features(include_suppressed=True)
    result = _delete_feature_impl(self, name)
    if not result.is_success:
        return result
    after = await self.list_features(include_suppressed=True)
    data = dict(result.data or {})
    data["removed_features"] = _removed_names(before, after) or [
        name.split("@", 1)[0]
    ]
    return AdapterResult(
        status=AdapterResultStatus.SUCCESS,
        data=data,
        execution_time=result.execution_time,
    )
mirror_feature async
mirror_feature(features: list[str], mirror_plane: str, merge: bool = True, mirror_bodies: bool = True) -> AdapterResult[dict[str, Any]]

Mirror solid bodies or features about a plane.

InsertMirrorFeature returns a Feature object even when it mirrored nothing, so the model's volume is measured before and after and the call is reported as failed if the volume did not change (a mirrored cut lowers it, a mirrored boss or body raises it). That check runs here rather than inside the COM closure because get_mass_properties is the read path known to work - reading CreateMassProperty().Volume inline returns None when a plane is still in the selection set, which silently disables the check.

Parameters:

Name Type Description Default
features list[str]

Names of the bodies (or features) to mirror. For a body mirror these are the names as they appear under Solid Bodies, which for a lofted wing is the loft's own name.

required
mirror_plane str

Plane to mirror about, e.g. "Right Plane".

required
merge bool

Merge the mirrored result with the original.

True
mirror_bodies bool

Mirror whole solid bodies rather than features. Defaults to True, which is what works for anything built from a loft or sweep: SolidWorks only resolves a feature mirror when the feature's own sketch sits on the mirror plane, so a wing lofted between stations offset from the centreline cannot be feature-mirrored. Measured on SW 2025: body mirroring a 1819569 mm^3 wing about the Right Plane gave 3636460 mm^3.

True

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: What was mirrored, and the volume

AdapterResult[dict[str, Any]]

before, after and its change. ERROR when nothing was selected,

AdapterResult[dict[str, Any]]

the call failed, or the volume did not change.

Source code in src/solidworks_mcp/adapters/solidworks/features.py
async def mirror_feature(
    self,
    features: list[str],
    mirror_plane: str,
    merge: bool = True,
    mirror_bodies: bool = True,
) -> AdapterResult[dict[str, Any]]:
    """Mirror solid bodies or features about a plane.

    ``InsertMirrorFeature`` returns a Feature object even when it mirrored
    nothing, so the model's volume is measured before and after and the
    call is reported as failed if the volume did not change (a mirrored
    cut lowers it, a mirrored boss or body raises it). That check runs
    here rather than inside the COM closure because ``get_mass_properties``
    is the read path known to work - reading ``CreateMassProperty().Volume``
    inline returns ``None`` when a plane is still in the selection set,
    which silently disables the check.

    Args:
        features: Names of the bodies (or features) to mirror. For a body
            mirror these are the names as they appear under Solid Bodies,
            which for a lofted wing is the loft's own name.
        mirror_plane: Plane to mirror about, e.g. ``"Right Plane"``.
        merge: Merge the mirrored result with the original.
        mirror_bodies: Mirror whole solid bodies rather than features.
            Defaults to ``True``, which is what works for anything built
            from a loft or sweep: SolidWorks only resolves a *feature*
            mirror when the feature's own sketch sits on the mirror plane,
            so a wing lofted between stations offset from the centreline
            cannot be feature-mirrored. Measured on SW 2025: body mirroring
            a 1819569 mm^3 wing about the Right Plane gave 3636460 mm^3.

    Returns:
        AdapterResult[dict[str, Any]]: What was mirrored, and the volume
        before, after and its change. ``ERROR`` when nothing was selected,
        the call failed, or the volume did not change.
    """
    adapter = self._adapter(self)
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    before = await self.get_mass_properties()
    volume_before = (
        before.data.volume if before.is_success and before.data else None
    )

    result = _mirror_feature_impl(
        self, features, mirror_plane, merge, mirror_bodies
    )
    if not result.is_success:
        return result

    after = await self.get_mass_properties()
    volume_after = after.data.volume if after.is_success and after.data else None

    if volume_before is None or volume_after is None:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                "The mirror call returned a feature but the volume could "
                "not be read before and after, so whether any geometry was "
                "produced is unknown."
            ),
        )
    if abs(volume_after - volume_before) <= volume_before * 0.001:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                f"The mirror produced no new geometry (volume "
                f"{volume_before:.1f} -> {volume_after:.1f} mm3). "
                "SolidWorks returned a feature but mirrored nothing. With "
                "mirror_bodies=False this happens whenever the feature's "
                "own sketch does not sit on the mirror plane; try "
                "mirror_bodies=True to mirror the solid body instead."
            ),
        )

    return _with_volume_change(result, volume_before, volume_after)
pattern_circular async
pattern_circular(features: list[str], axis: str, count: int, angle: float = 360.0, equal_spacing: bool = True) -> AdapterResult[dict[str, Any]]

Pattern features around an axis.

FeatureCircularPattern5 returns a Feature object whether or not it produced anything, so the model volume is measured before and after here - in the async layer, using get_mass_properties, which is the read path that works. Reading volume inline inside the COM closure returns None while a selection is active.

Note that instances landing off the parent body are silently dropped by SolidWorks, so the volume changes by less than count - 1 times the seed. That is correct behaviour, not a failure - only no change at all means the pattern did nothing. A pattern of a cut lowers the volume, a pattern of a boss raises it; both count.

Parameters:

Name Type Description Default
features list[str]

Names of the features to pattern.

required
axis str

Name of the axis to rotate about, e.g. "Axis1" from create_axis.

required
count int

Total number of instances including the original.

required
angle float

Total angle in degrees to spread them over. Defaults to a full 360.

360.0
equal_spacing bool

Space instances evenly across angle.

True

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: What was patterned and the volume

AdapterResult[dict[str, Any]]

before, after and its change. ERROR when nothing could be

AdapterResult[dict[str, Any]]

selected or the volume did not change.

Source code in src/solidworks_mcp/adapters/solidworks/features.py
async def pattern_circular(
    self,
    features: list[str],
    axis: str,
    count: int,
    angle: float = 360.0,
    equal_spacing: bool = True,
) -> AdapterResult[dict[str, Any]]:
    """Pattern features around an axis.

    ``FeatureCircularPattern5`` returns a Feature object whether or not it
    produced anything, so the model volume is measured before and after
    here - in the async layer, using ``get_mass_properties``, which is the
    read path that works. Reading volume inline inside the COM closure
    returns ``None`` while a selection is active.

    Note that instances landing off the parent body are silently dropped
    by SolidWorks, so the volume changes by less than ``count - 1`` times
    the seed. That is correct behaviour, not a failure - only *no* change
    at all means the pattern did nothing. A pattern of a cut lowers the
    volume, a pattern of a boss raises it; both count.

    Args:
        features: Names of the features to pattern.
        axis: Name of the axis to rotate about, e.g. ``"Axis1"`` from
            ``create_axis``.
        count: Total number of instances including the original.
        angle: Total angle in degrees to spread them over. Defaults to a
            full 360.
        equal_spacing: Space instances evenly across ``angle``.

    Returns:
        AdapterResult[dict[str, Any]]: What was patterned and the volume
        before, after and its change. ``ERROR`` when nothing could be
        selected or the volume did not change.
    """
    adapter = self._adapter(self)
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    before = await self.get_mass_properties()
    volume_before = (
        before.data.volume if before.is_success and before.data else None
    )

    result = _pattern_circular_impl(
        self, features, axis, count, angle, equal_spacing
    )
    if not result.is_success:
        return result

    after = await self.get_mass_properties()
    volume_after = after.data.volume if after.is_success and after.data else None

    if volume_before is None or volume_after is None:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                "The pattern call returned a feature but the volume could "
                "not be read before and after, so whether any instances "
                "were created is unknown."
            ),
        )
    if abs(volume_after - volume_before) <= volume_before * 0.0001:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                f"The circular pattern produced no geometry (volume "
                f"{volume_before:.1f} -> {volume_after:.1f} mm3). "
                f"SolidWorks returned a feature but patterned nothing. "
                f"Check that '{axis}' is a real axis in the tree and that "
                f"the instances land on the parent body."
            ),
        )

    return _with_volume_change(result, volume_before, volume_after)
rename_feature async
rename_feature(old_name: str, new_name: str) -> AdapterResult[dict[str, Any]]

Rename a feature on the active model's tree.

Parameters:

Name Type Description Default
old_name str

The feature's current name.

required
new_name str

The name to give it.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Delegates to

AdapterResult[dict[str, Any]]

func:_rename_feature_impl; see it for the payload shape.

Source code in src/solidworks_mcp/adapters/solidworks/features.py
async def rename_feature(
    self, old_name: str, new_name: str
) -> AdapterResult[dict[str, Any]]:
    """Rename a feature on the active model's tree.

    Args:
        old_name: The feature's current name.
        new_name: The name to give it.

    Returns:
        AdapterResult[dict[str, Any]]: Delegates to
        :func:`_rename_feature_impl`; see it for the payload shape.
    """
    return _rename_feature_impl(self, old_name, new_name)

SolidWorksIOMixin

Expose model open/save/create/configuration methods through a mixin.

Methods:
activate_document async
activate_document(title_or_path: str) -> AdapterResult[dict[str, Any]]

Make an already-open document the active one.

Matches title_or_path against the open documents by exact title, by full path, or by file name (all case-insensitive), then calls ISldWorks::ActivateDoc3 and updates the adapter's tracked currentModel. The resulting ActiveDoc is read back and a mismatch is reported as an error.

Parameters:

Name Type Description Default
title_or_path str

A window title ("bracket.SLDPRT"), a full path, or a bare file name of a document that is already open.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: activated (the title switched

AdapterResult[dict[str, Any]]

to) and verified (bool, or None when the readback could

AdapterResult[dict[str, Any]]

not be performed). ERROR when not connected, the argument is

AdapterResult[dict[str, Any]]

blank, or nothing open matches.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def activate_document(
    self, title_or_path: str
) -> AdapterResult[dict[str, Any]]:
    """Make an already-open document the active one.

    Matches ``title_or_path`` against the open documents by exact title,
    by full path, or by file name (all case-insensitive), then calls
    ``ISldWorks::ActivateDoc3`` and updates the adapter's tracked
    ``currentModel``. The resulting ``ActiveDoc`` is read back and a
    mismatch is reported as an error.

    Args:
        title_or_path: A window title (``"bracket.SLDPRT"``), a full path,
            or a bare file name of a document that is already open.

    Returns:
        AdapterResult[dict[str, Any]]: ``activated`` (the title switched
        to) and ``verified`` (bool, or ``None`` when the readback could
        not be performed). ``ERROR`` when not connected, the argument is
        blank, or nothing open matches.
    """
    adapter = self._adapter(self)
    if not adapter.is_connected():
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="Not connected to SolidWorks"
        )
    target = (title_or_path or "").strip()
    if not target:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="title_or_path is required"
        )

    def _activate() -> dict[str, Any]:
        app = adapter.swApp

        docs = _coerce_dispatch_sequence(
            adapter._attempt(lambda: app.GetDocuments(), default=None)
        )
        needle = target.lower()
        match: Any = None
        match_title: str | None = None
        open_titles: list[str] = []
        for doc in docs:
            title = adapter._attempt(
                lambda d=doc: adapter._get_attr_or_call(d, "GetTitle"),
                default=None,
            )
            path = adapter._attempt(
                lambda d=doc: adapter._get_attr_or_call(d, "GetPathName"),
                default=None,
            )
            if title:
                open_titles.append(str(title))
            candidates = {
                str(title).lower() if title else "",
                str(path).lower() if path else "",
                Path(str(path)).name.lower() if path else "",
            }
            candidates.discard("")
            if needle in candidates:
                match = doc
                match_title = str(title) if title else None
                break

        if match is None:
            raise Exception(
                f"No open document matches {target!r}. Open: "
                + (", ".join(open_titles) or "none")
            )
        if not match_title:
            match_title = adapter._attempt(
                lambda: adapter._get_attr_or_call(match, "GetTitle"), default=None
            )
        if not match_title:
            raise Exception(
                "Matched an open document but could not read its title to "
                "activate it"
            )

        activated = adapter._attempt(
            lambda: app.ActivateDoc3(match_title, False, 0, _byref_int()),
            default=None,
        )
        model = activated or adapter._attempt(
            lambda: getattr(app, "ActiveDoc", None), default=None
        )
        if model is not None:
            type_raw = adapter._attempt(
                lambda: adapter._get_attr_or_call(model, "GetType"), default=1
            )
            adapter._attempt(
                lambda: _sw_type_info.flag_doc(
                    model,
                    int(type_raw) if isinstance(type_raw, (int, float)) else 1,
                ),
                default=0,
            )
            adapter.currentModel = model

        active_after = adapter._attempt(
            lambda: getattr(app, "ActiveDoc", None), default=None
        )
        now_title = (
            adapter._attempt(
                lambda: adapter._get_attr_or_call(active_after, "GetTitle"),
                default=None,
            )
            if active_after is not None
            else None
        )
        if now_title is None:
            verified: bool | None = None
        else:
            verified = str(now_title) == str(match_title)
            if not verified:
                raise Exception(
                    f"Requested {match_title!r} but ActiveDoc is "
                    f"{now_title!r} after ActivateDoc3; activation did not take."
                )
        return {"activated": str(match_title), "verified": verified}

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("activate_document", _activate),
    )
add_drawing_view async
add_drawing_view(payload: Any = None) -> AdapterResult[dict[str, Any]]

Add a view of a model to the active drawing sheet.

The same operation as create_drawing_view; both tool entry points exist upstream and reach this one implementation.

Parameters:

Name Type Description Default
payload Any

Tool payload; see _place_view.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The new view's name and position.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def add_drawing_view(
    self, payload: Any = None
) -> AdapterResult[dict[str, Any]]:
    """Add a view of a model to the active drawing sheet.

    The same operation as ``create_drawing_view``; both tool entry points
    exist upstream and reach this one implementation.

    Args:
        payload: Tool payload; see ``_place_view``.

    Returns:
        AdapterResult[dict[str, Any]]: The new view's name and position.
    """
    return self._place_view(payload)
add_mate async
add_mate(component_a: str, component_b: str, entity_a: str = 'Front Plane', entity_b: str = 'Front Plane', mate_type: str = 'coincident', alignment: str = 'aligned', distance: float = 0.0, angle: float = 0.0, face_a: list[float] | None = None, face_b: list[float] | None = None) -> AdapterResult[dict[str, Any]]

Mate two components together.

Wraps IAssemblyDoc::AddMate5. A named entity (a reference plane or axis) is selected via IComponent2::FeatureByName + IFeature::Select2. A face is given as a point on it in the component's own part coordinates: the face is found among that component's faces only (IComponent2::GetBodies2, which report part coordinates; measured on SW 2026) and selected with IEntity::Select4. Searching one component means a pin and the hole it sits in, whose surfaces share points, can't be confused.

Parameters:

Name Type Description Default
component_a str

First component instance name, as reported by :meth:list_components.

required
component_b str

Second component instance name.

required
entity_a str

Named feature on the first component.

'Front Plane'
entity_b str

Named feature on the second component.

'Front Plane'
mate_type str

coincident, concentric, perpendicular, parallel, tangent, distance or angle.

'coincident'
alignment str

aligned, anti_aligned or closest.

'aligned'
distance float

Distance in millimetres, for a distance mate.

0.0
angle float

Angle in degrees, for an angle mate.

0.0
face_a list[float] | None

[x, y, z] mm on a face of the first component, in its part coordinates; used instead of entity_a.

None
face_b list[float] | None

The same for the second component.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The mate created, plus the bounding

AdapterResult[dict[str, Any]]

box before and after so the caller can see what moved. ERROR

AdapterResult[dict[str, Any]]

when the entities cannot be selected or SolidWorks rejects the

AdapterResult[dict[str, Any]]

mate.

Raises:

Type Description
Exception

Propagated through _handle_com_operation.

Example::

await adapter.add_mate("plate-1", "plate-2")
Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def add_mate(
    self,
    component_a: str,
    component_b: str,
    entity_a: str = "Front Plane",
    entity_b: str = "Front Plane",
    mate_type: str = "coincident",
    alignment: str = "aligned",
    distance: float = 0.0,
    angle: float = 0.0,
    face_a: list[float] | None = None,
    face_b: list[float] | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Mate two components together.

    Wraps ``IAssemblyDoc::AddMate5``. A named entity (a reference plane or
    axis) is selected via ``IComponent2::FeatureByName`` +
    ``IFeature::Select2``. A face is given as a point on it in the
    component's own part coordinates: the face is found among *that
    component's* faces only (``IComponent2::GetBodies2``, which report
    part coordinates; measured on SW 2026) and selected with
    ``IEntity::Select4``. Searching one component means a pin and the
    hole it sits in, whose surfaces share points, can't be confused.

    Args:
        component_a (str): First component instance name, as reported by
            :meth:`list_components`.
        component_b (str): Second component instance name.
        entity_a (str): Named feature on the first component.
        entity_b (str): Named feature on the second component.
        mate_type (str): ``coincident``, ``concentric``, ``perpendicular``,
            ``parallel``, ``tangent``, ``distance`` or ``angle``.
        alignment (str): ``aligned``, ``anti_aligned`` or ``closest``.
        distance (float): Distance in millimetres, for a distance mate.
        angle (float): Angle in degrees, for an angle mate.
        face_a (list[float] | None): ``[x, y, z]`` mm on a face of the
            first component, in its part coordinates; used instead of
            ``entity_a``.
        face_b (list[float] | None): The same for the second component.

    Returns:
        AdapterResult[dict[str, Any]]: The mate created, plus the bounding
        box before and after so the caller can see what moved.  ``ERROR``
        when the entities cannot be selected or SolidWorks rejects the
        mate.

    Raises:
        Exception: Propagated through ``_handle_com_operation``.

    Example::

        await adapter.add_mate("plate-1", "plate-2")
    """
    adapter = self._adapter(self)
    if _doc_type(adapter) != 2:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="add_mate requires an assembly document",
        )

    mate_key = str(mate_type).strip().lower()
    if mate_key not in _MATE_TYPES:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                f"Unknown mate type '{mate_type}'. "
                f"Use one of: {', '.join(sorted(_MATE_TYPES))}."
            ),
        )
    align_key = str(alignment).strip().lower()
    if align_key not in _MATE_ALIGNMENTS:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                f"Unknown alignment '{alignment}'. "
                f"Use one of: {', '.join(sorted(_MATE_ALIGNMENTS))}."
            ),
        )

    def _mate() -> dict[str, Any]:
        import math

        model = adapter.currentModel
        assembly = _sw_type_info.flagged(model, "IAssemblyDoc")

        wanted = {component_a: entity_a, component_b: entity_b}
        faces = {component_a: face_a, component_b: face_b}
        found = _find_components(adapter, assembly, [component_a, component_b])

        adapter._attempt(lambda: model.ClearSelection2(True), default=None)
        for index, component_name in enumerate((component_a, component_b)):
            wrapped = found[component_name]
            entity_name = wanted[component_name]
            point = faces[component_name]
            if point is not None:
                _select_component_face(
                    adapter, model, wrapped, component_name, point, index > 0
                )
                continue
            feature = adapter._attempt(
                lambda w=wrapped, e=entity_name: w.FeatureByName(e), default=None
            )
            if feature is None:
                raise Exception(
                    f"'{entity_name}' not found on {component_name}. "
                    "Only named tree features (reference planes and axes) "
                    "can be selected here."
                )
            flagged = _as_com(adapter, feature, "IFeature")
            if flagged is None or not adapter._attempt(
                lambda f=flagged, a=index > 0: f.Select2(a, 0), default=False
            ):
                raise Exception(
                    f"Failed to select '{entity_name}' on {component_name}"
                )

        selected = adapter._attempt(
            lambda: model.SelectionManager.GetSelectedObjectCount2(-1), default=0
        )
        if selected != 2:
            raise Exception(
                f"Expected 2 selected entities for the mate, got {selected}"
            )

        transforms_before = _component_transforms(adapter, assembly)
        status = _byref_int()
        mate = adapter._attempt(
            lambda: assembly.AddMate5(
                _MATE_TYPES[mate_key],
                _MATE_ALIGNMENTS[align_key],
                False,  # Flip
                distance / 1000.0,  # Distance (m)
                distance / 1000.0,  # upper limit
                distance / 1000.0,  # lower limit
                0.0,  # gear ratio numerator
                0.0,  # gear ratio denominator
                math.radians(float(angle)),
                math.radians(float(angle)),
                math.radians(float(angle)),
                False,  # ForPositioningOnly
                False,  # LockRotation
                0,  # WidthMateOption
                status,
            ),
            default=None,
        )
        adapter._attempt(lambda: model.EditRebuild3(), default=None)

        error_status = getattr(status, "value", None)
        # swAddMateError_e reports 1 for success on this build (measured:
        # a mate that demonstrably moved a component returned 1).
        if mate is None or (error_status not in (None, 1)):
            raise Exception(
                f"SolidWorks rejected the {mate_key} mate "
                f"(error status {error_status!r}). Check the two entities "
                "can actually satisfy this mate type."
            )

        transforms_after = _component_transforms(adapter, assembly)
        moved = sorted(
            name
            for name, matrix in transforms_after.items()
            if name in transforms_before and transforms_before[name] != matrix
        )
        # An empty snapshot means no transform could be read, which is
        # not the same as "nothing moved" - report it as unknown rather
        # than as a negative result the caller would read as fact.
        comparable = bool(transforms_before and transforms_after)
        return {
            "mate_type": mate_key,
            "alignment": align_key,
            "components": [component_a, component_b],
            "entities": [
                mate_entity_label(entity_a, face_a),
                mate_entity_label(entity_b, face_b),
            ],
            "distance": distance or None,
            "angle": angle or None,
            "moved_components": moved,
            "geometry_moved": bool(moved) if comparable else None,
        }

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("add_mate", _mate),
    )
add_note async
add_note(payload: Any = None) -> AdapterResult[dict[str, Any]]

Place a text note on the active drawing sheet.

Wraps IModelDoc2::InsertNote(Text) and then positions the returned annotation: InsertNote places the note wherever SolidWorks likes, so the position is applied afterwards via IAnnotation::SetPosition.

Parameters:

Name Type Description Default
payload Any

Tool payload. Reads text, position_x/position_y (or a two-element position) in millimetres, and font_size in points, matching the tool schema.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The note text and where it landed.

Example::

await adapter.add_note(
    {"text": "MATERIAL: AISI 1018", "position_x": 200.0,
     "position_y": 50.0}
)
Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def add_note(self, payload: Any = None) -> AdapterResult[dict[str, Any]]:
    """Place a text note on the active drawing sheet.

    Wraps ``IModelDoc2::InsertNote(Text)`` and then positions the returned
    annotation: ``InsertNote`` places the note wherever SolidWorks likes,
    so the position is applied afterwards via ``IAnnotation::SetPosition``.

    Args:
        payload: Tool payload. Reads ``text``, ``position_x``/``position_y``
            (or a two-element ``position``) in millimetres, and
            ``font_size`` in **points**, matching the tool schema.

    Returns:
        AdapterResult[dict[str, Any]]: The note text and where it landed.

    Example::

        await adapter.add_note(
            {"text": "MATERIAL: AISI 1018", "position_x": 200.0,
             "position_y": 50.0}
        )
    """
    adapter = self._adapter(self)
    guard = self._require_drawing()
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)

    data = _payload(payload)
    text = _first(data, "text", "note", default="")
    if not text:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="add_note requires text",
        )

    position = data.get("position")
    if isinstance(position, (list, tuple)) and len(position) >= 2:
        x, y = float(position[0]), float(position[1])
    else:
        x = float(_first(data, "position_x", "x", default=100.0))
        y = float(_first(data, "position_y", "y", default=50.0))

    # The schema expresses font size in points, not millimetres.
    font_points = float(_first(data, "font_size", default=0.0) or 0.0)
    font_mm = font_points * _POINTS_TO_MM

    def _add_note() -> dict[str, Any]:
        model = adapter.currentModel
        adapter._attempt(lambda: model.ClearSelection2(True), default=None)

        note = adapter._attempt(lambda: model.InsertNote(str(text)), default=None)
        if note is None:
            raise Exception(
                "InsertNote returned nothing - the note was not created."
            )

        positioned = False
        annotation = adapter._attempt(
            lambda: _sw_type_info.flagged(note, "INote").GetAnnotation(),
            default=None,
        )
        if annotation is not None:
            positioned = bool(
                adapter._attempt(
                    lambda: _sw_type_info.flagged(
                        annotation, "IAnnotation"
                    ).SetPosition(x / 1000.0, y / 1000.0, 0.0),
                    default=False,
                )
            )

        if font_mm:
            adapter._attempt(
                lambda: _sw_type_info.flagged(note, "INote").SetTextFormat(
                    0, False, font_mm / 1000.0
                ),
                default=None,
            )

        adapter._attempt(lambda: model.EditRebuild3(), default=None)
        return {
            "text": text,
            "position": {"x": x, "y": y},
            "positioned": positioned,
            "font_size_points": font_points or None,
        }

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("add_note", _add_note),
    )
auto_center_marks async
auto_center_marks(view_name: str, mark_holes: bool = True, mark_fillets: bool = False, mark_slots: bool = True) -> AdapterResult[dict[str, Any]]

Auto-insert centre marks on circular features in a drawing view.

Wraps IView::AutoInsertCenterMarks2 (falling back to AutoInsertCenterMarks on older builds) using the document's default size/gap/font. IView::GetCenterMarkCount() returns 0 regardless of marks actually present on SW 3DEXPERIENCE R2026x, so the count is derived instead from IView::GetAnnotations(): the view's annotations are enumerated and those flagged as swCenterMarkSym (swAnnotationType_e) are counted. center_marks_before == center_marks_after == 0 is reported when the annotation enumeration cannot be performed (COM unavailable or older builds).

Adding nothing is still a success - the view may simply have no un-marked circular features.

Parameters:

Name Type Description Default
view_name str

Name of a view on the active drawing.

required
mark_holes bool

Mark holes / bores. Defaults to True.

True
mark_fillets bool

Mark fillets. Defaults to False.

False
mark_slots bool

Mark slots. Defaults to True.

True

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The view, the feature types acted

AdapterResult[dict[str, Any]]

on, and center_marks_before / center_marks_after /

AdapterResult[dict[str, Any]]

center_marks_added. ERROR when the active document is not

AdapterResult[dict[str, Any]]

a drawing, no feature type is selected, or the view is not found.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def auto_center_marks(
    self,
    view_name: str,
    mark_holes: bool = True,
    mark_fillets: bool = False,
    mark_slots: bool = True,
) -> AdapterResult[dict[str, Any]]:
    """Auto-insert centre marks on circular features in a drawing view.

    Wraps ``IView::AutoInsertCenterMarks2`` (falling back to
    ``AutoInsertCenterMarks`` on older builds) using the document's
    default size/gap/font. ``IView::GetCenterMarkCount()`` returns 0
    regardless of marks actually present on SW 3DEXPERIENCE R2026x, so
    the count is derived instead from ``IView::GetAnnotations()``: the
    view's annotations are enumerated and those flagged as
    ``swCenterMarkSym`` (``swAnnotationType_e``) are counted.
    ``center_marks_before == center_marks_after == 0`` is reported when
    the annotation enumeration cannot be performed (COM unavailable or
    older builds).

    Adding nothing is still a success - the view may simply have no
    un-marked circular features.

    Args:
        view_name: Name of a view on the active drawing.
        mark_holes: Mark holes / bores. Defaults to ``True``.
        mark_fillets: Mark fillets. Defaults to ``False``.
        mark_slots: Mark slots. Defaults to ``True``.

    Returns:
        AdapterResult[dict[str, Any]]: The view, the feature types acted
        on, and ``center_marks_before`` / ``center_marks_after`` /
        ``center_marks_added``. ``ERROR`` when the active document is not
        a drawing, no feature type is selected, or the view is not found.
    """
    adapter = self._adapter(self)
    guard = self._require_drawing()
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)

    insert_type = (
        (_SW_CM_TYPE_HOLE if mark_holes else 0)
        | (_SW_CM_TYPE_FILLET if mark_fillets else 0)
        | (_SW_CM_TYPE_SLOT if mark_slots else 0)
    )
    if insert_type == 0:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                "Select at least one feature type: mark_holes, "
                "mark_fillets or mark_slots."
            ),
        )

    def _auto_marks() -> dict[str, Any]:
        drawing = _sw_type_info.flagged(adapter.currentModel, "IDrawingDoc")
        view = _resolve_drawing_view(adapter, drawing, view_name)
        if view is None:
            raise Exception(
                f"No view named {view_name!r} on the active drawing. "
                f"Views: {', '.join(_view_names(adapter, drawing)) or 'none'}"
            )

        def _center_mark_count() -> int:
            """Count annotations on the view flagged as swCenterMarkSym.

            Enumerates ``IView::GetAnnotations()`` and inspects each
            annotation's type via ``IAnnotation::GetType()``. Returns 0 when
            the enumeration cannot be performed (COM unavailable, older
            builds, or the annotation type is unknown). This is the count
            source for both ``center_marks_before`` and ``center_marks_after``.
            """
            raw_annos = adapter._attempt(
                lambda: view.GetAnnotations(), default=None
            )
            if not isinstance(raw_annos, (list, tuple)):
                return 0
            count = 0
            for raw in raw_annos:
                anno = _as_com(adapter, raw, "IAnnotation")
                if anno is None:
                    continue
                anno_type = adapter._attempt(
                    lambda anno=anno: anno.GetType(), default=None
                )
                if (
                    isinstance(anno_type, (int, float))
                    and anno_type == _SW_ANNOTATION_CENTER_MARK
                ):
                    count += 1
            return count

        before = _center_mark_count()

        ran = adapter._attempt(
            lambda: view.AutoInsertCenterMarks2(
                insert_type,
                _SW_CM_STYLE_SINGLE,
                True,  # LinearSlotCenter
                True,  # ArcSlotCenter
                True,  # UseDocumentDefaults
                0.0,  # Size (ignored when UseDocumentDefaults)
                0.0,  # Gap
                True,  # ExtendedLines
                False,  # CenterLineFont
                0.0,  # Angle
            ),
            default=None,
        )
        if ran is None:
            ran = adapter._attempt(
                lambda: view.AutoInsertCenterMarks(
                    insert_type,
                    _SW_CM_STYLE_SINGLE,
                    True,
                    True,
                    True,
                    0.0,
                    True,
                    False,
                    0.0,
                ),
                default=None,
            )

        adapter._attempt(lambda: adapter.currentModel.EditRebuild3(), default=None)

        after = _center_mark_count()

        return {
            "view": view_name,
            "feature_types": [
                label
                for label, on in (
                    ("holes", mark_holes),
                    ("fillets", mark_fillets),
                    ("slots", mark_slots),
                )
                if on
            ],
            "center_marks_before": before,
            "center_marks_after": after,
            "center_marks_added": after - before,
        }

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("auto_center_marks", _auto_marks),
    )
check_interference async
check_interference(params: Any = None) -> AdapterResult[dict[str, Any]]

Run SolidWorks' interference detection on the active assembly.

Uses IAssemblyDoc::InterferenceDetectionManager (IInterferenceDetectionMgr), not the older ToolsCheckInterference2. That call is declared Sub with two ByRef out-parameters, and on SW 2025 through pywin32 late binding it could not be made to report anything: pythoncom.Missing raises PyOleMissing can not be converted to a COM VARIANT, a plain None or a typed array VARIANT raises Type mismatch, passing a component array throws server-side, and a byref VT_VARIANT pair is accepted but leaves both out-parameters None for an assembly that demonstrably interferes. An inspection tool that always answers "no interference" is worse than one that refuses.

GetInterferenceCount is an ordinary return value, so 0 is a real measurement rather than a swallowed failure, and each IInterference carries the overlap Volume.

Parameters:

Name Type Description Default
params Any

Optional settings. coincident (bool, default False) treats touching faces as interference; include_multibody (bool, default True); ignore_hidden (bool, default False). A components list filters which interferences are reported. tolerance is accepted and ignored - SolidWorks' interference detection has no tolerance setting - and the payload says so.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: interference_found,

AdapterResult[dict[str, Any]]

interference_count, and per-interference component pairs with

AdapterResult[dict[str, Any]]

overlap volumes in mm^3. ERROR when there is no active model

AdapterResult[dict[str, Any]]

or the active document is not an assembly.

Raises:

Type Description
Exception

Propagated through _handle_com_operation.

Example::

await adapter.check_interference({"coincident": False})
Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def check_interference(
    self, params: Any = None
) -> AdapterResult[dict[str, Any]]:
    """Run SolidWorks' interference detection on the active assembly.

    Uses ``IAssemblyDoc::InterferenceDetectionManager``
    (``IInterferenceDetectionMgr``), **not** the older
    ``ToolsCheckInterference2``. That call is declared ``Sub`` with two
    ``ByRef`` out-parameters, and on SW 2025 through pywin32 late binding
    it could not be made to report anything: ``pythoncom.Missing`` raises
    ``PyOleMissing can not be converted to a COM VARIANT``, a plain
    ``None`` or a typed array VARIANT raises ``Type mismatch``, passing a
    component array throws server-side, and a byref ``VT_VARIANT`` pair is
    accepted but leaves both out-parameters ``None`` for an assembly that
    demonstrably interferes. An inspection tool that always answers "no
    interference" is worse than one that refuses.

    ``GetInterferenceCount`` is an ordinary return value, so ``0`` is a
    real measurement rather than a swallowed failure, and each
    ``IInterference`` carries the overlap ``Volume``.

    Args:
        params (Any): Optional settings. ``coincident`` (bool, default
            ``False``) treats touching faces as interference;
            ``include_multibody`` (bool, default ``True``);
            ``ignore_hidden`` (bool, default ``False``). A ``components``
            list filters which interferences are reported. ``tolerance``
            is accepted and ignored - SolidWorks' interference detection
            has no tolerance setting - and the payload says so.

    Returns:
        AdapterResult[dict[str, Any]]: ``interference_found``,
        ``interference_count``, and per-interference component pairs with
        overlap volumes in mm^3. ``ERROR`` when there is no active model
        or the active document is not an assembly.

    Raises:
        Exception: Propagated through ``_handle_com_operation``.

    Example::

        await adapter.check_interference({"coincident": False})
    """
    adapter = self._adapter(self)
    options = _payload_dict(params)

    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    doc_type = _doc_type(adapter)
    if doc_type != 2:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                "Interference detection requires an assembly document "
                f"(active document type is {doc_type!r}, expected 2)"
            ),
        )

    coincident = bool(options.get("coincident", False))
    include_multibody = bool(options.get("include_multibody", True))
    ignore_hidden = bool(options.get("ignore_hidden", False))
    wanted = options.get("components") or []
    wanted_names = {str(name) for name in wanted} if wanted else set()

    def _check() -> dict[str, Any]:
        assembly = _sw_type_info.flagged(adapter.currentModel, "IAssemblyDoc")
        manager = adapter._attempt(
            lambda: assembly.InterferenceDetectionManager, default=None
        )
        if manager is None:
            raise Exception(
                "InterferenceDetectionManager is unavailable on this "
                "assembly, so no interference check was performed."
            )
        manager = _sw_type_info.flagged(manager, "IInterferenceDetectionMgr")

        for name, value in (
            ("TreatCoincidenceAsInterference", coincident),
            ("IncludeMultibodyPartInterferences", include_multibody),
            ("IgnoreHiddenBodies", ignore_hidden),
            ("MakeInterferingPartsTransparent", False),
        ):
            adapter._attempt(
                lambda n=name, v=value: setattr(manager, n, v), default=None
            )

        try:
            # A real return value: 0 means SolidWorks found nothing, and a
            # COM failure raises instead of flattening into a false clean
            # result.
            count = int(manager.GetInterferenceCount() or 0)
            interferences = (
                adapter._attempt(lambda: manager.GetInterferences(), default=None)
                if count
                else None
            )
            details = _interference_details(adapter, interferences)
        finally:
            # Leaves the assembly out of interference-display mode even
            # when the read above fails.
            adapter._attempt(lambda: manager.Done(), default=None)

        if wanted_names:
            details = [
                item
                for item in details
                if wanted_names & set(item.get("components", []))
            ]

        return {
            "interference_found": bool(details) if wanted_names else count > 0,
            "interference_count": len(details) if wanted_names else count,
            "interferences": details,
            "coincident_treated_as_interference": coincident,
            "scope": sorted(wanted_names) if wanted_names else "whole assembly",
            # Said plainly rather than silently dropped: a caller passing a
            # tolerance would otherwise assume it was applied.
            "tolerance_applied": None,
        }

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("check_interference", _check),
    )
close_model async
close_model(save: bool = False) -> AdapterResult[None]

Close the current SolidWorks model and optionally save first.

Parameters:

Name Type Description Default
save bool

When True, calls Save before closing.

False

Returns:

Type Description
AdapterResult[None]

AdapterResult[None]: Result of the close operation.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def close_model(self, save: bool = False) -> AdapterResult[None]:
    """Close the current SolidWorks model and optionally save first.

    Args:
        save: When ``True``, calls ``Save`` before closing.

    Returns:
        AdapterResult[None]: Result of the close operation.
    """
    adapter = self._adapter(self)
    # After a close, SolidWorks activates another open document; close that
    # one, as the tool promises, rather than the document already closed.
    active = getattr(adapter.swApp, "ActiveDoc", None) if adapter.swApp else None
    if active is not None:
        adapter.currentModel = active
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.WARNING, error="No active model to close"
        )
    model = adapter.currentModel
    app = adapter.swApp
    if model is None or app is None:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="SolidWorks application is not connected",
        )

    def _close() -> None:
        """Close the model document.

        ``currentModel`` may be a raw ``swApp.ActiveDoc`` dispatch that was
        never run through ``flag_doc`` (``get_model_info`` assigns it
        straight from ``ActiveDoc``). On such a dispatch, late binding
        returns zero-arg accessors property-style, so ``model.GetTitle()``
        tries to *call* the returned string and raises
        ``'str' object is not callable`` (issue #91, COM pitfall #5).
        Read them through ``_get_attr_or_call`` which works either way.
        """
        if save:
            adapter._get_attr_or_call(model, "Save")
        title = adapter._get_attr_or_call(model, "GetTitle")
        app.CloseDoc(title)
        adapter.currentModel = None

    return cast(
        AdapterResult[None],
        adapter._handle_com_operation("close_model", _close),
    )
create_assembly async
create_assembly(name: str | None = None, template: str | None = None) -> AdapterResult[SolidWorksModel]

Create a new assembly document and set it as active.

Parameters:

Name Type Description Default
name str | None

Reserved for future naming policy.

None
template str | None

Optional .asmdot path. When given the assembly is made from it with NewDocument; otherwise NewAssembly and then the user-preference default templates are tried.

None

Returns:

Type Description
AdapterResult[SolidWorksModel]

AdapterResult[SolidWorksModel]: Metadata for the new assembly document.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def create_assembly(
    self, name: str | None = None, template: str | None = None
) -> AdapterResult[SolidWorksModel]:
    """Create a new assembly document and set it as active.

    Args:
        name: Reserved for future naming policy.
        template: Optional ``.asmdot`` path. When given the assembly is
            made from it with ``NewDocument``; otherwise ``NewAssembly``
            and then the user-preference default templates are tried.

    Returns:
        AdapterResult[SolidWorksModel]: Metadata for the new assembly document.
    """
    adapter = self._adapter(self)
    if not adapter.is_connected():
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="Not connected to SolidWorks"
        )

    def _create() -> SolidWorksModel:
        """Create a new assembly."""
        _ = name
        model = None
        app = adapter.swApp
        if app is None:
            raise Exception("SolidWorks application is not connected")

        explicit = self._explicit_template_path(template, ".asmdot")
        if explicit:
            model = app.NewDocument(explicit, 0, 0, 0)
        else:
            new_assembly = getattr(app, "NewAssembly", None)
            if callable(new_assembly):
                model = adapter._attempt(new_assembly)

            if not model:
                asm_template = self._resolve_template_path(
                    [9, 2, 3, 1, 0], ".asmdot"
                )
                if not asm_template:
                    raise Exception(
                        "No assembly template configured in SolidWorks; "
                        "pass template with the path of a .asmdot file"
                    )
                model = app.NewDocument(asm_template, 0, 0, 0)

        if not model:
            if explicit:
                raise Exception(f"Failed to create new assembly from {explicit!r}")
            raise Exception("Failed to create new assembly")

        adapter._attempt(lambda: _sw_type_info.flag_doc(model, 2), default=0)
        adapter.currentModel = model
        title = self._read_model_title(model)
        return SolidWorksModel(
            path="",
            name=title,
            type="Assembly",
            is_active=True,
            configuration="Default",
            properties={"created": datetime.now().isoformat()},
        )

    return cast(
        AdapterResult[SolidWorksModel],
        adapter._handle_com_operation("create_assembly", _create),
    )
create_drawing async
create_drawing(name: str | None = None, template: str | None = None) -> AdapterResult[SolidWorksModel]

Create a new drawing document and set it as active.

Parameters:

Name Type Description Default
name str | None

Reserved for future naming policy.

None
template str | None

Optional .drwdot path, used in place of the user-preference default. An explicit template that fails is an error; it does not fall back to a blank drawing.

None

Returns:

Type Description
AdapterResult[SolidWorksModel]

AdapterResult[SolidWorksModel]: Metadata for the new drawing document.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def create_drawing(
    self, name: str | None = None, template: str | None = None
) -> AdapterResult[SolidWorksModel]:
    """Create a new drawing document and set it as active.

    Args:
        name: Reserved for future naming policy.
        template: Optional ``.drwdot`` path, used in place of the
            user-preference default. An explicit template that fails is
            an error; it does not fall back to a blank drawing.

    Returns:
        AdapterResult[SolidWorksModel]: Metadata for the new drawing document.
    """
    adapter = self._adapter(self)
    if not adapter.is_connected():
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="Not connected to SolidWorks"
        )

    def _create() -> SolidWorksModel:
        """Create a new drawing."""
        _ = name
        app = adapter.swApp
        if app is None:
            raise Exception("SolidWorks application is not connected")

        # Slot 10 is swDefaultTemplateDrawing. Slot 1 was read here
        # before and comes back empty on SW 2025, after which the
        # fallback did GetUserPreferenceStringValue(0).replace("Part",
        # "Drawing") on another empty string - so NewDocument got "" and
        # every drawing operation was unreachable. Measured live on
        # SW 2025: 8=Part.prtdot, 9=Assembly.asmdot, 10=Drawing.drwdot,
        # 0-3 all empty.
        explicit = self._explicit_template_path(template, ".drwdot")
        drw_template = explicit or self._resolve_template_path(
            [10, 1, 0, 2, 3], ".drwdot"
        )
        model = None
        if drw_template:
            model = app.NewDocument(
                drw_template, _SW_DWG_PAPERS_USER_DEFINED, 0.2794, 0.2159
            )
        if not model and explicit:
            raise Exception(f"Failed to create a drawing from {explicit!r}")
        if not model:
            # No usable template (a settings reset blanks the slot, or it
            # names a deleted file). Like create_part's NewPart fallback,
            # let SolidWorks make a blank A-size drawing with no sheet
            # format; views can still be placed on it.
            model = adapter._attempt(
                lambda: app.NewDrawing2(
                    _SW_DWG_TEMPLATE_NONE, "", _SW_DWG_PAPER_A_SIZE, 0.2794, 0.2159
                )
            )
        if not model:
            if drw_template:
                tried = f"template {drw_template!r}"
            else:
                tried = "no drawing template is configured"
            raise Exception(
                f"Failed to create a drawing: {tried}, and a blank drawing "
                "without a template (NewDrawing2) also failed"
            )

        adapter._attempt(lambda: _sw_type_info.flag_doc(model, 3), default=0)
        adapter.currentModel = model
        title = self._read_model_title(model)
        return SolidWorksModel(
            path="",
            name=title,
            type="Drawing",
            is_active=True,
            configuration="Default",
            properties={"created": datetime.now().isoformat()},
        )

    return cast(
        AdapterResult[SolidWorksModel],
        adapter._handle_com_operation("create_drawing", _create),
    )
create_drawing_view async
create_drawing_view(payload: Any = None) -> AdapterResult[dict[str, Any]]

Place a view of a model on the active drawing sheet.

Parameters:

Name Type Description Default
payload Any

Tool payload; see _place_view.

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The new view's name and position.

Example::

await adapter.create_drawing_view(
    {"model_path": r"C:\parts\bracket.sldprt", "orientation": "front"}
)
Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def create_drawing_view(
    self, payload: Any = None
) -> AdapterResult[dict[str, Any]]:
    """Place a view of a model on the active drawing sheet.

    Args:
        payload: Tool payload; see ``_place_view``.

    Returns:
        AdapterResult[dict[str, Any]]: The new view's name and position.

    Example::

        await adapter.create_drawing_view(
            {"model_path": r"C:\\parts\\bracket.sldprt", "orientation": "front"}
        )
    """
    return self._place_view(payload)
create_part async
create_part(name: str | None = None, units: str | None = None, template: str | None = None) -> AdapterResult[SolidWorksModel]

Create a new part document and set it as active.

Parameters:

Name Type Description Default
name str | None

Reserved for future naming policy.

None
units str | None

Optional linear unit system for the new part - mm, cm, m, in or ft (aliases such as inch accepted). Applied after creation; the outcome is recorded under properties["units"] (or properties["units_error"] if it could not be applied). An unrecognised token is an error.

None
template str | None

Optional .prtdot path. When given the part is made from it with NewDocument; otherwise NewPart and then the user-preference default templates are tried.

None

Returns:

Type Description
AdapterResult[SolidWorksModel]

AdapterResult[SolidWorksModel]: Metadata for the new part document.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def create_part(
    self,
    name: str | None = None,
    units: str | None = None,
    template: str | None = None,
) -> AdapterResult[SolidWorksModel]:
    """Create a new part document and set it as active.

    Args:
        name: Reserved for future naming policy.
        units: Optional linear unit system for the new part - ``mm``,
            ``cm``, ``m``, ``in`` or ``ft`` (aliases such as ``inch``
            accepted). Applied after creation; the outcome is recorded
            under ``properties["units"]`` (or ``properties["units_error"]``
            if it could not be applied). An unrecognised token is an error.
        template: Optional ``.prtdot`` path. When given the part is made
            from it with ``NewDocument``; otherwise ``NewPart`` and then
            the user-preference default templates are tried.

    Returns:
        AdapterResult[SolidWorksModel]: Metadata for the new part document.
    """
    adapter = self._adapter(self)
    if not adapter.is_connected():
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="Not connected to SolidWorks"
        )

    unit_token: str | None = None
    if units is not None and str(units).strip():
        unit_token = _normalise_unit_system(str(units))
        if unit_token is None:
            return AdapterResult(
                status=AdapterResultStatus.ERROR,
                error=(
                    f"Unrecognised units {units!r}. "
                    "Expected one of: mm, cm, m, in, ft."
                ),
            )

    def _create() -> SolidWorksModel:
        """Create a new part."""
        _ = name
        model = None
        app = adapter.swApp
        if app is None:
            raise Exception("SolidWorks application is not connected")

        explicit = self._explicit_template_path(template, ".prtdot")
        if explicit:
            model = app.NewDocument(explicit, 0, 0, 0)
        else:
            new_part = getattr(app, "NewPart", None)
            if callable(new_part):
                model = adapter._attempt(new_part)

            if not model:
                part_template = self._resolve_template_path(
                    [8, 0, 1, 2, 3], ".prtdot"
                )
                if not part_template:
                    raise Exception(
                        "No part template configured in SolidWorks; "
                        "pass template with the path of a .prtdot file"
                    )
                model = app.NewDocument(part_template, 0, 0, 0)

        if not model:
            if explicit:
                raise Exception(f"Failed to create new part from {explicit!r}")
            raise Exception("Failed to create new part")

        adapter._attempt(lambda: _sw_type_info.flag_doc(model, 1), default=0)
        adapter.currentModel = model
        title = self._read_model_title(model)
        properties: dict[str, Any] = {"created": datetime.now().isoformat()}
        if unit_token is not None:
            # A units failure must not lose the part that was just made;
            # record it and carry on.
            try:
                properties["units"] = _apply_unit_system(adapter, model, unit_token)
            except Exception as exc:  # noqa: BLE001 - surfaced, not raised
                properties["units_error"] = str(exc)
        return SolidWorksModel(
            path="",
            name=title,
            type="Part",
            is_active=True,
            configuration="Default",
            properties=properties,
        )

    return cast(
        AdapterResult[SolidWorksModel],
        adapter._handle_com_operation("create_part", _create),
    )
create_technical_drawing async
create_technical_drawing(payload: Any = None) -> AdapterResult[dict[str, Any]]

Lay out the three standard views of a model on the active sheet.

Wraps IDrawingDoc::Create3rdAngleViews2 (or Create1stAngleViews2), which places front, top and side in one call. Both return a bare boolean, so the view list before and after is what actually confirms the views exist.

Parameters:

Name Type Description Default
payload Any

Tool payload. Reads model_path/model_file and third_angle (default True; projection set to "first_angle" selects the other).

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The view names that appeared.

Example::

await adapter.create_technical_drawing(
    {"model_file": r"C:\parts\bracket.sldprt"}
)
Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def create_technical_drawing(
    self, payload: Any = None
) -> AdapterResult[dict[str, Any]]:
    """Lay out the three standard views of a model on the active sheet.

    Wraps ``IDrawingDoc::Create3rdAngleViews2`` (or
    ``Create1stAngleViews2``), which places front, top and side in one
    call. Both return a bare boolean, so the view list before and after is
    what actually confirms the views exist.

    Args:
        payload: Tool payload. Reads ``model_path``/``model_file`` and
            ``third_angle`` (default ``True``; ``projection`` set to
            ``"first_angle"`` selects the other).

    Returns:
        AdapterResult[dict[str, Any]]: The view names that appeared.

    Example::

        await adapter.create_technical_drawing(
            {"model_file": r"C:\\parts\\bracket.sldprt"}
        )
    """
    adapter = self._adapter(self)
    guard = self._require_drawing()
    if guard is not None:
        return cast("AdapterResult[dict[str, Any]]", guard)

    data = _payload(payload)
    model_path = _first(data, "model_path", "model_file", "path")
    if not model_path:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="A model path is required (model_path or model_file)",
        )

    path = os.path.abspath(str(model_path))
    if not os.path.exists(path):
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=f"Model file not found: {model_path}",
        )

    third_angle = bool(data.get("third_angle", True))
    if str(data.get("projection", "")).lower() in {"first", "first_angle"}:
        third_angle = False

    def _standard() -> dict[str, Any]:
        drawing = _sw_type_info.flagged(adapter.currentModel, "IDrawingDoc")
        before = _view_names(adapter, drawing)

        created = adapter._attempt(
            lambda: (
                drawing.Create3rdAngleViews2(path)
                if third_angle
                else drawing.Create1stAngleViews2(path)
            ),
            default=False,
        )

        after = _view_names(adapter, drawing)
        added = [n for n in after if n not in before]
        if not added:
            raise Exception(
                f"No views were created from '{model_path}' (the call "
                f"returned {created!r}). Check the model has solid "
                "geometry and opens on its own."
            )

        return {
            "views": added,
            "model_path": path,
            "projection": "third_angle" if third_angle else "first_angle",
            "views_before": len(before),
            "views_after": len(after),
        }

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("create_technical_drawing", _standard),
    )
get_application_info async
get_application_info() -> AdapterResult[dict[str, Any]]

Read SolidWorks' revision, language and default templates.

Each value is read on its own, so one unreadable value is None and the rest still come back. Templates are reported by file name only, so no path under the user's profile leaves the adapter.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: See

AdapterResult[dict[str, Any]]

SolidWorksAdapter.get_application_info.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def get_application_info(self) -> AdapterResult[dict[str, Any]]:
    """Read SolidWorks' revision, language and default templates.

    Each value is read on its own, so one unreadable value is ``None``
    and the rest still come back. Templates are reported by file name
    only, so no path under the user's profile leaves the adapter.

    Returns:
        AdapterResult[dict[str, Any]]: See
        ``SolidWorksAdapter.get_application_info``.
    """
    adapter = self._adapter(self)
    app = adapter.swApp
    if app is None:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="Not connected to SolidWorks"
        )

    def _text(read: Any) -> str | None:
        value = adapter._attempt(read, default=None)
        return str(value) if value else None

    def _template(preference: int) -> dict[str, Any]:
        path = _text(lambda: app.GetUserPreferenceStringValue(preference))
        return {
            "file_name": os.path.basename(path) if path else None,
            "exists": bool(path) and os.path.isfile(str(path)),
        }

    def _read() -> dict[str, Any]:
        return {
            "revision": _text(
                lambda: adapter._get_attr_or_call(app, "RevisionNumber")
            ),
            "language": _text(lambda: app.GetCurrentLanguage()),
            "templates": {
                kind: _template(preference)
                for kind, preference in _SW_DEFAULT_TEMPLATES.items()
            },
        }

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("get_application_info", _read),
    )
get_dimension async
get_dimension(name: str) -> AdapterResult[dict[str, Any]]

Read a named model dimension in the unit its type gives it.

IDimension.SystemValue is metres for a length, radians for an angle and the number itself for a count; IDimension.GetType says which (:func:_dimension_reading). GetValue3 is not used: it is in the document's units, which this can't convert without reading the document's unit system.

Parameters:

Name Type Description Default
name str

Fully-qualified dimension name, e.g. "D1@Boss-Extrude1".

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: value and units ("mm",

AdapterResult[dict[str, Any]]

"deg", or None for a count or a dimension of unknown type).

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def get_dimension(self, name: str) -> AdapterResult[dict[str, Any]]:
    """Read a named model dimension in the unit its type gives it.

    ``IDimension.SystemValue`` is metres for a length, radians for an
    angle and the number itself for a count; ``IDimension.GetType`` says
    which (:func:`_dimension_reading`). ``GetValue3`` is not used: it is
    in the document's units, which this can't convert without reading
    the document's unit system.

    Args:
        name: Fully-qualified dimension name, e.g. ``"D1@Boss-Extrude1"``.

    Returns:
        AdapterResult[dict[str, Any]]: ``value`` and ``units`` ("mm",
        "deg", or ``None`` for a count or a dimension of unknown type).
    """
    adapter = self._adapter(self)
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    def _get() -> dict[str, Any]:
        """Get the dimension value and its unit."""
        dimension = adapter.currentModel.Parameter(name)
        if not dimension:
            raise Exception(
                f"Dimension '{name}' not found; list the names with "
                "list_model_dimensions"
            )
        dimension = _sw_type_info.flagged(dimension, "IDimension")
        value = adapter._attempt(lambda: dimension.SystemValue, default=None)
        if value is None:
            raise Exception(f"Failed to read dimension '{name}'")
        kind = adapter._attempt(lambda: dimension.GetType(), default=None)
        return _dimension_reading(kind, float(value))

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("get_dimension", _get),
    )
get_mass_properties async
get_mass_properties() -> AdapterResult[MassProperties]

Get mass properties for the active model.

Returns:

Type Description
AdapterResult[MassProperties]

AdapterResult[MassProperties]: Computed mass, volume, area, COM, and inertia.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def get_mass_properties(self) -> AdapterResult[MassProperties]:
    """Get mass properties for the active model.

    Returns:
        AdapterResult[MassProperties]: Computed mass, volume, area, COM, and inertia.
    """
    adapter = self._adapter(self)
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    def _get() -> MassProperties:
        """Get mass properties."""
        adapter._attempt(
            lambda: adapter.currentModel.ForceRebuild3(False), default=None
        )

        # Primary: Extension.CreateMassProperty() object API (most detailed)
        mass_props = adapter._attempt(
            lambda: adapter.currentModel.Extension.CreateMassProperty(),
            default=None,
        )

        if mass_props:
            volume = mass_props.Volume * 1e9
            surface_area = mass_props.SurfaceArea * 1e6
            mass = mass_props.Mass

            center_of_mass = [0.0, 0.0, 0.0]
            com = adapter._attempt(lambda: mass_props.CenterOfMass, default=None)
            if isinstance(com, (list, tuple)) and len(com) >= 3:
                center_of_mass = [com[0] * 1000, com[1] * 1000, com[2] * 1000]

            moi = adapter._attempt(
                lambda: mass_props.GetMomentOfInertia(0), default=None
            )
            if not isinstance(moi, (list, tuple)) or len(moi) < 9:
                moi = [0.0] * 9
        else:
            # Fallback: GetMassProperties as attribute (tuple) or callable (SW 2022)
            gmp = getattr(adapter.currentModel, "GetMassProperties", None)
            if callable(gmp):
                raw = adapter._attempt(gmp, default=None)
            elif isinstance(gmp, (list, tuple)):
                raw = gmp
            else:
                raw = None

            if not isinstance(raw, (list, tuple)) or len(raw) < 6:
                raise Exception("Failed to get mass properties")

            center_of_mass = [
                raw[0] * 1000.0,
                raw[1] * 1000.0,
                raw[2] * 1000.0,
            ]
            volume = raw[3] * 1e9
            surface_area = raw[4] * 1e6
            mass = raw[5]

            # GetMassProperties gives Lxx, Lyy, Lzz, Lxy, Lzx, Lyz at
            # 6-11 (API docs); moi is the row-major 3 x 3 tensor that
            # IMassProperty.GetMomentOfInertia returns.
            moi = [0.0] * 9
            if len(raw) >= 12:
                moi[0] = raw[6]
                moi[4] = raw[7]
                moi[8] = raw[8]
                moi[1] = raw[9]  # Lxy
                moi[2] = raw[10]  # Lzx = Ixz
                moi[5] = raw[11]  # Lyz

        return MassProperties(
            volume=volume,
            surface_area=surface_area,
            mass=mass,
            center_of_mass=center_of_mass,
            moments_of_inertia={
                "Ixx": moi[0],
                "Iyy": moi[4],
                "Izz": moi[8],
                "Ixy": moi[1],
                "Ixz": moi[2],
                "Iyz": moi[5],
            },
        )

    return cast(
        AdapterResult[MassProperties],
        adapter._handle_com_operation("get_mass_properties", _get),
    )
get_model_geometry_summary async
get_model_geometry_summary() -> AdapterResult[dict[str, Any]]

Summarise the active part or assembly without rebuilding it.

Unlike get_mass_properties this never calls ForceRebuild3, so it leaves the live selection intact (COM pitfall #23) and can be read right before a feature call. A value that cannot be read is None.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: See

AdapterResult[dict[str, Any]]

SolidWorksAdapter.get_model_geometry_summary.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def get_model_geometry_summary(self) -> AdapterResult[dict[str, Any]]:
    """Summarise the active part or assembly without rebuilding it.

    Unlike ``get_mass_properties`` this never calls ``ForceRebuild3``, so
    it leaves the live selection intact (COM pitfall #23) and can be read
    right before a feature call. A value that cannot be read is ``None``.

    Returns:
        AdapterResult[dict[str, Any]]: See
        ``SolidWorksAdapter.get_model_geometry_summary``.
    """
    adapter = self._adapter(self)
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )
    doc_type = _doc_type(adapter)
    if doc_type not in (1, 2):
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                "get_model_geometry_summary needs an active part or assembly "
                f"(active document type is {doc_type!r})"
            ),
        )

    def _summarise() -> dict[str, Any]:
        model = adapter.currentModel
        summary: dict[str, Any] = {}
        if doc_type == 1:
            part = _sw_type_info.flagged(model, "IPartDoc")
            bodies = _solid_bodies(adapter, part)
            volume = _bodies_volume_mm3(adapter, bodies)
            summary["volume_mm3"] = (
                volume if volume is not None else _model_volume_mm3(adapter, model)
            )
            box = _extreme_point_box(adapter, bodies or [])
            if box is None:
                box = _box_from_metres(
                    adapter._attempt(lambda: part.GetPartBox(True), default=None),
                    "part_box",
                )
            summary["document_type"] = "part"
            summary["solid_body_count"] = None if bodies is None else len(bodies)
        else:
            assembly = _sw_type_info.flagged(model, "IAssemblyDoc")
            summary["volume_mm3"] = _model_volume_mm3(adapter, model)
            box = _box_from_metres(
                adapter._attempt(lambda: assembly.GetBox(0), default=None),
                "assembly_box",
            )
            summary["document_type"] = "assembly"
            summary["solid_body_count"] = None
            summary["component_count"] = len(_component_names(adapter, assembly))
            summary["mate_count"] = _assembly_mate_count(adapter, model)
        summary["bounding_box_method"] = box.pop("method") if box else None
        summary["bounding_box_mm"] = box
        return summary

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("get_model_geometry_summary", _summarise),
    )
get_model_info async
get_model_info() -> AdapterResult[dict[str, Any]]

Collect summary metadata about the active model.

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Model information payload.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def get_model_info(self) -> AdapterResult[dict[str, Any]]:
    """Collect summary metadata about the active model.

    Returns:
        AdapterResult[dict[str, Any]]: Model information payload.
    """
    adapter = self._adapter(self)
    # Resync from ActiveDoc: the user may have opened or switched
    # documents in the SolidWorks UI since the last tool call (issue #91).
    self._sync_current_model_from_active()
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    def _get_info() -> dict[str, Any]:
        """Get model information."""
        # With late-bound SolidWorks COM, GetActiveConfiguration is
        # exposed as an object-valued property even though the API names
        # it like a method. Calling that COM object raises "member not
        # found", so read it directly.
        active_config = getattr(
            adapter.currentModel, "GetActiveConfiguration", None
        )
        # 'Name' on Configuration is a property, not a method.
        config_name = (
            getattr(active_config, "Name", "Default")
            if active_config
            else "Default"
        )
        # Try GetSaveFlag (method) first, fallback to property
        is_dirty_raw = adapter._attempt(
            lambda: adapter._get_attr_or_call(adapter.currentModel, "GetSaveFlag"),
            default=None,
        )
        is_dirty = bool(is_dirty_raw) if is_dirty_raw is not None else None
        feature_count = adapter._attempt(
            lambda: int(
                adapter.currentModel.FeatureManager.GetFeatureCount(True) or 0
            ),
            default=0,
        )
        rebuild_status_raw = adapter._attempt(
            lambda: adapter.currentModel.GetRebuildStatus(), default=None
        )
        # GetRebuildStatus returns 0=ok, 1=needs rebuild, or None=failed
        rebuild_status = (
            rebuild_status_raw if rebuild_status_raw is not None else None
        )
        return {
            "title": adapter._get_attr_or_call(adapter.currentModel, "GetTitle"),
            "path": adapter._get_attr_or_call(adapter.currentModel, "GetPathName"),
            "type": adapter._get_document_type(),
            "configuration": config_name,
            "is_dirty": is_dirty,
            "feature_count": feature_count,
            "rebuild_status": rebuild_status,
        }

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("get_model_info", _get_info),
    )
insert_component async
insert_component(file_path: str, x: float = 0.0, y: float = 0.0, z: float = 0.0) -> AdapterResult[dict[str, Any]]

Insert a part or sub-assembly into the active assembly.

Wraps IAssemblyDoc::AddComponent4(CompName, ConfigName, X, Y, Z), falling back to AddComponent5. Position is in millimetres.

The component file must contain solid geometry. SolidWorks silently refuses to insert an empty part — every overload returns None and the component count stays put. That behaviour is what made this look unimplementable until the save_file bug that was writing empty parts got fixed.

Success is confirmed by the assembly's component count going up, since AddComponent* gives no usable failure signal.

Parameters:

Name Type Description Default
file_path str

Absolute path to the .sldprt or .sldasm.

required
x float

X position in millimetres.

0.0
y float

Y position in millimetres.

0.0
z float

Z position in millimetres.

0.0

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: Component name and before/after

AdapterResult[dict[str, Any]]

counts. ERROR when the active document is not an assembly, the

AdapterResult[dict[str, Any]]

file is missing, or nothing was inserted.

Raises:

Type Description
Exception

Propagated through _handle_com_operation.

Example::

await adapter.insert_component(r"C:\parts\bracket.sldprt", 0, 0, 0)
Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def insert_component(
    self, file_path: str, x: float = 0.0, y: float = 0.0, z: float = 0.0
) -> AdapterResult[dict[str, Any]]:
    """Insert a part or sub-assembly into the active assembly.

    Wraps ``IAssemblyDoc::AddComponent4(CompName, ConfigName, X, Y, Z)``,
    falling back to ``AddComponent5``.  Position is in **millimetres**.

    **The component file must contain solid geometry.**  SolidWorks
    silently refuses to insert an empty part — every overload returns
    ``None`` and the component count stays put.  That behaviour is what
    made this look unimplementable until the ``save_file`` bug that was
    writing empty parts got fixed.

    Success is confirmed by the assembly's component count going up, since
    ``AddComponent*`` gives no usable failure signal.

    Args:
        file_path (str): Absolute path to the ``.sldprt`` or ``.sldasm``.
        x (float): X position in millimetres.
        y (float): Y position in millimetres.
        z (float): Z position in millimetres.

    Returns:
        AdapterResult[dict[str, Any]]: Component name and before/after
        counts.  ``ERROR`` when the active document is not an assembly, the
        file is missing, or nothing was inserted.

    Raises:
        Exception: Propagated through ``_handle_com_operation``.

    Example::

        await adapter.insert_component(r"C:\\parts\\bracket.sldprt", 0, 0, 0)
    """
    adapter = self._adapter(self)
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    path = os.path.abspath(file_path)
    if not os.path.exists(path):
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=f"Component file not found: {file_path}",
        )

    doc_type = _doc_type(adapter)
    if doc_type != 2:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                "insert_component requires an assembly document "
                f"(active document type is {doc_type!r}, expected 2). "
                "Call create_assembly first."
            ),
        )

    def _insert() -> dict[str, Any]:
        assembly = _sw_type_info.flagged(adapter.currentModel, "IAssemblyDoc")
        before = _component_names(adapter, assembly)

        # The document has to be loaded before it can be inserted, and the
        # errors/warnings out-parameters must be byref VARIANTs: with
        # pythoncom.Missing OpenDoc6 returns None and the part stays
        # unloaded, after which every AddComponent overload does nothing.
        app = adapter.swApp
        opened = adapter._attempt(
            lambda: app.OpenDoc6(
                path,
                2 if path.lower().endswith(".sldasm") else 1,
                1,
                "",
                _byref_int(),
                _byref_int(),
            ),
            default=None,
        )
        if not opened:
            raise Exception(
                f"Could not load '{file_path}' - OpenDoc6 returned nothing."
            )

        title = adapter._attempt(
            lambda: _sw_type_info.flagged(
                adapter.currentModel, "IModelDoc2"
            ).GetTitle(),
            default=None,
        )
        if title:
            adapter._attempt(
                lambda: app.ActivateDoc3(title, False, 0, _byref_int()),
                default=None,
            )

        component = adapter._attempt(
            lambda: assembly.AddComponent4(
                path, "", x / 1000.0, y / 1000.0, z / 1000.0
            ),
            default=None,
        )
        if component is None:
            component = adapter._attempt(
                lambda: assembly.AddComponent5(
                    path,
                    0,
                    "",
                    False,
                    "",
                    x / 1000.0,
                    y / 1000.0,
                    z / 1000.0,
                ),
                default=None,
            )

        adapter._attempt(lambda: assembly.EditRebuild3(), default=None)

        after = _component_names(adapter, assembly)
        if len(after) <= len(before):
            raise Exception(
                f"Component was not inserted - the assembly still has "
                f"{len(after)} component(s). The most common cause is a "
                f"part with no solid geometry: SolidWorks refuses those "
                f"silently. Check '{file_path}' opens with a body."
            )

        added = [n for n in after if n not in before]
        return {
            "component": added[-1] if added else after[-1],
            "file_path": path,
            "position": {"x": x, "y": y, "z": z},
            "components_before": len(before),
            "components_after": len(after),
        }

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("insert_component", _insert),
    )
list_components async
list_components() -> AdapterResult[list[str]]

List the top-level components of the active assembly.

Returns:

Type Description
AdapterResult[list[str]]

AdapterResult[list[str]]: Component names, or an error when the

AdapterResult[list[str]]

active document is not an assembly.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def list_components(self) -> AdapterResult[list[str]]:
    """List the top-level components of the active assembly.

    Returns:
        AdapterResult[list[str]]: Component names, or an error when the
        active document is not an assembly.
    """
    adapter = self._adapter(self)
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )
    doc_type = _doc_type(adapter)
    if doc_type != 2:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                "list_components requires an assembly document "
                f"(active document type is {doc_type!r}, expected 2)"
            ),
        )

    def _list() -> list[str]:
        assembly = _sw_type_info.flagged(adapter.currentModel, "IAssemblyDoc")
        return _component_names(adapter, assembly)

    return cast(
        AdapterResult[list[str]],
        adapter._handle_com_operation("list_components", _list),
    )
list_configurations async
list_configurations() -> AdapterResult[list[str]]

List all configuration names on the active model.

Returns:

Type Description
AdapterResult[list[str]]

AdapterResult[list[str]]: Configuration names, or empty list when unavailable.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def list_configurations(self) -> AdapterResult[list[str]]:
    """List all configuration names on the active model.

    Returns:
        AdapterResult[list[str]]: Configuration names, or empty list when unavailable.
    """
    adapter = self._adapter(self)
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="No active model",
        )

    def _list() -> list[str]:
        """List configurations."""
        raw_names = getattr(adapter.currentModel, "GetConfigurationNames", None)
        names = raw_names() if callable(raw_names) else raw_names
        if names is None:
            names = []
        if isinstance(names, str):
            return [names]

        normalized_names = [str(name) for name in names]
        if normalized_names:
            return normalized_names

        active_config = adapter._attempt(
            lambda: adapter.currentModel.GetActiveConfiguration(), default=None
        )
        # IConfiguration.Name is a property; it has no GetName method.
        active_name = adapter._attempt(
            lambda: adapter._get_attr_or_call(active_config, "Name"), default=None
        )
        if active_name:
            return [str(active_name)]
        return []

    return cast(
        AdapterResult[list[str]],
        adapter._handle_com_operation("list_configurations", _list),
    )
list_drawing_views async
list_drawing_views() -> AdapterResult[list[str]]

List the views on the active drawing.

Returns:

Type Description
AdapterResult[list[str]]

AdapterResult[list[str]]: View names, sheet formats excluded.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def list_drawing_views(self) -> AdapterResult[list[str]]:
    """List the views on the active drawing.

    Returns:
        AdapterResult[list[str]]: View names, sheet formats excluded.
    """
    adapter = self._adapter(self)
    guard = self._require_drawing()
    if guard is not None:
        return cast("AdapterResult[list[str]]", guard)

    def _list_views() -> list[str]:
        drawing = _sw_type_info.flagged(adapter.currentModel, "IDrawingDoc")
        return _view_names(adapter, drawing)

    return cast(
        AdapterResult[list[str]],
        adapter._handle_com_operation("list_drawing_views", _list_views),
    )
list_open_documents async
list_open_documents() -> AdapterResult[list[dict[str, Any]]]

Enumerate every document currently open in SolidWorks.

Read-only: uses ISldWorks::GetDocuments and does not touch the active-document selection. Each entry carries title, path, type (Part / Assembly / Drawing / Unknown) and is_active.

Returns:

Type Description
AdapterResult[list[dict[str, Any]]]

AdapterResult[list[dict[str, Any]]]: One entry per open document

AdapterResult[list[dict[str, Any]]]

(possibly empty). ERROR only when not connected.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def list_open_documents(self) -> AdapterResult[list[dict[str, Any]]]:
    """Enumerate every document currently open in SolidWorks.

    Read-only: uses ``ISldWorks::GetDocuments`` and does not touch the
    active-document selection. Each entry carries ``title``, ``path``,
    ``type`` (``Part`` / ``Assembly`` / ``Drawing`` / ``Unknown``) and
    ``is_active``.

    Returns:
        AdapterResult[list[dict[str, Any]]]: One entry per open document
        (possibly empty). ``ERROR`` only when not connected.
    """
    adapter = self._adapter(self)
    if not adapter.is_connected():
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="Not connected to SolidWorks"
        )

    def _list() -> list[dict[str, Any]]:
        app = adapter.swApp

        docs = _coerce_dispatch_sequence(
            adapter._attempt(lambda: app.GetDocuments(), default=None)
        )
        active = adapter._attempt(
            lambda: getattr(app, "ActiveDoc", None), default=None
        )
        active_title = (
            adapter._attempt(
                lambda: adapter._get_attr_or_call(active, "GetTitle"), default=None
            )
            if active is not None
            else None
        )
        active_path = (
            adapter._attempt(
                lambda: adapter._get_attr_or_call(active, "GetPathName"),
                default=None,
            )
            if active is not None
            else None
        )
        return [
            _describe_open_document(adapter, d, active_title, active_path)
            for d in docs
        ]

    return cast(
        AdapterResult[list[dict[str, Any]]],
        adapter._handle_com_operation("list_open_documents", _list),
    )
open_model async
open_model(file_path: str) -> AdapterResult[SolidWorksModel]

Open a SolidWorks model file and set it as active on the adapter.

Parameters:

Name Type Description Default
file_path str

Path to a .sldprt, .sldasm, or .slddrw file.

required

Returns:

Type Description
AdapterResult[SolidWorksModel]

AdapterResult[SolidWorksModel]: Model metadata for the opened document.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def open_model(self, file_path: str) -> AdapterResult[SolidWorksModel]:
    """Open a SolidWorks model file and set it as active on the adapter.

    Args:
        file_path: Path to a ``.sldprt``, ``.sldasm``, or ``.slddrw`` file.

    Returns:
        AdapterResult[SolidWorksModel]: Model metadata for the opened document.
    """
    adapter = self._adapter(self)
    if not adapter.is_connected():
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="Not connected to SolidWorks"
        )

    def _open() -> SolidWorksModel:
        """Open the model document."""
        resolved_path = os.path.abspath(file_path)
        file_path_lower = resolved_path.lower()
        if file_path_lower.endswith(".sldprt"):
            doc_type = adapter.constants["swDocPART"]
            model_type = "Part"
        elif file_path_lower.endswith(".sldasm"):
            doc_type = adapter.constants["swDocASSEMBLY"]
            model_type = "Assembly"
        elif file_path_lower.endswith(".slddrw"):
            doc_type = adapter.constants["swDocDRAWING"]
            model_type = "Drawing"
        else:
            raise ValueError(f"Unsupported file type: {resolved_path}")
        if not os.path.isfile(resolved_path):
            raise FileNotFoundError(f"File not found: {resolved_path}")

        app = adapter.swApp
        variant_ctor = getattr(getattr(win32com, "client", None), "VARIANT", None)
        vt_byref = int(getattr(pythoncom, "VT_BYREF", 0))
        vt_i4 = int(getattr(pythoncom, "VT_I4", 0))
        if callable(variant_ctor):
            errors = variant_ctor(vt_byref | vt_i4, 0)
            warnings = variant_ctor(vt_byref | vt_i4, 0)
        else:
            errors = 0
            warnings = 0
        model = app.OpenDoc6(resolved_path, doc_type, 1, "", errors, warnings)
        if not model:
            raise Exception(f"Failed to open model: {resolved_path}")

        adapter._attempt(
            lambda: _sw_type_info.flag_doc(model, int(doc_type)), default=0
        )

        adapter.currentModel = model
        title = self._read_model_title(model)

        # OpenDoc6 with swOpenDocOptions_Silent opens the file but does not
        # make it the active document - SolidWorks keeps the previously
        # focused window active. Without this, get_model_info and every
        # later tool call (exports especially) target the wrong document
        # (issue #91). ActivateDoc3 takes the titlebar name, not the path.
        if title:
            activated = adapter._attempt(
                lambda: app.ActivateDoc3(title, False, 0, _byref_int())
            )
            if activated is not None:
                adapter._attempt(
                    lambda: _sw_type_info.flag_doc(activated, int(doc_type)),
                    default=0,
                )
                adapter.currentModel = activated
        # The configuration SolidWorks opened, or None: never an assumed
        # "Default". IConfiguration.Name is a property, not a method.
        active_config = adapter._attempt(lambda: model.GetActiveConfiguration())
        config = (
            adapter._attempt(
                lambda: adapter._get_attr_or_call(active_config, "Name"),
                default=None,
            )
            if active_config
            else None
        )

        return SolidWorksModel(
            path=resolved_path,
            name=title,
            type=model_type,
            is_active=True,
            configuration=config,
            properties={
                "last_modified": (
                    model.GetSaveTime()
                    if callable(getattr(model, "GetSaveTime", None))
                    else None
                ),
            },
        )

    return cast(
        AdapterResult[SolidWorksModel],
        adapter._handle_com_operation("open_model", _open),
    )
pack_and_go_assembly async
pack_and_go_assembly(source_path: str, target_dir: str) -> AdapterResult[dict[str, Any]]

Copy an assembly and every file it uses into one folder.

SolidWorks lists the files (IModelDoc2.GetDependencies2) and each is copied, as saved on disk, into target_dir under its own name. The copy opens with its own parts: SolidWorks looks for a referenced file in the assembly's folder before the stored path. (SolidWorks' own Pack and Go, IPackAndGo, listed only the assembly on SolidWorks 2026.) No document is closed or changed.

Parameters:

Name Type Description Default
source_path str

The .sldasm file.

required
target_dir str

The folder to copy into; created if needed. A file already there with a copied file's name is replaced.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict]: source_assembly, target_dir,

AdapterResult[dict[str, Any]]

copied_files, source_files and unsaved_changes (the

AdapterResult[dict[str, Any]]

files open with changes the copy doesn't have).

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def pack_and_go_assembly(
    self,
    source_path: str,
    target_dir: str,
) -> AdapterResult[dict[str, Any]]:
    """Copy an assembly and every file it uses into one folder.

    SolidWorks lists the files (``IModelDoc2.GetDependencies2``) and each
    is copied, as saved on disk, into ``target_dir`` under its own name.
    The copy opens with its own parts: SolidWorks looks for a referenced
    file in the assembly's folder before the stored path. (SolidWorks'
    own Pack and Go, ``IPackAndGo``, listed only the assembly on
    SolidWorks 2026.) No document is closed or changed.

    Args:
        source_path: The ``.sldasm`` file.
        target_dir: The folder to copy into; created if needed. A file
            already there with a copied file's name is replaced.

    Returns:
        AdapterResult[dict]: ``source_assembly``, ``target_dir``,
        ``copied_files``, ``source_files`` and ``unsaved_changes`` (the
        files open with changes the copy doesn't have).
    """
    adapter = self._adapter(self)
    source = Path(source_path)
    out_dir = Path(target_dir)

    def _pack() -> dict[str, Any]:
        model = _open_or_find_assembly(adapter, source)
        _sw_type_info.flag_doc(model, 2)
        adapter.currentModel = model
        listed = dependency_paths(model.GetDependencies2(True, True, False))
        files = _files_to_pack(source, listed, out_dir)
        out_dir.mkdir(parents=True, exist_ok=True)
        copied = [str(shutil.copy2(f, out_dir / f.name)) for f in files]
        unsaved = [
            f.name
            for f in files
            if adapter._attempt(
                lambda f=f: bool(
                    adapter._get_attr_or_call(
                        adapter.swApp.GetOpenDocumentByName(str(f)),
                        "GetSaveFlag",
                    )
                ),
                default=False,
            )
        ]
        return {
            "source_assembly": str(source),
            "target_dir": str(out_dir),
            "copied_files": sorted(copied),
            "source_files": [str(f) for f in files],
            "unsaved_changes": unsaved,
        }

    result = adapter._handle_com_operation("pack_and_go_assembly", _pack)
    if not result.is_success:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=f"Pack and Go failed: {result.error}",
        )
    return AdapterResult(
        status=AdapterResultStatus.SUCCESS,
        data=result.data,
        execution_time=result.execution_time,
    )
rebuild_model async
rebuild_model(top_level_only: bool = False) -> AdapterResult[dict[str, Any]]

Force a rebuild and report exactly which features fail and why.

Parameters:

Name Type Description Default
top_level_only bool

Rebuild only the top level of an assembly. Defaults to False (full rebuild).

False

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: rebuilt, top_level_only,

AdapterResult[dict[str, Any]]

errors (see rebuild_diagnostics.explain), error_count,

AdapterResult[dict[str, Any]]

warning_count and a one-line summary. ERROR - still

AdapterResult[dict[str, Any]]

carrying that data - when any error-severity entry remains or

AdapterResult[dict[str, Any]]

SolidWorks reports the rebuild itself failed.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def rebuild_model(
    self, top_level_only: bool = False
) -> AdapterResult[dict[str, Any]]:
    """Force a rebuild and report exactly which features fail and why.

    Args:
        top_level_only: Rebuild only the top level of an assembly.
            Defaults to False (full rebuild).

    Returns:
        AdapterResult[dict[str, Any]]: ``rebuilt``, ``top_level_only``,
        ``errors`` (see ``rebuild_diagnostics.explain``), ``error_count``,
        ``warning_count`` and a one-line ``summary``. ERROR - still
        carrying that ``data`` - when any error-severity entry remains or
        SolidWorks reports the rebuild itself failed.
    """
    from .com_utils import strip_op_prefix
    from .rebuild_diagnostics import explain, summarize
    from .rebuild_inspector import collect_rebuild_issues

    adapter = self._adapter(self)
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    def _rebuild() -> dict[str, Any]:
        model = adapter.currentModel
        # ForceRebuild3 returns False whenever a feature fails to rebuild
        # (confirmed live 2026-10-13), so False is not "the rebuild call
        # broke" - collect the per-feature issues either way and only fall
        # back to a generic failure when SolidWorks names none.
        rebuilt = bool(model.ForceRebuild3(top_level_only))
        entries = [explain(i) for i in collect_rebuild_issues(adapter, model)]
        error_count, warning_count, summary = summarize(entries)
        if not rebuilt and not error_count:
            # Keep any warnings in data; only the cause is unnamed.
            summary = (
                "SolidWorks reported that the rebuild failed (ForceRebuild3 "
                "returned False) but listed no failing feature."
                + (f" Warnings: {summary}" if warning_count else "")
            )
        return {
            "rebuilt": rebuilt,
            "top_level_only": top_level_only,
            "errors": entries,
            "error_count": error_count,
            "warning_count": warning_count,
            "summary": summary,
        }

    result = cast(
        "AdapterResult[dict[str, Any]]",
        adapter._handle_com_operation("rebuild_model", _rebuild),
    )
    if not result.is_success:
        result.error = strip_op_prefix(result.error, "rebuild_model")
    elif result.data and (result.data["error_count"] or not result.data["rebuilt"]):
        result.status = AdapterResultStatus.ERROR
        result.error = result.data["summary"]
    return result
save_body_as_part async
save_body_as_part(body_name: str, file_path: str) -> AdapterResult[dict[str, Any]]

Extract one solid body from the active multibody part to a new file.

Wraps IFeatureManager::CreateSaveBodyFeature - the API behind Insert > Features > Save Bodies. body_name is matched against IPartDoc::GetBodies2(swSolidBody) by IBody2::Name; a Save Bodies feature is added and the standalone part is written to file_path. CreateSaveBodyFeature returns the feature (or None) but no write status, so success is confirmed by the file existing afterwards.

Parameters:

Name Type Description Default
body_name str

Name of a solid body in the active part.

required
file_path str

Absolute path for the new .sldprt; its parent directory must already exist.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The body, the written path, the new

AdapterResult[dict[str, Any]]

feature's name, and every solid-body name found. ERROR when

AdapterResult[dict[str, Any]]

the active document is not a part, the body is absent, the parent

AdapterResult[dict[str, Any]]

directory is missing, or no file was written.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def save_body_as_part(
    self, body_name: str, file_path: str
) -> AdapterResult[dict[str, Any]]:
    """Extract one solid body from the active multibody part to a new file.

    Wraps ``IFeatureManager::CreateSaveBodyFeature`` - the API behind
    Insert > Features > Save Bodies. ``body_name`` is matched against
    ``IPartDoc::GetBodies2(swSolidBody)`` by ``IBody2::Name``; a Save
    Bodies feature is added and the standalone part is written to
    ``file_path``. ``CreateSaveBodyFeature`` returns the feature (or
    ``None``) but no write status, so success is confirmed by the file
    existing afterwards.

    Args:
        body_name: Name of a solid body in the active part.
        file_path: Absolute path for the new ``.sldprt``; its parent
            directory must already exist.

    Returns:
        AdapterResult[dict[str, Any]]: The body, the written path, the new
        feature's name, and every solid-body name found. ``ERROR`` when
        the active document is not a part, the body is absent, the parent
        directory is missing, or no file was written.
    """
    adapter = self._adapter(self)
    self._sync_current_model_from_active()
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )
    if _doc_type(adapter) != 1:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="save_body_as_part requires an active part document",
        )
    if not str(body_name or "").strip():
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="body_name is required"
        )
    target = str(file_path or "").strip()
    if not target:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="file_path is required"
        )
    target = os.path.abspath(target)
    parent = os.path.dirname(target)
    if not os.path.isdir(parent):
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=f"Parent directory does not exist: {parent}",
        )

    def _save_body() -> dict[str, Any]:
        model = adapter.currentModel
        part = _sw_type_info.flagged(model, "IPartDoc")
        raw_bodies = adapter._attempt(
            lambda: part.GetBodies2(_SW_SOLID_BODY, False), default=None
        )
        bodies = (
            list(raw_bodies)
            if isinstance(raw_bodies, (list, tuple))
            else ([raw_bodies] if raw_bodies else [])
        )
        if not bodies:
            raise Exception("The active part has no solid bodies")

        names: list[str] = []
        match = None
        for body in bodies:
            flagged = _sw_type_info.flagged(body, "IBody2")
            name = adapter._attempt(
                lambda f=flagged: adapter._get_attr_or_call(f, "Name"),
                default=None,
            )
            if name is not None:
                names.append(str(name))
                if str(name) == body_name:
                    match = flagged
        if match is None:
            raise Exception(
                f"Body {body_name!r} not found. Solid bodies: "
                + (", ".join(names) or "none")
            )

        manager = adapter._attempt(lambda: model.FeatureManager, default=None)
        if manager is None:
            raise Exception("Part has no FeatureManager")
        manager = _sw_type_info.flagged(manager, "IFeatureManager")

        if os.path.exists(target):
            os.remove(target)

        feature = adapter._attempt(
            lambda: manager.CreateSaveBodyFeature(
                _variant_array(pythoncom.VT_DISPATCH, [match]),
                _variant_array(pythoncom.VT_BSTR, [target]),
                "",
                False,
                True,
            ),
            default=None,
        )
        adapter._attempt(lambda: model.EditRebuild3(), default=None)

        if not os.path.exists(target):
            raise Exception(
                f"CreateSaveBodyFeature did not write a file to {target}; "
                "the Save Bodies feature was rejected."
            )

        feat_name = (
            adapter._attempt(
                lambda: adapter._get_attr_or_call(feature, "Name"),
                default=None,
            )
            if feature is not None
            else None
        )
        return {
            "body": body_name,
            "file_path": target,
            "feature": str(feat_name) if feat_name else None,
            "solid_bodies": names,
        }

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("save_body_as_part", _save_body),
    )
save_file async
save_file(file_path: str | None = None) -> AdapterResult[None]

Save the active model to its current path or to a new file path.

Parameters:

Name Type Description Default
file_path str | None

Optional target path for Save As.

None

Returns:

Type Description
AdapterResult[None]

AdapterResult[None]: Result of the save operation.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def save_file(self, file_path: str | None = None) -> AdapterResult[None]:
    """Save the active model to its current path or to a new file path.

    Args:
        file_path: Optional target path for Save As.

    Returns:
        AdapterResult[None]: Result of the save operation.
    """
    adapter = self._adapter(self)
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    def _save() -> None:
        """Save the model."""
        if file_path:
            resolved_path = os.path.abspath(file_path)
            directory = os.path.dirname(resolved_path)
            if directory:
                os.makedirs(directory, exist_ok=True)

            current_path = adapter._attempt(
                lambda: adapter._get_attr_or_call(
                    adapter.currentModel, "GetPathName"
                ),
                default="",
            )
            same_file = bool(current_path) and os.path.normcase(
                os.path.abspath(str(current_path))
            ) == os.path.normcase(resolved_path)

            if same_file:
                # Saving a document over its own path is a plain Save.
                # It used to fall through to the Save-As branch below, which
                # closed the document and deleted the file before calling
                # SaveAs3 on the now-closed doc. That wrote an empty part and
                # lost the geometry.
                save_result = adapter._attempt(
                    lambda: adapter.currentModel.Save3(1, None, None)
                )
                if save_result is None:
                    save_fn = getattr(adapter.currentModel, "Save", None)
                    if callable(save_fn):
                        save_fn()
                if not os.path.exists(resolved_path):
                    raise Exception(f"File not written after save: {resolved_path}")
                return

            # A *different* document may be holding the target path open.
            # Close that one by name only - never the document being saved.
            if adapter.swApp:
                adapter._attempt(
                    lambda: adapter.swApp.CloseDoc(os.path.basename(resolved_path))
                )

            # Deliberately no os.remove here: SaveAs3 overwrites, and
            # deleting first meant a failed save destroyed the old file too.
            save_as3_result = adapter.currentModel.SaveAs3(resolved_path, 0, 0)
            if not self._is_success(save_as3_result):
                save_as = getattr(adapter.currentModel, "SaveAs", None)
                if callable(save_as):
                    fallback_result = save_as(resolved_path)
                    if not self._is_success(fallback_result):
                        raise Exception(f"Failed to save as: {resolved_path}")
                else:
                    raise Exception(f"Failed to save as: {resolved_path}")

            if not os.path.exists(resolved_path):
                raise Exception(f"File not written after save: {resolved_path}")
            return

        save_result = adapter._attempt(
            lambda: adapter.currentModel.Save3(1, None, None)
        )
        if save_result is None:
            save_fn = getattr(adapter.currentModel, "Save", None)
            if callable(save_fn):
                save_result = save_fn()
            else:
                raise Exception("Failed to save file")

        if self._is_success(save_result):
            return

        path_attr = getattr(adapter.currentModel, "GetPathName", "")
        model_path = path_attr() if callable(path_attr) else path_attr
        if model_path and os.path.exists(model_path):
            return
        raise Exception("Failed to save file")

    return cast(
        AdapterResult[None],
        adapter._handle_com_operation("save_file", _save),
    )
set_component_state async
set_component_state(component: str, fixed: bool | None = None, flexible: bool | None = None) -> AdapterResult[dict[str, Any]]

Fix or float a component, and make a sub-assembly rigid or flexible.

Fixing selects the component and calls IAssemblyDoc::FixComponent or UnfixComponent. Solving goes through IAssemblyDoc::CompConfigProperties5, which also sets suppression, visibility, configuration, BOM and envelope for the selected component: each is read from the component first and passed back, so only solving changes.

Parameters:

Name Type Description Default
component str

Instance name, as list_components reports it.

required
fixed bool | None

True fixes it, False floats it.

None
flexible bool | None

True flexible, False rigid (sub-assemblies).

None

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The state before and after.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def set_component_state(
    self,
    component: str,
    fixed: bool | None = None,
    flexible: bool | None = None,
) -> AdapterResult[dict[str, Any]]:
    """Fix or float a component, and make a sub-assembly rigid or flexible.

    Fixing selects the component and calls ``IAssemblyDoc::FixComponent``
    or ``UnfixComponent``. Solving goes through
    ``IAssemblyDoc::CompConfigProperties5``, which also sets suppression,
    visibility, configuration, BOM and envelope for the selected
    component: each is read from the component first and passed back, so
    only solving changes.

    Args:
        component (str): Instance name, as ``list_components`` reports it.
        fixed (bool | None): True fixes it, False floats it.
        flexible (bool | None): True flexible, False rigid (sub-assemblies).

    Returns:
        AdapterResult[dict[str, Any]]: The state before and after.
    """
    adapter = self._adapter(self)
    if _doc_type(adapter) != 2:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="set_component_state requires an assembly document",
        )
    if fixed is None and flexible is None:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error="Nothing to change: give fixed, flexible or both.",
        )

    def _state() -> dict[str, Any]:
        model = adapter.currentModel
        assembly = _sw_type_info.flagged(model, "IAssemblyDoc")
        target = _find_components(adapter, assembly, [component])[component]
        before = _component_state(adapter, target)
        sel_mgr = _sw_type_info.flagged(model.SelectionManager, "ISelectionMgr")

        def select() -> None:
            adapter._attempt(lambda: model.ClearSelection2(True), default=None)
            data = adapter._attempt(
                lambda: sel_mgr.CreateSelectData(), default=None
            )
            if not adapter._attempt(
                lambda: target.Select4(False, data, False), default=False
            ):
                raise Exception(f"Failed to select {component}")

        if fixed is not None:
            select()
            call = assembly.FixComponent if fixed else assembly.UnfixComponent
            adapter._attempt(call, default=None)
        if flexible is not None:
            select()
            config = before["referenced_configuration"] or ""
            applied = adapter._attempt(
                lambda: assembly.CompConfigProperties5(
                    before["suppression"],
                    1 if flexible else 0,  # swComponentSolvingOption_e
                    bool(before["visible"]),
                    bool(config),
                    config,
                    bool(before["exclude_from_bom"]),
                    bool(before["envelope"]),
                ),
                default=False,
            )
            if not applied:
                raise Exception(
                    f"SolidWorks refused to make {component} "
                    f"{'flexible' if flexible else 'rigid'}; only a "
                    "sub-assembly can be flexible."
                )
        adapter._attempt(lambda: model.ClearSelection2(True), default=None)
        adapter._attempt(lambda: model.EditRebuild3(), default=None)
        after = _component_state(adapter, target)
        if fixed is not None and after["fixed"] is not fixed:
            raise Exception(
                f"{component} is still {'floating' if fixed else 'fixed'} "
                "after the change."
            )
        return {"component": component, "before": before, "after": after}

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("set_component_state", _state),
    )
set_dimension async
set_dimension(name: str, value: float) -> AdapterResult[None]

Set a named model dimension in millimetres and rebuild.

Parameters:

Name Type Description Default
name str

Fully-qualified dimension name.

required
value float

New value in millimetres.

required

Returns:

Type Description
AdapterResult[None]

AdapterResult[None]: Result of the set operation.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def set_dimension(self, name: str, value: float) -> AdapterResult[None]:
    """Set a named model dimension in millimetres and rebuild.

    Args:
        name: Fully-qualified dimension name.
        value: New value in millimetres.

    Returns:
        AdapterResult[None]: Result of the set operation.
    """
    adapter = self._adapter(self)
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    def _set() -> None:
        """Set the dimension value."""
        dimension = adapter.currentModel.Parameter(name)
        if not dimension:
            raise Exception(f"Dimension '{name}' not found")

        # SetValue3 has gen_py parameter mapping issues on SW 2025.
        # SystemValue (in meters) is reliable.
        value_m = value / 1000.0
        adapter._attempt(
            lambda: setattr(dimension, "SystemValue", value_m),
            default=None,
        )

        # Rebuild: try EditRebuild3 first, fall back to ForceRebuild3
        rebuilt = adapter._attempt(
            lambda: adapter.currentModel.EditRebuild3(), default=None
        )
        if rebuilt is None:
            rebuilt = adapter._attempt(
                lambda: adapter.currentModel.ForceRebuild3(True), default=None
            )
        if rebuilt is None:
            raise Exception("Failed to set dimension")

    return cast(
        AdapterResult[None],
        adapter._handle_com_operation("set_dimension", _set),
    )
set_units async
set_units(unit_system: str) -> AdapterResult[dict[str, Any]]

Set the active document's linear unit system.

Accepts mm, cm, m, in or ft (plus common aliases such as inch / millimeters). The choice is applied by setting both swUnitSystem and the exact swUnitsLinear preference, then reading the linear unit back - a mismatch is reported as an error.

Parameters:

Name Type Description Default
unit_system str

The target unit token.

required

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: What was applied and whether the

AdapterResult[dict[str, Any]]

readback confirmed it. ERROR when there is no active model or

AdapterResult[dict[str, Any]]

the token is not recognised.

Source code in src/solidworks_mcp/adapters/solidworks/io.py
async def set_units(self, unit_system: str) -> AdapterResult[dict[str, Any]]:
    """Set the active document's linear unit system.

    Accepts ``mm``, ``cm``, ``m``, ``in`` or ``ft`` (plus common aliases
    such as ``inch`` / ``millimeters``). The choice is applied by setting
    both ``swUnitSystem`` and the exact ``swUnitsLinear`` preference, then
    reading the linear unit back - a mismatch is reported as an error.

    Args:
        unit_system: The target unit token.

    Returns:
        AdapterResult[dict[str, Any]]: What was applied and whether the
        readback confirmed it. ``ERROR`` when there is no active model or
        the token is not recognised.
    """
    adapter = self._adapter(self)
    # Resync from ActiveDoc in case the user switched documents in the UI.
    self._sync_current_model_from_active()
    if not adapter.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    token = _normalise_unit_system(unit_system)
    if token is None:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                f"Unrecognised unit system {unit_system!r}. "
                "Expected one of: mm, cm, m, in, ft."
            ),
        )

    def _apply() -> dict[str, Any]:
        return _apply_unit_system(adapter, adapter.currentModel, token)

    return cast(
        AdapterResult[dict[str, Any]],
        adapter._handle_com_operation("set_units", _apply),
    )

SolidWorksMCPError

Bases: Exception

Base exception for SolidWorks MCP Server errors.

SolidWorksSelectionMixin

Expose feature-selection and feature-list methods through a mixin.

Methods:
get_selected_entities async
get_selected_entities() -> AdapterResult[list[dict[str, Any]]]

Report the faces/edges/vertices currently highlighted in the live SolidWorks UI, so an agent can see exactly what the user clicked on instead of guessing from a screenshot (see com-api-pitfalls.md #21 for why a screenshot alone is not reliable proof of document/view state).

Refreshes currentModel from swApp.ActiveDoc first, the same way list_features does, since the user's selection lives on whatever document is actually active in the UI right now -- which may not be the document this adapter last created or touched.

Returns:

Type Description
AdapterResult[list[dict[str, Any]]]

AdapterResult[list[dict[str, Any]]]: One entry per selected

AdapterResult[list[dict[str, Any]]]

entity, each with at least index and type

AdapterResult[list[dict[str, Any]]]

("FACE"/"EDGE"/"VERTEX"/"UNKNOWN(<n>)"). Faces

AdapterResult[list[dict[str, Any]]]

additionally carry bounding_box_mm, center_mm,

AdapterResult[list[dict[str, Any]]]

area_mm2 and normal (when planar). Edges carry

AdapterResult[list[dict[str, Any]]]

start_mm/end_mm and a straight-line length_mm

AdapterResult[list[dict[str, Any]]]

(the true length of a curved edge will be longer). Vertices

AdapterResult[list[dict[str, Any]]]

carry point_mm. Any field that fails to resolve is simply

AdapterResult[list[dict[str, Any]]]

omitted rather than failing the whole call -- a partial

AdapterResult[list[dict[str, Any]]]

description is more useful than none.

Source code in src/solidworks_mcp/adapters/solidworks/selection.py
async def get_selected_entities(self) -> AdapterResult[list[dict[str, Any]]]:
    """Report the faces/edges/vertices currently highlighted in the live
    SolidWorks UI, so an agent can see exactly what the user clicked on
    instead of guessing from a screenshot (see com-api-pitfalls.md #21 for
    why a screenshot alone is not reliable proof of document/view state).

    Refreshes ``currentModel`` from ``swApp.ActiveDoc`` first, the same
    way ``list_features`` does, since the user's selection lives on
    whatever document is actually active in the UI right now -- which
    may not be the document this adapter last created or touched.

    Returns:
        AdapterResult[list[dict[str, Any]]]: One entry per selected
        entity, each with at least ``index`` and ``type``
        (``"FACE"``/``"EDGE"``/``"VERTEX"``/``"UNKNOWN(<n>)"``). Faces
        additionally carry ``bounding_box_mm``, ``center_mm``,
        ``area_mm2`` and ``normal`` (when planar). Edges carry
        ``start_mm``/``end_mm`` and a straight-line ``length_mm``
        (the true length of a curved edge will be longer). Vertices
        carry ``point_mm``. Any field that fails to resolve is simply
        omitted rather than failing the whole call -- a partial
        description is more useful than none.
    """
    sw_app = getattr(self, "swApp", None)
    active_model = getattr(sw_app, "ActiveDoc", None) if sw_app else None
    if active_model is not None:
        self.currentModel = active_model
    if not self.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    def _describe() -> list[dict[str, Any]]:
        model = self.currentModel
        sel_mgr = model.SelectionManager
        self._attempt(
            lambda: _sw_type_info.flag_methods(sel_mgr, "ISelectionMgr"),
            default=0,
        )
        count = (
            self._attempt(lambda: sel_mgr.GetSelectedObjectCount2(-1), default=0)
            or 0
        )

        results: list[dict[str, Any]] = []
        for i in range(1, int(count) + 1):
            raw_type = self._attempt(
                lambda i=i: sel_mgr.GetSelectedObjectType3(i, -1), default=None
            )
            obj = self._attempt(
                lambda i=i: sel_mgr.GetSelectedObject6(i, -1), default=None
            )
            if obj is None:
                continue
            self._attempt(
                lambda o=obj: _sw_type_info.flag_methods(
                    o, "IFace2", "IEdge", "IVertex"
                ),
                default=0,
            )

            type_name = _SELECTED_ENTITY_TYPE_NAMES.get(
                int(raw_type) if isinstance(raw_type, (int, float)) else -1,
                f"UNKNOWN({raw_type})",
            )
            entry: dict[str, Any] = {"index": i, "type": type_name}

            if type_name == "FACE":
                box = self._attempt(lambda o=obj: o.GetBox(), default=None)
                if box and len(box) == 6:
                    entry["bounding_box_mm"] = {
                        "min": [c * 1000 for c in box[0:3]],
                        "max": [c * 1000 for c in box[3:6]],
                    }
                    entry["center_mm"] = [
                        (box[j] + box[j + 3]) / 2 * 1000 for j in range(3)
                    ]
                area = self._attempt(lambda o=obj: o.GetArea(), default=None)
                if area is not None:
                    entry["area_mm2"] = area * 1e6
                normal = self._attempt(lambda o=obj: o.Normal, default=None)
                if normal:
                    entry["normal"] = [round(float(n), 6) for n in normal]

            elif type_name == "EDGE":
                # Shared with select_face_edges: start/end/length plus
                # curve kind and, for circles, centre/axis/radius.
                entry.update(describe_edge(self, obj))

            elif type_name == "VERTEX":
                point = self._attempt(lambda o=obj: o.GetPoint(), default=None)
                if point:
                    entry["point_mm"] = [c * 1000 for c in point]

            results.append(entry)
        return results

    return self._handle_com_operation("get_selected_entities", _describe)
select_face_edges async
select_face_edges(point: list[float] | None = None, side: str | None = None, plane: str | None = None, edge_type: str = 'all', append: bool = False) -> AdapterResult[dict[str, Any]]

Multi-select every edge bounding one face of the active part.

See SolidWorksAdapter.select_face_edges for the contract; the face resolution and selection live in face_selection.FaceEdgeSelector.

Source code in src/solidworks_mcp/adapters/solidworks/selection.py
async def select_face_edges(
    self,
    point: list[float] | None = None,
    side: str | None = None,
    plane: str | None = None,
    edge_type: str = "all",
    append: bool = False,
) -> AdapterResult[dict[str, Any]]:
    """Multi-select every edge bounding one face of the active part.

    See ``SolidWorksAdapter.select_face_edges`` for the contract; the face
    resolution and selection live in ``face_selection.FaceEdgeSelector``.
    """
    problem = validate_face_query(point, side, plane, edge_type)
    if problem:
        return AdapterResult(status=AdapterResultStatus.ERROR, error=problem)
    if not self.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )
    from .io import _doc_type

    doc_type = _doc_type(self)
    if doc_type is not None and doc_type != 1:
        return AdapterResult(
            status=AdapterResultStatus.ERROR,
            error=(
                "select_face_edges requires an active part document "
                f"(active document type is {doc_type})."
            ),
        )

    def _select_edges() -> dict[str, Any]:
        selector = FaceEdgeSelector(self)
        faces = selector.resolve(point, side, plane)
        edges = selector.edges(faces, edge_type)
        if not edges:
            kind = "" if edge_type == "all" else f"{edge_type} "
            raise LookupError(
                f"The selected face has no {kind}edges "
                f"(face centre {faces[0].describe()['center_mm']} mm)."
            )
        count = selector.select(edges, append)
        described = [f.describe() for f in faces]
        return {
            "face": described[0],
            "faces": described,
            "edge_type": edge_type,
            "edges": [info for _edge, info in edges],
            "selected_edges": len(edges),
            "selection_count": count,
        }

    return self._run_clean("select_face_edges", _select_edges)
select_feature async
select_feature(feature_name: str, append: bool = False) -> AdapterResult[dict[str, Any]]

Select a named feature, sketch, plane or component.

Parameters:

Name Type Description Default
feature_name str

Feature name, optionally @document qualified.

required
append bool

Add to the current selection instead of replacing it.

False

Returns:

Type Description
AdapterResult[dict[str, Any]]

AdapterResult[dict[str, Any]]: The selector payload plus

AdapterResult[dict[str, Any]]

selection_count. ERROR - never SUCCESS with

AdapterResult[dict[str, Any]]

selected: False - when no strategy could select the name.

Source code in src/solidworks_mcp/adapters/solidworks/selection.py
async def select_feature(
    self, feature_name: str, append: bool = False
) -> AdapterResult[dict[str, Any]]:
    """Select a named feature, sketch, plane or component.

    Args:
        feature_name: Feature name, optionally ``@document`` qualified.
        append: Add to the current selection instead of replacing it.

    Returns:
        AdapterResult[dict[str, Any]]: The selector payload plus
        ``selection_count``. ERROR - never SUCCESS with
        ``selected: False`` - when no strategy could select the name.
    """
    if not self.currentModel:
        return AdapterResult(
            status=AdapterResultStatus.ERROR, error="No active model"
        )

    def _select() -> dict[str, Any]:
        payload = self._feature_selector.select_feature(feature_name, append)
        if not payload or not payload.get("selected"):
            raise LookupError(
                f"Feature not found or could not be selected: {feature_name}"
            )
        payload["selection_count"] = selection_count(self, self.currentModel)
        return payload

    return self._run_clean("select_feature", _select)

SolidWorksSketchMixin

Expose sketch creation and editing methods via mixin-local implementation.

_ComSessionCoordinator

_ComSessionCoordinator(adapter: PyWin32Adapter)

Coordinate COM apartment initialisation and SolidWorks application lifecycle.

This collaborator is responsible for the low-level mechanics of connecting to the SolidWorks COM server: initialising the COM apartment, acquiring the SldWorks.Application object (with retries), waiting for the server to become ready, toggling automation preferences, and tearing everything down cleanly on disconnect.

It is instantiated once inside PyWin32Adapter.__init__ and accessed via self._com_coordinator.

Attributes:

Name Type Description
_adapter

Back-reference to the owning PyWin32Adapter instance.

Store a back-reference to the owning adapter.

Parameters:

Name Type Description Default
adapter PyWin32Adapter

The PyWin32Adapter instance that created this coordinator. Must remain alive for the full lifetime of the coordinator.

required
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def __init__(self, adapter: "PyWin32Adapter") -> None:
    """Store a back-reference to the owning adapter.

    Args:
        adapter: The ``PyWin32Adapter`` instance that created this
            coordinator.  Must remain alive for the full lifetime of the
            coordinator.
    """
    self._adapter = adapter
Methods:
acquire_solidworks_application async
acquire_solidworks_application() -> Any

Acquire a live SolidWorks COM application object with retries.

Attempts up to 8 connect-cycles. Each cycle first tries win32com.client.GetActiveObject("SldWorks.Application") to bind to a running instance, then falls back to win32com.client.Dispatch("SldWorks.Application") to start one. Between cycles the coroutine sleeps 1 second so that a slow SolidWorks launch has time to register its COM class.

Returns:

Name Type Description
Any Any

The live SldWorks.Application COM object.

Raises:

Type Description
SolidWorksMCPError

After all retries are exhausted, wraps the last pywintypes.com_error or raises a generic message when the returned object is None.

Side Effects

Sets adapter.swApp to the acquired COM object.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
async def acquire_solidworks_application(self) -> Any:
    """Acquire a live SolidWorks COM application object with retries.

    Attempts up to 8 connect-cycles.  Each cycle first tries
    ``win32com.client.GetActiveObject("SldWorks.Application")`` to bind to
    a running instance, then falls back to
    ``win32com.client.Dispatch("SldWorks.Application")`` to start one.
    Between cycles the coroutine sleeps 1 second so that a slow
    SolidWorks launch has time to register its COM class.

    Returns:
        Any: The live ``SldWorks.Application`` COM object.

    Raises:
        SolidWorksMCPError: After all retries are exhausted, wraps the
            last ``pywintypes.com_error`` or raises a generic message
            when the returned object is ``None``.

    Side Effects:
        Sets ``adapter.swApp`` to the acquired COM object.
    """
    self._adapter.swApp = None
    last_error: Exception | None = None
    # Force late binding (dynamic.Dispatch) — the gen_py wrapper provides
    # method-name lookup for flag_methods but early-bound dispatches
    # reject VARIANT pass-by-ref params used by OpenDoc6 and friends.
    for _ in range(8):
        try:
            raw = win32com.client.GetActiveObject("SldWorks.Application")
            app = _dynamic_dispatch(raw) if raw is not None else None
            if app is not None:
                self._adapter.swApp = app
                return app
        except pywintypes.com_error as active_error:
            last_error = active_error

        try:
            app = _dynamic_dispatch("SldWorks.Application")
            if app is not None:
                self._adapter.swApp = app
                return app
        except pywintypes.com_error as dispatch_error:
            last_error = dispatch_error

        await asyncio.sleep(1.0)

    if last_error is not None:
        raise SolidWorksMCPError(str(last_error))
    raise SolidWorksMCPError("SolidWorks COM application instance is None")
connect async
connect() -> None

Orchestrate the full SolidWorks connection sequence.

Performs in order:

  1. Initialise the COM apartment.
  2. Acquire the SldWorks.Application COM object (with retries).
  3. Wait for the server to become ready.
  4. Show the application window (hidden instead when SOLIDWORKS_MCP_HEADLESS is set).
  5. Suppress interactive dialogs for automation.

On any failure the adapter state is cleaned up (swApp set to None, COM apartment uninitialised) before re-raising.

Raises:

Type Description
SolidWorksMCPError

Wraps any underlying COM or timeout error.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
async def connect(self) -> None:
    """Orchestrate the full SolidWorks connection sequence.

    Performs in order:

    1. Initialise the COM apartment.
    2. Acquire the ``SldWorks.Application`` COM object (with retries).
    3. Wait for the server to become ready.
    4. Show the application window (hidden instead when
       ``SOLIDWORKS_MCP_HEADLESS`` is set).
    5. Suppress interactive dialogs for automation.

    On any failure the adapter state is cleaned up (``swApp`` set to
    ``None``, COM apartment uninitialised) before re-raising.

    Raises:
        SolidWorksMCPError: Wraps any underlying COM or timeout error.
    """
    try:
        self.initialize_com_apartment()
        app = await self.acquire_solidworks_application()
        self._adapter._attempt(
            lambda: sw_type_info.flag_methods(app, "ISldWorks"), default=0
        )
        await self.wait_for_server_ready(app)
        app.Visible = not _headless_requested()
        self.set_automation_preferences(app, interactive=False)
    except Exception as exc:
        self._adapter.swApp = None
        self.uninitialize_com_apartment()
        raise SolidWorksMCPError(f"Failed to connect to SolidWorks: {exc}") from exc
disconnect async
disconnect() -> None

Clear session state and release the COM apartment.

Resets all adapter model/sketch references to None and always uninitialises the COM apartment via finally so the apartment is freed even if the SolidWorks process is already unstable.

Disconnect deliberately avoids additional UI preference COM calls. Real SolidWorks sessions can hit RPC teardown failures when toggles are restored during shutdown, so automation-safe preferences are applied on connect and left untouched during disconnect.

Side Effects

Sets adapter.currentModel, adapter.currentSketch, adapter.currentSketchManager, and adapter.swApp to None. Clears the sketch entity registry. Calls CoUninitialize.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
async def disconnect(self) -> None:
    """Clear session state and release the COM apartment.

    Resets all adapter model/sketch references to ``None`` and always
    uninitialises the COM apartment via ``finally`` so the apartment is
    freed even if the SolidWorks process is already unstable.

    Disconnect deliberately avoids additional UI preference COM calls.
    Real SolidWorks sessions can hit RPC teardown failures when toggles are
    restored during shutdown, so automation-safe preferences are applied on
    connect and left untouched during disconnect.

    Side Effects:
        Sets ``adapter.currentModel``, ``adapter.currentSketch``,
        ``adapter.currentSketchManager``, and ``adapter.swApp`` to
        ``None``.  Clears the sketch entity registry.  Calls
        ``CoUninitialize``.
    """
    try:
        self._adapter.currentModel = None
        self._adapter.currentSketch = None
        self._adapter.currentSketchManager = None
        self._adapter._reset_sketch_entity_registry()
        self._adapter.swApp = None
    finally:
        self.uninitialize_com_apartment()
initialize_com_apartment
initialize_com_apartment() -> None

Initialise the COM apartment via pythoncom.CoInitialize.

Safe to call multiple times; subsequent calls are no-ops if the apartment is already initialised (tracked via adapter._com_initialized).

Side Effects

Sets adapter._com_initialized to True on first call.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def initialize_com_apartment(self) -> None:
    """Initialise the COM apartment via ``pythoncom.CoInitialize``.

    Safe to call multiple times; subsequent calls are no-ops if the
    apartment is already initialised (tracked via
    ``adapter._com_initialized``).

    Side Effects:
        Sets ``adapter._com_initialized`` to ``True`` on first call.
    """
    if self._adapter._com_initialized:
        return
    pythoncom.CoInitialize()
    self._adapter._com_initialized = True
    # SolidWorks COM objects are STA-bound: only this thread may use them.
    self._adapter._com_thread_id = threading.get_ident()
set_automation_preferences staticmethod
set_automation_preferences(app: Any, *, interactive: bool) -> None

Toggle the SolidWorks warning and question dialog preferences.

Sets user preference toggles 150 and 149 to suppress (or restore) popup dialogs during automated workflows. Also manages the sketch-dimension input toggles so sketch dimensions do not require the interactive Modify confirmation while automation is active.

Verified against the SolidWorks 2026 constants type library, the relevant sketch-dimension toggles are:

  • swInputDimValOnCreate = 10
  • swSketchAcceptNumericInput = 372
  • swSketchCreateDimensionOnlyWhenEntered = 520
  • swScaleSketchOnFirstDimension = 642

These stay disabled for the full automation session instead of being toggled around individual dimension calls, because once SolidWorks enters the modal approval flow, extra COM cleanup chatter tends to make recovery less reliable.

Should be called with interactive=False immediately after connecting and interactive=True before disconnecting.

Parameters:

Name Type Description Default
app Any

The live SldWorks.Application COM object.

required
interactive bool

When False, dialogs are suppressed for unattended operation. When True, normal interactive behaviour is restored.

required
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
@staticmethod
def set_automation_preferences(app: Any, *, interactive: bool) -> None:
    """Toggle the SolidWorks warning and question dialog preferences.

    Sets user preference toggles 150 and 149 to suppress (or restore)
    popup dialogs during automated workflows. Also manages the
    sketch-dimension input toggles so sketch dimensions do not require the
    interactive Modify confirmation while automation is active.

    Verified against the SolidWorks 2026 constants type library, the
    relevant sketch-dimension toggles are:

    * ``swInputDimValOnCreate`` = 10
    * ``swSketchAcceptNumericInput`` = 372
    * ``swSketchCreateDimensionOnlyWhenEntered`` = 520
    * ``swScaleSketchOnFirstDimension`` = 642

    These stay disabled for the full automation session instead of being
    toggled around individual dimension calls, because once SolidWorks
    enters the modal approval flow, extra COM cleanup chatter tends to make
    recovery less reliable.

    Should be called with ``interactive=False`` immediately after
    connecting and ``interactive=True`` before disconnecting.

    Args:
        app: The live ``SldWorks.Application`` COM object.
        interactive: When ``False``, dialogs are suppressed for
            unattended operation.  When ``True``, normal interactive
            behaviour is restored.
    """
    app.SetUserPreferenceToggle(150, interactive)
    app.SetUserPreferenceToggle(149, interactive)
    if not interactive:
        app.SetUserPreferenceToggle(10, False)
        app.SetUserPreferenceToggle(372, False)
        app.SetUserPreferenceToggle(520, False)
        app.SetUserPreferenceToggle(642, False)
uninitialize_com_apartment
uninitialize_com_apartment() -> None

Release the COM apartment via pythoncom.CoUninitialize.

Only called when this coordinator was the one that initialised the apartment (adapter._com_initialized is True). Resets the flag afterwards so reconnect attempts work correctly.

Side Effects

Sets adapter._com_initialized to False.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def uninitialize_com_apartment(self) -> None:
    """Release the COM apartment via ``pythoncom.CoUninitialize``.

    Only called when this coordinator was the one that initialised the
    apartment (``adapter._com_initialized`` is ``True``).  Resets the
    flag afterwards so reconnect attempts work correctly.

    Side Effects:
        Sets ``adapter._com_initialized`` to ``False``.
    """
    if not self._adapter._com_initialized:
        return
    pythoncom.CoUninitialize()
    self._adapter._com_initialized = False
    self._adapter._com_thread_id = None
wait_for_server_ready async
wait_for_server_ready(app: Any) -> None

Poll until the SolidWorks COM server is responsive.

Reads app.RevisionNumber up to 10 times with 0.5-second pauses between attempts. This guards against race conditions when SolidWorks is still loading after the COM object is first obtained.

Parameters:

Name Type Description Default
app Any

The SldWorks.Application COM object returned by :meth:acquire_solidworks_application.

required

Raises:

Type Description
SolidWorksMCPError

If the server does not respond within 10 × 0.5 s = 5 seconds.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
async def wait_for_server_ready(self, app: Any) -> None:
    """Poll until the SolidWorks COM server is responsive.

    Reads ``app.RevisionNumber`` up to 10 times with 0.5-second pauses
    between attempts.  This guards against race conditions when SolidWorks
    is still loading after the COM object is first obtained.

    Args:
        app: The ``SldWorks.Application`` COM object returned by
            :meth:`acquire_solidworks_application`.

    Raises:
        SolidWorksMCPError: If the server does not respond within
            10 × 0.5 s = 5 seconds.
    """
    if not hasattr(app, "RevisionNumber"):
        return

    for _ in range(10):
        revision = self._adapter._attempt(
            lambda: self._adapter._get_attr_or_call(app, "RevisionNumber"),
            default=None,
        )
        if revision is not None:
            return
        await asyncio.sleep(0.5)

    raise SolidWorksMCPError(
        "SolidWorks COM server did not become ready in time. "
        "Confirm SolidWorks is fully launched and dismiss any startup dialogs."
    )

_DocumentRoutingService

_DocumentRoutingService(adapter: PyWin32Adapter)

Resolve document identity and select the export-target document.

Provides two helpers that isolate the policy for identifying which COM document object to use during export operations. The service prefers the SolidWorks ActiveDoc when it matches the adapter's current model, and falls back gracefully when path or title data is unavailable.

Attributes:

Name Type Description
_adapter

Back-reference to the owning PyWin32Adapter instance.

Store a back-reference to the owning adapter.

Parameters:

Name Type Description Default
adapter PyWin32Adapter

The PyWin32Adapter instance that created this service.

required
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def __init__(self, adapter: "PyWin32Adapter") -> None:
    """Store a back-reference to the owning adapter.

    Args:
        adapter: The ``PyWin32Adapter`` instance that created this
            service.
    """
    self._adapter = adapter
Methods:
document_identity
document_identity(document: Any) -> tuple[str | None, str | None]

Return the normalised absolute path and display title for a COM document.

Tries document.GetPathName() first; falls back to getattr(document, 'GetPathName', None) for COM objects that expose the path as a property rather than a method. Applies the same two-step pattern for GetTitle.

Parameters:

Name Type Description Default
document Any

A SolidWorks IModelDoc2 COM object, or None.

required

Returns:

Type Description
str | None

tuple[str | None, str | None]: (absolute_path, title) where

str | None

absolute_path is os.path.abspath normalised and

tuple[str | None, str | None]

title is the raw display title. Either element is None

tuple[str | None, str | None]

when the corresponding attribute is absent or empty.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def document_identity(self, document: Any) -> tuple[str | None, str | None]:
    """Return the normalised absolute path and display title for a COM document.

    Tries ``document.GetPathName()`` first; falls back to
    ``getattr(document, 'GetPathName', None)`` for COM objects that expose
    the path as a property rather than a method.  Applies the same
    two-step pattern for ``GetTitle``.

    Args:
        document: A SolidWorks ``IModelDoc2`` COM object, or ``None``.

    Returns:
        tuple[str | None, str | None]: ``(absolute_path, title)`` where
        ``absolute_path`` is ``os.path.abspath`` normalised and
        ``title`` is the raw display title.  Either element is ``None``
        when the corresponding attribute is absent or empty.
    """
    if document is None:
        return None, None

    raw_path = self._adapter._attempt(lambda: document.GetPathName(), default=None)
    if raw_path is None:
        raw_path = self._adapter._attempt(
            lambda: getattr(document, "GetPathName", None), default=None
        )
    path_value = str(raw_path).strip() if raw_path else ""
    normalized_path = os.path.abspath(path_value) if path_value else None

    raw_title = self._adapter._attempt(lambda: document.GetTitle(), default=None)
    if raw_title is None:
        raw_title = self._adapter._attempt(
            lambda: getattr(document, "GetTitle", None), default=None
        )
    title_value = str(raw_title).strip() if raw_title else ""

    return normalized_path, title_value or None
resolve_export_target_doc
resolve_export_target_doc() -> Any

Resolve the safest document to use for export operations.

Policy (in order):

  1. If adapter.currentModel is None, use swApp.ActiveDoc.
  2. If swApp.ActiveDoc is None, use adapter.currentModel.
  3. If both have path strings and the paths match, prefer swApp.ActiveDoc (it may have a more up-to-date state).
  4. If both have title strings that match, prefer swApp.ActiveDoc.
  5. Otherwise fall back to adapter.currentModel.

Returns:

Name Type Description
Any Any

The SolidWorks IModelDoc2 COM object to use for export,

Any

or None when neither swApp nor currentModel is set.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def resolve_export_target_doc(self) -> Any:
    """Resolve the safest document to use for export operations.

    Policy (in order):

    1. If ``adapter.currentModel`` is ``None``, use ``swApp.ActiveDoc``.
    2. If ``swApp.ActiveDoc`` is ``None``, use ``adapter.currentModel``.
    3. If both have path strings and the paths match, prefer
       ``swApp.ActiveDoc`` (it may have a more up-to-date state).
    4. If both have title strings that match, prefer ``swApp.ActiveDoc``.
    5. Otherwise fall back to ``adapter.currentModel``.

    Returns:
        Any: The SolidWorks ``IModelDoc2`` COM object to use for export,
        or ``None`` when neither ``swApp`` nor ``currentModel`` is set.
    """
    active_doc = (
        getattr(self._adapter.swApp, "ActiveDoc", None)
        if self._adapter.swApp
        else None
    )
    if self._adapter.currentModel is None:
        return active_doc
    if active_doc is None:
        return self._adapter.currentModel

    current_path, current_title = self.document_identity(self._adapter.currentModel)
    active_path, active_title = self.document_identity(active_doc)

    if current_path and active_path:
        return (
            active_doc
            if current_path == active_path
            else self._adapter.currentModel
        )
    if current_title and active_title and current_title == active_title:
        return active_doc
    return self._adapter.currentModel

_FeatureSelectionService

_FeatureSelectionService(adapter: PyWin32Adapter)

Encapsulate feature-selection strategies and name-candidate expansion.

SolidWorks allows features to be selected by bare name or by the <name>@<document> qualified syntax. Several selection APIs also exist with different levels of support across SolidWorks versions. This service centralises all of that complexity so the main adapter methods stay thin.

Attributes:

Name Type Description
_adapter

Back-reference to the owning PyWin32Adapter instance.

Store a back-reference to the owning adapter.

Parameters:

Name Type Description Default
adapter PyWin32Adapter

The PyWin32Adapter instance that created this service.

required
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def __init__(self, adapter: "PyWin32Adapter") -> None:
    """Store a back-reference to the owning adapter.

    Args:
        adapter: The ``PyWin32Adapter`` instance that created this
            service.
    """
    self._adapter = adapter
Methods:
build_feature_candidate_names
build_feature_candidate_names(feature_name: str, target_doc: Any) -> list[str]

Build a list of bare and document-qualified selection candidates.

SolidWorks requires the "<name>@<document>" syntax for SelectByID2 on assembly contexts. This method tries to read the document title and produces both bare and qualified variants so that callers do not need to duplicate that logic.

Parameters:

Name Type Description Default
feature_name str

The bare feature name as supplied by the caller (e.g. "Boss-Extrude1").

required
target_doc Any

SolidWorks IModelDoc2 COM object whose title is used to form the qualified name. Errors reading the title are silently swallowed.

required

Returns:

Type Description
list[str]

list[str]: Candidate list, always including feature_name as

list[str]

the first element. Qualified variants are appended when a non-

list[str]

empty title is available. Example::

["Boss-Extrude1", "Boss-Extrude1@Part1", "Boss-Extrude1@Part1.SLDPRT"]

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def build_feature_candidate_names(
    self, feature_name: str, target_doc: Any
) -> list[str]:
    """Build a list of bare and document-qualified selection candidates.

    SolidWorks requires the ``"<name>@<document>"`` syntax for
    ``SelectByID2`` on assembly contexts.  This method tries to read the
    document title and produces both bare and qualified variants so that
    callers do not need to duplicate that logic.

    Args:
        feature_name: The bare feature name as supplied by the caller
            (e.g. ``"Boss-Extrude1"``).
        target_doc: SolidWorks ``IModelDoc2`` COM object whose title is
            used to form the qualified name.  Errors reading the title
            are silently swallowed.

    Returns:
        list[str]: Candidate list, always including ``feature_name`` as
        the first element.  Qualified variants are appended when a non-
        empty title is available.  Example::

            ["Boss-Extrude1", "Boss-Extrude1@Part1", "Boss-Extrude1@Part1.SLDPRT"]
    """
    doc_title = ""
    doc_stem = ""
    try:
        raw_title = str(target_doc.GetTitle() or "").strip()
        if raw_title:
            doc_title = raw_title
            doc_stem = raw_title.rsplit(".", 1)[0]
    except Exception:
        pass

    candidates: list[str] = [feature_name]
    if doc_stem:
        candidates.append(f"{feature_name}@{doc_stem}")
    if doc_title and doc_title != doc_stem:
        candidates.append(f"{feature_name}@{doc_title}")
    return candidates
list_features
list_features(include_suppressed: bool = False, max_assembly_depth: int = 2) -> list[dict[str, Any]]

Enumerate model features, including assembly component trees.

For a Part document, returns that document's own feature list only (unchanged from prior behavior). For an Assembly document, also flattens in every resolved top-level component's features — and, recursively, sub-assembly components' features up to max_assembly_depth — tagged with component/component_path so callers can tell which document each descriptor came from.

Parameters:

Name Type Description Default
include_suppressed bool

Include suppressed features when True.

False
max_assembly_depth int

Sub-assembly recursion budget. Ignored for Part documents.

2

Returns:

Type Description
list[dict[str, Any]]

list[dict[str, Any]]: Ordered, flattened feature descriptors.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def list_features(
    self, include_suppressed: bool = False, max_assembly_depth: int = 2
) -> list[dict[str, Any]]:
    """Enumerate model features, including assembly component trees.

    For a Part document, returns that document's own feature list only
    (unchanged from prior behavior). For an Assembly document, also
    flattens in every resolved top-level component's features — and,
    recursively, sub-assembly components' features up to
    ``max_assembly_depth`` — tagged with ``component``/``component_path``
    so callers can tell which document each descriptor came from.

    Args:
        include_suppressed: Include suppressed features when ``True``.
        max_assembly_depth: Sub-assembly recursion budget. Ignored for
            Part documents.

    Returns:
        list[dict[str, Any]]: Ordered, flattened feature descriptors.
    """
    document = self._adapter.currentModel
    features = self._list_document_features(document, include_suppressed)

    # GetType resolves as a property (not a method) on some COM dispatch
    # instances - notably a freshly-fetched swApp.ActiveDoc, as opposed
    # to a document just returned by OpenDoc6/NewDocument. Calling it
    # with parens unconditionally raises "'int' object is not callable"
    # on those, silently swallowed by _attempt, which would otherwise
    # make every Assembly document look like doc_type 0. Route through
    # _get_attr_or_call so both forms work regardless of flagging state.
    doc_type = self._adapter._attempt(
        lambda: int(self._adapter._get_attr_or_call(document, "GetType") or 0),
        default=0,
    )
    if doc_type == self._adapter.constants.get("swDocASSEMBLY", 2):
        sw_type_info.flag_doc(document, doc_type)
        features.extend(
            self._list_assembly_component_features(
                document, include_suppressed, max_assembly_depth - 1, None
            )
        )

    return features
normalize_feature_name staticmethod
normalize_feature_name(raw_name: str | None) -> str

Normalise a feature name for case-insensitive comparison.

Strips surrounding whitespace and quotes then converts to casefold-lowercase so that mixed-case names such as 'Boss-Extrude1', " boss-extrude1 " and '"Boss-Extrude1"' all compare equal.

Parameters:

Name Type Description Default
raw_name str | None

Raw feature name string, or None.

required

Returns:

Name Type Description
str str

Normalised name. Empty string when raw_name is

str

None or blank.

Example::

svc.normalize_feature_name('"Boss-Extrude1"') == 'boss-extrude1'
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
@staticmethod
def normalize_feature_name(raw_name: str | None) -> str:
    """Normalise a feature name for case-insensitive comparison.

    Strips surrounding whitespace and quotes then converts to
    ``casefold``-lowercase so that mixed-case names such as
    ``'Boss-Extrude1'``, ``" boss-extrude1 "`` and ``'"Boss-Extrude1"'``
    all compare equal.

    Args:
        raw_name: Raw feature name string, or ``None``.

    Returns:
        str: Normalised name.  Empty string when ``raw_name`` is
        ``None`` or blank.

    Example::

        svc.normalize_feature_name('"Boss-Extrude1"') == 'boss-extrude1'
    """
    return str(raw_name or "").strip().strip('"').casefold()
select_feature
select_feature(feature_name: str, append: bool = False) -> dict[str, Any]

Run the feature-selection strategies in order; first success wins.

Order: FeatureByName (cheapest, proven live), SelectByID2 over candidate names x entity types (planes, mates), component selection (assemblies), then a feature-tree walk.

Parameters:

Name Type Description Default
feature_name str

Feature name requested by caller.

required
append bool

Add to the current selection. Defaults to False.

False

Returns:

Type Description
dict[str, Any]

dict[str, Any]: Selection payload; selected is False only when

dict[str, Any]

every strategy failed (the adapter maps that to an ERROR).

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def select_feature(self, feature_name: str, append: bool = False) -> dict[str, Any]:
    """Run the feature-selection strategies in order; first success wins.

    Order: ``FeatureByName`` (cheapest, proven live), ``SelectByID2``
    over candidate names x entity types (planes, mates), component
    selection (assemblies), then a feature-tree walk.

    Args:
        feature_name: Feature name requested by caller.
        append: Add to the current selection. Defaults to False.

    Returns:
        dict[str, Any]: Selection payload; ``selected`` is False only when
        every strategy failed (the adapter maps that to an ERROR).
    """
    target_doc = self._adapter.currentModel

    result = self.try_select_by_feature_name(target_doc, feature_name, append)
    if result:
        return result

    candidate_names = self.build_feature_candidate_names(feature_name, target_doc)

    result = self.try_select_by_extension(
        target_doc, candidate_names, feature_name, append
    )
    if result:
        return result

    result = self.try_select_by_component(
        target_doc, candidate_names, feature_name, append
    )
    if result:
        return result

    result = self.try_select_by_feature_tree(
        target_doc, feature_name, candidate_names, append
    )
    if result:
        return result

    return {
        "selected": False,
        "feature_name": feature_name,
        "selected_name": feature_name,
    }
try_select_by_component
try_select_by_component(target_doc: Any, candidate_names: list[str], feature_name: str, append: bool = False) -> dict[str, Any] | None

Attempt component selection for assembly-context features.

Calls target_doc.GetComponentByName (when available) and then tries three selector methods on the resulting component object: Select4(False, None, False), Select(False,), and Select2(False, 0).

This path handles assembly components that are not reachable through SelectByID2 alone.

Parameters:

Name Type Description Default
target_doc Any

SolidWorks IAssemblyDoc or IModelDoc2 COM object.

required
candidate_names list[str]

List of candidate name strings. Only the part before the first @ is used as the component name.

required
feature_name str

Original caller-supplied name for the result payload.

required
append bool

Add to the current selection. Defaults to False.

False

Returns:

Type Description
dict[str, Any] | None

dict[str, Any] | None: Success dict (same schema as

dict[str, Any] | None

meth:try_select_by_extension) or None when not applicable

dict[str, Any] | None

or all attempts fail.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def try_select_by_component(
    self,
    target_doc: Any,
    candidate_names: list[str],
    feature_name: str,
    append: bool = False,
) -> dict[str, Any] | None:
    """Attempt component selection for assembly-context features.

    Calls ``target_doc.GetComponentByName`` (when available) and then
    tries three selector methods on the resulting component object:
    ``Select4(False, None, False)``, ``Select(False,)``, and
    ``Select2(False, 0)``.

    This path handles assembly components that are not reachable through
    ``SelectByID2`` alone.

    Args:
        target_doc: SolidWorks ``IAssemblyDoc`` or ``IModelDoc2`` COM
            object.
        candidate_names: List of candidate name strings.  Only the part
            before the first ``@`` is used as the component name.
        feature_name: Original caller-supplied name for the result
            payload.
        append: Add to the current selection. Defaults to False.

    Returns:
        dict[str, Any] | None: Success dict (same schema as
        :meth:`try_select_by_extension`) or ``None`` when not applicable
        or all attempts fail.
    """
    get_component_by_name = getattr(target_doc, "GetComponentByName", None)
    if not callable(get_component_by_name):
        return None

    for candidate in candidate_names:
        component_name = candidate.split("@", 1)[0]
        component = self._adapter._attempt(
            lambda name=component_name: get_component_by_name(name),  # type: ignore[misc]
            default=None,
        )
        if component is None:
            continue
        for method_name, args in [
            ("Select4", (append, None, False)),
            ("Select", (append,)),
            ("Select2", (append, 0)),
        ]:
            selector = getattr(component, method_name, None)
            if not callable(selector):
                continue
            try:
                if bool(selector(*args)):
                    return {
                        "selected": True,
                        "feature_name": feature_name,
                        "selected_name": component_name,
                        "entity_type": f"component:{method_name}",
                    }
            except Exception:
                continue
    return None
try_select_by_extension
try_select_by_extension(target_doc: Any, candidate_names: list[str], feature_name: str, append: bool = False) -> dict[str, Any] | None

Attempt feature selection via Extension.SelectByID2.

Iterates the Cartesian product of candidate_names × entity-type strings. Entity types tried (in order): "BODYFEATURE", "COMPONENT", "SKETCH", "PLANE", "MATE", "" (auto). Returns on the first successful selection.

Parameters:

Name Type Description Default
target_doc Any

SolidWorks IModelDoc2 COM object.

required
candidate_names list[str]

Ordered list of name strings to try (bare and qualified variants from :meth:build_feature_candidate_names).

required
feature_name str

The original caller-supplied name, included in the result payload for traceability.

required
append bool

Add to the current selection. Defaults to False.

False

Returns:

Type Description
dict[str, Any] | None

dict[str, Any] | None: On success, a result dict with keys

dict[str, Any] | None

selected, feature_name, selected_name,

dict[str, Any] | None

entity_type. None when all attempts fail.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def try_select_by_extension(
    self,
    target_doc: Any,
    candidate_names: list[str],
    feature_name: str,
    append: bool = False,
) -> dict[str, Any] | None:
    """Attempt feature selection via ``Extension.SelectByID2``.

    Iterates the Cartesian product of ``candidate_names`` × entity-type
    strings.  Entity types tried (in order): ``"BODYFEATURE"``,
    ``"COMPONENT"``, ``"SKETCH"``, ``"PLANE"``, ``"MATE"``, ``""`` (auto).
    Returns on the first successful selection.

    Args:
        target_doc: SolidWorks ``IModelDoc2`` COM object.
        candidate_names: Ordered list of name strings to try (bare and
            qualified variants from :meth:`build_feature_candidate_names`).
        feature_name: The original caller-supplied name, included in the
            result payload for traceability.
        append: Add to the current selection. Defaults to False.

    Returns:
        dict[str, Any] | None: On success, a result dict with keys
        ``selected``, ``feature_name``, ``selected_name``,
        ``entity_type``.  ``None`` when all attempts fail.
    """
    entity_types = ["BODYFEATURE", "COMPONENT", "SKETCH", "PLANE", "MATE", ""]
    for candidate in candidate_names:
        for entity_type in entity_types:
            try:
                # Callout must be a VT_DISPATCH null; Python None is a
                # Type mismatch (runbook #11, confirmed live 2026-10-13).
                selected = target_doc.Extension.SelectByID2(
                    candidate,
                    entity_type,
                    0,
                    0,
                    0,
                    append,
                    0,
                    _null_callout(),
                    0,
                )
                if selected:
                    return {
                        "selected": True,
                        "feature_name": feature_name,
                        "selected_name": candidate,
                        "entity_type": entity_type or "auto",
                    }
            except Exception:
                continue
    return None
try_select_by_feature_name
try_select_by_feature_name(target_doc: Any, feature_name: str, append: bool = False) -> dict[str, Any] | None

Select via FeatureByName + IFeature::Select2 (first strategy).

Delegates to the shared features._select_feature_by_name helper - the path suppress/rename already rely on, and the one confirmed live on R2026x for solid features, sketches and reference planes. It does not resolve assembly components, which fall through to :meth:try_select_by_component.

Parameters:

Name Type Description Default
target_doc Any

SolidWorks IModelDoc2 COM object (the adapter's currentModel; the helper reads it from the adapter).

required
feature_name str

Feature name; any @document qualifier is stripped by the helper.

required
append bool

Add to the current selection. Defaults to False.

False

Returns:

Type Description
dict[str, Any] | None

dict[str, Any] | None: Success payload (same schema as

dict[str, Any] | None

meth:try_select_by_extension) with entity_type set to

dict[str, Any] | None

"feature-name", or None when it could not be selected.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def try_select_by_feature_name(
    self,
    target_doc: Any,
    feature_name: str,
    append: bool = False,
) -> dict[str, Any] | None:
    """Select via ``FeatureByName`` + ``IFeature::Select2`` (first strategy).

    Delegates to the shared ``features._select_feature_by_name`` helper -
    the path suppress/rename already rely on, and the one confirmed live
    on R2026x for solid features, sketches and reference planes. It does
    not resolve assembly components, which fall through to
    :meth:`try_select_by_component`.

    Args:
        target_doc: SolidWorks ``IModelDoc2`` COM object (the adapter's
            ``currentModel``; the helper reads it from the adapter).
        feature_name: Feature name; any ``@document`` qualifier is
            stripped by the helper.
        append: Add to the current selection. Defaults to False.

    Returns:
        dict[str, Any] | None: Success payload (same schema as
        :meth:`try_select_by_extension`) with ``entity_type`` set to
        ``"feature-name"``, or ``None`` when it could not be selected.
    """
    if "@" in feature_name:
        # FeatureByName only sees this document's own tree. A name
        # qualified with anything but this document (e.g. a component
        # path "Front Plane@Part1-1@Assem1") must reach the qualified
        # SelectByID2 strategies, or the assembly's own "Front Plane"
        # would be selected instead.
        bare = feature_name.split("@", 1)[0]
        own_names = self.build_feature_candidate_names(bare, target_doc)
        if feature_name not in own_names:
            return None
    feature = _select_feature_by_name(self._adapter, feature_name, append)
    if feature is None:
        return None
    selected_name = self._adapter._attempt(
        lambda: str(feature.Name or ""), default=""
    )
    return {
        "selected": True,
        "feature_name": feature_name,
        "selected_name": selected_name or feature_name.split("@", 1)[0],
        "entity_type": "feature-name",
    }
try_select_by_feature_tree
try_select_by_feature_tree(target_doc: Any, feature_name: str, candidate_names: list[str], append: bool = False) -> dict[str, Any] | None

Walk the SolidWorks feature tree and select the first matching feature.

Iterates via FirstFeature / GetNextFeature (up to 10 000 features as a runaway guard) and compares each feature's Name attribute against the normalised candidate set. Matching is case-insensitive and also strips the @document qualifier.

This is the most expensive selection strategy and is only attempted after :meth:try_select_by_extension and :meth:try_select_by_component have both failed.

Parameters:

Name Type Description Default
target_doc Any

SolidWorks IModelDoc2 COM object.

required
feature_name str

Original caller-supplied name for the result payload.

required
candidate_names list[str]

Candidate names used to build the normalised match set.

required
append bool

Add to the current selection. Defaults to False.

False

Returns:

Type Description
dict[str, Any] | None

dict[str, Any] | None: Success dict (same schema as

dict[str, Any] | None

meth:try_select_by_extension) with entity_type set to

dict[str, Any] | None

"feature-tree", or None when no match is found.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def try_select_by_feature_tree(
    self,
    target_doc: Any,
    feature_name: str,
    candidate_names: list[str],
    append: bool = False,
) -> dict[str, Any] | None:
    """Walk the SolidWorks feature tree and select the first matching feature.

    Iterates via ``FirstFeature`` / ``GetNextFeature`` (up to 10 000
    features as a runaway guard) and compares each feature\'s ``Name``
    attribute against the normalised candidate set.  Matching is
    case-insensitive and also strips the ``@document`` qualifier.

    This is the most expensive selection strategy and is only attempted
    after :meth:`try_select_by_extension` and
    :meth:`try_select_by_component` have both failed.

    Args:
        target_doc: SolidWorks ``IModelDoc2`` COM object.
        feature_name: Original caller-supplied name for the result
            payload.
        candidate_names: Candidate names used to build the normalised
            match set.
        append: Add to the current selection. Defaults to False.

    Returns:
        dict[str, Any] | None: Success dict (same schema as
        :meth:`try_select_by_extension`) with ``entity_type`` set to
        ``"feature-tree"``, or ``None`` when no match is found.
    """
    normalized_candidates = {
        self.normalize_feature_name(c)
        for c in candidate_names
        if self.normalize_feature_name(c)
    }
    normalized_bases = {c.split("@", 1)[0] for c in normalized_candidates if c}

    feature = self._adapter._attempt(lambda: target_doc.FirstFeature())
    guard = 0
    while feature and guard < 10000:
        guard += 1
        feature_ref = feature
        # Unflagged, GetNextFeature/Select2 resolve as properties and the
        # walk dies with "Member not found" (confirmed live 2026-10-13).
        self._adapter._attempt(
            lambda f=feature_ref: sw_type_info.flag_members(  # type: ignore[misc]
                f, "GetNextFeature", "Select2"
            ),
            default=0,
        )
        tree_name = self._adapter._attempt(
            lambda current_feature=feature_ref: str(current_feature.Name or ""),  # type: ignore[misc]
            default="",
        )
        if self._matches_candidate_name(
            tree_name, normalized_candidates, normalized_bases
        ):
            try:
                if feature.Select2(append, 0):
                    return {
                        "selected": True,
                        "feature_name": feature_name,
                        "selected_name": tree_name or feature_name,
                        "entity_type": "feature-tree",
                    }
            except Exception:
                pass
        next_feature = self._adapter._attempt(
            lambda current_feature=feature_ref: current_feature.GetNextFeature()  # type: ignore[misc]
        )
        if next_feature is None:
            break
        feature = next_feature
    return None

_SketchGeometryService

_SketchGeometryService(adapter: PyWin32Adapter)

Manage transient sketch-entity storage and provide geometry helpers.

Maintains a per-sketch registry that maps stable string IDs (e.g. "Line_1", "Circle_3") to live SolidWorks COM entity objects. The registry is reset each time a new sketch is opened or closed.

Also provides geometry primitives used by the sketch-dimension pipeline: coordinate extraction, segment endpoint reading, shared-vertex detection, and smart-dimension placement-point calculation.

Attributes:

Name Type Description
_adapter

Back-reference to the owning PyWin32Adapter instance.

Store a back-reference to the owning adapter.

Parameters:

Name Type Description Default
adapter PyWin32Adapter

The PyWin32Adapter instance that created this service. Provides access to _sketch_entities and _sketch_entity_counter.

required
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def __init__(self, adapter: "PyWin32Adapter") -> None:
    """Store a back-reference to the owning adapter.

    Args:
        adapter: The ``PyWin32Adapter`` instance that created this
            service.  Provides access to ``_sketch_entities`` and
            ``_sketch_entity_counter``.
    """
    self._adapter = adapter
Methods:
angular_dimension_placement
angular_dimension_placement(entity1: Any, entity2: Any) -> tuple[float, float, float, int] | None

Compute the text-placement point for an angular dimension between two lines.

Detects the shared vertex, derives the two ray directions, bisects the angle, offsets the placement point by 15–30 mm along the bisector (clamped to 60 % of the shorter leg), and converts the bisector to a dimension-direction constant.

Parameters:

Name Type Description Default
entity1 Any

First SolidWorks sketch-line COM object.

required
entity2 Any

Second SolidWorks sketch-line COM object (must share a vertex with entity1).

required

Returns:

Type Description
tuple[float, float, float, int] | None

tuple[float, float, float, int] | None:

tuple[float, float, float, int] | None

(text_x, text_y, text_z, direction) in metres, or None

tuple[float, float, float, int] | None

when no shared vertex is found or segment data is unavailable.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def angular_dimension_placement(
    self, entity1: Any, entity2: Any
) -> tuple[float, float, float, int] | None:
    """Compute the text-placement point for an angular dimension between two lines.

    Detects the shared vertex, derives the two ray directions, bisects
    the angle, offsets the placement point by 15–30 mm along the bisector
    (clamped to 60 % of the shorter leg), and converts the bisector to a
    dimension-direction constant.

    Args:
        entity1: First SolidWorks sketch-line COM object.
        entity2: Second SolidWorks sketch-line COM object (must share a
            vertex with ``entity1``).

    Returns:
        tuple[float, float, float, int] | None:
        ``(text_x, text_y, text_z, direction)`` in metres, or ``None``
        when no shared vertex is found or segment data is unavailable.
    """
    endpoints1 = self.read_segment_endpoints(entity1)
    endpoints2 = self.read_segment_endpoints(entity2)
    if endpoints1 is None or endpoints2 is None:
        return None

    pts1 = endpoints1
    pts2 = endpoints2
    tol = 1e-6
    vertex = None
    ray1 = None
    ray2 = None
    for p1 in pts1:
        for p2 in pts2:
            if (
                abs(p1[0] - p2[0]) <= tol
                and abs(p1[1] - p2[1]) <= tol
                and abs(p1[2] - p2[2]) <= tol
            ):
                vertex = p1
                ray1 = pts1[1] if pts1[0] == p1 else pts1[0]
                ray2 = pts2[1] if pts2[0] == p2 else pts2[0]
                break
        if vertex is not None:
            break

    if vertex is None or ray1 is None or ray2 is None:
        return None

    v1x = ray1[0] - vertex[0]
    v1y = ray1[1] - vertex[1]
    v2x = ray2[0] - vertex[0]
    v2y = ray2[1] - vertex[1]
    l1 = (v1x * v1x + v1y * v1y) ** 0.5
    l2 = (v2x * v2x + v2y * v2y) ** 0.5
    if l1 <= 1e-9 or l2 <= 1e-9:
        return None

    b1x = v1x / l1
    b1y = v1y / l1
    b2x = v2x / l2
    b2y = v2y / l2
    bis_x = b1x + b2x
    bis_y = b1y + b2y
    if abs(bis_x) <= 1e-9 and abs(bis_y) <= 1e-9:
        bis_x = -b1y
        bis_y = b1x

    bis_len = (bis_x * bis_x + bis_y * bis_y) ** 0.5
    bis_x /= bis_len
    bis_y /= bis_len
    offset = max(0.015, min(0.03, min(l1, l2) * 0.6))
    text_x = vertex[0] + bis_x * offset
    text_y = vertex[1] + bis_y * offset
    direction = self.smart_dimension_direction(bis_x, bis_y)
    return (text_x, text_y, vertex[2], direction)
point_xyz
point_xyz(point_obj: Any) -> tuple[float, float, float] | None

Extract the XYZ coordinates from a SolidWorks point-like COM object.

Tries two access patterns:

  1. Direct attribute access — point_obj.X, point_obj.Y, point_obj.Z (IMathPoint, IVertex).
  2. Method call — point_obj.GetCoords() returning a sequence of at least three numbers.

All values are in metres (SolidWorks internal units).

Parameters:

Name Type Description Default
point_obj Any

Any COM object that might expose XYZ coordinates. None is accepted and returns None immediately.

required

Returns:

Type Description
tuple[float, float, float] | None

tuple[float, float, float] | None: (x, y, z) in metres, or

tuple[float, float, float] | None

None when no coordinate pattern matches.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def point_xyz(self, point_obj: Any) -> tuple[float, float, float] | None:
    """Extract the XYZ coordinates from a SolidWorks point-like COM object.

    Tries two access patterns:

    1. Direct attribute access — ``point_obj.X``, ``point_obj.Y``,
       ``point_obj.Z`` (``IMathPoint``, ``IVertex``).
    2. Method call — ``point_obj.GetCoords()`` returning a sequence of
       at least three numbers.

    All values are in **metres** (SolidWorks internal units).

    Args:
        point_obj: Any COM object that might expose XYZ coordinates.
            ``None`` is accepted and returns ``None`` immediately.

    Returns:
        tuple[float, float, float] | None: ``(x, y, z)`` in metres, or
        ``None`` when no coordinate pattern matches.
    """
    if point_obj is None:
        return None

    if (
        hasattr(point_obj, "X")
        and hasattr(point_obj, "Y")
        and hasattr(point_obj, "Z")
    ):
        x = self._adapter._attempt(lambda: float(point_obj.X), default=None)
        y = self._adapter._attempt(lambda: float(point_obj.Y), default=None)
        z = self._adapter._attempt(lambda: float(point_obj.Z), default=None)
        if x is not None and y is not None and z is not None:
            return (x, y, z)

    coords = self._adapter._attempt(lambda: point_obj.GetCoords(), default=None)
    if isinstance(coords, (list, tuple)) and len(coords) >= 3:
        return (float(coords[0]), float(coords[1]), float(coords[2]))

    return None
read_segment_endpoints
read_segment_endpoints(entity: Any) -> tuple[tuple[float, float, float], tuple[float, float, float]] | None

Read the start and end coordinates of a sketch line or arc segment.

Accesses entity.GetStartPoint and entity.GetEndPoint (as properties, not method calls). Both must return sequences of at least three numeric values.

Parameters:

Name Type Description Default
entity Any

SolidWorks ISketchLine or ISketchArc COM object.

required

Returns:

Type Description
tuple[tuple[float, float, float], tuple[float, float, float]] | None

tuple[tuple, tuple] | None: ((x1, y1, z1), (x2, y2, z2)) in

tuple[tuple[float, float, float], tuple[float, float, float]] | None

metres, or None when the attributes are absent or empty.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def read_segment_endpoints(
    self, entity: Any
) -> tuple[tuple[float, float, float], tuple[float, float, float]] | None:
    """Read the start and end coordinates of a sketch line or arc segment.

    Accesses ``entity.GetStartPoint`` and ``entity.GetEndPoint`` (as
    properties, not method calls).  Both must return sequences of at least
    three numeric values.

    Args:
        entity: SolidWorks ``ISketchLine`` or ``ISketchArc`` COM object.

    Returns:
        tuple[tuple, tuple] | None: ``((x1, y1, z1), (x2, y2, z2))`` in
        metres, or ``None`` when the attributes are absent or empty.
    """
    start = self._adapter._attempt(lambda: entity.GetStartPoint, default=None)
    end = self._adapter._attempt(lambda: entity.GetEndPoint, default=None)
    if (
        isinstance(start, tuple)
        and len(start) >= 3
        and isinstance(end, tuple)
        and len(end) >= 3
    ):
        return (
            (float(start[0]), float(start[1]), float(start[2])),
            (float(end[0]), float(end[1]), float(end[2])),
        )
    return None
register_entity
register_entity(prefix: str, entity: Any) -> str

Store a COM entity handle and return a stable string identifier.

Increments the counter, derives an ID of the form "<prefix>_<counter>" (e.g. "Line_3"), stores the entity in adapter._sketch_entities, and returns the ID so callers can reference the entity in later dimension or constraint calls.

Parameters:

Name Type Description Default
prefix str

Descriptor for the entity type, e.g. "Line", "Circle", "Arc", "Rectangle".

required
entity Any

Live SolidWorks COM entity object returned by the SketchManager create call.

required

Returns:

Name Type Description
str str

Stable entity ID, e.g. "Circle_2".

Example::

line_id = service.register_entity("Line", sw_line_obj)
# line_id == "Line_1"
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def register_entity(self, prefix: str, entity: Any) -> str:
    """Store a COM entity handle and return a stable string identifier.

    Increments the counter, derives an ID of the form
    ``"<prefix>_<counter>"`` (e.g. ``"Line_3"``), stores the entity in
    ``adapter._sketch_entities``, and returns the ID so callers can
    reference the entity in later dimension or constraint calls.

    Args:
        prefix: Descriptor for the entity type, e.g. ``"Line"``,
            ``"Circle"``, ``"Arc"``, ``"Rectangle"``.
        entity: Live SolidWorks COM entity object returned by the
            ``SketchManager`` create call.

    Returns:
        str: Stable entity ID, e.g. ``"Circle_2"``.

    Example::

        line_id = service.register_entity("Line", sw_line_obj)
        # line_id == "Line_1"
    """
    self._adapter._sketch_entity_counter += 1
    entity_id = f"{prefix}_{self._adapter._sketch_entity_counter}"
    self._adapter._sketch_entities[entity_id] = entity
    return entity_id
reset_registry
reset_registry() -> None

Clear the sketch entity registry and reset the ID counter.

Should be called at the start of every new sketch (via adapter._reset_sketch_entity_registry) so that entity IDs from a previous sketch do not bleed into the current session.

Side Effects

Clears adapter._sketch_entities and sets adapter._sketch_entity_counter to 0.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def reset_registry(self) -> None:
    """Clear the sketch entity registry and reset the ID counter.

    Should be called at the start of every new sketch (via
    ``adapter._reset_sketch_entity_registry``) so that entity IDs from a
    previous sketch do not bleed into the current session.

    Side Effects:
        Clears ``adapter._sketch_entities`` and sets
        ``adapter._sketch_entity_counter`` to ``0``.
    """
    self._adapter._sketch_entities.clear()
    self._adapter._sketch_entity_centers.clear()
    self._adapter._sketch_entity_counter = 0
segment_point_objects
segment_point_objects(entity: Any) -> tuple[Any | None, Any | None]

Return the COM point objects at the start and end of a segment.

Unlike :meth:read_segment_endpoints, this returns the raw COM objects (ISketchPoint) rather than coordinate tuples, so they can be used in selection calls for angular dimensioning.

Parameters:

Name Type Description Default
entity Any

SolidWorks sketch segment COM object.

required

Returns:

Type Description
Any | None

tuple[Any | None, Any | None]: (start_point_obj, end_point_obj);

Any | None

either element may be None if the attribute is absent.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def segment_point_objects(self, entity: Any) -> tuple[Any | None, Any | None]:
    """Return the COM point objects at the start and end of a segment.

    Unlike :meth:`read_segment_endpoints`, this returns the raw COM
    objects (``ISketchPoint``) rather than coordinate tuples, so they can
    be used in selection calls for angular dimensioning.

    Args:
        entity: SolidWorks sketch segment COM object.

    Returns:
        tuple[Any | None, Any | None]: ``(start_point_obj, end_point_obj)``;
        either element may be ``None`` if the attribute is absent.
    """
    start = self._adapter._attempt(lambda: entity.GetStartPoint2, default=None)
    end = self._adapter._attempt(lambda: entity.GetEndPoint2, default=None)
    return (start, end)
select_entity
select_entity(entity: Any, append: bool) -> bool

Select a COM entity in the SolidWorks selection manager.

Tries three COM selection methods in decreasing API version order: Select4 (modern), Select2 (legacy), Select (oldest). The first call that returns a truthy value terminates the search.

Parameters:

Name Type Description Default
entity Any

Live SolidWorks COM sketch entity object (line, arc, circle, etc.).

required
append bool

When True, the entity is added to the existing selection set. When False, existing selections are cleared first.

required

Returns:

Name Type Description
bool bool

True if any of the three select methods succeeded;

bool

False if all failed or raised.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def select_entity(self, entity: Any, append: bool) -> bool:
    """Select a COM entity in the SolidWorks selection manager.

    Tries three COM selection methods in decreasing API version order:
    ``Select4`` (modern), ``Select2`` (legacy), ``Select`` (oldest).  The
    first call that returns a truthy value terminates the search.

    Args:
        entity: Live SolidWorks COM sketch entity object (line, arc,
            circle, etc.).
        append: When ``True``, the entity is added to the existing
            selection set.  When ``False``, existing selections are
            cleared first.

    Returns:
        bool: ``True`` if any of the three select methods succeeded;
        ``False`` if all failed or raised.
    """
    selected = self._adapter._attempt(
        lambda: bool(entity.Select4(append, None)),
        default=False,
    )
    if selected:
        return True

    selected = self._adapter._attempt(
        lambda: bool(entity.Select2(append, 0)),
        default=False,
    )
    if selected:
        return True

    return bool(
        self._adapter._attempt(
            lambda: bool(entity.Select(append)),
            default=False,
        )
    )
set_display_dimension_value
set_display_dimension_value(display_dim: Any, value_mm: float) -> None

Set the numeric value of a display-dimension object.

Converts the value from millimetres to metres and tries three COM paths in order:

  1. GetDimension2(0).SetSystemValue3(value_m, 1, None) — modern.
  2. GetDimension().SetSystemValue2(value_m, 1) — legacy.
  3. Direct SystemValue property assignment — oldest.

Parameters:

Name Type Description Default
display_dim Any

SolidWorks display-dimension COM object returned by Extension.AddDimension.

required
value_mm float

Desired dimension value in millimetres. Converted to metres internally.

required
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def set_display_dimension_value(self, display_dim: Any, value_mm: float) -> None:
    """Set the numeric value of a display-dimension object.

    Converts the value from millimetres to metres and tries three COM
    paths in order:

    1. ``GetDimension2(0).SetSystemValue3(value_m, 1, None)`` — modern.
    2. ``GetDimension().SetSystemValue2(value_m, 1)`` — legacy.
    3. Direct ``SystemValue`` property assignment — oldest.

    Args:
        display_dim: SolidWorks display-dimension COM object returned by
            ``Extension.AddDimension``.
        value_mm: Desired dimension value in **millimetres**.  Converted
            to metres internally.
    """
    value_m = value_mm / 1000.0
    dimension_obj = self._adapter._attempt(
        lambda: display_dim.GetDimension2(0), default=None
    )
    if dimension_obj is None:
        dimension_obj = self._adapter._attempt(
            lambda: display_dim.GetDimension(), default=None
        )
    if dimension_obj is None:
        dimension_obj = display_dim

    if (
        self._adapter._attempt(
            lambda: dimension_obj.SetSystemValue3(value_m, 1, None), default=None
        )
        is not None
    ):
        return
    if (
        self._adapter._attempt(
            lambda: dimension_obj.SetSystemValue2(value_m, 1), default=None
        )
        is not None
    ):
        return

    if hasattr(dimension_obj, "SystemValue"):
        dimension_obj.SystemValue = value_m
set_point_xyz
set_point_xyz(point_obj: Any, x: float, y: float, z: float) -> bool

Set coordinates on a SolidWorks point-like COM object.

Tries SetCoords(x, y, z) first (modern API), then SetCoords2(x, y, z) (older variant). All values should be in metres.

Parameters:

Name Type Description Default
point_obj Any

COM point object to update, or None.

required
x float

New X coordinate in metres.

required
y float

New Y coordinate in metres.

required
z float

New Z coordinate in metres.

required

Returns:

Name Type Description
bool bool

True when a setter call succeeded; False otherwise

bool

(including when point_obj is None).

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def set_point_xyz(self, point_obj: Any, x: float, y: float, z: float) -> bool:
    """Set coordinates on a SolidWorks point-like COM object.

    Tries ``SetCoords(x, y, z)`` first (modern API), then
    ``SetCoords2(x, y, z)`` (older variant).  All values should be in
    **metres**.

    Args:
        point_obj: COM point object to update, or ``None``.
        x: New X coordinate in metres.
        y: New Y coordinate in metres.
        z: New Z coordinate in metres.

    Returns:
        bool: ``True`` when a setter call succeeded; ``False`` otherwise
        (including when ``point_obj`` is ``None``).
    """
    if point_obj is None:
        return False
    if (
        self._adapter._attempt(lambda: point_obj.SetCoords(x, y, z), default=None)
        is not None
    ):
        return True
    if (
        self._adapter._attempt(lambda: point_obj.SetCoords2(x, y, z), default=None)
        is not None
    ):
        return True
    return False
shared_segment_vertex
shared_segment_vertex(entity1: Any, entity2: Any) -> tuple[Any, Any, Any] | None

Find a vertex point shared by two sketch segments.

Compares all four endpoint-object pairs (2 from each segment) using a coordinate tolerance of 1e-6 m. Used by the angular-dimension pipeline to locate the pivot vertex between two lines.

Parameters:

Name Type Description Default
entity1 Any

First SolidWorks sketch segment COM object.

required
entity2 Any

Second SolidWorks sketch segment COM object.

required

Returns:

Type Description
tuple[Any, Any, Any] | None

tuple[Any, Any, Any] | None: ``(shared_point_obj, point1_obj,

tuple[Any, Any, Any] | None

point2_obj)whereshared_point_obj`` is the common vertex

tuple[Any, Any, Any] | None

(same object as point1_obj in current implementation), or

tuple[Any, Any, Any] | None

None when no shared vertex is found.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def shared_segment_vertex(
    self, entity1: Any, entity2: Any
) -> tuple[Any, Any, Any] | None:
    """Find a vertex point shared by two sketch segments.

    Compares all four endpoint-object pairs (2 from each segment) using a
    coordinate tolerance of 1e-6 m.  Used by the angular-dimension
    pipeline to locate the pivot vertex between two lines.

    Args:
        entity1: First SolidWorks sketch segment COM object.
        entity2: Second SolidWorks sketch segment COM object.

    Returns:
        tuple[Any, Any, Any] | None: ``(shared_point_obj, point1_obj,
        point2_obj)`` where ``shared_point_obj`` is the common vertex
        (same object as ``point1_obj`` in current implementation), or
        ``None`` when no shared vertex is found.
    """
    points1 = self.segment_point_objects(entity1)
    points2 = self.segment_point_objects(entity2)
    tol = 1e-6
    for point1 in points1:
        xyz1 = self.point_xyz(point1)
        if xyz1 is None:
            continue
        for point2 in points2:
            xyz2 = self.point_xyz(point2)
            if xyz2 is None:
                continue
            if (
                abs(xyz1[0] - xyz2[0]) <= tol
                and abs(xyz1[1] - xyz2[1]) <= tol
                and abs(xyz1[2] - xyz2[2]) <= tol
            ):
                return (point1, point1, point2)
    return None
single_line_dimension_placement
single_line_dimension_placement(entity: Any) -> tuple[float, float, float, int] | None

Compute the text-placement point and direction for a linear dimension.

Reads the segment midpoint, computes the outward normal (perpendicular to the segment direction), offsets the placement point by 35 % of the segment length (clamped to 10–20 mm), and converts the normal to a SolidWorks dimension-direction constant.

Parameters:

Name Type Description Default
entity Any

SolidWorks ISketchLine or ISketchArc COM object.

required

Returns:

Type Description
tuple[float, float, float, int] | None

tuple[float, float, float, int] | None:

tuple[float, float, float, int] | None

(text_x, text_y, text_z, direction) in metres, or None

tuple[float, float, float, int] | None

when endpoint data is unavailable.

Example::

placement = service.single_line_dimension_placement(line_obj)
if placement:
    text_x, text_y, text_z, direction = placement
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def single_line_dimension_placement(
    self, entity: Any
) -> tuple[float, float, float, int] | None:
    """Compute the text-placement point and direction for a linear dimension.

    Reads the segment midpoint, computes the outward normal (perpendicular
    to the segment direction), offsets the placement point by 35 % of the
    segment length (clamped to 10–20 mm), and converts the normal to a
    SolidWorks dimension-direction constant.

    Args:
        entity: SolidWorks ``ISketchLine`` or ``ISketchArc`` COM object.

    Returns:
        tuple[float, float, float, int] | None:
        ``(text_x, text_y, text_z, direction)`` in metres, or ``None``
        when endpoint data is unavailable.

    Example::

        placement = service.single_line_dimension_placement(line_obj)
        if placement:
            text_x, text_y, text_z, direction = placement
    """
    endpoints = self.read_segment_endpoints(entity)
    if endpoints is None:
        return None

    (x1, y1, z1), (x2, y2, z2) = endpoints
    dx = x2 - x1
    dy = y2 - y1
    length = (dx * dx + dy * dy) ** 0.5
    if length <= 1e-9:
        return None

    mid_x = (x1 + x2) / 2.0
    mid_y = (y1 + y2) / 2.0
    mid_z = (z1 + z2) / 2.0

    normal_x = -dy / length
    normal_y = dx / length
    offset = max(0.01, min(0.02, length * 0.35))
    text_x = mid_x + normal_x * offset
    text_y = mid_y + normal_y * offset
    direction = self.smart_dimension_direction(normal_x, normal_y)
    return (text_x, text_y, mid_z, direction)
smart_dimension_direction
smart_dimension_direction(dx: float, dy: float) -> int

Map a 2-D direction vector to a SolidWorks dimension-direction constant.

The dominant axis determines horizontal vs vertical; within each axis the sign selects left/right or up/down.

Parameters:

Name Type Description Default
dx float

Horizontal component of the dimension normal vector.

required
dy float

Vertical component of the dimension normal vector.

required

Returns:

Name Type Description
int int

One of the four swSmartDimensionDirection* constants

int

from adapter.constants:

int

swSmartDimensionDirectionRight,

int

swSmartDimensionDirectionLeft,

int

swSmartDimensionDirectionUp, or

int

swSmartDimensionDirectionDown.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def smart_dimension_direction(self, dx: float, dy: float) -> int:
    """Map a 2-D direction vector to a SolidWorks dimension-direction constant.

    The dominant axis determines horizontal vs vertical; within each axis
    the sign selects left/right or up/down.

    Args:
        dx: Horizontal component of the dimension normal vector.
        dy: Vertical component of the dimension normal vector.

    Returns:
        int: One of the four ``swSmartDimensionDirection*`` constants
        from ``adapter.constants``:
        ``swSmartDimensionDirectionRight``,
        ``swSmartDimensionDirectionLeft``,
        ``swSmartDimensionDirectionUp``, or
        ``swSmartDimensionDirectionDown``.
    """
    if abs(dx) >= abs(dy):
        return (
            self._adapter.constants["swSmartDimensionDirectionRight"]
            if dx >= 0.0
            else self._adapter.constants["swSmartDimensionDirectionLeft"]
        )
    return (
        self._adapter.constants["swSmartDimensionDirectionUp"]
        if dy >= 0.0
        else self._adapter.constants["swSmartDimensionDirectionDown"]
    )

Functions:

_byref_int

_byref_int() -> Any

Return a byref long VARIANT for a SolidWorks out-parameter.

See :func:_byref_bstr for why pythoncom.Missing does not work.

Returns:

Name Type Description
Any Any

A VARIANT(VT_BYREF | VT_I4, 0), or 0 when pywin32 is

Any

unavailable (test/mock environments).

Source code in src/solidworks_mcp/adapters/solidworks/io.py
def _byref_int() -> Any:
    """Return a byref long VARIANT for a SolidWorks out-parameter.

    See :func:`_byref_bstr` for why ``pythoncom.Missing`` does not work.

    Returns:
        Any: A ``VARIANT(VT_BYREF | VT_I4, 0)``, or ``0`` when pywin32 is
        unavailable (test/mock environments).
    """
    variant_ctor = getattr(getattr(win32com, "client", None), "VARIANT", None)
    if not callable(variant_ctor):
        return _ByrefFallback(0)
    return variant_ctor(
        int(getattr(pythoncom, "VT_BYREF", 0)) | int(getattr(pythoncom, "VT_I4", 0)), 0
    )

_byref_long

_byref_long(initial: int = 0) -> Any

Return a VT_BYREF | VT_I4 VARIANT for a SolidWorks ByRef Long out-parameter.

pywin32 late binding will not marshal a plain Python int into a ByRef Long argument - the call fails with DISP_E_TYPEMISMATCH (0x80020005). RunMacro2's trailing nRetval needs the same treatment as OpenDoc6's errors/warnings (issue #91).

Falls back to initial (a plain int) when pywin32 is unavailable so the mock/CI test path keeps working.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def _byref_long(initial: int = 0) -> Any:
    """Return a ``VT_BYREF | VT_I4`` VARIANT for a SolidWorks ByRef Long
    out-parameter.

    pywin32 late binding will not marshal a plain Python ``int`` into a
    ``ByRef Long`` argument - the call fails with ``DISP_E_TYPEMISMATCH``
    (``0x80020005``). ``RunMacro2``'s trailing ``nRetval`` needs the same
    treatment as ``OpenDoc6``'s ``errors``/``warnings`` (issue #91).

    Falls back to ``initial`` (a plain int) when pywin32 is unavailable so
    the mock/CI test path keeps working.
    """
    variant_ctor = getattr(getattr(win32com, "client", None), "VARIANT", None)
    if not callable(variant_ctor):
        return initial
    return variant_ctor(
        int(getattr(pythoncom, "VT_BYREF", 0)) | int(getattr(pythoncom, "VT_I4", 0)),
        initial,
    )

_dynamic_dispatch

_dynamic_dispatch(arg: Any) -> Any

Forward to win32com.client.dynamic.Dispatch via the imported module reference. Forces late binding so VARIANT pass-by-ref params used by OpenDoc6 keep working even when the makepy gen_py wrapper is loaded.

Tests that need to stub dispatch should monkeypatch win32com.client.dynamic.Dispatch directly — the monkeypatch propagates through this module reference because Python modules are singletons.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def _dynamic_dispatch(arg: Any) -> Any:
    """Forward to ``win32com.client.dynamic.Dispatch`` via the imported
    module reference. Forces late binding so VARIANT pass-by-ref params
    used by ``OpenDoc6`` keep working even when the makepy ``gen_py``
    wrapper is loaded.

    Tests that need to stub dispatch should monkeypatch
    ``win32com.client.dynamic.Dispatch`` directly — the monkeypatch
    propagates through this module reference because Python modules
    are singletons.
    """
    return _dynamic_module.Dispatch(arg)

_headless_requested

_headless_requested() -> bool

True when SOLIDWORKS_MCP_HEADLESS asks the SW window to stay hidden.

Off by default - a normal MCP session keeps the SolidWorks window visible. The live test suite sets it so connect() does not re-show the window on every per-test reconnect (each show/hide is a full window relayout).

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def _headless_requested() -> bool:
    """True when ``SOLIDWORKS_MCP_HEADLESS`` asks the SW window to stay hidden.

    Off by default - a normal MCP session keeps the SolidWorks window visible.
    The live test suite sets it so ``connect()`` does not re-show the window on
    every per-test reconnect (each show/hide is a full window relayout).
    """
    return os.getenv("SOLIDWORKS_MCP_HEADLESS", "").strip().lower() in {
        "1",
        "true",
        "yes",
        "on",
    }

_null_callout

_null_callout() -> Any

Return a VT_DISPATCH null for SelectByID2's Callout parameter.

A plain Python None marshals as VT_NULL, which SolidWorks rejects with DISP_E_TYPEMISMATCH - measured as (-2147352571, 'Type mismatch.', None, 8). See runbook item 11.

Returns:

Name Type Description
Any Any

A VARIANT(VT_DISPATCH, None), or a non-None sentinel

Any

when pywin32 is unavailable (mock/Linux runs, where no real COM call

Any

will be made anyway, but the value must still never be bare None).

Source code in src/solidworks_mcp/adapters/solidworks/features.py
def _null_callout() -> Any:
    """Return a VT_DISPATCH null for ``SelectByID2``'s ``Callout`` parameter.

    A plain Python ``None`` marshals as ``VT_NULL``, which SolidWorks rejects
    with ``DISP_E_TYPEMISMATCH`` - measured as
    ``(-2147352571, 'Type mismatch.', None, 8)``. See runbook item 11.

    Returns:
        Any: A ``VARIANT(VT_DISPATCH, None)``, or a non-``None`` sentinel
        when pywin32 is unavailable (mock/Linux runs, where no real COM call
        will be made anyway, but the value must still never be bare ``None``).
    """
    try:
        import pythoncom
        import win32com.client as _win32com
    except ImportError:
        return _NULL_CALLOUT_SENTINEL
    return _win32com.VARIANT(pythoncom.VT_DISPATCH, None)

_parse_vb_module_name

_parse_vb_module_name(macro_path: str) -> str

Resolve the VB module name RunMacro2 needs for a macro file.

RunMacro2(path, ModuleName, ProcedureName, ...) fails with swRunMacroError_Invalidindex (22) when ModuleName does not match a module in the project, so the guess matters:

  • A real .swp authored by SOLIDWORKS is an OLE compound file. Its plaintext PROJECT stream carries Module=<name>, and SW names that module <stem>1 (sample_macro -> sample_macro1) - the file stem is the wrong guess. Read the real name out of the raw bytes.
  • A hand-written text macro carries Attribute VB_Name = "...".
  • Otherwise fall back to the file stem, then "SolidWorksMacro" (the name the macro recorder uses).

Parameters:

Name Type Description Default
macro_path str

The macro path value.

required

Returns:

Name Type Description
str str

The resulting text value.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def _parse_vb_module_name(macro_path: str) -> str:
    """Resolve the VB module name ``RunMacro2`` needs for a macro file.

    ``RunMacro2(path, ModuleName, ProcedureName, ...)`` fails with
    ``swRunMacroError_Invalidindex`` (22) when ``ModuleName`` does not match a
    module in the project, so the guess matters:

    - A real ``.swp`` authored by SOLIDWORKS is an OLE compound file. Its
      plaintext ``PROJECT`` stream carries ``Module=<name>``, and SW names that
      module ``<stem>1`` (``sample_macro`` -> ``sample_macro1``) - the file stem
      is the wrong guess. Read the real name out of the raw bytes.
    - A hand-written text macro carries ``Attribute VB_Name = "..."``.
    - Otherwise fall back to the file stem, then ``"SolidWorksMacro"`` (the name
      the macro recorder uses).

    Args:
        macro_path (str): The macro path value.

    Returns:
        str: The resulting text value.
    """
    try:
        with open(macro_path, "rb") as fh:
            raw = fh.read()
    except OSError:
        raw = b""

    if raw[:8] == _OLE2_MAGIC:
        # PROJECT stream line: Module=<name>  (CRLF-delimited ASCII)
        match = re.search(rb"[\r\n]Module=([^\r\n\x00]{1,255})", raw)
        if match:
            name = match.group(1).decode("latin-1", "replace").strip().strip('"')
            if name:
                return name
    else:
        for line in raw.decode("utf-8", "replace").splitlines():
            line = line.strip()
            if line.lower().startswith("attribute vb_name"):
                # Attribute VB_Name = "SolidWorksMacro"
                _, _, rhs = line.partition("=")
                return rhs.strip().strip('"').strip("'")

    stem = os.path.splitext(os.path.basename(macro_path))[0]
    if stem and not stem.startswith(".") and stem.strip("."):
        return stem
    return "SolidWorksMacro"

_run_macro_error_name

_run_macro_error_name(code: Any) -> str

Render a swRunMacroError_e code as "<n> (<Name>)" when known.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def _run_macro_error_name(code: Any) -> str:
    """Render a swRunMacroError_e code as ``"<n> (<Name>)"`` when known."""
    try:
        code_int = int(code)
    except (TypeError, ValueError):
        return str(code)
    name = _SW_RUN_MACRO_ERROR.get(code_int)
    return f"{code_int} ({name})" if name else str(code_int)

_select_feature_by_name

_select_feature_by_name(adapter: Any, name: str, append: bool = False) -> Any

Select a feature or sketch by name and return it.

Resolves the entity with IModelDoc2::FeatureByName and selects it with IFeature::Select2(append, 0) - the same path used elsewhere in this adapter. SelectByID2 is avoided here because it needs an entity-type string that differs between sketches and solid features, and because its Callout argument raises Type mismatch unless passed an explicit VT_DISPATCH null. Any name@document qualifier is stripped first.

Parameters:

Name Type Description Default
adapter Any

A connected adapter with a valid currentModel.

required
name str

Feature or sketch name, e.g. "Boss-Extrude1" or "Sketch1".

required
append bool

Add to the current selection instead of clearing it first. Defaults to False.

False

Returns:

Name Type Description
Any Any

The selected IFeature, or None when it does not exist or

Any

could not be selected.

Source code in src/solidworks_mcp/adapters/solidworks/features.py
def _select_feature_by_name(adapter: Any, name: str, append: bool = False) -> Any:
    """Select a feature or sketch by name and return it.

    Resolves the entity with ``IModelDoc2::FeatureByName`` and selects it with
    ``IFeature::Select2(append, 0)`` - the same path used elsewhere in this
    adapter. ``SelectByID2`` is avoided here because it needs an entity-type
    string that differs between sketches and solid features, and because its
    ``Callout`` argument raises ``Type mismatch`` unless passed an explicit
    ``VT_DISPATCH`` null. Any ``name@document`` qualifier is stripped first.

    Args:
        adapter: A connected adapter with a valid ``currentModel``.
        name: Feature or sketch name, e.g. ``"Boss-Extrude1"`` or ``"Sketch1"``.
        append: Add to the current selection instead of clearing it first.
            Defaults to False.

    Returns:
        Any: The selected ``IFeature``, or ``None`` when it does not exist or
        could not be selected.
    """
    bare = name.split("@", 1)[0]
    if not append:
        adapter._attempt(
            lambda: adapter.currentModel.ClearSelection2(True), default=None
        )
    feature = adapter._attempt(
        lambda: adapter.currentModel.FeatureByName(bare), default=None
    )
    if not feature:
        return None
    selected = adapter._attempt(lambda: feature.Select2(append, 0), default=False)
    return feature if selected else None

_settle_after

_settle_after(operation_name: str) -> None

Pause after a COM operation so SolidWorks can finish its UI work.

SolidWorks does part of each operation's work later, in its idle loop (CommandManager/task-pane refresh). Firing the next call while that is in progress re-enters it, and SolidWorks 2026 crashed there repeatedly under back-to-back API calls (COM pitfall #26). A human never drives it that fast. Tunable via SOLIDWORKS_MCP_COM_SETTLE_SECONDS (every operation) and SOLIDWORKS_MCP_DOC_SETTLE_SECONDS (document open/create/close).

Parameters:

Name Type Description Default
operation_name str

The _handle_com_operation name just completed.

required
Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def _settle_after(operation_name: str) -> None:
    """Pause after a COM operation so SolidWorks can finish its UI work.

    SolidWorks does part of each operation's work later, in its idle loop
    (CommandManager/task-pane refresh). Firing the next call while that is in
    progress re-enters it, and SolidWorks 2026 crashed there repeatedly under
    back-to-back API calls (COM pitfall #26). A human never drives it that
    fast. Tunable via ``SOLIDWORKS_MCP_COM_SETTLE_SECONDS`` (every operation)
    and ``SOLIDWORKS_MCP_DOC_SETTLE_SECONDS`` (document open/create/close).

    Args:
        operation_name: The ``_handle_com_operation`` name just completed.
    """
    if operation_name in _DOCUMENT_OPERATIONS:
        pause = _settle_seconds(
            "SOLIDWORKS_MCP_DOC_SETTLE_SECONDS", _DEFAULT_DOC_SETTLE_SECONDS
        )
    else:
        pause = _settle_seconds(
            "SOLIDWORKS_MCP_COM_SETTLE_SECONDS", _DEFAULT_COM_SETTLE_SECONDS
        )
    if pause > 0:
        time.sleep(pause)

_settle_seconds

_settle_seconds(variable: str, default: float) -> float

Read a non-negative settle pause from the environment.

Source code in src/solidworks_mcp/adapters/pywin32_adapter.py
def _settle_seconds(variable: str, default: float) -> float:
    """Read a non-negative settle pause from the environment."""
    try:
        return max(0.0, float(os.getenv(variable, default)))
    except ValueError:
        return default