Files
cdsl-cad/onshape_to_cdsl/tests/test_pipeline.py
T
2026-09-02 13:51:35 +08:00

127 lines
7.2 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
import shutil
import tempfile
import unittest
from onshape_to_cdsl.compare import strict_compare
from onshape_to_cdsl.convert import convert_one
from onshape_to_cdsl.issues import write_issue_register
from onshape_to_cdsl.merge_scans import merge_scans
from onshape_to_cdsl.select import select
from onshape_to_cdsl.sketches import sketch_to_cdsl, workplane_from_matrix
from onshape_to_cdsl.source_urls import read_url_file
from onshape_to_cdsl.units import length_mm
class PipelineTests(unittest.TestCase):
def test_url_file_and_units(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
mapping = Path(temporary) / "objects.yml"
mapping.write_text("{'00000352': 'https://cad.onshape.com/documents/doc/w/work/e/element'}\n", encoding="utf-8")
reference = read_url_file(mapping)[0]
self.assertEqual(reference.sample_id, "00000352")
self.assertEqual(reference.did, "doc")
self.assertAlmostEqual(length_mm(".063 in"), 1.6002)
self.assertAlmostEqual(length_mm("1.2*cm"), 12.0)
with self.assertRaises(ValueError):
length_mm("width")
def test_solved_rectangle_becomes_closed_analytic_contour(self) -> None:
matrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, .002, 0, 0, 0, 1]
self.assertEqual(workplane_from_matrix(matrix)["origin_mm"], [0.0, 0.0, 2.0])
point = lambda x, y: {"x": x / 1000, "y": y / 1000, "z": .002}
entities = []
for start, end in [((0, 0), (10, 0)), ((10, 0), (10, 5)), ((10, 5), (0, 5)), ((0, 5), (0, 0))]:
entities.append({"sketchEntityType": "skLineSegment", "isConstruction": False, "startPosition3d": point(*start), "endPosition3d": point(*end)})
cdsl = sketch_to_cdsl({"featureId": "sketch_one", "sketchMatrix": matrix, "entities": entities})
contour = cdsl["profile"]["contours"][0]
self.assertTrue(contour["closed"])
self.assertEqual(len(contour["segments"]), 4)
self.assertEqual(contour["segments"][0]["type"], "line")
def test_conversion_rejects_unsupported_history(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary) / "raw" / "sample_01"
root.mkdir(parents=True)
(root / "features.json").write_text(json.dumps({"features": [{"featureId": "x", "featureType": "loft", "parameters": []}]}), encoding="utf-8")
(root / "sketches.json").write_text(json.dumps({"sketches": []}), encoding="utf-8")
result = convert_one(root, Path(temporary) / "converted")
self.assertEqual(result["status"], "rejected")
self.assertEqual(result["diagnostics"][0]["status"], "deferred")
def test_issue_register_summarizes_engine_capability_gaps(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
converted = Path(temporary) / "converted"
sample = converted / "sample_01"
sample.mkdir(parents=True)
(sample / "conversion.json").write_text(json.dumps({"sample_id": "sample_01", "status": "rejected", "diagnostics": [{"feature_id": "loft_1", "feature_type": "loft", "status": "deferred", "reason": "loft executor is not implemented by the engine"}]}), encoding="utf-8")
register = write_issue_register(converted, [{"sample_id": "sample_01", "status": "rejected", "failure_reason": "conversion_not_executable"}])
self.assertEqual(register["summary"], {"engine_capability_gap": 1})
document = (Path(temporary) / "reconstruction_issues.md").read_text(encoding="utf-8")
self.assertIn("Engine Work Queue", document)
self.assertIn("loft executor", document)
def test_deterministic_stratified_selection(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
scan = Path(temporary) / "scan.json"
records = [{"sample_id": str(index), "status": "scanned", "feature_count": index, "feature_types": {"extrude": 1}, "curve_types": {"line": 4}} for index in range(8)]
scan.write_text(json.dumps({"records": records}), encoding="utf-8")
one = select(scan, Path(temporary) / "one.json", count=4, seed=7)
two = select(scan, Path(temporary) / "two.json", count=4, seed=7)
self.assertEqual([item["sample_id"] for item in one["records"]], [item["sample_id"] for item in two["records"]])
def test_merge_scan_batches_rejects_duplicate_ids(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
first, second = root / "first.json", root / "second.json"
first.write_text(json.dumps({"records": [{"sample_id": "0001", "status": "scanned"}]}), encoding="utf-8")
second.write_text(json.dumps({"records": [{"sample_id": "0002", "status": "failed"}]}), encoding="utf-8")
merged = merge_scans([first, second], root / "merged.json")
self.assertEqual([item["sample_id"] for item in merged["records"]], ["0001", "0002"])
second.write_text(json.dumps({"records": [{"sample_id": "0001", "status": "failed"}]}), encoding="utf-8")
with self.assertRaises(ValueError):
merge_scans([first, second], root / "bad.json")
def test_strict_comparator_accepts_identical_and_rejects_translation(self) -> None:
from build123d import Box, export_step
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
source, same, shifted = root / "source.step", root / "same.step", root / "shifted.step"
box = Box(10, 10, 10)
export_step(box, source)
export_step(box, same)
moved = Box(10, 10, 10).translate((0.02, 0, 0))
export_step(moved, shifted)
self.assertTrue(strict_compare(source, same)["passed"])
rejection = strict_compare(source, shifted)
self.assertFalse(rejection["passed"])
self.assertIn("bbox_exceeds_0.01mm", rejection["failure_reasons"])
def test_00000352_offline_end_to_end_when_fixture_is_available(self) -> None:
fixture = Path.cwd() / "json_to_cdsl/input/onshape_complete/00000352"
if not (fixture / "model.step").exists():
self.skipTest("optional local 00000352 raw fixture is not installed")
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
raw = root / "raw" / "00000352"
raw.parent.mkdir()
shutil.copytree(fixture, raw)
result = convert_one(raw, root / "converted")
self.assertEqual(result["status"], "converted")
from cdsl_engine.runtime import rebuild_cdsl
from cdsl_engine.semantic_validation import validate_semantic_cdsl
cdsl_path = root / "converted/00000352/candidate.cdsl.json"
cdsl = json.loads(cdsl_path.read_text(encoding="utf-8"))
validate_semantic_cdsl(cdsl)
rebuilt = root / "rebuild.step"
rebuild_cdsl(cdsl, rebuilt, strict=True)
comparison = strict_compare(raw / "model.step", rebuilt)
self.assertTrue(comparison["passed"], comparison["failure_reasons"])
if __name__ == "__main__":
unittest.main()