This commit is contained in:
2026-09-04 11:16:23 +08:00
parent 9e27f11bb7
commit 921ba5eab3
36 changed files with 2665 additions and 29 deletions
+147
View File
@@ -0,0 +1,147 @@
from __future__ import annotations
import json
import tempfile
import unittest
from pathlib import Path
from cadfs_to_cdsl.describe import describe_samples, select_description_samples
SAMPLE_FS = r'''
FeatureScript 1511;
import(path : "onshape/std/geometry.fs", version : "1511.0");
const mm = millimeter;
const FACE = EntityType.FACE;
function v(x, y){return vector(x, y);}
function sQuery(a, b, c) {return sketchEntityQuery(a, b, c);}
export const myFeature = defineFeature(function(context is Context, id is Id, definition is map)
precondition{}
{
{
var Q0;
Q0=qCreatedBy(makeId("Top.planeOp"),FACE);
var sketch = newSketch(context, id + "F0", { "sketchPlane" : qUnion([Q0])});
skLineSegment(sketch, "E0", {"start": v(-269.41, -156.6) * mm, "end": v(-0.92, 311.62) * mm});
skLineSegment(sketch, "E1", {"start": v(-0.92, 311.62) * mm, "end": v(270.33, -155.02) * mm});
skLineSegment(sketch, "E2", {"start": v(270.33, -155.02) * mm, "end": v(-269.41, -156.6) * mm});
skSolve(sketch);
}
{
var Q0;
Q0 = qSketchRegion(id + "F0", true);
extrude(context, id + "F1", {"entities" : qUnion([Q0]), "depth" : 1828.8 * mm});
}
});
'''
SAMPLE_ANNOTATION = """Step 1 - Sketch
Draw a closed triangle.
Step 2 - Extrude NEW
Extrude the triangular area upward a distance of 1828.8 mm.
"""
class FailingVision:
def describe(self, *, sample_id: str, image_path: Path, local_facts: dict[str, object]) -> dict[str, object]:
raise RuntimeError("vision offline")
class StaticVision:
def describe(self, *, sample_id: str, image_path: Path, local_facts: dict[str, object]) -> dict[str, object]:
return {
"category": "视觉增强三角楔块(候选)",
"category_confidence": 0.7,
"candidate_names": ["视觉楔块"],
"summary_zh": "视觉模型认为该模型是三角楔块候选件。",
"possible_functions": ["可能用于定位。"],
"applications": ["夹具。"],
"structural_features": ["斜面"],
"geometric_features": ["三角截面"],
"keywords_zh": ["视觉识别"],
"keywords_en": ["vision wedge"],
"uncertainties": ["视觉语义仍需人工确认。"],
}
class DescribeTests(unittest.TestCase):
def test_selects_only_requested_shard(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
self._write_sample(root, "0005", "00050089")
self._write_sample(root, "0006", "00060001")
selected = select_description_samples(root, shard="0005")
self.assertEqual([sample.sample_id for sample in selected], ["00050089"])
def test_hybrid_falls_back_and_writes_txt_manifest(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
self._write_sample(root, "0005", "00050089")
records, summary = describe_samples(root, shard="0005", mode="hybrid", vision_client=FailingVision())
self.assertEqual(summary["sample_count"], 1)
self.assertEqual(summary["statuses"], {"local_fallback": 1})
txt_path = root / "description_txt/0005/00050089.txt"
self.assertTrue(txt_path.is_file())
text = txt_path.read_text(encoding="utf-8")
self.assertIn("样本ID:00050089", text)
self.assertIn("三角棱柱", text)
self.assertIn("证据与不确定性", text)
manifest = root / "description_txt/description_manifest.jsonl"
rows = [json.loads(line) for line in manifest.read_text(encoding="utf-8").splitlines()]
self.assertEqual(len(rows), 1)
self.assertEqual(rows[0]["sample_id"], "00050089")
self.assertEqual(rows[0]["status"], "local_fallback")
self.assertIn("vision_failed", [item["code"] for item in records[0]["diagnostics"]])
def test_hybrid_uses_vision_when_available(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
self._write_sample(root, "0005", "00050089")
records, _summary = describe_samples(root, shard="0005", mode="hybrid", vision_client=StaticVision())
self.assertEqual(records[0]["status"], "described_hybrid")
self.assertEqual(records[0]["category"], "视觉增强三角楔块(候选)")
self.assertIn("视觉楔块", records[0]["candidate_names"])
def test_existing_txt_is_skipped_without_force(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
self._write_sample(root, "0005", "00050089")
first, _ = describe_samples(root, shard="0005", mode="local")
txt_path = root / "description_txt/0005/00050089.txt"
before = txt_path.read_text(encoding="utf-8")
second, _ = describe_samples(root, shard="0005", mode="local")
self.assertEqual(second[0]["status"], first[0]["status"])
self.assertEqual(txt_path.read_text(encoding="utf-8"), before)
@staticmethod
def _write_sample(root: Path, shard: str, sample_id: str) -> None:
for directory, suffix, content in (
("featurescript_rp", ".txt", SAMPLE_FS),
("text_annotations", ".txt", SAMPLE_ANNOTATION),
("step_abc", ".step", "ISO-10303-21;\nEND-ISO-10303-21;\n"),
("stl_abc", ".stl", "solid sample\nendsolid sample\n"),
):
path = root / directory / shard / f"{sample_id}{suffix}"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
image = root / "multiview_images_abc" / shard / f"{sample_id}.png"
image.parent.mkdir(parents=True, exist_ok=True)
image.write_bytes(
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"
b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00"
b"\x90wS\xde\x00\x00\x00\x00IEND\xaeB`\x82"
)
if __name__ == "__main__":
unittest.main()