first commit

This commit is contained in:
2026-07-22 13:48:46 +08:00
commit c87751c3dc
2820 changed files with 726976 additions and 0 deletions
@@ -0,0 +1,933 @@
import contextlib
import io
import shutil
import unittest
from array import array
from pathlib import Path
from unittest import mock
from tests.python.support.paths import add_repo_path
add_repo_path("skills/cad/scripts/inspect")
from inspect_refs import cli as inspect_cli
from inspect_refs import inspect as refs_inspect
from cadpy import cad_ref_syntax as refs_syntax
from cadpy import assembly_spec
from cadpy import generation as cad_generation
from cadpy import step_targets
from cadpy.glb_topology import STEP_TOPOLOGY_SCHEMA_VERSION
from cadpy.render import part_glb_path
from cadpy.selector_types import SelectorBundle, SelectorProfile
from cadpy.source_hash import python_source_hash
from tests.python.support.cad_test_roots import IsolatedCadRoots
def _refs_manifest(cad_ref: str) -> dict[str, object]:
return {
"schemaVersion": STEP_TOPOLOGY_SCHEMA_VERSION,
"profile": "refs",
"cadPath": cad_ref,
"stepPath": f"{cad_ref}.step",
"stepHash": "step-hash-123",
"bbox": {"min": [0.0, 0.0, 0.0], "max": [10.0, 10.0, 10.0]},
"stats": {
"occurrenceCount": 2,
"leafOccurrenceCount": 1,
"shapeCount": 1,
"faceCount": 2,
"edgeCount": 2,
"vertexCount": 1,
},
"tables": {
"occurrenceColumns": [
"id",
"path",
"name",
"sourceName",
"parentId",
"transform",
"bbox",
"shapeStart",
"shapeCount",
"faceStart",
"faceCount",
"edgeStart",
"edgeCount",
"vertexStart",
"vertexCount",
],
"shapeColumns": [
"id",
"occurrenceId",
"ordinal",
"kind",
"bbox",
"center",
"area",
"volume",
"faceStart",
"faceCount",
"edgeStart",
"edgeCount",
"vertexStart",
"vertexCount",
],
"faceColumns": [
"id",
"occurrenceId",
"shapeId",
"ordinal",
"surfaceType",
"area",
"center",
"normal",
"bbox",
"edgeStart",
"edgeCount",
"relevance",
"flags",
"params",
"triangleStart",
"triangleCount",
],
"edgeColumns": [
"id",
"occurrenceId",
"shapeId",
"ordinal",
"curveType",
"length",
"center",
"bbox",
"faceStart",
"faceCount",
"vertexStart",
"vertexCount",
"relevance",
"flags",
"params",
"segmentStart",
"segmentCount",
],
"vertexColumns": [
"id",
"occurrenceId",
"shapeId",
"ordinal",
"center",
"bbox",
"edgeStart",
"edgeCount",
"relevance",
"flags",
],
},
"occurrences": [
[
"o1",
"1",
"Root",
"Root",
None,
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
{"min": [0.0, 0.0, 0.0], "max": [10.0, 10.0, 10.0]},
0,
1,
0,
2,
0,
2,
0,
1,
],
[
"o1.2",
"1.2",
"Bracket",
"Bracket",
"o1",
[1, 0, 0, 5, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
{"min": [5.0, 0.0, 0.0], "max": [10.0, 10.0, 10.0]},
0,
1,
0,
2,
0,
2,
0,
1,
],
],
"shapes": [
[
"o1.2.s1",
"o1.2",
1,
"solid",
{"min": [5.0, 0.0, 0.0], "max": [10.0, 10.0, 10.0]},
[7.5, 5.0, 5.0],
100.0,
250.0,
0,
2,
0,
2,
0,
1,
]
],
"faces": [
[
"o1.2.f1",
"o1.2",
"o1.2.s1",
1,
"plane",
20.0,
[6.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
{"min": [5.0, 0.0, 0.0], "max": [7.0, 2.0, 0.0]},
0,
2,
80,
0,
{"origin": [5.0, 0.0, 0.0], "axis": [0.0, 0.0, 1.0]},
0,
0,
],
[
"o1.2.f2",
"o1.2",
"o1.2.s1",
2,
"cylinder",
12.0,
[7.0, 2.0, 1.0],
[1.0, 0.0, 0.0],
{"min": [6.0, 1.0, 0.0], "max": [8.0, 3.0, 2.0]},
1,
0,
60,
0,
{"center": [7.0, 2.0, 1.0], "axis": [1.0, 0.0, 0.0], "radius": 1.0},
0,
0,
],
],
"edges": [
[
"o1.2.e1",
"o1.2",
"o1.2.s1",
1,
"line",
4.0,
[6.0, 1.0, 0.0],
{"min": [5.0, 0.0, 0.0], "max": [7.0, 2.0, 0.0]},
0,
2,
0,
1,
90,
0,
{"origin": [5.0, 0.0, 0.0], "direction": [1.0, 0.0, 0.0]},
0,
0,
],
[
"o1.2.e2",
"o1.2",
"o1.2.s1",
2,
"line",
3.0,
[5.5, 0.5, 0.0],
{"min": [5.0, 0.0, 0.0], "max": [6.0, 1.0, 0.0]},
2,
1,
1,
1,
75,
0,
{"origin": [5.0, 0.0, 0.0], "direction": [0.0, 1.0, 0.0]},
0,
0,
],
],
"vertices": [
[
"o1.2.v1",
"o1.2",
"o1.2.s1",
1,
[5.0, 0.0, 0.0],
{"min": [5.0, 0.0, 0.0], "max": [5.0, 0.0, 0.0]},
0,
2,
95,
0,
]
],
"assemblyMates": [
{
"id": "m1",
"label": "m1",
"sourceLabel": "face_to_face:block_pocket_floor_offset:bottom_center",
"type": "face_to_face",
"fixed": "block_pocket_floor:offset",
"moving": "bottom_center",
"parameters": {"offset": 0.2},
"fixedEndpoint": {
"position": [6.0, 1.0, 0.0],
"axes": {
"z": [0.0, 0.0, 1.0],
},
},
"movingEndpoint": {
"position": [7.0, 2.0, 1.0],
"axes": {
"z": [0.0, 0.0, 1.0],
},
},
}
],
"relations": {
"faceEdgeRows": [0, 1, 0],
"edgeFaceRows": [0, 1, 0],
"edgeVertexRows": [0, 0],
"vertexEdgeRows": [0, 1],
},
}
def _summary_manifest(cad_ref: str) -> dict[str, object]:
return {
"schemaVersion": STEP_TOPOLOGY_SCHEMA_VERSION,
"profile": "summary",
"cadPath": cad_ref,
"stepPath": f"{cad_ref}.step",
"stepHash": "step-hash-123",
"bbox": {"min": [0.0, 0.0, 0.0], "max": [10.0, 10.0, 10.0]},
"stats": {
"occurrenceCount": 1,
"leafOccurrenceCount": 1,
"shapeCount": 1,
"faceCount": 2,
"edgeCount": 2,
"vertexCount": 1,
},
"tables": {
"occurrenceColumns": [
"id",
"path",
"name",
"sourceName",
"parentId",
"transform",
"bbox",
"shapeStart",
"shapeCount",
"faceStart",
"faceCount",
"edgeStart",
"edgeCount",
"vertexStart",
"vertexCount",
],
"shapeColumns": [],
"faceColumns": [],
"edgeColumns": [],
"vertexColumns": [],
},
"occurrences": [
[
"o1",
"1",
"Part",
"Part",
None,
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
{"min": [0.0, 0.0, 0.0], "max": [10.0, 10.0, 10.0]},
0,
1,
0,
2,
0,
2,
0,
1,
]
],
"shapes": [],
"faces": [],
"edges": [],
"vertices": [],
}
class InspectRefsSyntaxTests(unittest.TestCase):
def test_normalize_selector_list_inherits_occurrence_prefix(self) -> None:
selectors = refs_syntax.normalize_selector_list("o1.2.f12,f13,e7,v2,s3")
self.assertEqual(
["o1.2.f12", "o1.2.f13", "o1.2.e7", "o1.2.v2", "o1.2.s3"],
selectors,
)
class InspectRefsTests(unittest.TestCase):
def setUp(self) -> None:
self._isolated_roots = IsolatedCadRoots(self, prefix="refs-inspect-")
tempdir = self._isolated_roots.temporary_cad_directory(prefix="tmp-refs-inspect-")
self._tempdir = tempdir
self.temp_root = Path(tempdir.name)
self.relative_dir = self.temp_root.relative_to(assembly_spec.CAD_ROOT).as_posix()
self.lookup_ref = f"{self.relative_dir}/sample"
self.cad_ref = self.lookup_ref
self.step_path = self.temp_root / "sample.step"
self.step_path.write_text("ISO-10303-21; END-ISO-10303-21;\n")
self.addCleanup(self._tempdir.cleanup)
self.addCleanup(lambda: shutil.rmtree(self.temp_root, ignore_errors=True))
def _touch_glb(self, step_path: Path | None = None) -> Path:
glb_path = part_glb_path(step_path or self.step_path)
glb_path.parent.mkdir(parents=True, exist_ok=True)
glb_path.write_bytes(b"glb")
return glb_path
@contextlib.contextmanager
def _mock_glb_topology(
self,
manifest: dict[str, object],
*,
step_path: Path | None = None,
buffers: dict[str, array] | None = None,
include_selector: bool = True,
include_index: bool = True,
current_hash: str | None = None,
strip_selector_keys: tuple[str, ...] = (),
):
resolved_step_path = step_path or self.step_path
edge_rendering = {
"visibilityClasses": ["feature", "tangent", "seam", "degenerate"],
"generatedVisibilityClasses": ["feature"],
"visibilityClassCounts": {"feature": 1},
"generatedVisibilityClassCounts": {"feature": 1},
}
mesh = {
"linearDeflection": 0.006,
"angularDeflection": 0.2,
"relative": True,
"resolution": {
"profile": "extra-fine",
"hints": {
"effectiveComplexityScore": 2,
"curvaturePressureScore": 2,
"leafOccurrenceCount": 1,
"occurrenceFaceCount": 2,
"occurrenceEdgeCount": 2,
},
},
}
topology_manifest = {"schemaVersion": STEP_TOPOLOGY_SCHEMA_VERSION, **manifest}
source_kind = str(topology_manifest.get("sourceKind") or "step").strip().lower()
source_path = resolved_step_path.with_suffix(".py") if source_kind == "python" else resolved_step_path
topology_manifest.setdefault("sourceKind", source_kind)
topology_manifest.setdefault("sourcePath", self._manifest_path(source_path))
topology_manifest.setdefault("stepPath", self._manifest_path(resolved_step_path))
topology_manifest.setdefault("edgeRendering", edge_rendering)
topology_manifest.setdefault("mesh", mesh)
edge_manifest = {
"schemaVersion": STEP_TOPOLOGY_SCHEMA_VERSION,
"profile": "surface-edges",
"edgeRendering": edge_rendering,
"primitiveAttributes": {
"barycentric": "_CAD_EDGE_BARYCENTRIC",
"class": "_CAD_EDGE_CLASS",
},
"buffers": {"views": {"surfaceHalfEdges": {}}},
}
if source_kind == "python":
edge_manifest["sourceKind"] = "python"
edge_manifest["sourcePath"] = topology_manifest.get("sourcePath")
edge_manifest["sourceHash"] = topology_manifest.get("sourceHash")
if topology_manifest.get("stepHash"):
edge_manifest["stepHash"] = topology_manifest.get("stepHash")
else:
edge_manifest["sourceKind"] = "step"
edge_manifest["sourcePath"] = topology_manifest.get("sourcePath")
edge_manifest["stepHash"] = topology_manifest.get("stepHash")
self._touch_glb(resolved_step_path)
stack = contextlib.ExitStack()
with stack:
stack.enter_context(mock.patch.object(step_targets, "find_step_path", return_value=resolved_step_path))
expected_step_hash = str(topology_manifest.get("stepHash") or "") if current_hash is None else current_hash
stack.enter_context(mock.patch.object(step_targets, "step_file_hash", return_value=expected_step_hash))
stack.enter_context(mock.patch.object(cad_generation, "step_file_hash", return_value=expected_step_hash))
stack.enter_context(
mock.patch.object(
step_targets,
"read_step_topology_manifest_from_glb",
return_value=topology_manifest if include_index else None,
)
)
stack.enter_context(
mock.patch.object(
step_targets,
"read_step_display_edge_manifest_from_glb",
return_value=edge_manifest,
)
)
stack.enter_context(mock.patch.object(step_targets, "glb_primitives_have_surface_edge_attributes", return_value=True))
stack.enter_context(mock.patch.object(step_targets, "glb_surface_edge_class_has_nonzero_values", return_value=True))
selector_topology_manifest = {
key: value for key, value in topology_manifest.items() if key not in strip_selector_keys
}
stack.enter_context(
mock.patch.object(
step_targets,
"read_step_topology_bundle_from_glb",
return_value=(
SelectorBundle(manifest=selector_topology_manifest, buffers=buffers or {})
if include_selector
else None
),
)
)
yield
def _manifest_path(self, path: Path) -> str:
resolved = path.resolve()
try:
return resolved.relative_to(assembly_spec.REPO_ROOT).as_posix()
except ValueError:
return resolved.as_posix()
def test_whole_entry_summary_uses_glb_index(self) -> None:
with self._mock_glb_topology(_summary_manifest(self.cad_ref), include_selector=False):
result = refs_inspect.inspect_cad_refs(self.cad_ref)
self.assertTrue(result["ok"])
token = result["tokens"][0]
self.assertEqual(1, token["summary"]["occurrenceCount"])
self.assertEqual(2, token["summary"]["faceCount"])
self.assertEqual([], token["selections"])
def test_facts_kind_falls_back_to_index_manifest_for_assembly(self) -> None:
manifest = {**_refs_manifest(self.cad_ref), "entryKind": "assembly"}
with self._mock_glb_topology(manifest, strip_selector_keys=("entryKind", "assembly")):
result = refs_inspect.inspect_cad_refs(self.cad_ref, facts=True)
self.assertTrue(result["ok"])
token = result["tokens"][0]
self.assertEqual("assembly", token["summary"]["kind"])
self.assertEqual("assembly", token["entryFacts"]["kind"])
def test_python_backed_glb_only_entry_inspects_without_step_file(self) -> None:
self.step_path.unlink()
script_path = self.step_path.with_suffix(".py")
script_path.write_text("def gen_step():\n return object()\n", encoding="utf-8")
source_identity = python_source_hash(script_path)
manifest = {
**_summary_manifest(self.cad_ref),
"sourceKind": "python",
"sourceHash": source_identity.source_hash,
}
with self._mock_glb_topology(manifest, include_selector=False):
result = refs_inspect.inspect_cad_refs(self.cad_ref)
self.assertTrue(result["ok"])
token = result["tokens"][0]
self.assertEqual(refs_inspect._relative_to_repo(self.step_path), token["stepPath"])
self.assertEqual(1, token["summary"]["occurrenceCount"])
def test_context_provider_can_supply_in_memory_entry_context(self) -> None:
requested_profiles = []
def provider(cad_path, profile):
requested_profiles.append(profile)
if cad_path != self.cad_ref:
return None
manifest = _summary_manifest(cad_path)
return refs_inspect.EntryContext(
cad_path=cad_path,
kind="part",
source_path=self.step_path,
step_path=self.step_path,
manifest=manifest,
selector_index=refs_inspect.lookup.build_selector_index(manifest),
)
result = refs_inspect.inspect_cad_refs(self.cad_ref, context_provider=provider)
self.assertTrue(result["ok"])
self.assertEqual([SelectorProfile.SUMMARY], requested_profiles)
self.assertEqual(2, result["tokens"][0]["summary"]["faceCount"])
def test_face_lookup_resolves_single_occurrence_alias_and_detail(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.inspect_cad_refs(self.cad_ref, "#o1.2.f1", detail=True)
self.assertTrue(result["ok"])
selection = result["tokens"][0]["selections"][0]
self.assertEqual("face", selection["selectorType"])
self.assertEqual("o1.2.f1", selection["normalizedSelector"])
self.assertEqual("plane area=20.0", selection["summary"])
self.assertEqual(["e1", "e2"], selection["detail"]["adjacentEdgeSelectors"])
def test_assembly_mate_lookup_resolves_numbered_ref(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.inspect_cad_refs(self.cad_ref, "#m1", detail=True, positioning=True)
self.assertTrue(result["ok"])
selection = result["tokens"][0]["selections"][0]
self.assertEqual("mate", selection["selectorType"])
self.assertEqual("m1", selection["normalizedSelector"])
self.assertEqual("#m1", selection["copyText"])
self.assertEqual("Mate face_to_face:block_pocket_floor_offset:bottom_center", selection["label"])
self.assertEqual("face_to_face block_pocket_floor:offset -> bottom_center", selection["summary"])
self.assertEqual("face_to_face:block_pocket_floor_offset:bottom_center", selection["detail"]["sourceLabel"])
self.assertEqual({"offset": 0.2}, selection["detail"]["parameters"])
self.assertEqual("mate", selection["positioning"]["selectorType"])
self.assertEqual([6.0, 1.0, 0.0], selection["positioning"]["fixedEndpoint"]["position"])
def test_vertex_lookup_resolves_corner_detail(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.inspect_cad_refs(self.cad_ref, "#o1.2.v1", detail=True)
self.assertTrue(result["ok"])
selection = result["tokens"][0]["selections"][0]
self.assertEqual("vertex", selection["selectorType"])
self.assertEqual("o1.2.v1", selection["normalizedSelector"])
self.assertEqual("corner edges=2", selection["summary"])
self.assertEqual(["e1", "e2"], selection["detail"]["adjacentEdgeSelectors"])
self.assertEqual(["f1", "f2"], selection["detail"]["adjacentFaceSelectors"])
def test_single_occurrence_alias_is_compacted_in_copy_text(self) -> None:
with self._mock_glb_topology(_summary_manifest(self.cad_ref)):
result = refs_inspect.inspect_cad_refs(self.cad_ref, "#f2", detail=True)
self.assertFalse(result["ok"])
with self._mock_glb_topology(
{
**_refs_manifest(self.cad_ref),
"stats": {
"occurrenceCount": 1,
"leafOccurrenceCount": 1,
"shapeCount": 1,
"faceCount": 2,
"edgeCount": 2,
"vertexCount": 1,
},
"occurrences": [_refs_manifest(self.cad_ref)["occurrences"][1]],
},
):
result = refs_inspect.inspect_cad_refs(self.cad_ref, "#v1", detail=True)
self.assertTrue(result["ok"])
selection = result["tokens"][0]["selections"][0]
self.assertEqual("v1", selection["displaySelector"])
self.assertEqual("#v1", selection["copyText"])
def test_old_part_selector_syntax_is_rejected(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.inspect_cad_refs(self.cad_ref, "#p:legacy.f1")
self.assertFalse(result["ok"])
self.assertEqual("selector", result["errors"][0]["kind"])
def test_topology_flag_returns_full_selector_lists(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.inspect_cad_refs(self.cad_ref, include_topology=True)
self.assertTrue(result["ok"])
topology = result["tokens"][0]["topology"]
self.assertIn("f1", topology["faces"])
self.assertIn("e1", topology["edges"])
self.assertIn("v1", topology["vertices"])
def test_detail_uses_glb_buffer_backed_relation_rows(self) -> None:
manifest = {
**_refs_manifest(self.cad_ref),
"relations": {
"faceEdgeRowsView": "faceEdgeRows",
"edgeFaceRowsView": "edgeFaceRows",
"edgeVertexRowsView": "edgeVertexRows",
"vertexEdgeRowsView": "vertexEdgeRows",
},
}
buffers = {
"faceEdgeRows": array("I", [0, 1, 0]),
"edgeFaceRows": array("I", [0, 1, 0]),
"edgeVertexRows": array("I", [0, 0]),
"vertexEdgeRows": array("I", [0, 1]),
}
with self._mock_glb_topology(manifest, buffers=buffers):
result = refs_inspect.inspect_cad_refs(self.cad_ref, "#o1.2.f1", detail=True)
self.assertTrue(result["ok"])
selection = result["tokens"][0]["selections"][0]
self.assertEqual(["e1", "e2"], selection["detail"]["adjacentEdgeSelectors"])
def test_missing_glb_topology_is_an_inspect_error(self) -> None:
result = refs_inspect.inspect_cad_refs(self.cad_ref)
self.assertFalse(result["ok"])
error = result["errors"][0]
self.assertEqual("glb_regeneration_failed", error["code"])
self.assertIn("\nRegenerate STEP artifacts with the following command using the CAD skill:", error["message"])
self.assertNotIn("scripts.step", error["message"])
self.assertIn("regenerateCommand", error)
self.assertEqual("python scripts/step", error["regenerateCommand"])
def test_missing_selector_topology_is_an_inspect_error(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref), include_selector=False):
result = refs_inspect.inspect_cad_refs(self.cad_ref, "#f1")
self.assertFalse(result["ok"])
error = result["errors"][0]
self.assertEqual("glb_regeneration_failed", error["code"])
self.assertIn("\nRegenerate STEP artifacts with the following command using the CAD skill:", error["message"])
def test_missing_step_topology_is_an_inspect_error(self) -> None:
with self._mock_glb_topology(_summary_manifest(self.cad_ref), include_index=False):
result = refs_inspect.inspect_cad_refs(self.cad_ref)
self.assertFalse(result["ok"])
error = result["errors"][0]
self.assertEqual("glb_regeneration_failed", error["code"])
self.assertIn("\nRegenerate STEP artifacts with the following command using the CAD skill:", error["message"])
def test_unsupported_step_topology_is_an_inspect_error(self) -> None:
self._touch_glb()
manifest = {**_summary_manifest(self.cad_ref), "schemaVersion": STEP_TOPOLOGY_SCHEMA_VERSION + 1}
with mock.patch.object(step_targets, "find_step_path", return_value=self.step_path), mock.patch.object(
step_targets,
"read_step_topology_manifest_from_glb",
return_value=manifest,
):
result = refs_inspect.inspect_cad_refs(self.cad_ref)
self.assertFalse(result["ok"])
error = result["errors"][0]
self.assertEqual("glb_regeneration_failed", error["code"])
self.assertIn("\nRegenerate STEP artifacts with the following command using the CAD skill:", error["message"])
def test_stale_glb_topology_is_an_inspect_error(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref), current_hash="new-step-hash"):
result = refs_inspect.inspect_cad_refs(self.cad_ref)
self.assertFalse(result["ok"])
error = result["errors"][0]
self.assertEqual("glb_regeneration_failed", error["code"])
self.assertIn("\nRegenerate STEP artifacts with the following command using the CAD skill:", error["message"])
def test_legacy_cad_ref_mismatch_is_accepted_when_hash_matches(self) -> None:
with self._mock_glb_topology({**_refs_manifest("other/ref"), "stepHash": "step-hash-123"}):
result = refs_inspect.inspect_cad_refs(self.cad_ref)
self.assertTrue(result["ok"])
self.assertEqual(self.cad_ref, result["tokens"][0]["cadPath"])
def test_non_leaf_occurrence_detail_reports_children(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.inspect_cad_refs(self.cad_ref, "#o1", detail=True)
self.assertTrue(result["ok"])
selection = result["tokens"][0]["selections"][0]
self.assertEqual("occurrence", selection["selectorType"])
self.assertEqual("o1", selection["normalizedSelector"])
self.assertEqual(1, selection["detail"]["childCount"])
self.assertEqual(["o1.2"], selection["detail"]["descendantOccurrenceIds"])
def test_assembly_topology_lookup_resolves_from_generated_step(self) -> None:
assembly_cad_ref = f"{self.relative_dir}/sample-assembly"
assembly_path = self.temp_root / "sample-assembly.py"
assembly_step_path = self.temp_root / "sample-assembly.step"
assembly_path.write_text(
"def gen_step():\n"
" return {'instances': []}\n",
encoding="utf-8",
)
assembly_step_path.write_text("ISO-10303-21; END-ISO-10303-21;\n", encoding="utf-8")
source_identity = python_source_hash(assembly_path)
with mock.patch.object(
step_targets,
"resolve_cad_source_path",
return_value=("assembly", assembly_path),
), self._mock_glb_topology(
{
**_refs_manifest(assembly_cad_ref),
"sourceKind": "python",
"sourceHash": source_identity.source_hash,
"stepHash": cad_generation.step_file_hash(assembly_step_path),
},
step_path=assembly_step_path,
):
result = refs_inspect.inspect_cad_refs(assembly_cad_ref, "#o1.2.f1", detail=True)
self.assertTrue(result["ok"])
selection = result["tokens"][0]["selections"][0]
self.assertEqual("assembly", result["tokens"][0]["summary"]["kind"])
self.assertEqual("face", selection["selectorType"])
self.assertEqual("o1.2.f1", selection["normalizedSelector"])
def test_positioning_flag_returns_plane_facts(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.inspect_cad_refs(
self.cad_ref,
"#o1.2.f1",
positioning=True,
)
self.assertTrue(result["ok"])
positioning = result["tokens"][0]["selections"][0]["positioning"]
self.assertEqual("plane", positioning["kind"])
self.assertEqual("z", positioning["axis"])
self.assertEqual(0.0, positioning["coordinate"])
self.assertEqual([0.0, 0.0, 1.0], positioning["normal"])
def test_planes_flag_returns_entry_planes(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.inspect_cad_refs(
self.cad_ref,
planes=True,
plane_coordinate_tolerance=0.01,
plane_min_area_ratio=0.0,
plane_limit=1,
)
self.assertTrue(result["ok"])
planes = result["tokens"][0]["planes"]
self.assertEqual(1, len(planes))
self.assertEqual("z", planes[0]["axis"])
def test_refs_text_format_includes_entry_reports(self) -> None:
result = {
"ok": True,
"tokens": [
{
"cadPath": self.cad_ref,
"summary": {"faceCount": 2, "edgeCount": 2},
"entryFacts": {
"size": [10.0, 10.0, 10.0],
"center": [5.0, 5.0, 5.0],
"extentAxis": "x",
"diag": 17.320508,
"kind": "part",
},
"planes": [
{
"axis": "z",
"coordinate": 0.0,
"normalSign": 1,
"faceCount": 1,
"totalArea": 100.0,
}
],
"selections": [],
}
],
"errors": [],
}
text = inspect_cli._format_refs_text(result, quiet=False, verbose=False)
self.assertIn("facts: size=[10, 10, 10]", text)
self.assertIn("planes: 1 major groups", text)
self.assertIn("z=0", text)
def test_diff_planes_returns_entry_planes(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.diff_entry_targets(
self.cad_ref,
self.cad_ref,
planes=True,
plane_limit=1,
)
self.assertTrue(result["ok"])
self.assertEqual(1, len(result["diff"]["leftMajorPlanes"]))
self.assertEqual(1, len(result["diff"]["rightMajorPlanes"]))
def test_cli_parses_current_agentic_commands(self) -> None:
parser = inspect_cli.build_parser()
refs_args = parser.parse_args(["refs", "entry.step", "#f1", "--detail", "--facts"])
self.assertEqual("refs", refs_args.command)
self.assertTrue(refs_args.detail)
self.assertTrue(refs_args.facts)
diff_args = parser.parse_args(
["diff", "left", "right", "--planes", "--plane-coordinate-tolerance", "0.02", "--plane-limit", "3"]
)
self.assertTrue(diff_args.planes)
self.assertEqual(0.02, diff_args.plane_coordinate_tolerance)
self.assertEqual(3, diff_args.plane_limit)
with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit) as render_exit:
parser.parse_args(["render", "list", "part.step", "--format", "text"])
self.assertEqual(2, render_exit.exception.code)
worker_args = parser.parse_args(["worker"])
self.assertEqual("worker", worker_args.command)
top_level_verbose_args = parser.parse_args(["--verbose", "refs", "entry.step", "#f1"])
self.assertTrue(top_level_verbose_args.verbose)
with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit) as verbose_render_exit:
parser.parse_args(["--verbose", "render", "view", "part.step", "--output", "part.png"])
self.assertEqual(2, verbose_render_exit.exception.code)
def test_worker_response_wraps_inspect_result(self) -> None:
response = inspect_cli._worker_response('{"id":"missing-input","argv":["refs"]}')
self.assertFalse(response["ok"])
self.assertEqual("missing-input", response["id"])
self.assertEqual(2, response["exitCode"])
self.assertIn("No STEP/CAD entry target provided", response["result"]["errors"][0]["message"])
def test_frame_command_returns_occurrence_axes(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.inspect_target_frame(self.cad_ref, "#o1.2")
self.assertTrue(result["ok"])
self.assertEqual([5.0, 0.0, 0.0], result["frame"]["translation"])
self.assertEqual([1.0, 0.0, 0.0], result["frame"]["localAxes"]["x"])
def test_measure_targets_returns_signed_axis_distance(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.measure_targets(
self.cad_ref,
"#o1.2.f1",
"#o1.2.f2",
axis="x",
)
self.assertTrue(result["ok"])
self.assertEqual("x", result["axis"])
self.assertEqual(5.0, result["from"]["coordinate"])
self.assertEqual(7.0, result["to"]["coordinate"])
self.assertEqual(2.0, result["measurement"]["signedDistance"])
def test_align_targets_returns_flush_translation_delta(self) -> None:
with self._mock_glb_topology(_refs_manifest(self.cad_ref)):
result = refs_inspect.align_targets(
self.cad_ref,
"#o1.2.f1",
"#o1.2.f2",
axis="x",
)
self.assertTrue(result["ok"])
self.assertEqual([2.0, 0.0, 0.0], result["alignment"]["translationVector"])
self.assertEqual(2.0, result["alignment"]["transformTranslationDelta"]["3"])
if __name__ == "__main__":
unittest.main()