"""underframe-deflection 算法适配器 (v1.2)。 v1.2 变更: - 算法包升级至 delivery_v1.2(新增 single_beam 单梁管线) - PYTHONPATH 指向 delivery_v1.2 v1.1 变更: - 入口改为 run_measurement_pipeline(返回 dict,同时落盘 report.json / xlsx / PLY) - 新增 cad_path(CAD 基准 PLY/STL)和 roi_path(ROI JSON)可选输入 - 新增 skip_registration / enable_bilateral 配置项 - 结果从 report dict 中读取,而非 DeflectionResult 对象字段 """ from __future__ import annotations import os import sys import traceback from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) from _plugin_io import ( emit_progress, ensure_ascii_ply, first_pointcloud, load_input, write_error_result, write_result, ) def _status_for(value: float | None, tolerance: float | None) -> str: if value is None or tolerance is None: return "info" if abs(value) <= tolerance: return "pass" if abs(value) <= tolerance * 1.5: return "warning" return "fail" def _safe_float(v: object) -> float | None: try: return float(v) # type: ignore[arg-type] except (TypeError, ValueError): return None def main() -> int: payload = load_input(sys.argv) inputs = payload.get("inputs") or {} config = inputs.get("config") or {} out_dir = Path(payload["outDir"]) out_dir.mkdir(parents=True, exist_ok=True) try: emit_progress(0.05, "加载 underframe_deflection v1.2 算法包") from underframe_deflection.pipeline import run_measurement_pipeline # type: ignore pc_path = first_pointcloud(payload) emit_progress(0.15, f"读取点云:{os.path.basename(pc_path)}") # 若是二进制 PLY,先转成 ASCII PLY(算法内置 reader 不依赖 open3d) pc_path = ensure_ascii_ply(pc_path, out_dir / "_ply_cache") profile = str(config.get("profile") or "auto") skip_registration = bool(config.get("skipRegistration") or False) enable_bilateral = bool(config.get("enableBilateral") if "enableBilateral" in config else True) # 可选 CAD 基准(PLY/STL)和 ROI JSON cad_path = inputs.get("cadPath") or None roi_path = inputs.get("roiPath") or None emit_progress(0.20, "调用 run_measurement_pipeline") # run_measurement_pipeline 内部会打印进度到 stdout, # 我们在调用前后各报一次进度即可。 report: dict = run_measurement_pipeline( input_cloud=pc_path, out_dir=str(out_dir), profile=profile, cad_path=cad_path, roi_path=roi_path, skip_registration=skip_registration, enable_bilateral=enable_bilateral, skip_excel=False, ) emit_progress(0.92, "整理结果") # ── 从 report dict 中提取核心测量值 ────────────────────────────────── deflection = report.get("deflection") or {} left_sel = (deflection.get("left") or {}).get("selected_measurement") or {} right_sel = (deflection.get("right") or {}).get("selected_measurement") or {} left_mm = _safe_float(left_sel.get("signed_deflection_mm")) right_mm = _safe_float(right_sel.get("signed_deflection_mm")) registration = report.get("registration") or {} reg_rms = _safe_float(registration.get("rms_mm")) quality_flags: list[str] = report.get("quality_flags") or [] # 容差:高铁底架挠度通常要求 ≤ 3 mm(可在 manifest inputs 中暴露) tol = _safe_float(config.get("toleranceMm")) or 3.0 measurements = [ { "name": "左梁挠度", "value": left_mm if left_mm is not None else float("nan"), "unit": "mm", "tolerance": tol, "status": _status_for(left_mm, tol), }, { "name": "右梁挠度", "value": right_mm if right_mm is not None else float("nan"), "unit": "mm", "tolerance": tol, "status": _status_for(right_mm, tol), }, ] if reg_rms is not None: measurements.append( { "name": "配准 RMS", "value": reg_rms, "unit": "mm", "status": "pass" if reg_rms <= 1.0 else "warning" if reg_rms <= 2.0 else "fail", } ) # ── 3D 叠加层:优先 out_dir/ply/ 子目录,回退到 out_dir 根目录 ────────── overlay3d: dict = {"kind": "none"} ply_files: list = [] ply_dir = out_dir / "ply" if ply_dir.exists(): ply_files = sorted(ply_dir.glob("*.ply")) if not ply_files: # 算法直接输出到 out_dir 根目录;优先取 full overlay for name in ("roi_measurement_overlay_full.ply", "roi_cropped_cloud.ply"): candidate = out_dir / name if candidate.exists(): ply_files = [candidate] break if not ply_files: ply_files = sorted(out_dir.glob("*.ply")) if ply_files: rel = ply_files[0].relative_to(out_dir) overlay3d = { "kind": "ply", "file": str(rel), } payload_out = { "schemaVersion": report.get("schema_version", "1.1"), "status": "completed", "measurements": measurements, "overlay3d": overlay3d, "extra": { "algorithmVersion": report.get("algorithm_version"), "qualityFlags": quality_flags, "registration": registration, "deflectionFull": deflection, }, } write_result(out_dir, payload_out) emit_progress(1.0, "完成") return 0 except Exception as exc: traceback.print_exc() write_error_result( out_dir, f"underframe-deflection 计算失败:{exc}", extra={"traceback": traceback.format_exc()}, ) return 1 if __name__ == "__main__": sys.exit(main())