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()