Files
massive-cloud/server/plugins/adapters/coupler_panel_run.py
lipan 29f1d45d66 feat: 算法升级、整车配准集成与性能分析文档
算法更新: delivery v1.2.1、平整度v1.5、测距v2.2、整车配准v1.3
适配器: vehicle-registration 新增、各算法 manifest 和 adapter 更新
后端: taskCadFiles/source 字段迁移、derivedPointClouds 注册、取消任务联动
前端: ElapsedTime 时长显示、两步算法流程、canRun 逻辑修复
文档: 新增5份算法性能分析报告
2026-06-25 08:22:20 +00:00

225 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""coupler-panel 算法适配器 (v1.2.1)。
v1.2.1 变更:
- 算法包升级至 delivery_v1.2.1
- 新增项点 Excel 报表(write_inspection_report):
项点名称/理论尺寸/公差要求/测量结果/误差结果/测量结论
- PYTHONPATH 指向 delivery_v1.2.1/coupler_panel_release
v1.2 变更:
- 新增 autotune 自动调参与 layer_select 分层选点
v1.1 变更:
- 入口从 compute_coupler_measurement → run_coupler_measurement_pipeline
- 新增 D_y_max_mm / D_z_max_mm 字段
- 支持可选 CAD STP 路径(cad_stp_path)和 ROI JSON(roi_json)
"""
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 _status_for(value: float, tolerance: float | None) -> str:
if tolerance is None:
return "info"
if abs(value) <= tolerance:
return "pass"
if abs(value) <= tolerance * 1.5:
return "warning"
return "fail"
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, "加载 coupler_panel v1.2.1 算法包")
from coupler_panel.pipeline import run_coupler_measurement_pipeline # type: ignore
pc_path = first_pointcloud(payload)
emit_progress(0.15, f"读取点云:{os.path.basename(pc_path)}")
profile = str(config.get("profile") or "auto")
right_axis_sign = int(config.get("rightAxisSign") or 1)
# 可选 CAD STP 路径(前端传入 cadPath 时使用)
cad_stp_path = inputs.get("cadPath") or None
# ROI 裁剪:优先用 roiJson 文件,其次用前端填的 6 个坐标参数
roi_json = inputs.get("roiJson") or None
roi_spec = None
if not roi_json:
def _f(key: str) -> float | None:
v = config.get(key)
try:
return float(v) if v is not None and str(v).strip() != "" else None
except (TypeError, ValueError):
return None
x_min, x_max = _f("roiXMin"), _f("roiXMax")
y_min, y_max = _f("roiYMin"), _f("roiYMax")
z_min, z_max = _f("roiZMin"), _f("roiZMax")
# 至少有一对轴的范围被填写时才构建 ROI
if any(v is not None for v in (x_min, x_max, y_min, y_max, z_min, z_max)):
from coupler_panel.roi import ROISpec # type: ignore
roi_spec = ROISpec(
x_min=x_min if x_min is not None else float("-inf"),
x_max=x_max if x_max is not None else float("inf"),
y_min=y_min if y_min is not None else float("-inf"),
y_max=y_max if y_max is not None else float("inf"),
z_min=z_min if z_min is not None else float("-inf"),
z_max=z_max if z_max is not None else float("inf"),
)
emit_progress(0.20, f"ROI 裁剪:x[{x_min},{x_max}] y[{y_min},{y_max}] z[{z_min},{z_max}]")
emit_progress(0.25, "调用 run_coupler_measurement_pipeline")
def _on_progress(stage: str, fraction: float) -> None:
emit_progress(0.25 + min(1.0, max(0.0, fraction)) * 0.70, stage)
result = run_coupler_measurement_pipeline(
input_cloud=pc_path,
out_dir=str(out_dir),
profile=profile,
cad_stp_path=cad_stp_path,
roi_spec=roi_spec, # 优先用坐标参数构建的 ROISpec
roi_json=roi_json, # 备选:直接传 ROI JSON 文件
progress=_on_progress,
)
emit_progress(0.97, "整理结果")
# right_axis_sign 影响 theta_right_deg 的符号约定
theta_right = float(result.theta_right_deg) * right_axis_sign
theta_ref = float(result.theta_ref_deg)
# ── 项点 Excel 报表(v1.2.1 新增)────────────────────────────────────
# 用未截断的拟合平面法向量算原始角度(避免微小角被钳为 0),
# 公差按斜率形式给出:参考面 0.8/375mm,纵向中心 1/700mm。
# 产物 xlsx 由 write_result 自动发现并写入 files 字段。
try:
import math
from coupler_panel.reporting import InspectionItem, write_inspection_report # type: ignore
raw_right, raw_ref = theta_right, theta_ref
report_dict = result.report if isinstance(result.report, dict) else {}
normal = (report_dict.get("plane") or {}).get("normal")
if normal and len(normal) == 3:
nx, ny, nz = (float(v) for v in normal)
raw_right = math.degrees(math.atan2(ny, nx)) * right_axis_sign
raw_ref = math.degrees(math.atan2(nz, nx))
item_ref = InspectionItem(
name="车钩面板距参考面的右偏转角(头车一位端处车钩)",
theta_deg=raw_ref,
baseline_mm=375.0,
tol_value_mm=0.8,
)
item_long = InspectionItem(
name="车钩面板距纵向中心的右偏转角(头车一位端处车钩)",
theta_deg=raw_right,
baseline_mm=700.0,
tol_value_mm=1.0,
)
write_inspection_report(item_ref, out_dir / "report_ref_plane.xlsx")
write_inspection_report(item_long, out_dir / "report_longitudinal.xlsx")
except Exception:
# Excel 报表失败不影响主结果
traceback.print_exc()
dx_y = float(result.dx_y_max_mm)
dx_z = float(result.dx_z_max_mm)
D_y = float(result.D_y_max_mm)
D_z = float(result.D_z_max_mm)
measurements = [
{
"name": "偏转角 θ_right (右向)",
"value": theta_right,
"unit": "deg",
"tolerance": 0.5,
"status": _status_for(theta_right, 0.5),
},
{
"name": "偏转角 θ_ref (参考向)",
"value": theta_ref,
"unit": "deg",
"tolerance": 0.5,
"status": _status_for(theta_ref, 0.5),
},
{
"name": "最大 x 偏差 dx_y",
"value": dx_y,
"unit": "mm",
"tolerance": 1.0,
"status": _status_for(dx_y, 1.0),
},
{
"name": "最大 x 偏差 dx_z",
"value": dx_z,
"unit": "mm",
"tolerance": 1.0,
"status": _status_for(dx_z, 1.0),
},
{
"name": "面板 y 向最大偏差 D_y",
"value": D_y,
"unit": "mm",
"status": "info",
},
{
"name": "面板 z 向最大偏差 D_z",
"value": D_z,
"unit": "mm",
"status": "info",
},
]
payload_out = {
"schemaVersion": getattr(result, "schema_version", "1.1"),
"status": "completed",
"measurements": measurements,
"overlay3d": {"kind": "none"},
"extra": {
"algorithmVersion": getattr(result, "algorithm_version", None),
"qualityFlags": list(result.quality_flags or []),
"normal": list(result.normal) if result.normal else None,
"centerC": list(result.center_C) if result.center_C else None,
"report": result.report if isinstance(result.report, dict) else None,
},
}
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"coupler-panel 计算失败:{exc}",
extra={"traceback": traceback.format_exc()},
)
return 1
if __name__ == "__main__":
sys.exit(main())