134 lines
5.2 KiB
Python
134 lines
5.2 KiB
Python
"""用 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()
|