Initial commit
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
"""用 SolidWorks evidence 的 document_truth 验收 output3 CDSL 重建结果。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from build123d import CenterOf, import_step
|
||||
|
||||
|
||||
FORBIDDEN_CDSL_KEYS = {
|
||||
"compiler_context",
|
||||
"entities",
|
||||
"contour_edges_mm",
|
||||
"contour_regions_mm",
|
||||
"_raw_entities",
|
||||
"vertices",
|
||||
}
|
||||
|
||||
|
||||
def _find_forbidden(value: Any, path: str = "$") -> list[str]:
|
||||
found: list[str] = []
|
||||
if isinstance(value, dict):
|
||||
for key, item in value.items():
|
||||
child = f"{path}.{key}"
|
||||
if key in FORBIDDEN_CDSL_KEYS:
|
||||
found.append(child)
|
||||
found.extend(_find_forbidden(item, child))
|
||||
elif isinstance(value, list):
|
||||
for index, item in enumerate(value):
|
||||
found.extend(_find_forbidden(item, f"{path}[{index}]"))
|
||||
return found
|
||||
|
||||
|
||||
def validate(cdsl_path: Path, evidence_dir: Path) -> dict[str, Any]:
|
||||
cdsl = json.loads(cdsl_path.read_text(encoding="utf-8"))
|
||||
part_id = str(cdsl["part_id"])
|
||||
source_name = str(cdsl["meta"]["source"])
|
||||
evidence = json.loads((evidence_dir / source_name).read_text(encoding="utf-8"))
|
||||
truth = evidence["document_truth"]
|
||||
mass = truth["mass_properties"]
|
||||
|
||||
step_path = cdsl_path.with_name(f"{part_id}_rebuilt.step")
|
||||
report_path = cdsl_path.with_name(f"{part_id}.rebuild_report.json")
|
||||
report = json.loads(report_path.read_text(encoding="utf-8"))
|
||||
solid = import_step(str(step_path))
|
||||
|
||||
truth_volume = float(mass["volume"]) * 1e9
|
||||
truth_area = float(mass["surface_area"]) * 1e6
|
||||
truth_com = [float(value) * 1000.0 for value in mass["center_of_mass"]]
|
||||
rebuilt_com_vector = solid.center(CenterOf.MASS)
|
||||
rebuilt_com = [rebuilt_com_vector.X, rebuilt_com_vector.Y, rebuilt_com_vector.Z]
|
||||
bbox = solid.bounding_box()
|
||||
rebuilt_bbox = [bbox.min.X, bbox.min.Y, bbox.min.Z, bbox.max.X, bbox.max.Y, bbox.max.Z]
|
||||
truth_bbox = [float(value) * 1000.0 for value in truth["geometry"]["bounding_box"]]
|
||||
|
||||
volume_error_pct = abs(float(solid.volume) - truth_volume) / truth_volume * 100.0
|
||||
area_error_pct = abs(float(solid.area) - truth_area) / truth_area * 100.0
|
||||
com_delta_mm = math.dist(rebuilt_com, truth_com)
|
||||
bbox_max_delta_mm = max(abs(a - b) for a, b in zip(rebuilt_bbox, truth_bbox))
|
||||
forbidden = _find_forbidden(cdsl)
|
||||
engine = report.get("engine_result", {}).get("engine")
|
||||
|
||||
checks = {
|
||||
"engine_cdsl_only": engine == "cdsl_only",
|
||||
"no_forbidden_geometry_payload": not forbidden,
|
||||
"volume_error_le_1pct": volume_error_pct <= 1.0,
|
||||
"surface_area_error_le_1pct": area_error_pct <= 1.0,
|
||||
"center_of_mass_delta_le_0_1mm": com_delta_mm <= 0.1,
|
||||
"bbox_delta_le_0_01mm": bbox_max_delta_mm <= 0.01,
|
||||
}
|
||||
return {
|
||||
"part_id": part_id,
|
||||
"cdsl_path": str(cdsl_path),
|
||||
"rebuilt_step": str(step_path),
|
||||
"source_evidence": str(evidence_dir / source_name),
|
||||
"cdsl_lines": len(cdsl_path.read_text(encoding="utf-8").splitlines()),
|
||||
"feature_count": len(cdsl.get("features") or []),
|
||||
"sketch_count": len((cdsl.get("geometry") or {}).get("sketches") or []),
|
||||
"engine": engine,
|
||||
"forbidden_paths": forbidden,
|
||||
"metrics": {
|
||||
"truth_volume_mm3": truth_volume,
|
||||
"rebuilt_volume_mm3": float(solid.volume),
|
||||
"volume_error_pct": volume_error_pct,
|
||||
"truth_surface_area_mm2": truth_area,
|
||||
"rebuilt_surface_area_mm2": float(solid.area),
|
||||
"surface_area_error_pct": area_error_pct,
|
||||
"center_of_mass_delta_mm": com_delta_mm,
|
||||
"bbox_max_delta_mm": bbox_max_delta_mm,
|
||||
},
|
||||
"checks": checks,
|
||||
"passed": all(checks.values()),
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--evidence", type=Path, required=True)
|
||||
parser.add_argument("--report", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
results = [
|
||||
validate(path, args.evidence)
|
||||
for path in sorted(args.output.glob("cylinder_*/*.cdsl.json"))
|
||||
]
|
||||
summary = {
|
||||
"schema": "cad.cdsl.output3.validation.v1",
|
||||
"count": len(results),
|
||||
"passed_count": sum(item["passed"] for item in results),
|
||||
"failed_count": sum(not item["passed"] for item in results),
|
||||
"max_volume_error_pct": max(item["metrics"]["volume_error_pct"] for item in results),
|
||||
"max_surface_area_error_pct": max(item["metrics"]["surface_area_error_pct"] for item in results),
|
||||
"max_center_of_mass_delta_mm": max(item["metrics"]["center_of_mass_delta_mm"] for item in results),
|
||||
"max_bbox_delta_mm": max(item["metrics"]["bbox_max_delta_mm"] for item in results),
|
||||
"results": results,
|
||||
}
|
||||
args.report.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.report.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
print(
|
||||
f"validated={summary['count']} passed={summary['passed_count']} "
|
||||
f"failed={summary['failed_count']} report={args.report}"
|
||||
)
|
||||
if summary["failed_count"]:
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user