* ci: add caching and fix workflow failures - Add UV package caching to all workflows for faster dependency installation - Add pre-commit hook caching with OS-specific cache keys - Skip no-commit-to-branch hook in CI (fails on main branch) - Remove broken apt cache action (doesn't work with PPAs) - Simplify FreeCAD command detection (PPA installs to standard PATH) - Add FreeCAD Python version logging for debugging * ci: Add code Rabbit configuration file * ci: fix issues in CI workflows * ci: add uv.lock * ci: tweaks * ci: Fix errors and add UUID generation and checking * ci: Use the GitHub FreeCAD release latest stables * ci: skip macro test for now, due to headless mode * feat: Add a multi export macro * ci: Fix tests * ci: fix docker build workflow * ci: skip trufflehog in GitHub Actions due to wasm panic bug TruffleHog has a known wasm/go-re2 panic bug that causes failures in GitHub Actions environment. The hook still runs locally during development for secrets detection. - Add trufflehog to SKIP env var in pre-commit.yaml - Update trufflehog to v3.88.7 (latest) - Add reference to upstream issue #3321 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: use boolean holes instead of PartDesign::Hole in CI PartDesign::Hole has a CADKernelError bug in FreeCAD AppImage headless mode on Linux (used in GitHub Actions) where it fails with "Cannot make face from profile". The SmartCutter class already supports boolean holes as an alternative. Changes: - Modify SmartCutter.execute() to use boolean holes by default - Update all test assertions for Part::Feature output type - Update test docstrings and class descriptions - Remove PartDesign-specific checks (Group, Sketcher::SketchObject) - Update workflow comment explaining the CI limitation Boolean holes work reliably in both GUI and headless mode across all FreeCAD configurations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ci: clean up GitHub Actions workflows * ci: Dependabot improvements * ci: add CodeQL scanning workflow * ci: AI review suggested improvements * ci: add coderabbit updates for intentional decisions --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
583 lines
16 KiB
Python
583 lines
16 KiB
Python
"""Integration tests for FreeCAD MCP GUI mode.
|
|
|
|
These tests verify that the MCP bridge works correctly when FreeCAD is running
|
|
in GUI mode (with full graphical interface). They test GUI-specific features
|
|
like visibility, display modes, colors, and camera operations.
|
|
|
|
Note: These tests require a running FreeCAD GUI server.
|
|
Start it with: just run-gui
|
|
|
|
To run these tests:
|
|
pytest tests/integration/test_gui_mode.py -v
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
import pytest
|
|
|
|
if TYPE_CHECKING:
|
|
import xmlrpc.client
|
|
from collections.abc import Generator
|
|
|
|
# Mark all tests in this module as integration tests and gui tests
|
|
pytestmark = [pytest.mark.integration, pytest.mark.gui]
|
|
|
|
# Note: xmlrpc_proxy fixture is defined in conftest.py
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def temp_dir() -> Generator[str, None, None]:
|
|
"""Create a temporary directory for test files."""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
yield tmpdir
|
|
|
|
|
|
def execute_code(proxy: xmlrpc.client.ServerProxy, code: str) -> dict[str, Any]:
|
|
"""Execute Python code via the MCP bridge and return the result."""
|
|
result: dict[str, Any] = proxy.execute(code) # type: ignore[assignment]
|
|
assert result.get("success"), f"Execution failed: {result.get('error_traceback')}"
|
|
return result
|
|
|
|
|
|
class TestGUIConnection:
|
|
"""Tests for GUI mode connectivity and detection."""
|
|
|
|
def test_ping(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Test that the server responds to ping."""
|
|
result: dict[str, Any] = xmlrpc_proxy.ping() # type: ignore[assignment]
|
|
assert result["pong"] is True
|
|
assert "timestamp" in result
|
|
|
|
def test_gui_mode_detected(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Test that FreeCAD is running in GUI mode (GuiUp=True)."""
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
_result_ = {"gui_up": FreeCAD.GuiUp}
|
|
""",
|
|
)
|
|
# In GUI mode, GuiUp should be True (1)
|
|
assert result["result"]["gui_up"] == 1 or result["result"]["gui_up"] is True
|
|
|
|
|
|
class TestGUIObjectCreation:
|
|
"""Tests for object creation in GUI mode."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_document(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Create a fresh document for each test."""
|
|
execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
# Close any existing GUITestDoc
|
|
if "GUITestDoc" in FreeCAD.listDocuments():
|
|
FreeCAD.closeDocument("GUITestDoc")
|
|
doc = FreeCAD.newDocument("GUITestDoc")
|
|
_result_ = True
|
|
""",
|
|
)
|
|
|
|
def test_create_box_with_view_object(
|
|
self, xmlrpc_proxy: xmlrpc.client.ServerProxy
|
|
) -> None:
|
|
"""Test creating a box with ViewObject in GUI mode."""
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
import Part
|
|
|
|
doc = FreeCAD.ActiveDocument
|
|
box = Part.makeBox(10, 20, 30)
|
|
obj = doc.addObject("Part::Feature", "GUIBox")
|
|
obj.Shape = box
|
|
doc.recompute()
|
|
|
|
has_view_object = hasattr(obj, "ViewObject") and obj.ViewObject is not None
|
|
|
|
_result_ = {
|
|
"name": obj.Name,
|
|
"volume": obj.Shape.Volume,
|
|
"has_view_object": has_view_object,
|
|
"valid": obj.Shape.isValid()
|
|
}
|
|
""",
|
|
)
|
|
assert result["result"]["name"] == "GUIBox"
|
|
assert abs(result["result"]["volume"] - 6000.0) < 0.01
|
|
assert result["result"]["has_view_object"] is True
|
|
assert result["result"]["valid"] is True
|
|
|
|
|
|
class TestVisibility:
|
|
"""Tests for object visibility operations in GUI mode."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_document(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Create a document with test objects."""
|
|
execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
import Part
|
|
|
|
if "VisibilityTestDoc" in FreeCAD.listDocuments():
|
|
FreeCAD.closeDocument("VisibilityTestDoc")
|
|
doc = FreeCAD.newDocument("VisibilityTestDoc")
|
|
|
|
box = Part.makeBox(10, 10, 10)
|
|
obj = doc.addObject("Part::Feature", "VisBox")
|
|
obj.Shape = box
|
|
doc.recompute()
|
|
_result_ = True
|
|
""",
|
|
)
|
|
|
|
def test_hide_object(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Test hiding an object."""
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
|
|
doc = FreeCAD.ActiveDocument
|
|
obj = doc.getObject("VisBox")
|
|
|
|
# Hide the object
|
|
obj.ViewObject.Visibility = False
|
|
|
|
_result_ = {
|
|
"visible": obj.ViewObject.Visibility
|
|
}
|
|
""",
|
|
)
|
|
assert result["result"]["visible"] is False
|
|
|
|
def test_show_object(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Test showing an object."""
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
|
|
doc = FreeCAD.ActiveDocument
|
|
obj = doc.getObject("VisBox")
|
|
|
|
# First hide, then show
|
|
obj.ViewObject.Visibility = False
|
|
obj.ViewObject.Visibility = True
|
|
|
|
_result_ = {
|
|
"visible": obj.ViewObject.Visibility
|
|
}
|
|
""",
|
|
)
|
|
assert result["result"]["visible"] is True
|
|
|
|
|
|
class TestDisplayMode:
|
|
"""Tests for display mode operations in GUI mode."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_document(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Create a document with test objects."""
|
|
execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
import Part
|
|
|
|
if "DisplayModeTestDoc" in FreeCAD.listDocuments():
|
|
FreeCAD.closeDocument("DisplayModeTestDoc")
|
|
doc = FreeCAD.newDocument("DisplayModeTestDoc")
|
|
|
|
box = Part.makeBox(10, 10, 10)
|
|
obj = doc.addObject("Part::Feature", "DisplayBox")
|
|
obj.Shape = box
|
|
doc.recompute()
|
|
_result_ = True
|
|
""",
|
|
)
|
|
|
|
def test_get_display_modes(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Test getting available display modes."""
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
|
|
doc = FreeCAD.ActiveDocument
|
|
obj = doc.getObject("DisplayBox")
|
|
|
|
modes = obj.ViewObject.listDisplayModes()
|
|
|
|
_result_ = {
|
|
"modes": list(modes),
|
|
"current": obj.ViewObject.DisplayMode
|
|
}
|
|
""",
|
|
)
|
|
# Typical modes: "Flat Lines", "Shaded", "Wireframe", etc.
|
|
assert len(result["result"]["modes"]) > 0
|
|
assert result["result"]["current"] is not None
|
|
|
|
def test_set_display_mode(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Test setting display mode."""
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
|
|
doc = FreeCAD.ActiveDocument
|
|
obj = doc.getObject("DisplayBox")
|
|
|
|
modes = obj.ViewObject.listDisplayModes()
|
|
# Set to a different mode if available
|
|
target_mode = "Wireframe" if "Wireframe" in modes else modes[0]
|
|
obj.ViewObject.DisplayMode = target_mode
|
|
|
|
_result_ = {
|
|
"mode_set": obj.ViewObject.DisplayMode,
|
|
"target_mode": target_mode
|
|
}
|
|
""",
|
|
)
|
|
assert result["result"]["mode_set"] == result["result"]["target_mode"]
|
|
|
|
|
|
class TestObjectColor:
|
|
"""Tests for object color operations in GUI mode."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_document(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Create a document with test objects."""
|
|
execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
import Part
|
|
|
|
if "ColorTestDoc" in FreeCAD.listDocuments():
|
|
FreeCAD.closeDocument("ColorTestDoc")
|
|
doc = FreeCAD.newDocument("ColorTestDoc")
|
|
|
|
box = Part.makeBox(10, 10, 10)
|
|
obj = doc.addObject("Part::Feature", "ColorBox")
|
|
obj.Shape = box
|
|
doc.recompute()
|
|
_result_ = True
|
|
""",
|
|
)
|
|
|
|
def test_set_shape_color(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Test setting object shape color."""
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
|
|
doc = FreeCAD.ActiveDocument
|
|
obj = doc.getObject("ColorBox")
|
|
|
|
# Set color to red (RGB as tuple of floats 0-1)
|
|
obj.ViewObject.ShapeColor = (1.0, 0.0, 0.0)
|
|
|
|
color = obj.ViewObject.ShapeColor
|
|
|
|
_result_ = {
|
|
"r": color[0],
|
|
"g": color[1],
|
|
"b": color[2]
|
|
}
|
|
""",
|
|
)
|
|
assert result["result"]["r"] == pytest.approx(1.0, rel=0.01)
|
|
assert result["result"]["g"] == pytest.approx(0.0, abs=0.01)
|
|
assert result["result"]["b"] == pytest.approx(0.0, abs=0.01)
|
|
|
|
def test_set_transparency(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Test setting object transparency."""
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
|
|
doc = FreeCAD.ActiveDocument
|
|
obj = doc.getObject("ColorBox")
|
|
|
|
# Set 50% transparency
|
|
obj.ViewObject.Transparency = 50
|
|
|
|
_result_ = {
|
|
"transparency": obj.ViewObject.Transparency
|
|
}
|
|
""",
|
|
)
|
|
assert result["result"]["transparency"] == 50
|
|
|
|
|
|
class TestCameraOperations:
|
|
"""Tests for camera/view operations in GUI mode."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_document(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Create a document with test objects."""
|
|
execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
import Part
|
|
|
|
if "CameraTestDoc" in FreeCAD.listDocuments():
|
|
FreeCAD.closeDocument("CameraTestDoc")
|
|
doc = FreeCAD.newDocument("CameraTestDoc")
|
|
|
|
box = Part.makeBox(10, 10, 10)
|
|
obj = doc.addObject("Part::Feature", "CameraBox")
|
|
obj.Shape = box
|
|
doc.recompute()
|
|
_result_ = True
|
|
""",
|
|
)
|
|
|
|
def test_fit_all(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Test fit all view operation."""
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
import FreeCADGui
|
|
|
|
doc = FreeCAD.ActiveDocument
|
|
view = FreeCADGui.ActiveDocument.ActiveView
|
|
|
|
# Fit all objects in view
|
|
view.fitAll()
|
|
|
|
_result_ = {"success": True}
|
|
""",
|
|
)
|
|
assert result["result"]["success"] is True
|
|
|
|
def test_set_view_direction(self, xmlrpc_proxy: xmlrpc.client.ServerProxy) -> None:
|
|
"""Test setting view direction (front, top, etc.)."""
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
import FreeCADGui
|
|
|
|
doc = FreeCAD.ActiveDocument
|
|
view = FreeCADGui.ActiveDocument.ActiveView
|
|
|
|
# Set to front view
|
|
view.viewFront()
|
|
|
|
# Get camera orientation
|
|
cam = view.getCameraOrientation()
|
|
|
|
_result_ = {
|
|
"success": True,
|
|
"camera_orientation": [cam.Q[0], cam.Q[1], cam.Q[2], cam.Q[3]]
|
|
}
|
|
""",
|
|
)
|
|
assert result["result"]["success"] is True
|
|
|
|
|
|
class TestScreenshot:
|
|
"""Tests for screenshot functionality in GUI mode."""
|
|
|
|
def test_capture_screenshot(
|
|
self, xmlrpc_proxy: xmlrpc.client.ServerProxy, temp_dir: str
|
|
) -> None:
|
|
"""Test capturing a screenshot."""
|
|
# First create a document with objects
|
|
execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
import Part
|
|
|
|
if "ScreenshotTestDoc" in FreeCAD.listDocuments():
|
|
FreeCAD.closeDocument("ScreenshotTestDoc")
|
|
doc = FreeCAD.newDocument("ScreenshotTestDoc")
|
|
|
|
box = Part.makeBox(20, 20, 20)
|
|
obj = doc.addObject("Part::Feature", "ScreenshotBox")
|
|
obj.Shape = box
|
|
obj.ViewObject.ShapeColor = (0.0, 0.5, 1.0) # Blue color
|
|
doc.recompute()
|
|
_result_ = True
|
|
""",
|
|
)
|
|
|
|
screenshot_path = Path(temp_dir) / "test_screenshot.png"
|
|
screenshot_path_str = str(screenshot_path)
|
|
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
f"""
|
|
import FreeCAD
|
|
import FreeCADGui
|
|
import os
|
|
|
|
doc = FreeCAD.ActiveDocument
|
|
view = FreeCADGui.ActiveDocument.ActiveView
|
|
|
|
# Fit objects in view
|
|
view.fitAll()
|
|
|
|
# Save screenshot
|
|
view.saveImage({screenshot_path_str!r}, 800, 600, "White")
|
|
|
|
_result_ = {{
|
|
"saved": os.path.exists({screenshot_path_str!r}),
|
|
"path": {screenshot_path_str!r}
|
|
}}
|
|
""",
|
|
)
|
|
assert result["result"]["saved"] is True
|
|
assert screenshot_path.exists()
|
|
# Verify file has content
|
|
assert screenshot_path.stat().st_size > 0
|
|
|
|
|
|
class TestComplexGUIWorkflow:
|
|
"""Tests for complex workflows that combine GUI and modeling features."""
|
|
|
|
def test_create_assembly_with_colors(
|
|
self, xmlrpc_proxy: xmlrpc.client.ServerProxy
|
|
) -> None:
|
|
"""Test creating multiple objects with different colors."""
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
"""
|
|
import FreeCAD
|
|
import Part
|
|
|
|
if "AssemblyTestDoc" in FreeCAD.listDocuments():
|
|
FreeCAD.closeDocument("AssemblyTestDoc")
|
|
doc = FreeCAD.newDocument("AssemblyTestDoc")
|
|
|
|
# Create base plate (gray)
|
|
base = Part.makeBox(100, 100, 5)
|
|
base_obj = doc.addObject("Part::Feature", "BasePlate")
|
|
base_obj.Shape = base
|
|
base_obj.ViewObject.ShapeColor = (0.5, 0.5, 0.5)
|
|
|
|
# Create pillar 1 (red)
|
|
pillar1 = Part.makeBox(10, 10, 50, FreeCAD.Vector(10, 10, 5))
|
|
pillar1_obj = doc.addObject("Part::Feature", "Pillar1")
|
|
pillar1_obj.Shape = pillar1
|
|
pillar1_obj.ViewObject.ShapeColor = (1.0, 0.0, 0.0)
|
|
|
|
# Create pillar 2 (green)
|
|
pillar2 = Part.makeBox(10, 10, 50, FreeCAD.Vector(80, 10, 5))
|
|
pillar2_obj = doc.addObject("Part::Feature", "Pillar2")
|
|
pillar2_obj.Shape = pillar2
|
|
pillar2_obj.ViewObject.ShapeColor = (0.0, 1.0, 0.0)
|
|
|
|
# Create pillar 3 (blue)
|
|
pillar3 = Part.makeBox(10, 10, 50, FreeCAD.Vector(80, 80, 5))
|
|
pillar3_obj = doc.addObject("Part::Feature", "Pillar3")
|
|
pillar3_obj.Shape = pillar3
|
|
pillar3_obj.ViewObject.ShapeColor = (0.0, 0.0, 1.0)
|
|
|
|
# Create pillar 4 (yellow)
|
|
pillar4 = Part.makeBox(10, 10, 50, FreeCAD.Vector(10, 80, 5))
|
|
pillar4_obj = doc.addObject("Part::Feature", "Pillar4")
|
|
pillar4_obj.Shape = pillar4
|
|
pillar4_obj.ViewObject.ShapeColor = (1.0, 1.0, 0.0)
|
|
|
|
# Create top plate (semi-transparent gray)
|
|
top = Part.makeBox(100, 100, 5, FreeCAD.Vector(0, 0, 55))
|
|
top_obj = doc.addObject("Part::Feature", "TopPlate")
|
|
top_obj.Shape = top
|
|
top_obj.ViewObject.ShapeColor = (0.5, 0.5, 0.5)
|
|
top_obj.ViewObject.Transparency = 30
|
|
|
|
doc.recompute()
|
|
|
|
_result_ = {
|
|
"object_count": len(doc.Objects),
|
|
"objects": [obj.Name for obj in doc.Objects],
|
|
"all_visible": all(obj.ViewObject.Visibility for obj in doc.Objects)
|
|
}
|
|
""",
|
|
)
|
|
assert result["result"]["object_count"] == 6
|
|
assert "BasePlate" in result["result"]["objects"]
|
|
assert "TopPlate" in result["result"]["objects"]
|
|
assert result["result"]["all_visible"] is True
|
|
|
|
def test_export_with_screenshot(
|
|
self, xmlrpc_proxy: xmlrpc.client.ServerProxy, temp_dir: str
|
|
) -> None:
|
|
"""Test creating model, setting view, taking screenshot, and exporting."""
|
|
step_path = Path(temp_dir) / "workflow_export.step"
|
|
screenshot_path = Path(temp_dir) / "workflow_screenshot.png"
|
|
step_path_str = str(step_path)
|
|
screenshot_path_str = str(screenshot_path)
|
|
|
|
result = execute_code(
|
|
xmlrpc_proxy,
|
|
f"""
|
|
import FreeCAD
|
|
import FreeCADGui
|
|
import Part
|
|
import os
|
|
|
|
if "WorkflowTestDoc" in FreeCAD.listDocuments():
|
|
FreeCAD.closeDocument("WorkflowTestDoc")
|
|
doc = FreeCAD.newDocument("WorkflowTestDoc")
|
|
|
|
# Create a simple bracket shape
|
|
# Base
|
|
base = Part.makeBox(50, 30, 5)
|
|
# Vertical support
|
|
vertical = Part.makeBox(5, 30, 40, FreeCAD.Vector(0, 0, 5))
|
|
# Top flange with hole
|
|
top_flange = Part.makeBox(20, 30, 5, FreeCAD.Vector(0, 0, 45))
|
|
hole = Part.makeCylinder(4, 10, FreeCAD.Vector(10, 15, 40))
|
|
|
|
# Combine and cut
|
|
bracket = base.fuse(vertical).fuse(top_flange).cut(hole)
|
|
bracket_obj = doc.addObject("Part::Feature", "Bracket")
|
|
bracket_obj.Shape = bracket
|
|
bracket_obj.ViewObject.ShapeColor = (0.2, 0.4, 0.8)
|
|
|
|
doc.recompute()
|
|
|
|
# Set isometric view
|
|
view = FreeCADGui.ActiveDocument.ActiveView
|
|
view.viewIsometric()
|
|
view.fitAll()
|
|
|
|
# Take screenshot
|
|
view.saveImage({screenshot_path_str!r}, 800, 600, "White")
|
|
|
|
# Export to STEP
|
|
bracket_obj.Shape.exportStep({step_path_str!r})
|
|
|
|
_result_ = {{
|
|
"bracket_valid": bracket_obj.Shape.isValid(),
|
|
"bracket_volume": bracket_obj.Shape.Volume,
|
|
"screenshot_exists": os.path.exists({screenshot_path_str!r}),
|
|
"step_exists": os.path.exists({step_path_str!r})
|
|
}}
|
|
""",
|
|
)
|
|
assert result["result"]["bracket_valid"] is True
|
|
assert result["result"]["bracket_volume"] > 0
|
|
assert result["result"]["screenshot_exists"] is True
|
|
assert result["result"]["step_exists"] is True
|
|
assert screenshot_path.exists()
|
|
assert step_path.exists()
|