from __future__ import annotations import argparse def build_parser() -> argparse.ArgumentParser: common = argparse.ArgumentParser(add_help=False) common.add_argument("--config", default=argparse.SUPPRESS, help="YAML config path, default uses built-in settings") parser = argparse.ArgumentParser(description="Industrial geometry measurement workflow") parser.add_argument("--config", default=None, help="YAML config path, default uses built-in settings") subparsers = parser.add_subparsers(dest="command", required=True) cad = subparsers.add_parser("cad2mesh", parents=[common], help="Convert STEP CAD to STL Mesh") cad.add_argument("--cad", required=False, help="Input STEP file") cad.add_argument("--mesh", required=False, help="Output STL file") cad.add_argument("--precision", type=float, default=None, help="Mesh precision in mm") denoise = subparsers.add_parser("denoise", parents=[common], help="Add noise and run denoise filters") denoise.add_argument("--pointcloud", "-p", default=None, help="Input clean PLY point cloud") denoise.add_argument("--noise-std", type=float, default=None, help="Gaussian noise std") denoise.add_argument("--no-visualize", action="store_true", help="Disable Open3D windows") registration = subparsers.add_parser("registration", parents=[common], help="Run simulated point-cloud registration") registration.add_argument("--pointcloud", "-p", default=None, help="Input PLY point cloud") registration.add_argument("--mesh", "-m", default=None, help="Reference STL/OBJ/PLY mesh") registration.add_argument("--voxel-size", "-v", type=float, default=None, help="Registration voxel size") registration.add_argument("--roll", type=float, default=None, help="Rotation around X axis in degrees") registration.add_argument("--pitch", type=float, default=None, help="Rotation around Y axis in degrees") registration.add_argument("--yaw", type=float, default=None, help="Rotation around Z axis in degrees") registration.add_argument("--trans-range", type=float, default=None, help="Random translation range") registration.add_argument("--no-visualize", action="store_true", help="Disable Open3D windows") contour = subparsers.add_parser( "contour", parents=[common], help="Quick contour/flatness/planarity evaluation against STEP CAD", ) contour.add_argument("--pointcloud", "-p", default=None, help="Input PLY point cloud") contour.add_argument("--cad", "-c", default=None, help="Reference STEP CAD file") contour.add_argument("--mesh", "-m", default=None, help="Legacy reference STL/OBJ/PLY mesh") contour.add_argument( "--tasks", default=None, help="Comma-separated task ids or sample names. Example: 13 expands 13_1/13_2; 13_1 runs that sample only.", ) contour.add_argument("--all", action="store_true", help="Run contour evaluation for all Mesh/CAD ids under data root") contour.add_argument("--data-root", default="datasets", help="Dataset root for --tasks/--all batch mode") contour.add_argument( "--pointcloud-kind", choices=["noise", "clean"], default="noise", help="Point cloud source for batch mode: noise=Noise_PointCloud, clean=PointCloud", ) contour.add_argument("--mesh-deflection", type=float, default=None, help="CAD meshing deflection") contour.add_argument("--point-chunk-size", type=int, default=None, help="Point chunk size") contour.add_argument("--triangle-chunk-size", type=int, default=None, help="Triangle chunk size") contour.add_argument("--save", default=None, help="Optional colored point-cloud output path") contour.add_argument( "--excel-output", nargs="?", const="excel/contour.xlsx", default=None, help="Optional Excel output path; defaults to excel/contour.xlsx when used without a value", ) contour.add_argument( "--result-template", default="excel/测量结果.xlsx", help="Measurement-result template used by --excel-output", ) contour.add_argument( "--combined-context-pointcloud", default="datasets/train/train_2_10M.ply", help="Full point cloud to include in batch combined_contour.ply", ) contour.add_argument("--no-visualize", action="store_true", help="Disable Open3D windows") curvature = subparsers.add_parser("curvature", parents=[common], help="Estimate and visualize point-cloud curvature") curvature.add_argument("--pointcloud", "-p", default=None, help="Input PLY point cloud") curvature.add_argument("--radius", type=float, default=None, help="Neighborhood radius") curvature.add_argument("--max-nn", type=int, default=None, help="Max neighbor count") curvature.add_argument("--color-scale", type=float, default=None, help="Color scale") curvature.add_argument("--save", default=None, help="Optional colored point-cloud output path") curvature.add_argument("--no-visualize", action="store_true", help="Disable Open3D windows") plane = subparsers.add_parser("plane", parents=[common], help="Estimate plane with RANSAC") plane.add_argument("--pointcloud", "-p", default=None, help="Optional input PLY point cloud") plane.add_argument("--threshold-mm", type=float, default=None, help="RANSAC distance threshold in mm") plane.add_argument("--max-iterations", type=int, default=None, help="RANSAC max iterations") plane.add_argument("--no-visualize", action="store_true", help="Disable Open3D windows") translate_align = subparsers.add_parser( "translate-align", parents=[common], help="Translate-align point cloud to STEP CAD with front/tail centroids", ) translate_align.add_argument("--cad", "-c", default="datasets/CAD/M02.stp", help="Reference STEP CAD file") translate_align.add_argument( "--pointcloud", "-p", default="datasets/shift_test_output/", help="Input PLY point cloud or a directory containing shifted_whole_pointcloud.ply", ) translate_align.add_argument("--output-dir", "-o", default="results", help="Output directory") translate_align.add_argument("--axis", choices=["x", "y", "z"], default="x", help="Train length axis") translate_align.add_argument("--crop-ratio", type=float, default=0.01, help="Front/tail crop ratio") translate_align.add_argument("--mesh-deflection", type=float, default=0.1, help="CAD meshing deflection") translate_align.add_argument("--cad-sample-points", type=int, default=30000, help="Points sampled from CAD mesh") translate_align.add_argument("--local-sample-points", type=int, default=1000, help="Points sampled from each end") translate_align.add_argument("--scale-pointcloud", type=float, default=1.0, help="Scale factor applied to point cloud") translate_align.add_argument("--no-visualize", action="store_true", help="Disable Open3D windows") full = subparsers.add_parser("full", parents=[common], help="Run configured full workflow") full.add_argument("--no-visualize", action="store_true", help="Disable Open3D windows") return parser def main() -> None: parser = build_parser() args = parser.parse_args() from pipeline import MeasurementPipeline pipeline = MeasurementPipeline(config_path=args.config) if args.command == "cad2mesh": pipeline.run_cad2mesh(cad_path=args.cad, mesh_path=args.mesh, precision=args.precision) elif args.command == "denoise": pipeline.run_denoise(pointcloud_path=args.pointcloud, noise_std=args.noise_std, no_visualize=args.no_visualize) elif args.command == "registration": pipeline.run_registration( pointcloud_path=args.pointcloud, mesh_path=args.mesh, voxel_size=args.voxel_size, roll=args.roll, pitch=args.pitch, yaw=args.yaw, trans_range=args.trans_range, no_visualize=args.no_visualize, ) elif args.command == "contour": if args.all or args.tasks: pipeline.run_contour_batch( task_ids=args.tasks, all_tasks=args.all, data_root=args.data_root, pointcloud_kind=args.pointcloud_kind, mesh_deflection=args.mesh_deflection, point_chunk_size=args.point_chunk_size, triangle_chunk_size=args.triangle_chunk_size, save_path=args.save, excel_output_path=args.excel_output, result_template_path=args.result_template, combined_context_pointcloud_path=args.combined_context_pointcloud, no_visualize=args.no_visualize, ) else: pipeline.run_contour( pointcloud_path=args.pointcloud, cad_path=args.cad, mesh_path=args.mesh, mesh_deflection=args.mesh_deflection, point_chunk_size=args.point_chunk_size, triangle_chunk_size=args.triangle_chunk_size, save_path=args.save, excel_output_path=args.excel_output, result_template_path=args.result_template, no_visualize=args.no_visualize, ) elif args.command == "curvature": pipeline.run_curvature( pointcloud_path=args.pointcloud, radius=args.radius, max_nn=args.max_nn, color_scale=args.color_scale, save_path=args.save, no_visualize=args.no_visualize, ) elif args.command == "plane": pipeline.run_plane( pointcloud_path=args.pointcloud, threshold_mm=args.threshold_mm, max_iterations=args.max_iterations, no_visualize=args.no_visualize, ) elif args.command == "translate-align": pipeline.run_translate_align_pointcloud_to_cad( cad_path=args.cad, pointcloud_path=args.pointcloud, output_dir=args.output_dir, axis=args.axis, crop_ratio=args.crop_ratio, mesh_deflection=args.mesh_deflection, cad_sample_points=args.cad_sample_points, local_sample_points=args.local_sample_points, scale_pointcloud=args.scale_pointcloud, no_visualize=args.no_visualize, ) elif args.command == "full": pipeline.run_full(no_visualize=args.no_visualize) else: parser.error(f"Unsupported command: {args.command}") if __name__ == "__main__": main()