feat: 升级算法包 delivery_v1.1 → v1.2,新增任务级 CAD 与测距面板

- 算法包替换为 delivery_v1.2(coupler_panel 新增 autotune/layer_select,underframe_deflection 新增 single_beam 管线),删除 delivery_v1.1
- manifests/adapters 的 PYTHONPATH 与版本说明同步更新到 v1.2
- 新增任务级 CAD 文件支持(task_cad_files 表、上传/读取路由、TaskManager 全选与加载显示)
- 新增 train-measurement 自定义前端面板(YAML 编辑器 + 可视化 ROI 配置 + 3D 取点)
- PointCloudViewer 支持 PLY 叠加层渲染与 ROI 拾取
- Dockerfile 改用 Miniforge 安装 pythonocc-core,修复 libgomp/libstdc++ 依赖
This commit is contained in:
2026-06-11 02:30:04 +00:00
parent 16811100d1
commit cec0766e67
143 changed files with 3087 additions and 12604 deletions

View File

@@ -0,0 +1,161 @@
"""``coupler-measure`` command-line entry point.
Three modes::
# measure an existing point cloud (production-like flow)
coupler-measure --input-cloud scan.ply --out-dir runs/demo
# research: synthesise a cloud from a STEP file and measure it
coupler-measure --stp panel.stp --out-dir runs/from_cad
# research: parameter sweep
coupler-measure --sweep matrix.yaml --out-dir runs/sweep_xx
"""
from __future__ import annotations
import argparse
import json
import logging
import sys
from pathlib import Path
log = logging.getLogger("coupler_panel.cli")
def _build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
prog="coupler-measure",
description="Coupler-panel CAD-STP to point-cloud measurement & validation.",
)
src = p.add_mutually_exclusive_group(required=True)
src.add_argument("--input-cloud", type=Path, help="Path to a PLY/XYZ/CSV/PCD point cloud.")
src.add_argument("--stp", type=Path, help="Path to a STEP file (research mode).")
src.add_argument("--sweep", type=Path, help="Path to a sweep matrix YAML.")
p.add_argument("--out-dir", type=Path, required=True, help="Output directory.")
p.add_argument("--config", type=Path, default=None, help="YAML config overrides.")
p.add_argument(
"--profile",
type=str,
default="auto",
help="auto | default | high_density | low_density | custom",
)
p.add_argument(
"--target-holes",
type=Path,
default=None,
help="DEPRECATED — the main pipeline no longer performs 4-hole Kabsch "
"registration (it cancels the panel deflection signal). The flag "
"is accepted for backward compatibility but is ignored.",
)
p.add_argument(
"--render",
action="store_true",
help="After measurement, render PNG diagnostics into <out-dir>/images/.",
)
p.add_argument("-v", "--verbose", action="count", default=0, help="-v: INFO, -vv: DEBUG")
return p
def _setup_logging(verbose: int) -> None:
level = logging.WARNING
if verbose == 1:
level = logging.INFO
elif verbose >= 2:
level = logging.DEBUG
logging.basicConfig(
level=level,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
def _on_progress(stage: str, fraction: float) -> None:
log.info("[%5.1f%%] %s", fraction * 100, stage)
def _maybe_render(out_dir: Path) -> None:
try:
from .viz import render_all
render_all(out_dir)
except ImportError:
log.warning("matplotlib not installed; skip --render")
except Exception as exc:
log.warning("render_all failed: %s", exc)
def main(argv: list[str] | None = None) -> int:
args = _build_parser().parse_args(argv)
_setup_logging(args.verbose)
try:
if args.input_cloud is not None:
from .pipeline import run_coupler_measurement_pipeline
if args.target_holes is not None:
log.warning(
"--target-holes is deprecated and ignored; the main pipeline "
"no longer performs Kabsch registration."
)
result = run_coupler_measurement_pipeline(
input_cloud=args.input_cloud,
out_dir=args.out_dir,
config_path=args.config,
profile=args.profile,
progress=_on_progress,
)
print(
json.dumps(
{
"theta_right_deg": result.theta_right_deg,
"theta_ref_deg": result.theta_ref_deg,
"dx_y_max_mm": result.dx_y_max_mm,
"dx_z_max_mm": result.dx_z_max_mm,
"quality_flags": result.quality_flags,
},
indent=2,
ensure_ascii=False,
)
)
elif args.stp is not None:
from .pipeline import run_from_cad_pipeline
summary = run_from_cad_pipeline(
stp_path=args.stp,
out_dir=args.out_dir,
config_path=args.config,
profile=args.profile,
progress=_on_progress,
)
print(
json.dumps(
{
"diff": summary["diff"],
"quality_flags": summary["measured"]["quality_flags"],
},
indent=2,
ensure_ascii=False,
)
)
elif args.sweep is not None:
from importlib import import_module
mod = import_module("experiments.sweep_runner")
mod.main(matrix_path=args.sweep, out_dir=args.out_dir, config_path=args.config)
if args.render:
_maybe_render(args.out_dir)
except Exception as exc:
log.exception("pipeline failed: %s", exc)
return 2
return 0
if __name__ == "__main__":
sys.exit(main())