"""Offline CAD collisions for the fixed arm pedestal and shoulder-lift link. Use the same source-bound triangle clipping as the jaw recipe. Separate convex slices avoid spanning the pedestal's stepped silhouette or the link's full length with one broad hull. These are conservative parts, not triangle-mesh collisions. """ import argparse import json from pathlib import Path from generate_gripper_collisions import ROOT, generate # Original STL coordinates, millimeters; seams overlap by 1 mm. PARTS = [ ("base_foot", "Base_08q-v1", [(1, 1, 16.0)]), ("base_column", "Base_08q-v1", [(1, -1, -15.0), (1, 1, 59.0)]), ("base_crown", "Base_08q-v1", [(1, -1, -58.0)]), ("upper_arm_end", "SO_ARM100_08k_116_Square-v1", [(0, 1, -28.0)]), ("upper_arm_beam", "SO_ARM100_08k_116_Square-v1", [(0, -1, 29.0), (0, 1, 50.0)]), ("upper_arm_hinge", "SO_ARM100_08k_116_Square-v1", [(0, -1, -49.0)]), ] SOURCE_HASHES = { "Base_08q-v1.stl": "a05be37db52657615ac69423fe577979efa0cd0634a5e9546bbc85537f003172", "SO_ARM100_08k_116_Square-v1.stl": ( "64fdc308759ff58756e3c39a0ea8018d6b37b551bc3d29238b6e86bc5acae666" ), } def main(): parser = argparse.ArgumentParser(description="从固定版本 STL 重建 LeKiwi 底座/上臂碰撞体") parser.add_argument("--source", type=Path, default=ROOT / "build/lekiwi/URDF/meshes") parser.add_argument( "--output", type=Path, default=ROOT / "robot_profiles/lekiwi-arm-collision.json" ) parser.add_argument("--check", action="store_true", help="只验证已签入的碰撞数据可重建") args = parser.parse_args() result = generate(args.source, definitions=PARTS, source_hashes=SOURCE_HASHES, revision=3) if args.check: if json.loads(args.output.read_text()) != result: raise ValueError("底座/上臂碰撞数据与固定 CAD/生成器不一致") else: args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(json.dumps(result, indent=2) + "\n") status = "验证通过" if args.check else args.output print(f"LeKiwi 底座/上臂碰撞体:{len(result['parts'])} 个分段凸包,{status}") if __name__ == "__main__": main()