import math from pathlib import Path from tempfile import TemporaryDirectory from app.cad_agent.adapters.artifact_store import FileArtifactStore from app.cad_agent.adapters.runtime import ProfileCadRuntime from app.cad_agent.ports import AdapterUnavailable from app.cad_agent.application.single_stage import SingleStageExecutor from app.services.engine_service import validate_cdsl, validate_cdsl_shape from app.settings import get_settings def _base_feature() -> dict: return { "name": "base", "operation": "extrude_add_blind", "params": {"distance_mm": 8, "result_mode": "new_body"}, "sketch": { "workplane": { "origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1], }, "profile": { "type": "polygon", "vertices": [[-40, -25], [40, -25], [40, 25], [-40, 25]], }, }, } def _authoring(*features: dict) -> dict: return { "schema_version": "cad.author.v1", "units": "mm", "bodies": [{"name": "main", "features": list(features)}], } def test_authoring_compiles_to_runtime_cdsl_and_publishes_one_revision() -> None: runtime = ProfileCadRuntime(get_settings()) authoring = _authoring(_base_feature()) runtime_cdsl, audit = runtime.compile_authoring(authoring) assert runtime_cdsl["schema"] == "cad.runtime.v1" assert runtime_cdsl["bodies"] == [{"id": "body_001", "name": "main"}] assert runtime_cdsl["features"][0]["id"] == "feature_001" assert audit["feature_ids"] == {"base": "feature_001"} validate_cdsl_shape(runtime_cdsl, runtime.engine) validate_cdsl(runtime_cdsl, runtime.engine) with TemporaryDirectory() as temporary: artifacts = FileArtifactStore(Path(temporary) / "artifacts") task_id = "cad_abcdefghijkl" artifacts.initialize_task(task_id, "make a rectangular plate") result = SingleStageExecutor(None, artifacts, runtime).execute(task_id, authoring) assert result["status"] == "completed" revision = str(result["revision_id"]) assert result["executed_feature_ids"] == ["feature_001"] for relative in ("model.step", "model.glb", "model.cdsl.json", "rebuild-report.json", "renders/render-manifest.json"): assert artifacts.artifact_path(task_id, f"revisions/{revision}/{relative}").is_file() def test_selector_failure_keeps_the_successful_prefix() -> None: runtime = ProfileCadRuntime(get_settings()) invalid_fillet = { "name": "bad_fillet", "operation": "fillet", "depends_on": ["base"], "params": {"radius_mm": 1}, # extrude.end is a face output. Using it for an edge-only operation # is a deliberate selector-kind failure, not a geometry fallback. "selectors": [{"kind": "edge", "source": "base.top_planar_face"}], } runtime_cdsl, _audit = runtime.compile_authoring(_authoring(_base_feature(), invalid_fillet)) with TemporaryDirectory() as temporary: built, diagnostics = runtime.rebuild_best_effort( runtime_cdsl, temporary, "cad_abcdefghijkl", "stage_selector_failure", ) assert built["executed_feature_ids"] == ["feature_001"] assert diagnostics[0]["feature_id"] == "feature_002" assert diagnostics[0]["code"] == "SELECTOR_KIND_MISMATCH" def test_cylinder_cap_selector_is_compiled_into_hole_host_face_and_executes() -> None: runtime = ProfileCadRuntime(get_settings()) base = { "name": "base_flange", "operation": "cylinder_add", "params": { "radius_mm": 30, "height_mm": 10, "axis": {"origin_mm": [0, 0, 0], "direction": [0, 0, 1]}, }, } bore = { "name": "center_bore", "operation": "hole_wizard", "params": { "hole_type": "simple", "diameter_mm": 12, "depth_mm": 10, "end_condition": {"type": "through_all", "solidworks_code": 1}, "positions": [{"mm": [0, 0, 10]}], }, "selectors": [{"kind": "face", "source": "base_flange.top_planar_face", "match": "unique"}], } runtime_cdsl, _audit = runtime.compile_authoring(_authoring(base, bore)) compiled_bore = runtime_cdsl["features"][1] assert compiled_bore["depends_on"] == ["feature_001"] assert "selectors" not in compiled_bore assert compiled_bore["params"]["host_face"] == { "kind": "face", "output_role": "cylinder.end", "owner_feature_id": "feature_001", "source": "runtime_snapshot", "confidence": 1.0, "match_mode": "unique", } with TemporaryDirectory() as temporary: built, diagnostics = runtime.rebuild_best_effort( runtime_cdsl, temporary, "cad_abcdefghijkl", "stage_cylinder_host", ) assert diagnostics == [] assert built["executed_feature_ids"] == ["feature_001", "feature_002"] def test_authoring_circle_diameter_is_lowered_to_runtime_radius() -> None: runtime = ProfileCadRuntime(get_settings()) authoring = _authoring({ "name": "round_base", "operation": "extrude_add_blind", "params": {"distance_mm": 8, "result_mode": "new_body"}, "sketch": { "workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]}, "profile": {"type": "circle", "diameter_mm": 20, "center_mm": [0, 0]}, }, }) compiled, _audit = runtime.compile_authoring(authoring) assert compiled["geometry"]["sketches"][0]["profile"] == { "type": "circle", "center": [0.0, 0.0], "radius_mm": 10.0, } def test_flange_bolt_host_is_built_before_source_topology_is_replaced() -> None: """An exposed base cap remains a valid bolt host before later fusions/cuts.""" runtime = ProfileCadRuntime(get_settings()) circle_sketch = lambda z, normal, diameter: { "workplane": {"origin_mm": [0, 0, z], "x_dir": [1, 0, 0], "normal": normal}, "profile": {"type": "circle", "diameter_mm": diameter, "center_mm": [0, 0]}, } bolt_positions = [ {"mm": [43 * math.cos(math.radians(angle)), 43 * math.sin(math.radians(angle)), 12]} for angle in range(0, 360, 45) ] authoring = _authoring( { "name": "base_flange", "operation": "cylinder_add", "params": {"radius_mm": 60, "height_mm": 12, "axis": {"origin_mm": [0, 0, 0], "direction": [0, 0, 1]}}, }, { "name": "rear_shallow_pad", "operation": "extrude_add_blind", "depends_on": ["bolt_holes"], "params": {"distance_mm": 4, "result_mode": "fuse", "reverse": False}, "sketch": circle_sketch(0, [0, 0, -1], 105), }, { "name": "rear_guide_boss", "operation": "extrude_add_blind", "depends_on": ["rear_shallow_pad"], "params": {"distance_mm": 12, "result_mode": "fuse", "reverse": False}, "sketch": circle_sketch(0, [0, 0, -1], 38), }, { "name": "bolt_holes", "operation": "hole_wizard", "params": { "hole_type": "counterbore", "diameter_mm": 8, "depth_mm": 12, "end_condition": {"type": "through_all_both", "solidworks_code": 7}, "counterbore": {"diameter_mm": 10, "depth_mm": 4}, "positions": bolt_positions, }, "selectors": [{"kind": "face", "source": "base_flange.top_planar_face", "match": "unique"}], }, { "name": "front_hub_boss", "operation": "extrude_add_blind", "depends_on": ["bolt_holes"], "params": {"distance_mm": 14, "result_mode": "fuse", "reverse": False}, "sketch": circle_sketch(12, [0, 0, 1], 56), }, { "name": "center_through_cut", "operation": "extrude_cut_through", "depends_on": ["front_hub_boss", "rear_guide_boss"], "params": {"end_condition": {"type": "through_all_both", "solidworks_code": 7}}, "sketch": circle_sketch(0, [0, 0, 1], 32), }, ) compiled, audit = runtime.compile_authoring(authoring) assert audit["implicit_selector_dependencies"] == {"bolt_holes": ["base_flange"]} with TemporaryDirectory() as temporary: rebuilt = runtime.rebuild(compiled, temporary, "cad_abcdefghijkl", "flange_source_order") assert rebuilt["health"]["feature_count"] == 6 def test_service_failure_during_prefix_export_is_not_reclassified_as_a_model_error(monkeypatch) -> None: runtime = ProfileCadRuntime(get_settings()) runtime_cdsl, _audit = runtime.compile_authoring(_authoring(_base_feature())) def unavailable(*_args, **_kwargs): raise AdapterUnavailable("renderer unavailable") monkeypatch.setattr(runtime, "rebuild", unavailable) with TemporaryDirectory() as temporary: try: runtime.rebuild_best_effort(runtime_cdsl, temporary, "cad_abcdefghijkl", "stage_service_failure") except AdapterUnavailable as error: assert "renderer unavailable" in str(error) else: raise AssertionError("service failure was incorrectly converted into a model repair diagnostic")