"""vehicle-registration 整车配准适配器 (v1.3)。 调用 alg/整车配准v1.3 的 translate_align_by_front_tail: - 输入:整车点云 PLY + 标准 STEP CAD - 原理:车头/车尾各采样少量点算质心差,融合成整体平移矩阵 - v1.3 新增:ICP 精配准、CAD 采样缓存、配准误差统计(RMSE/p95/p99) - 输出: aligned_pointcloud_translation_only.ply 配准后的完整点云 T_translation_only.txt 4x4 平移矩阵 result.json 额外约定 derivedPointClouds 字段:runner 会把列出的 PLY 回写为该任务的新点云记录(source='registered'),供后续测量算法选用。 """ 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, first_pointcloud, load_input, write_error_result, write_result, ) 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, "加载整车配准算法包 v1.3") from translate_align_pointcloud_to_cad import translate_align_by_front_tail # type: ignore pc_path = first_pointcloud(payload) cad_path = inputs.get("cadPath") if not cad_path: raise RuntimeError("整车配准需要 STEP CAD 文件,请在任务下上传并选中 CAD。") axis = str(config.get("axis") or "x") crop_ratio = float(config.get("cropRatio") or 0.01) mesh_deflection = float(config.get("meshDeflection") or 0.1) cad_sample_points = int(config.get("cadSamplePoints") or 30000) local_sample_points = int(config.get("localSamplePoints") or 1000) scale_pointcloud = float(config.get("scalePointcloud") or 1.0) # v1.3 新增参数 use_icp = bool(config.get("useIcp") if config.get("useIcp") is not None else True) icp_sample_points = int(config.get("icpSamplePoints") or 200000) compute_error_stats = bool(config.get("computeErrorStats") if config.get("computeErrorStats") is not None else True) use_cad_cache = bool(config.get("useCadCache") if config.get("useCadCache") is not None else True) emit_progress( 0.15, f"开始配准:{os.path.basename(pc_path)} ← {os.path.basename(str(cad_path))}" + (" + ICP" if use_icp else ""), ) result = translate_align_by_front_tail( cad_path=str(cad_path), pointcloud_path=pc_path, output_dir=str(out_dir), axis=axis, crop_ratio=crop_ratio, mesh_deflection=mesh_deflection, cad_sample_points=cad_sample_points, local_sample_points=local_sample_points, scale_pointcloud=scale_pointcloud, visualize=False, use_cad_cache=use_cad_cache, use_icp=use_icp, icp_sample_points=icp_sample_points, compute_error_stats=compute_error_stats, save_debug_samples=False, ) emit_progress(0.92, "整理结果") t_final = [float(v) for v in result["translation"]] ft_diff = float(result["front_tail_translation_difference"]) elapsed = float(result.get("elapsed_seconds") or 0) measurements = [ {"name": "最终平移量 Tx", "value": t_final[0], "unit": "mm", "status": "info"}, {"name": "最终平移量 Ty", "value": t_final[1], "unit": "mm", "status": "info"}, {"name": "最终平移量 Tz", "value": t_final[2], "unit": "mm", "status": "info"}, { "name": "前后端平移差", "value": ft_diff, "unit": "mm", "tolerance": 50.0, "status": "pass" if ft_diff <= 50.0 else "warning", }, ] # ICP 结果指标 icp_stats = result.get("icp_stats") if icp_stats: measurements.append({ "name": "ICP fitness", "value": float(icp_stats.get("fitness", 0)), "unit": "", "status": "pass" if float(icp_stats.get("fitness", 0)) >= 0.9 else "warning", }) measurements.append({ "name": "ICP inlier RMSE", "value": float(icp_stats.get("inlier_rmse", 0)), "unit": "mm", "status": "info", }) # 配准误差统计 error_stats = result.get("error_stats") if error_stats: rmse = float(error_stats.get("rmse", 0)) p95 = float(error_stats.get("p95", 0)) measurements += [ {"name": "配准 RMSE", "value": rmse, "unit": "mm", "status": "pass" if rmse <= 5.0 else "warning"}, {"name": "配准误差 p95", "value": p95, "unit": "mm", "status": "info"}, {"name": "配准误差 p99", "value": float(error_stats.get("p99", 0)), "unit": "mm", "status": "info"}, ] aligned_name = Path(result["aligned_path"]).name src_stem = Path(pc_path).stem # 下载文件:矩阵 + 误差统计(如有) files = [Path(result["matrix_path"]).name] error_stats_path = result.get("error_stats_path") if error_stats_path and Path(error_stats_path).exists(): files.append(Path(error_stats_path).name) payload_out = { "schemaVersion": 1, "status": "completed", "message": ( f"整车配准完成({'平移 + ICP' if use_icp else '仅平移'},耗时 {elapsed:.1f}s)。" "配准后的点云已加入任务点云列表,请在后续算法中选用。" ), "measurements": measurements, "overlay3d": {"kind": "none"}, "files": files, "derivedPointClouds": [ {"file": aligned_name, "name": f"{src_stem}_已配准.ply"} ], "extra": { "initialTranslation": [float(v) for v in result.get("initial_translation", result["translation"])], "finalTranslation": t_final, "tFront": [float(v) for v in result["t_front"]], "tTail": [float(v) for v in result["t_tail"]], "matrix": [[float(v) for v in row] for row in result["T_final"]], "icpStats": icp_stats, "elapsedSeconds": elapsed, "sourcePointCloud": os.path.basename(pc_path), "cad": os.path.basename(str(cad_path)), }, } 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"整车配准失败:{exc}", extra={"traceback": traceback.format_exc()}, ) return 1 if __name__ == "__main__": sys.exit(main())