From 08eb7a6b78eefb0445fa0b1b0896327f6c2e4348 Mon Sep 17 00:00:00 2001 From: duongtd Date: Tue, 28 Jul 2026 23:35:42 +0700 Subject: [PATCH] first commit --- README.md | 84 ++ __pycache__/generate_mprim.cpython-38.pyc | Bin 0 -> 2278 bytes __pycache__/kinematics.cpython-38.pyc | Bin 0 -> 5944 bytes __pycache__/mprim_config.cpython-38.pyc | Bin 0 -> 5981 bytes __pycache__/mprim_core.cpython-38.pyc | Bin 0 -> 8219 bytes configs/__pycache__/ackermann.cpython-38.pyc | Bin 0 -> 877 bytes .../__pycache__/differential.cpython-38.pyc | Bin 0 -> 1316 bytes .../omnidirectional.cpython-38.pyc | Bin 0 -> 947 bytes configs/ackermann.py | 35 + configs/differential.py | 46 + configs/omnidirectional.py | 38 + diff.mprim | 1223 +++++++++++++++++ diff_5cm.mprim | 1223 +++++++++++++++++ diff_5cm_angle0.png | Bin 0 -> 41404 bytes diff_angle0.png | Bin 0 -> 41814 bytes diff_angle1.png | Bin 0 -> 49473 bytes generate_mprim.py | 66 + kinematics.py | 140 ++ mprim_config.py | 198 +++ mprim_core.py | 216 +++ test_mprim_gen.py | 198 +++ visualize_mprim.py | 92 ++ 22 files changed, 3559 insertions(+) create mode 100644 README.md create mode 100644 __pycache__/generate_mprim.cpython-38.pyc create mode 100644 __pycache__/kinematics.cpython-38.pyc create mode 100644 __pycache__/mprim_config.cpython-38.pyc create mode 100644 __pycache__/mprim_core.cpython-38.pyc create mode 100644 configs/__pycache__/ackermann.cpython-38.pyc create mode 100644 configs/__pycache__/differential.cpython-38.pyc create mode 100644 configs/__pycache__/omnidirectional.cpython-38.pyc create mode 100644 configs/ackermann.py create mode 100644 configs/differential.py create mode 100644 configs/omnidirectional.py create mode 100644 diff.mprim create mode 100644 diff_5cm.mprim create mode 100644 diff_5cm_angle0.png create mode 100644 diff_angle0.png create mode 100644 diff_angle1.png create mode 100644 generate_mprim.py create mode 100644 kinematics.py create mode 100644 mprim_config.py create mode 100644 mprim_core.py create mode 100644 test_mprim_gen.py create mode 100644 visualize_mprim.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..b079200 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# mprim_gen — Sinh file `.mprim` cho SBPL lattice planner (Python) + +Port lại từ các script MATLAB gốc (`genmprim*.m` của Maxim Likhachev), viết lại +theo kiến trúc module hóa để dễ thêm robot mới mà **không phải sửa code logic**. + +## Cấu trúc file + +``` +mprim_gen/ +├── mprim_config.py # RobotConfig — định nghĩa thông số + validate (KHÔNG có logic sinh) +├── kinematics.py # 2 interpolator: unicycle (diff + ackermann) / holonomic (omni) +├── mprim_core.py # engine dùng chung: build primitive set, xoay theo góc, tự kiểm, ghi file +├── generate_mprim.py # CLI chạy +├── test_mprim_gen.py # test tự động: ràng buộc SBPL + chất lượng hình học +├── visualize_mprim.py # vẽ primitive tại 1 góc, kèm vạch heading từng pose +└── configs/ # MỖI ROBOT = 1 file config nhỏ ở đây + ├── differential.py + ├── omnidirectional.py + └── ackermann.py +``` + +**Nguyên tắc:** khi thêm robot mới, bạn KHÔNG đụng vào `mprim_core.py` hay +`kinematics.py` — chỉ copy 1 file trong `configs/`, đổi số liệu, chạy lại. + +## Cách dùng + +```bash +python3 generate_mprim.py configs/differential.py -o diff.mprim +python3 generate_mprim.py configs/omnidirectional.py -o omni.mprim +python3 generate_mprim.py configs/ackermann.py -o ackermann.mprim + +python3 test_mprim_gen.py # chạy toàn bộ test trước khi tích hợp +python3 visualize_mprim.py diff.mprim --angle 1 # kiểm tra bằng mắt ở góc lẻ +``` + +## Những gì generator ĐẢM BẢO trên file output + +Đây là các ràng buộc SBPL enforce lúc nạp file (`ReadinMotionPrimitive` trong +`environment_navxythetalat.cpp`) — engine tự kiểm sau khi sinh, vi phạm là raise +ngay lúc generate thay vì fail lúc khởi động planner trên robot: + +1. `additionalactioncostmult` là **số nguyên** (SBPL đọc bằng `fscanf %d`, + giá trị 1.5/2.5 làm hỏng cả file). +2. Pose cuối của quỹ đạo rời rạc hóa ra **đúng** `endpose_c`. +3. **Heading cuối khớp chính xác** góc rời rạc đích (không còn kiểu lệch 10 độ + do ép `l = 0` mà không tính lại `rv` như bản MATLAB gốc). +4. Khoảng cách giữa 2 intermediate pose **<= 1 cell và ~ đều nhau** trên mọi + primitive (`pose_spacing_m`) → path SBPL trả ra có pose cách đều, footprint + sweep không bỏ sót cell. +5. Không có primitive suy biến, cảnh báo khi trùng endpose, cảnh báo khi cung + sau quantize vi phạm `min_turning_radius_m` (ackermann). + +## Thông số cần đo/lấy từ robot thật để điền vào config + +| Thông số | Cách lấy | +|---|---| +| `resolution_m` | PHẢI khớp `resolution` của costmap (global/local costmap yaml) | +| `forward_long_m` | quãng đường robot đi trong ~1 chu kỳ điều khiển ở vận tốc hành trình | +| `turn_radius_m` | >= `max_vel/max_yawrate` của robot, và >= `resolution/(1-cos(bước góc))` để quantize không làm méo cung (~0.66m với lưới 5cm, 16 góc) | +| `min_turning_radius_m` (Ackermann) | đo trực tiếp trên robot (bán kính hẹp nhất không trượt bánh) | +| `sidestep_m`, `diagonal_m` (omni) | quãng đường đi ngang/chéo robot làm an toàn được | +| `max_vel_mps`, `max_yawrate_radps` | từ config local planner (vd `robot_max_v_ac`/`robot_max_w_ac`) — chỉ để sanity-check | +| cost mult | **số nguyên**; điều chỉnh planner ưu tiên hành động nào. Bộ tham chiếu an toàn: backward=40, turninplace=20, forwardandturn=2 | + +## Khác biệt so với bản MATLAB gốc (lưu ý quan trọng) + +Bản gốc định nghĩa **tay 3 bộ template số nguyên riêng** cho góc 0°/45°/22.5°. +Bản Python định nghĩa primitive **một lần theo mét** (bán kính cung, chiều dài) +rồi tự xoay + làm tròn về cell cho từng góc. Ưu điểm: dễ tham số hóa theo robot +thật, primitive trái/phải đối xứng tuyệt đối (template tay của bản gốc bị lệch +trái/phải tới 8°). Đánh đổi: sai số lượng tử hóa nhỏ ở các góc lẻ (< nửa cell, +được dàn đều dọc quỹ đạo, heading không bị ảnh hưởng). + +Khác biệt hành vi so với bản gốc: khi nghiệm "đoạn thẳng + cung" đòi đảo chiều +(do quantize), bản gốc ép `l = 0` nhưng giữ nguyên `rv` làm heading cuối lệch +tới ~10° (chỉ in warning trong MATLAB console); bản này tính lại `rv` để heading +cuối luôn chính xác — đây là điều kiện SBPL dùng để chấp nhận file. + +## Tích hợp vào planner + +File output dùng trực tiếp với `sbpl_lattice_planner` qua key `primitive_filename` +trong `sbpl_global_params.yaml`. Sau khi đổi file mprim cần kiểm tra: +`resolution_m` khớp costmap, và log khởi động planner không có +`ERROR: incorrect primitive`. diff --git a/__pycache__/generate_mprim.cpython-38.pyc b/__pycache__/generate_mprim.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99beeaa1fca62d3d2658a44dc7141079a45fe7fd GIT binary patch literal 2278 zcma)6&2Jk;6yI5|?e+R2PEz_MRE>~8wP@@jgai>(SWXI78n;Trhg_=dW@l`#yB}tD zx5d%=0I9?QRiYA{N~Bn=1f@da2nmQoD$bneW^v+QKtkfpZW5Z7Lua*b=FPl$^XC2D z{N5`TH3{0MfAspVcZH(ZPk=UKG^G=~%h{uXubV}_~JWktFPPtunrrXmr$68BcQr7 zV3t!I47ItZn%20`#EKP#1{_Rw9^zM$DbsU1Ru>1%bGtZE{90Ah z8oOh&hso~GZg*b8&<4Yx=ee)qrw%{ZMXXMTGU^7FVT+)iZI|b`7O@yL1Iu&cjd;$( z&QSNlU=RjL$j|7@Ft1bQ7_KXh*OtS{&K(yAz1{B}%)B)(z|Lgnt~C!44~F{*IDGRn zan8QBun&8&ZB2GQ56@t;H`yK!u?^CyYD@3#e${#ho0IKtU19fw0S*b>%VC^i-SilR zAaD&|w_NHNfo1xjSGB*O-lSFq@#50{!s>89VPf`hW_5vBMdqc@walSuQ+?q1)aO$@ z&-Pr;vCL!wcIFe@^nnrdOem0pmV%K2e*=Dh0Yo76(Uwe5B#q?}inUY(H9V3d)K|9B zkvv973Nn#Q#58*mUF zK+5h}aW=nz@^ibrM-L3VW=*z#bMdvr62FC41T2l%&FN~jTDexq@toxhJQmp28dt)= zviUK8Kux{FJSR?yZhIyq)N^?`IXPJpMm%GC2BD1S=_jydFR*OS@Qol~TzlWJL%PVA z$59KhWdx+Q`uf?k`c1#)GqYA-UeSeKTcv(b zOArV2YKIX7ym?;4vzMnA!K+^YA)yi~z^6gaj(~28$^9m%Ndy;z_6rOIOoE1PqmdLz zeVL?)LejT^c4939#32!tY3O0?dB)gfs%6`|99B&~gB&wkeO4 zK#Sxk6)793kK|E$lnL@AFRc3ouqZ@nki|&3D{W>rpe&*c`w1*cK`{ay)}}U7$^3$J zb!L=>wQ`gNYbX{ZeSDutP%P7}@CCTT<^ScNy)uZ4Nz^f+zR9eCz&6GwJP%tRefj&3 zlbubVCoBW zI8`QQ@C>trc8t&t*ii^+J(JMVdiu)6rK>m;pw2_B@7_zuc6|y9X0`(B2KDjWIlP{| z@v=3*qdKhFrLGe5?2k@9T?1~yi{l1&;cU7Bvi%=1|d4E#&cYY zjfC#U#S>?WmzJ8X#pU{HvvFBpu3zG%1K!aC;1`}rZo(DUa3~YRz|#<;8}RfE@hEu8 zrfx;$nbnz_-fFSug?Y*kSc1;} y%|u9W9YHORi8xE@n+fxr69M@zflx{+WCcF?lq{H{oKYkh?fw5K*-}oORnfnJNWeM( literal 0 HcmV?d00001 diff --git a/__pycache__/kinematics.cpython-38.pyc b/__pycache__/kinematics.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0560387e7181a70181de2efb59d8b4c9ed36d253 GIT binary patch literal 5944 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 LX8JNinMeNvY6w`> literal 0 HcmV?d00001 diff --git a/__pycache__/mprim_config.cpython-38.pyc b/__pycache__/mprim_config.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d408756c49566e387e8749264a2d59bab1b8dc35 GIT binary patch literal 5981 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 literal 0 HcmV?d00001 diff --git a/__pycache__/mprim_core.cpython-38.pyc b/__pycache__/mprim_core.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83ea93c067aefd4b7050238bf19f2c939b9dc933 GIT binary patch literal 8219 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 JT`T}*e*rV;Di;6% literal 0 HcmV?d00001 diff --git a/configs/__pycache__/differential.cpython-38.pyc b/configs/__pycache__/differential.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64cc8a3ad08ca3c37f299c808fba60849241d9ba GIT binary patch literal 1316 zcmY*Y&1)M+6kn|r+m$WHNlbEm^khNM8YhNQ>Z-ww!EKX+t6Y2;Rx_jBiFaqzon1M) zwWku=91Mk$hC)Or!BEpC%_Z7H4+{Ac3f)6t8z}f6=%J^+S$*IPyvKX*_x63v{Ps?* zR<-b4|HHrg?V@G9G0xd@8J&+%^e3p8wQsQ!vx8D-@7rN{zg)7o&C0xdx56sy9GheF zC=2WzR%MGQORUD0QO+Ye&sNxl2dn$%*u~GS`laOGl4Wfx*%2Nv(dqDr%UHMput@Yc ztZhJhG>|^@1ZY>(8?f!?>3t07JUxOysu&$m`1{N6z?BfI(PN1v6x_JAhnc?t z&4*7yXbW810HYJUA>Y(~gr$0U`W19B=t3M7LE3;@30|O_-I~GzINN9-s%8)8p28jc z*8^HHpV(~r>5Sx9|Z_K0R!xITU4(pGn z#M^jk(od-0sGcH~th${o?wOYxb0DjIDkKlxSkP9#%d@4kobY~lXwGr-bOpbhcrTk1 zo-H3tdy=?l8gZ?HBo<1N zFk6;M*yfSyxY7%_&dyJso@if1u}PQ)0wq24t5f0_+pvS^n0IwnE9}T+BGCw4^j%G6 zsfKboyYg17$Ug%VzL2C~AsGYHX$*M{(X*9l$ywy`+gzQ+uS_#1B%T46Orr_eXTjyJ z(wyk7OGUBeV(8u@Jsyw{SG!=GzIzzCF(c3sbS0Q&;f>#?er}f_YIyuyij;MpwC&qVL zT*uD1gU6*auA-f@%xQG{*}?~*VoAV1G#t?lRF#Dl+b-FqiqT`Kpk<_FmkVtyyRiTI EA296h-~a#s literal 0 HcmV?d00001 diff --git a/configs/__pycache__/omnidirectional.cpython-38.pyc b/configs/__pycache__/omnidirectional.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d5b46a868de10980f288bb42a213d0a517b5520 GIT binary patch literal 947 zcmZ8fy>8S%5Vn2JB_z2Yf|O9Ypx~M~f`$TdLXs;F{IOyJv6W-L-aYqVNKU zf`Ss>ASESSLBSJHvfY7%bgzIY*zv`ekQsS2GvDW#o!xoSXskL`UVl%H|1LYuF9@#6 zZ5uqcpYB^joRLFZ;-xP4Mjo$?YOX^)QlqtFpZH{n)XB2_R>%#qYIUvDb<(ifwA!-j zllHsK(Gt1&-05r-FKyAD6k`?#k_$$dqEXJIz-e%o(+G=#hX927l&V*>vmNx3>Ex|Q z0zI9)4^lRryeWb_p)yeNNaog^PCi9JKK=4_duEF}9nY){0GhWkjb}_yj&l|b&oXLS z*V@J(KY*NfXKnlQMfACQ;r!@!+?fl&xRl@B_K&?Yg2`rUi_OxM|ukkEoJkEaH@E(;7>4iWNaRktzp5l}M$CZM=Gfqmz6~WTG)iA#!U_#E%?Jeqe8O-H7i;~E1Z%V?ktxQt+4?V6*0}tJ&tzJM2mQS#dxNLF;of0?ci+_KlygVj0TP%$`(YwE4M|~d zJ}2Su!Ojjk)uC2VxO*@_5Qjs1$>F?7Ohq`aqRYj-veDbwnOS+rB`H$+2$o3Sv&Q$@ Vp67b554QA5dl}ULU8v{%{0GbOLSg^_ literal 0 HcmV?d00001 diff --git a/configs/ackermann.py b/configs/ackermann.py new file mode 100644 index 0000000..b5497aa --- /dev/null +++ b/configs/ackermann.py @@ -0,0 +1,35 @@ +"""Config Ackermann (xe có bánh lái). Chỉnh số liệu theo robot thực tế.""" + +from mprim_config import RobotConfig, KinematicType + +config = RobotConfig( + name="ackermann", # tên file output: ackermann.mprim + kinematic_type=KinematicType.ACKERMANN, # xe bánh lái -> nội suy cung tròn unicycle, + # có kiểm tra min_turning_radius + + resolution_m=0.05, # [m] PHẢI khớp costmap resolution + numberofangles=16, # 16 góc heading -> 1 bước góc = 22.5 độ + + forward_short_m=0.20, # [m] bước tiến ngắn (xe dài nên bước ngắn cũng dài hơn AMR) + forward_long_m=0.60, # [m] bước tiến dài + backward_m=0.20, # [m] bước lùi thẳng + + min_turning_radius_m=0.45, # [m] bán kính quay tối thiểu VẬT LÝ, đo từ robot thật + turn_steps=(1,), # chỉ rẽ 22.5 độ mỗi primitive + turn_radius_m=0.70, # [m] cao hơn min_turning_radius làm dự phòng quantize; với + # lưới 5cm cần >= ~0.66 để cung không bị méo (validate + # sẽ cảnh báo nếu thiếu) + + has_backward_prims=True, # cho phép lùi thẳng + has_backward_turn_prims=True, # xe bánh lái cần lùi + đánh lái để quay đầu + has_turn_in_place_prims=False, # xe Ackermann KHÔNG quay tại chỗ được + + # SỐ NGUYÊN (SBPL đọc %d). Ackermann lùi tốn hơn nhiều (khó điều khiển, hạn chế tầm nhìn). + forwardcostmult=1, # tiến thẳng — mốc chuẩn + backwardcostmult=6, # lùi thẳng + forwardandturncostmult=3, # tiến + đánh lái + backwardandturncostmult=8, # lùi + đánh lái — đắt nhất, chỉ dành cho quay đầu + turninplacecostmult=5, # không dùng khi has_turn_in_place_prims=False + + pose_spacing_m=0.05, # [m] khoảng cách pose trên path +) diff --git a/configs/differential.py b/configs/differential.py new file mode 100644 index 0000000..ec638ee --- /dev/null +++ b/configs/differential.py @@ -0,0 +1,46 @@ +"""Config differential drive (2 bánh vi sai). Chỉnh số liệu theo robot thực tế. + +Cost mult lấy theo bộ tuned unicycle_highcost_5cm.mprim (backward=40, turninplace=20, +forwardandturn=2): lùi và quay tại chỗ đắt tới mức chỉ được chọn khi không còn đường +khác — an toàn cho AMR có cảm biến chính hướng về phía trước. Muốn robot thoải mái +lùi/xoay hơn thì hạ dần (vd 5/5/3) nhưng đó là quyết định vận hành có chủ đích. +""" + +from mprim_config import RobotConfig, KinematicType + +config = RobotConfig( + name="differential", # tên file output: differential.mprim + kinematic_type=KinematicType.DIFFERENTIAL, # 2 bánh vi sai -> nội suy unicycle + + resolution_m=0.05, # [m] PHẢI khớp costmap resolution (costmap_global_params.yaml) + numberofangles=16, # 16 góc heading -> 1 bước góc = 22.5 độ + + forward_short_m=0.10, # [m] bước tiến ngắn ~2 cell, để tinh chỉnh gần goal + forward_long_m=0.40, # [m] bước tiến dài ~8 cell — quãng đường 1 chu kỳ điều khiển + # ở tốc độ hành trình + backward_m=0.10, # [m] bước lùi thẳng + + # Rẽ = cung tròn: (1,) nghĩa là chỉ có primitive rẽ 22.5 độ (giống bộ tham chiếu). + # Thêm 2 vào tuple nếu cần rẽ 45 độ — đổi lại branching factor tăng, planner chậm hơn. + turn_steps=(1,), + turn_radius_m=0.70, # [m] >= max_vel/max_yawrate = 0.4/0.6 ~ 0.67 để local planner + # bám cung không phải giảm tốc; >= 0.66 để quantize 5cm + # không làm méo cung + + has_backward_prims=True, # cho phép lùi thẳng (cost 40 nên chỉ dùng khi kẹt) + has_backward_turn_prims=False, # lùi + rẽ tắt: hành động mù cảm biến, bật lại nếu thật cần + has_turn_in_place_prims=True, # cho phép quay tại chỗ (cost 20 nên hiếm khi được chọn) + + # SỐ NGUYÊN (SBPL đọc %d) + forwardcostmult=1, # tiến thẳng — mốc chuẩn + backwardcostmult=40, # lùi 0.1m "đắt" bằng tiến 4m -> chỉ lùi khi không còn cách + forwardandturncostmult=2, # rẽ đắt gấp đôi đi thẳng -> ưu tiên đường ít cua + backwardandturncostmult=40, # (không dùng khi has_backward_turn_prims=False) + turninplacecostmult=20, # xoay 22.5 độ "đắt" bằng tiến ~4m -> tránh xoay giữa đường + + pose_spacing_m=0.05, # [m] = resolution -> path SBPL trả ra có pose cách đều ~5cm + + # giới hạn động học thật (hybrid_local_planner_params.yaml: robot_max_v_ac / robot_max_w_ac) + max_vel_mps=0.4, # [m/s] vận tốc dài tối đa + max_yawrate_radps=0.6, # [rad/s] vận tốc xoay tối đa +) diff --git a/configs/omnidirectional.py b/configs/omnidirectional.py new file mode 100644 index 0000000..0f37c49 --- /dev/null +++ b/configs/omnidirectional.py @@ -0,0 +1,38 @@ +"""Config omnidirectional (mecanum/omni/swerve). Chỉnh số liệu theo robot thực tế.""" + +from mprim_config import RobotConfig, KinematicType + +config = RobotConfig( + name="omnidirectional", # tên file output: omnidirectional.mprim + kinematic_type=KinematicType.OMNIDIRECTIONAL, # holonomic -> nội suy tuyến tính x/y/theta + + resolution_m=0.05, # [m] PHẢI khớp costmap resolution + numberofangles=16, # 16 góc heading -> 1 bước góc = 22.5 độ + + forward_short_m=0.10, # [m] bước tiến ngắn ~2 cell + forward_long_m=0.40, # [m] bước tiến dài ~8 cell + backward_m=0.10, # [m] bước lùi thẳng + + turn_steps=(1,), # chỉ có primitive rẽ 22.5 độ; thêm 2 nếu cần rẽ 45 độ + turn_radius_m=0.70, # [m] bán kính cung rẽ (omni vẫn dùng cung cho mượt) + + sidestep_m=0.15, # [m] đặc thù omni: đi ngang giữ nguyên heading + diagonal_m=0.15, # [m] đặc thù omni: đi chéo 45 độ giữ nguyên heading + + has_backward_prims=True, # cho phép lùi thẳng + has_backward_turn_prims=True, # omni lùi không mù như diff-drive, giữ bật + has_turn_in_place_prims=True, # cho phép quay tại chỗ + + # SỐ NGUYÊN (SBPL đọc %d). Omni lùi/xoay "rẻ" hơn diff-drive vì không cần quay đầu. + forwardcostmult=1, # tiến thẳng — mốc chuẩn + backwardcostmult=3, # lùi rẻ hơn diff-drive (robot đối xứng, không mù hẳn) + forwardandturncostmult=3, # vừa đi vừa rẽ + backwardandturncostmult=4, # lùi + rẽ + turninplacecostmult=4, # quay tại chỗ + sidestepcostmult=2, # đi ngang — hơi đắt hơn tiến để không "cua" bằng sidestep + diagonalcostmult=2, # đi chéo 45 độ + + pose_spacing_m=0.05, # [m] khoảng cách pose trên path + + # chưa khai báo max_vel/max_yawrate: bỏ qua sanity-check động học +) diff --git a/diff.mprim b/diff.mprim new file mode 100644 index 0000000..31bc3b1 --- /dev/null +++ b/diff.mprim @@ -0,0 +1,1223 @@ +resolution_m: 0.050000 +numberofangles: 16 +totalnumberofprimitives: 112 +primID: 0 +startangle_c: 0 +endpose_c: 2 0 0 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 0.0000 +0.0500 0.0000 0.0000 +0.1000 0.0000 0.0000 +primID: 1 +startangle_c: 0 +endpose_c: 8 0 0 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 0.0000 +0.0500 0.0000 0.0000 +0.1000 0.0000 0.0000 +0.1500 0.0000 0.0000 +0.2000 0.0000 0.0000 +0.2500 0.0000 0.0000 +0.3000 0.0000 0.0000 +0.3500 0.0000 0.0000 +0.4000 0.0000 0.0000 +primID: 2 +startangle_c: 0 +endpose_c: -2 0 0 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 0.0000 +-0.0500 0.0000 0.0000 +-0.1000 0.0000 0.0000 +primID: 3 +startangle_c: 0 +endpose_c: 5 1 1 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 0.0000 +0.0427 0.0014 0.0654 +0.0853 0.0056 0.1309 +0.1275 0.0126 0.1963 +0.1691 0.0224 0.2618 +0.2100 0.0349 0.3272 +0.2500 0.0500 0.3927 +primID: 4 +startangle_c: 0 +endpose_c: 5 -1 15 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 0.0000 +0.0427 -0.0014 -0.0654 +0.0853 -0.0056 -0.1309 +0.1275 -0.0126 -0.1963 +0.1691 -0.0224 -0.2618 +0.2100 -0.0349 -0.3272 +0.2500 -0.0500 -0.3927 +primID: 5 +startangle_c: 0 +endpose_c: 0 0 1 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.0000 +0.0000 0.0000 0.0982 +0.0000 0.0000 0.1963 +0.0000 0.0000 0.2945 +0.0000 0.0000 0.3927 +primID: 6 +startangle_c: 0 +endpose_c: 0 0 15 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.0000 +0.0000 0.0000 -0.0982 +0.0000 0.0000 -0.1963 +0.0000 0.0000 -0.2945 +0.0000 0.0000 -0.3927 +primID: 0 +startangle_c: 1 +endpose_c: 2 1 1 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 0.3927 +0.0333 0.0167 0.3927 +0.0667 0.0333 0.3927 +0.1000 0.0500 0.3927 +primID: 1 +startangle_c: 1 +endpose_c: 7 3 1 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 0.3927 +0.0438 0.0188 0.3927 +0.0875 0.0375 0.3927 +0.1313 0.0563 0.3927 +0.1750 0.0750 0.3927 +0.2188 0.0938 0.3927 +0.2625 0.1125 0.3927 +0.3063 0.1313 0.3927 +0.3500 0.1500 0.3927 +primID: 2 +startangle_c: 1 +endpose_c: -2 -1 1 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 0.3927 +-0.0333 -0.0167 0.3927 +-0.0667 -0.0333 0.3927 +-0.1000 -0.0500 0.3927 +primID: 3 +startangle_c: 1 +endpose_c: 5 3 2 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 0.3927 +0.0453 0.0188 0.3927 +0.0903 0.0380 0.4377 +0.1337 0.0607 0.5246 +0.1750 0.0871 0.6115 +0.2139 0.1169 0.6985 +0.2500 0.1500 0.7854 +primID: 4 +startangle_c: 1 +endpose_c: 5 1 0 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 0.3927 +0.0400 0.0151 0.3290 +0.0809 0.0276 0.2632 +0.1225 0.0374 0.1974 +0.1647 0.0444 0.1316 +0.2073 0.0486 0.0658 +0.2500 0.0500 0.0000 +primID: 5 +startangle_c: 1 +endpose_c: 0 0 2 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.3927 +0.0000 0.0000 0.4909 +0.0000 0.0000 0.5890 +0.0000 0.0000 0.6872 +0.0000 0.0000 0.7854 +primID: 6 +startangle_c: 1 +endpose_c: 0 0 0 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.3927 +0.0000 0.0000 0.2945 +0.0000 0.0000 0.1963 +0.0000 0.0000 0.0982 +0.0000 0.0000 0.0000 +primID: 0 +startangle_c: 2 +endpose_c: 1 1 2 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 0.7854 +0.0250 0.0250 0.7854 +0.0500 0.0500 0.7854 +primID: 1 +startangle_c: 2 +endpose_c: 6 6 2 +additionalactioncostmult: 1 +intermediateposes: 10 +0.0000 0.0000 0.7854 +0.0333 0.0333 0.7854 +0.0667 0.0667 0.7854 +0.1000 0.1000 0.7854 +0.1333 0.1333 0.7854 +0.1667 0.1667 0.7854 +0.2000 0.2000 0.7854 +0.2333 0.2333 0.7854 +0.2667 0.2667 0.7854 +0.3000 0.3000 0.7854 +primID: 2 +startangle_c: 2 +endpose_c: -1 -1 2 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 0.7854 +-0.0250 -0.0250 0.7854 +-0.0500 -0.0500 0.7854 +primID: 3 +startangle_c: 2 +endpose_c: 3 5 3 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 0.7854 +0.0250 0.0266 0.8345 +0.0484 0.0547 0.8836 +0.0700 0.0842 0.9327 +0.0898 0.1151 0.9817 +0.1078 0.1472 1.0308 +0.1239 0.1804 1.0799 +0.1379 0.2147 1.1290 +0.1500 0.2500 1.1781 +primID: 4 +startangle_c: 2 +endpose_c: 5 3 1 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 0.7854 +0.0266 0.0250 0.7363 +0.0547 0.0484 0.6872 +0.0842 0.0700 0.6381 +0.1151 0.0898 0.5890 +0.1472 0.1078 0.5400 +0.1804 0.1239 0.4909 +0.2147 0.1379 0.4418 +0.2500 0.1500 0.3927 +primID: 5 +startangle_c: 2 +endpose_c: 0 0 3 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.7854 +0.0000 0.0000 0.8836 +0.0000 0.0000 0.9817 +0.0000 0.0000 1.0799 +0.0000 0.0000 1.1781 +primID: 6 +startangle_c: 2 +endpose_c: 0 0 1 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.7854 +0.0000 0.0000 0.6872 +0.0000 0.0000 0.5890 +0.0000 0.0000 0.4909 +0.0000 0.0000 0.3927 +primID: 0 +startangle_c: 3 +endpose_c: 1 2 3 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 1.1781 +0.0167 0.0333 1.1781 +0.0333 0.0667 1.1781 +0.0500 0.1000 1.1781 +primID: 1 +startangle_c: 3 +endpose_c: 3 7 3 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 1.1781 +0.0188 0.0438 1.1781 +0.0375 0.0875 1.1781 +0.0563 0.1313 1.1781 +0.0750 0.1750 1.1781 +0.0938 0.2188 1.1781 +0.1125 0.2625 1.1781 +0.1313 0.3063 1.1781 +0.1500 0.3500 1.1781 +primID: 2 +startangle_c: 3 +endpose_c: -1 -2 3 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 1.1781 +-0.0167 -0.0333 1.1781 +-0.0333 -0.0667 1.1781 +-0.0500 -0.1000 1.1781 +primID: 3 +startangle_c: 3 +endpose_c: 1 5 4 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.1781 +0.0151 0.0400 1.2418 +0.0276 0.0809 1.3076 +0.0374 0.1225 1.3734 +0.0444 0.1647 1.4392 +0.0486 0.2073 1.5050 +0.0500 0.2500 1.5708 +primID: 4 +startangle_c: 3 +endpose_c: 3 5 2 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.1781 +0.0188 0.0453 1.1781 +0.0380 0.0903 1.1331 +0.0607 0.1337 1.0462 +0.0871 0.1750 0.9593 +0.1169 0.2139 0.8723 +0.1500 0.2500 0.7854 +primID: 5 +startangle_c: 3 +endpose_c: 0 0 4 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.1781 +0.0000 0.0000 1.2763 +0.0000 0.0000 1.3744 +0.0000 0.0000 1.4726 +0.0000 0.0000 1.5708 +primID: 6 +startangle_c: 3 +endpose_c: 0 0 2 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.1781 +0.0000 0.0000 1.0799 +0.0000 0.0000 0.9817 +0.0000 0.0000 0.8836 +0.0000 0.0000 0.7854 +primID: 0 +startangle_c: 4 +endpose_c: 0 2 4 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 1.5708 +0.0000 0.0500 1.5708 +0.0000 0.1000 1.5708 +primID: 1 +startangle_c: 4 +endpose_c: 0 8 4 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 1.5708 +0.0000 0.0500 1.5708 +0.0000 0.1000 1.5708 +0.0000 0.1500 1.5708 +0.0000 0.2000 1.5708 +0.0000 0.2500 1.5708 +0.0000 0.3000 1.5708 +0.0000 0.3500 1.5708 +0.0000 0.4000 1.5708 +primID: 2 +startangle_c: 4 +endpose_c: 0 -2 4 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 1.5708 +0.0000 -0.0500 1.5708 +0.0000 -0.1000 1.5708 +primID: 3 +startangle_c: 4 +endpose_c: -1 5 5 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.5708 +-0.0014 0.0427 1.6362 +-0.0056 0.0853 1.7017 +-0.0126 0.1275 1.7671 +-0.0224 0.1691 1.8326 +-0.0349 0.2100 1.8980 +-0.0500 0.2500 1.9635 +primID: 4 +startangle_c: 4 +endpose_c: 1 5 3 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.5708 +0.0014 0.0427 1.5053 +0.0056 0.0853 1.4399 +0.0126 0.1275 1.3744 +0.0224 0.1691 1.3090 +0.0349 0.2100 1.2435 +0.0500 0.2500 1.1781 +primID: 5 +startangle_c: 4 +endpose_c: 0 0 5 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.5708 +0.0000 0.0000 1.6690 +0.0000 0.0000 1.7671 +0.0000 0.0000 1.8653 +0.0000 0.0000 1.9635 +primID: 6 +startangle_c: 4 +endpose_c: 0 0 3 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.5708 +0.0000 0.0000 1.4726 +0.0000 0.0000 1.3744 +0.0000 0.0000 1.2763 +0.0000 0.0000 1.1781 +primID: 0 +startangle_c: 5 +endpose_c: -1 2 5 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 1.9635 +-0.0167 0.0333 1.9635 +-0.0333 0.0667 1.9635 +-0.0500 0.1000 1.9635 +primID: 1 +startangle_c: 5 +endpose_c: -3 7 5 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 1.9635 +-0.0188 0.0438 1.9635 +-0.0375 0.0875 1.9635 +-0.0563 0.1313 1.9635 +-0.0750 0.1750 1.9635 +-0.0938 0.2188 1.9635 +-0.1125 0.2625 1.9635 +-0.1313 0.3063 1.9635 +-0.1500 0.3500 1.9635 +primID: 2 +startangle_c: 5 +endpose_c: 1 -2 5 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 1.9635 +0.0167 -0.0333 1.9635 +0.0333 -0.0667 1.9635 +0.0500 -0.1000 1.9635 +primID: 3 +startangle_c: 5 +endpose_c: -3 5 6 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.9635 +-0.0188 0.0453 1.9635 +-0.0380 0.0903 2.0085 +-0.0607 0.1337 2.0954 +-0.0871 0.1750 2.1823 +-0.1169 0.2139 2.2693 +-0.1500 0.2500 2.3562 +primID: 4 +startangle_c: 5 +endpose_c: -1 5 4 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.9635 +-0.0151 0.0400 1.8998 +-0.0276 0.0809 1.8340 +-0.0374 0.1225 1.7682 +-0.0444 0.1647 1.7024 +-0.0486 0.2073 1.6366 +-0.0500 0.2500 1.5708 +primID: 5 +startangle_c: 5 +endpose_c: 0 0 6 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.9635 +0.0000 0.0000 2.0617 +0.0000 0.0000 2.1598 +0.0000 0.0000 2.2580 +0.0000 0.0000 2.3562 +primID: 6 +startangle_c: 5 +endpose_c: 0 0 4 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.9635 +0.0000 0.0000 1.8653 +0.0000 0.0000 1.7671 +0.0000 0.0000 1.6690 +0.0000 0.0000 1.5708 +primID: 0 +startangle_c: 6 +endpose_c: -1 1 6 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 2.3562 +-0.0250 0.0250 2.3562 +-0.0500 0.0500 2.3562 +primID: 1 +startangle_c: 6 +endpose_c: -6 6 6 +additionalactioncostmult: 1 +intermediateposes: 10 +0.0000 0.0000 2.3562 +-0.0333 0.0333 2.3562 +-0.0667 0.0667 2.3562 +-0.1000 0.1000 2.3562 +-0.1333 0.1333 2.3562 +-0.1667 0.1667 2.3562 +-0.2000 0.2000 2.3562 +-0.2333 0.2333 2.3562 +-0.2667 0.2667 2.3562 +-0.3000 0.3000 2.3562 +primID: 2 +startangle_c: 6 +endpose_c: 1 -1 6 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 2.3562 +0.0250 -0.0250 2.3562 +0.0500 -0.0500 2.3562 +primID: 3 +startangle_c: 6 +endpose_c: -5 3 7 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 2.3562 +-0.0266 0.0250 2.4053 +-0.0547 0.0484 2.4544 +-0.0842 0.0700 2.5035 +-0.1151 0.0898 2.5525 +-0.1472 0.1078 2.6016 +-0.1804 0.1239 2.6507 +-0.2147 0.1379 2.6998 +-0.2500 0.1500 2.7489 +primID: 4 +startangle_c: 6 +endpose_c: -3 5 5 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 2.3562 +-0.0250 0.0266 2.3071 +-0.0484 0.0547 2.2580 +-0.0700 0.0842 2.2089 +-0.0898 0.1151 2.1598 +-0.1078 0.1472 2.1108 +-0.1239 0.1804 2.0617 +-0.1379 0.2147 2.0126 +-0.1500 0.2500 1.9635 +primID: 5 +startangle_c: 6 +endpose_c: 0 0 7 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 2.3562 +0.0000 0.0000 2.4544 +0.0000 0.0000 2.5525 +0.0000 0.0000 2.6507 +0.0000 0.0000 2.7489 +primID: 6 +startangle_c: 6 +endpose_c: 0 0 5 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 2.3562 +0.0000 0.0000 2.2580 +0.0000 0.0000 2.1598 +0.0000 0.0000 2.0617 +0.0000 0.0000 1.9635 +primID: 0 +startangle_c: 7 +endpose_c: -2 1 7 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 2.7489 +-0.0333 0.0167 2.7489 +-0.0667 0.0333 2.7489 +-0.1000 0.0500 2.7489 +primID: 1 +startangle_c: 7 +endpose_c: -7 3 7 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 2.7489 +-0.0438 0.0188 2.7489 +-0.0875 0.0375 2.7489 +-0.1313 0.0563 2.7489 +-0.1750 0.0750 2.7489 +-0.2188 0.0938 2.7489 +-0.2625 0.1125 2.7489 +-0.3063 0.1313 2.7489 +-0.3500 0.1500 2.7489 +primID: 2 +startangle_c: 7 +endpose_c: 2 -1 7 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 2.7489 +0.0333 -0.0167 2.7489 +0.0667 -0.0333 2.7489 +0.1000 -0.0500 2.7489 +primID: 3 +startangle_c: 7 +endpose_c: -5 1 8 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 2.7489 +-0.0400 0.0151 2.8126 +-0.0809 0.0276 2.8784 +-0.1225 0.0374 2.9442 +-0.1647 0.0444 3.0100 +-0.2073 0.0486 3.0758 +-0.2500 0.0500 3.1416 +primID: 4 +startangle_c: 7 +endpose_c: -5 3 6 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 2.7489 +-0.0453 0.0188 2.7489 +-0.0903 0.0380 2.7039 +-0.1337 0.0607 2.6170 +-0.1750 0.0871 2.5300 +-0.2139 0.1169 2.4431 +-0.2500 0.1500 2.3562 +primID: 5 +startangle_c: 7 +endpose_c: 0 0 8 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 2.7489 +0.0000 0.0000 2.8471 +0.0000 0.0000 2.9452 +0.0000 0.0000 3.0434 +0.0000 0.0000 3.1416 +primID: 6 +startangle_c: 7 +endpose_c: 0 0 6 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 2.7489 +0.0000 0.0000 2.6507 +0.0000 0.0000 2.5525 +0.0000 0.0000 2.4544 +0.0000 0.0000 2.3562 +primID: 0 +startangle_c: 8 +endpose_c: -2 0 8 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 3.1416 +-0.0500 0.0000 3.1416 +-0.1000 0.0000 3.1416 +primID: 1 +startangle_c: 8 +endpose_c: -8 0 8 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 3.1416 +-0.0500 0.0000 3.1416 +-0.1000 0.0000 3.1416 +-0.1500 0.0000 3.1416 +-0.2000 0.0000 3.1416 +-0.2500 0.0000 3.1416 +-0.3000 0.0000 3.1416 +-0.3500 0.0000 3.1416 +-0.4000 0.0000 3.1416 +primID: 2 +startangle_c: 8 +endpose_c: 2 0 8 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 3.1416 +0.0500 0.0000 3.1416 +0.1000 0.0000 3.1416 +primID: 3 +startangle_c: 8 +endpose_c: -5 -1 9 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 3.1416 +-0.0427 -0.0014 3.2070 +-0.0853 -0.0056 3.2725 +-0.1275 -0.0126 3.3379 +-0.1691 -0.0224 3.4034 +-0.2100 -0.0349 3.4688 +-0.2500 -0.0500 3.5343 +primID: 4 +startangle_c: 8 +endpose_c: -5 1 7 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 3.1416 +-0.0427 0.0014 3.0761 +-0.0853 0.0056 3.0107 +-0.1275 0.0126 2.9452 +-0.1691 0.0224 2.8798 +-0.2100 0.0349 2.8143 +-0.2500 0.0500 2.7489 +primID: 5 +startangle_c: 8 +endpose_c: 0 0 9 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.1416 +0.0000 0.0000 3.2398 +0.0000 0.0000 3.3379 +0.0000 0.0000 3.4361 +0.0000 0.0000 3.5343 +primID: 6 +startangle_c: 8 +endpose_c: 0 0 7 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.1416 +0.0000 0.0000 3.0434 +0.0000 0.0000 2.9452 +0.0000 0.0000 2.8471 +0.0000 0.0000 2.7489 +primID: 0 +startangle_c: 9 +endpose_c: -2 -1 9 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 3.5343 +-0.0333 -0.0167 3.5343 +-0.0667 -0.0333 3.5343 +-0.1000 -0.0500 3.5343 +primID: 1 +startangle_c: 9 +endpose_c: -7 -3 9 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 3.5343 +-0.0438 -0.0188 3.5343 +-0.0875 -0.0375 3.5343 +-0.1313 -0.0563 3.5343 +-0.1750 -0.0750 3.5343 +-0.2188 -0.0938 3.5343 +-0.2625 -0.1125 3.5343 +-0.3063 -0.1313 3.5343 +-0.3500 -0.1500 3.5343 +primID: 2 +startangle_c: 9 +endpose_c: 2 1 9 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 3.5343 +0.0333 0.0167 3.5343 +0.0667 0.0333 3.5343 +0.1000 0.0500 3.5343 +primID: 3 +startangle_c: 9 +endpose_c: -5 -3 10 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 3.5343 +-0.0453 -0.0188 3.5343 +-0.0903 -0.0380 3.5793 +-0.1337 -0.0607 3.6662 +-0.1750 -0.0871 3.7531 +-0.2139 -0.1169 3.8401 +-0.2500 -0.1500 3.9270 +primID: 4 +startangle_c: 9 +endpose_c: -5 -1 8 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 3.5343 +-0.0400 -0.0151 3.4706 +-0.0809 -0.0276 3.4048 +-0.1225 -0.0374 3.3390 +-0.1647 -0.0444 3.2732 +-0.2073 -0.0486 3.2074 +-0.2500 -0.0500 3.1416 +primID: 5 +startangle_c: 9 +endpose_c: 0 0 10 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.5343 +0.0000 0.0000 3.6325 +0.0000 0.0000 3.7306 +0.0000 0.0000 3.8288 +0.0000 0.0000 3.9270 +primID: 6 +startangle_c: 9 +endpose_c: 0 0 8 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.5343 +0.0000 0.0000 3.4361 +0.0000 0.0000 3.3379 +0.0000 0.0000 3.2398 +0.0000 0.0000 3.1416 +primID: 0 +startangle_c: 10 +endpose_c: -1 -1 10 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 3.9270 +-0.0250 -0.0250 3.9270 +-0.0500 -0.0500 3.9270 +primID: 1 +startangle_c: 10 +endpose_c: -6 -6 10 +additionalactioncostmult: 1 +intermediateposes: 10 +0.0000 0.0000 3.9270 +-0.0333 -0.0333 3.9270 +-0.0667 -0.0667 3.9270 +-0.1000 -0.1000 3.9270 +-0.1333 -0.1333 3.9270 +-0.1667 -0.1667 3.9270 +-0.2000 -0.2000 3.9270 +-0.2333 -0.2333 3.9270 +-0.2667 -0.2667 3.9270 +-0.3000 -0.3000 3.9270 +primID: 2 +startangle_c: 10 +endpose_c: 1 1 10 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 3.9270 +0.0250 0.0250 3.9270 +0.0500 0.0500 3.9270 +primID: 3 +startangle_c: 10 +endpose_c: -3 -5 11 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 3.9270 +-0.0250 -0.0266 3.9761 +-0.0484 -0.0547 4.0252 +-0.0700 -0.0842 4.0743 +-0.0898 -0.1151 4.1233 +-0.1078 -0.1472 4.1724 +-0.1239 -0.1804 4.2215 +-0.1379 -0.2147 4.2706 +-0.1500 -0.2500 4.3197 +primID: 4 +startangle_c: 10 +endpose_c: -5 -3 9 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 3.9270 +-0.0266 -0.0250 3.8779 +-0.0547 -0.0484 3.8288 +-0.0842 -0.0700 3.7797 +-0.1151 -0.0898 3.7306 +-0.1472 -0.1078 3.6816 +-0.1804 -0.1239 3.6325 +-0.2147 -0.1379 3.5834 +-0.2500 -0.1500 3.5343 +primID: 5 +startangle_c: 10 +endpose_c: 0 0 11 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.9270 +0.0000 0.0000 4.0252 +0.0000 0.0000 4.1233 +0.0000 0.0000 4.2215 +0.0000 0.0000 4.3197 +primID: 6 +startangle_c: 10 +endpose_c: 0 0 9 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.9270 +0.0000 0.0000 3.8288 +0.0000 0.0000 3.7306 +0.0000 0.0000 3.6325 +0.0000 0.0000 3.5343 +primID: 0 +startangle_c: 11 +endpose_c: -1 -2 11 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 4.3197 +-0.0167 -0.0333 4.3197 +-0.0333 -0.0667 4.3197 +-0.0500 -0.1000 4.3197 +primID: 1 +startangle_c: 11 +endpose_c: -3 -7 11 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 4.3197 +-0.0188 -0.0438 4.3197 +-0.0375 -0.0875 4.3197 +-0.0563 -0.1313 4.3197 +-0.0750 -0.1750 4.3197 +-0.0938 -0.2188 4.3197 +-0.1125 -0.2625 4.3197 +-0.1313 -0.3063 4.3197 +-0.1500 -0.3500 4.3197 +primID: 2 +startangle_c: 11 +endpose_c: 1 2 11 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 4.3197 +0.0167 0.0333 4.3197 +0.0333 0.0667 4.3197 +0.0500 0.1000 4.3197 +primID: 3 +startangle_c: 11 +endpose_c: -1 -5 12 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 4.3197 +-0.0151 -0.0400 4.3834 +-0.0276 -0.0809 4.4492 +-0.0374 -0.1225 4.5150 +-0.0444 -0.1647 4.5808 +-0.0486 -0.2073 4.6466 +-0.0500 -0.2500 4.7124 +primID: 4 +startangle_c: 11 +endpose_c: -3 -5 10 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 4.3197 +-0.0188 -0.0453 4.3197 +-0.0380 -0.0903 4.2747 +-0.0607 -0.1337 4.1878 +-0.0871 -0.1750 4.1008 +-0.1169 -0.2139 4.0139 +-0.1500 -0.2500 3.9270 +primID: 5 +startangle_c: 11 +endpose_c: 0 0 12 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 4.3197 +0.0000 0.0000 4.4179 +0.0000 0.0000 4.5160 +0.0000 0.0000 4.6142 +0.0000 0.0000 4.7124 +primID: 6 +startangle_c: 11 +endpose_c: 0 0 10 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 4.3197 +0.0000 0.0000 4.2215 +0.0000 0.0000 4.1233 +0.0000 0.0000 4.0252 +0.0000 0.0000 3.9270 +primID: 0 +startangle_c: 12 +endpose_c: 0 -2 12 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 4.7124 +0.0000 -0.0500 4.7124 +0.0000 -0.1000 4.7124 +primID: 1 +startangle_c: 12 +endpose_c: 0 -8 12 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 4.7124 +0.0000 -0.0500 4.7124 +0.0000 -0.1000 4.7124 +0.0000 -0.1500 4.7124 +0.0000 -0.2000 4.7124 +0.0000 -0.2500 4.7124 +0.0000 -0.3000 4.7124 +0.0000 -0.3500 4.7124 +0.0000 -0.4000 4.7124 +primID: 2 +startangle_c: 12 +endpose_c: 0 2 12 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 4.7124 +0.0000 0.0500 4.7124 +0.0000 0.1000 4.7124 +primID: 3 +startangle_c: 12 +endpose_c: 1 -5 13 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 4.7124 +0.0014 -0.0427 4.7778 +0.0056 -0.0853 4.8433 +0.0126 -0.1275 4.9087 +0.0224 -0.1691 4.9742 +0.0349 -0.2100 5.0396 +0.0500 -0.2500 5.1051 +primID: 4 +startangle_c: 12 +endpose_c: -1 -5 11 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 4.7124 +-0.0014 -0.0427 4.6469 +-0.0056 -0.0853 4.5815 +-0.0126 -0.1275 4.5160 +-0.0224 -0.1691 4.4506 +-0.0349 -0.2100 4.3851 +-0.0500 -0.2500 4.3197 +primID: 5 +startangle_c: 12 +endpose_c: 0 0 13 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 4.7124 +0.0000 0.0000 4.8106 +0.0000 0.0000 4.9087 +0.0000 0.0000 5.0069 +0.0000 0.0000 5.1051 +primID: 6 +startangle_c: 12 +endpose_c: 0 0 11 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 4.7124 +0.0000 0.0000 4.6142 +0.0000 0.0000 4.5160 +0.0000 0.0000 4.4179 +0.0000 0.0000 4.3197 +primID: 0 +startangle_c: 13 +endpose_c: 1 -2 13 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 5.1051 +0.0167 -0.0333 5.1051 +0.0333 -0.0667 5.1051 +0.0500 -0.1000 5.1051 +primID: 1 +startangle_c: 13 +endpose_c: 3 -7 13 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 5.1051 +0.0188 -0.0438 5.1051 +0.0375 -0.0875 5.1051 +0.0563 -0.1313 5.1051 +0.0750 -0.1750 5.1051 +0.0938 -0.2188 5.1051 +0.1125 -0.2625 5.1051 +0.1313 -0.3063 5.1051 +0.1500 -0.3500 5.1051 +primID: 2 +startangle_c: 13 +endpose_c: -1 2 13 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 5.1051 +-0.0167 0.0333 5.1051 +-0.0333 0.0667 5.1051 +-0.0500 0.1000 5.1051 +primID: 3 +startangle_c: 13 +endpose_c: 3 -5 14 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 5.1051 +0.0188 -0.0453 5.1051 +0.0380 -0.0903 5.1501 +0.0607 -0.1337 5.2370 +0.0871 -0.1750 5.3239 +0.1169 -0.2139 5.4109 +0.1500 -0.2500 5.4978 +primID: 4 +startangle_c: 13 +endpose_c: 1 -5 12 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 5.1051 +0.0151 -0.0400 5.0414 +0.0276 -0.0809 4.9756 +0.0374 -0.1225 4.9098 +0.0444 -0.1647 4.8440 +0.0486 -0.2073 4.7782 +0.0500 -0.2500 4.7124 +primID: 5 +startangle_c: 13 +endpose_c: 0 0 14 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.1051 +0.0000 0.0000 5.2033 +0.0000 0.0000 5.3014 +0.0000 0.0000 5.3996 +0.0000 0.0000 5.4978 +primID: 6 +startangle_c: 13 +endpose_c: 0 0 12 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.1051 +0.0000 0.0000 5.0069 +0.0000 0.0000 4.9087 +0.0000 0.0000 4.8106 +0.0000 0.0000 4.7124 +primID: 0 +startangle_c: 14 +endpose_c: 1 -1 14 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 5.4978 +0.0250 -0.0250 5.4978 +0.0500 -0.0500 5.4978 +primID: 1 +startangle_c: 14 +endpose_c: 6 -6 14 +additionalactioncostmult: 1 +intermediateposes: 10 +0.0000 0.0000 5.4978 +0.0333 -0.0333 5.4978 +0.0667 -0.0667 5.4978 +0.1000 -0.1000 5.4978 +0.1333 -0.1333 5.4978 +0.1667 -0.1667 5.4978 +0.2000 -0.2000 5.4978 +0.2333 -0.2333 5.4978 +0.2667 -0.2667 5.4978 +0.3000 -0.3000 5.4978 +primID: 2 +startangle_c: 14 +endpose_c: -1 1 14 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 5.4978 +-0.0250 0.0250 5.4978 +-0.0500 0.0500 5.4978 +primID: 3 +startangle_c: 14 +endpose_c: 5 -3 15 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 5.4978 +0.0266 -0.0250 5.5469 +0.0547 -0.0484 5.5960 +0.0842 -0.0700 5.6450 +0.1151 -0.0898 5.6941 +0.1472 -0.1078 5.7432 +0.1804 -0.1239 5.7923 +0.2147 -0.1379 5.8414 +0.2500 -0.1500 5.8905 +primID: 4 +startangle_c: 14 +endpose_c: 3 -5 13 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 5.4978 +0.0250 -0.0266 5.4487 +0.0484 -0.0547 5.3996 +0.0700 -0.0842 5.3505 +0.0898 -0.1151 5.3014 +0.1078 -0.1472 5.2524 +0.1239 -0.1804 5.2033 +0.1379 -0.2147 5.1542 +0.1500 -0.2500 5.1051 +primID: 5 +startangle_c: 14 +endpose_c: 0 0 15 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.4978 +0.0000 0.0000 5.5960 +0.0000 0.0000 5.6941 +0.0000 0.0000 5.7923 +0.0000 0.0000 5.8905 +primID: 6 +startangle_c: 14 +endpose_c: 0 0 13 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.4978 +0.0000 0.0000 5.3996 +0.0000 0.0000 5.3014 +0.0000 0.0000 5.2033 +0.0000 0.0000 5.1051 +primID: 0 +startangle_c: 15 +endpose_c: 2 -1 15 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 5.8905 +0.0333 -0.0167 5.8905 +0.0667 -0.0333 5.8905 +0.1000 -0.0500 5.8905 +primID: 1 +startangle_c: 15 +endpose_c: 7 -3 15 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 5.8905 +0.0438 -0.0188 5.8905 +0.0875 -0.0375 5.8905 +0.1313 -0.0563 5.8905 +0.1750 -0.0750 5.8905 +0.2188 -0.0938 5.8905 +0.2625 -0.1125 5.8905 +0.3063 -0.1313 5.8905 +0.3500 -0.1500 5.8905 +primID: 2 +startangle_c: 15 +endpose_c: -2 1 15 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 5.8905 +-0.0333 0.0167 5.8905 +-0.0667 0.0333 5.8905 +-0.1000 0.0500 5.8905 +primID: 3 +startangle_c: 15 +endpose_c: 5 -1 0 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 5.8905 +0.0400 -0.0151 5.9542 +0.0809 -0.0276 6.0200 +0.1225 -0.0374 6.0858 +0.1647 -0.0444 6.1516 +0.2073 -0.0486 6.2174 +0.2500 -0.0500 6.2832 +primID: 4 +startangle_c: 15 +endpose_c: 5 -3 14 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 5.8905 +0.0453 -0.0188 5.8905 +0.0903 -0.0380 5.8455 +0.1337 -0.0607 5.7586 +0.1750 -0.0871 5.6716 +0.2139 -0.1169 5.5847 +0.2500 -0.1500 5.4978 +primID: 5 +startangle_c: 15 +endpose_c: 0 0 0 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.8905 +0.0000 0.0000 5.9887 +0.0000 0.0000 6.0868 +0.0000 0.0000 6.1850 +0.0000 0.0000 6.2832 +primID: 6 +startangle_c: 15 +endpose_c: 0 0 14 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.8905 +0.0000 0.0000 5.7923 +0.0000 0.0000 5.6941 +0.0000 0.0000 5.5960 +0.0000 0.0000 5.4978 diff --git a/diff_5cm.mprim b/diff_5cm.mprim new file mode 100644 index 0000000..31bc3b1 --- /dev/null +++ b/diff_5cm.mprim @@ -0,0 +1,1223 @@ +resolution_m: 0.050000 +numberofangles: 16 +totalnumberofprimitives: 112 +primID: 0 +startangle_c: 0 +endpose_c: 2 0 0 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 0.0000 +0.0500 0.0000 0.0000 +0.1000 0.0000 0.0000 +primID: 1 +startangle_c: 0 +endpose_c: 8 0 0 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 0.0000 +0.0500 0.0000 0.0000 +0.1000 0.0000 0.0000 +0.1500 0.0000 0.0000 +0.2000 0.0000 0.0000 +0.2500 0.0000 0.0000 +0.3000 0.0000 0.0000 +0.3500 0.0000 0.0000 +0.4000 0.0000 0.0000 +primID: 2 +startangle_c: 0 +endpose_c: -2 0 0 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 0.0000 +-0.0500 0.0000 0.0000 +-0.1000 0.0000 0.0000 +primID: 3 +startangle_c: 0 +endpose_c: 5 1 1 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 0.0000 +0.0427 0.0014 0.0654 +0.0853 0.0056 0.1309 +0.1275 0.0126 0.1963 +0.1691 0.0224 0.2618 +0.2100 0.0349 0.3272 +0.2500 0.0500 0.3927 +primID: 4 +startangle_c: 0 +endpose_c: 5 -1 15 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 0.0000 +0.0427 -0.0014 -0.0654 +0.0853 -0.0056 -0.1309 +0.1275 -0.0126 -0.1963 +0.1691 -0.0224 -0.2618 +0.2100 -0.0349 -0.3272 +0.2500 -0.0500 -0.3927 +primID: 5 +startangle_c: 0 +endpose_c: 0 0 1 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.0000 +0.0000 0.0000 0.0982 +0.0000 0.0000 0.1963 +0.0000 0.0000 0.2945 +0.0000 0.0000 0.3927 +primID: 6 +startangle_c: 0 +endpose_c: 0 0 15 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.0000 +0.0000 0.0000 -0.0982 +0.0000 0.0000 -0.1963 +0.0000 0.0000 -0.2945 +0.0000 0.0000 -0.3927 +primID: 0 +startangle_c: 1 +endpose_c: 2 1 1 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 0.3927 +0.0333 0.0167 0.3927 +0.0667 0.0333 0.3927 +0.1000 0.0500 0.3927 +primID: 1 +startangle_c: 1 +endpose_c: 7 3 1 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 0.3927 +0.0438 0.0188 0.3927 +0.0875 0.0375 0.3927 +0.1313 0.0563 0.3927 +0.1750 0.0750 0.3927 +0.2188 0.0938 0.3927 +0.2625 0.1125 0.3927 +0.3063 0.1313 0.3927 +0.3500 0.1500 0.3927 +primID: 2 +startangle_c: 1 +endpose_c: -2 -1 1 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 0.3927 +-0.0333 -0.0167 0.3927 +-0.0667 -0.0333 0.3927 +-0.1000 -0.0500 0.3927 +primID: 3 +startangle_c: 1 +endpose_c: 5 3 2 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 0.3927 +0.0453 0.0188 0.3927 +0.0903 0.0380 0.4377 +0.1337 0.0607 0.5246 +0.1750 0.0871 0.6115 +0.2139 0.1169 0.6985 +0.2500 0.1500 0.7854 +primID: 4 +startangle_c: 1 +endpose_c: 5 1 0 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 0.3927 +0.0400 0.0151 0.3290 +0.0809 0.0276 0.2632 +0.1225 0.0374 0.1974 +0.1647 0.0444 0.1316 +0.2073 0.0486 0.0658 +0.2500 0.0500 0.0000 +primID: 5 +startangle_c: 1 +endpose_c: 0 0 2 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.3927 +0.0000 0.0000 0.4909 +0.0000 0.0000 0.5890 +0.0000 0.0000 0.6872 +0.0000 0.0000 0.7854 +primID: 6 +startangle_c: 1 +endpose_c: 0 0 0 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.3927 +0.0000 0.0000 0.2945 +0.0000 0.0000 0.1963 +0.0000 0.0000 0.0982 +0.0000 0.0000 0.0000 +primID: 0 +startangle_c: 2 +endpose_c: 1 1 2 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 0.7854 +0.0250 0.0250 0.7854 +0.0500 0.0500 0.7854 +primID: 1 +startangle_c: 2 +endpose_c: 6 6 2 +additionalactioncostmult: 1 +intermediateposes: 10 +0.0000 0.0000 0.7854 +0.0333 0.0333 0.7854 +0.0667 0.0667 0.7854 +0.1000 0.1000 0.7854 +0.1333 0.1333 0.7854 +0.1667 0.1667 0.7854 +0.2000 0.2000 0.7854 +0.2333 0.2333 0.7854 +0.2667 0.2667 0.7854 +0.3000 0.3000 0.7854 +primID: 2 +startangle_c: 2 +endpose_c: -1 -1 2 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 0.7854 +-0.0250 -0.0250 0.7854 +-0.0500 -0.0500 0.7854 +primID: 3 +startangle_c: 2 +endpose_c: 3 5 3 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 0.7854 +0.0250 0.0266 0.8345 +0.0484 0.0547 0.8836 +0.0700 0.0842 0.9327 +0.0898 0.1151 0.9817 +0.1078 0.1472 1.0308 +0.1239 0.1804 1.0799 +0.1379 0.2147 1.1290 +0.1500 0.2500 1.1781 +primID: 4 +startangle_c: 2 +endpose_c: 5 3 1 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 0.7854 +0.0266 0.0250 0.7363 +0.0547 0.0484 0.6872 +0.0842 0.0700 0.6381 +0.1151 0.0898 0.5890 +0.1472 0.1078 0.5400 +0.1804 0.1239 0.4909 +0.2147 0.1379 0.4418 +0.2500 0.1500 0.3927 +primID: 5 +startangle_c: 2 +endpose_c: 0 0 3 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.7854 +0.0000 0.0000 0.8836 +0.0000 0.0000 0.9817 +0.0000 0.0000 1.0799 +0.0000 0.0000 1.1781 +primID: 6 +startangle_c: 2 +endpose_c: 0 0 1 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 0.7854 +0.0000 0.0000 0.6872 +0.0000 0.0000 0.5890 +0.0000 0.0000 0.4909 +0.0000 0.0000 0.3927 +primID: 0 +startangle_c: 3 +endpose_c: 1 2 3 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 1.1781 +0.0167 0.0333 1.1781 +0.0333 0.0667 1.1781 +0.0500 0.1000 1.1781 +primID: 1 +startangle_c: 3 +endpose_c: 3 7 3 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 1.1781 +0.0188 0.0438 1.1781 +0.0375 0.0875 1.1781 +0.0563 0.1313 1.1781 +0.0750 0.1750 1.1781 +0.0938 0.2188 1.1781 +0.1125 0.2625 1.1781 +0.1313 0.3063 1.1781 +0.1500 0.3500 1.1781 +primID: 2 +startangle_c: 3 +endpose_c: -1 -2 3 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 1.1781 +-0.0167 -0.0333 1.1781 +-0.0333 -0.0667 1.1781 +-0.0500 -0.1000 1.1781 +primID: 3 +startangle_c: 3 +endpose_c: 1 5 4 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.1781 +0.0151 0.0400 1.2418 +0.0276 0.0809 1.3076 +0.0374 0.1225 1.3734 +0.0444 0.1647 1.4392 +0.0486 0.2073 1.5050 +0.0500 0.2500 1.5708 +primID: 4 +startangle_c: 3 +endpose_c: 3 5 2 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.1781 +0.0188 0.0453 1.1781 +0.0380 0.0903 1.1331 +0.0607 0.1337 1.0462 +0.0871 0.1750 0.9593 +0.1169 0.2139 0.8723 +0.1500 0.2500 0.7854 +primID: 5 +startangle_c: 3 +endpose_c: 0 0 4 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.1781 +0.0000 0.0000 1.2763 +0.0000 0.0000 1.3744 +0.0000 0.0000 1.4726 +0.0000 0.0000 1.5708 +primID: 6 +startangle_c: 3 +endpose_c: 0 0 2 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.1781 +0.0000 0.0000 1.0799 +0.0000 0.0000 0.9817 +0.0000 0.0000 0.8836 +0.0000 0.0000 0.7854 +primID: 0 +startangle_c: 4 +endpose_c: 0 2 4 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 1.5708 +0.0000 0.0500 1.5708 +0.0000 0.1000 1.5708 +primID: 1 +startangle_c: 4 +endpose_c: 0 8 4 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 1.5708 +0.0000 0.0500 1.5708 +0.0000 0.1000 1.5708 +0.0000 0.1500 1.5708 +0.0000 0.2000 1.5708 +0.0000 0.2500 1.5708 +0.0000 0.3000 1.5708 +0.0000 0.3500 1.5708 +0.0000 0.4000 1.5708 +primID: 2 +startangle_c: 4 +endpose_c: 0 -2 4 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 1.5708 +0.0000 -0.0500 1.5708 +0.0000 -0.1000 1.5708 +primID: 3 +startangle_c: 4 +endpose_c: -1 5 5 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.5708 +-0.0014 0.0427 1.6362 +-0.0056 0.0853 1.7017 +-0.0126 0.1275 1.7671 +-0.0224 0.1691 1.8326 +-0.0349 0.2100 1.8980 +-0.0500 0.2500 1.9635 +primID: 4 +startangle_c: 4 +endpose_c: 1 5 3 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.5708 +0.0014 0.0427 1.5053 +0.0056 0.0853 1.4399 +0.0126 0.1275 1.3744 +0.0224 0.1691 1.3090 +0.0349 0.2100 1.2435 +0.0500 0.2500 1.1781 +primID: 5 +startangle_c: 4 +endpose_c: 0 0 5 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.5708 +0.0000 0.0000 1.6690 +0.0000 0.0000 1.7671 +0.0000 0.0000 1.8653 +0.0000 0.0000 1.9635 +primID: 6 +startangle_c: 4 +endpose_c: 0 0 3 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.5708 +0.0000 0.0000 1.4726 +0.0000 0.0000 1.3744 +0.0000 0.0000 1.2763 +0.0000 0.0000 1.1781 +primID: 0 +startangle_c: 5 +endpose_c: -1 2 5 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 1.9635 +-0.0167 0.0333 1.9635 +-0.0333 0.0667 1.9635 +-0.0500 0.1000 1.9635 +primID: 1 +startangle_c: 5 +endpose_c: -3 7 5 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 1.9635 +-0.0188 0.0438 1.9635 +-0.0375 0.0875 1.9635 +-0.0563 0.1313 1.9635 +-0.0750 0.1750 1.9635 +-0.0938 0.2188 1.9635 +-0.1125 0.2625 1.9635 +-0.1313 0.3063 1.9635 +-0.1500 0.3500 1.9635 +primID: 2 +startangle_c: 5 +endpose_c: 1 -2 5 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 1.9635 +0.0167 -0.0333 1.9635 +0.0333 -0.0667 1.9635 +0.0500 -0.1000 1.9635 +primID: 3 +startangle_c: 5 +endpose_c: -3 5 6 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.9635 +-0.0188 0.0453 1.9635 +-0.0380 0.0903 2.0085 +-0.0607 0.1337 2.0954 +-0.0871 0.1750 2.1823 +-0.1169 0.2139 2.2693 +-0.1500 0.2500 2.3562 +primID: 4 +startangle_c: 5 +endpose_c: -1 5 4 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 1.9635 +-0.0151 0.0400 1.8998 +-0.0276 0.0809 1.8340 +-0.0374 0.1225 1.7682 +-0.0444 0.1647 1.7024 +-0.0486 0.2073 1.6366 +-0.0500 0.2500 1.5708 +primID: 5 +startangle_c: 5 +endpose_c: 0 0 6 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.9635 +0.0000 0.0000 2.0617 +0.0000 0.0000 2.1598 +0.0000 0.0000 2.2580 +0.0000 0.0000 2.3562 +primID: 6 +startangle_c: 5 +endpose_c: 0 0 4 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 1.9635 +0.0000 0.0000 1.8653 +0.0000 0.0000 1.7671 +0.0000 0.0000 1.6690 +0.0000 0.0000 1.5708 +primID: 0 +startangle_c: 6 +endpose_c: -1 1 6 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 2.3562 +-0.0250 0.0250 2.3562 +-0.0500 0.0500 2.3562 +primID: 1 +startangle_c: 6 +endpose_c: -6 6 6 +additionalactioncostmult: 1 +intermediateposes: 10 +0.0000 0.0000 2.3562 +-0.0333 0.0333 2.3562 +-0.0667 0.0667 2.3562 +-0.1000 0.1000 2.3562 +-0.1333 0.1333 2.3562 +-0.1667 0.1667 2.3562 +-0.2000 0.2000 2.3562 +-0.2333 0.2333 2.3562 +-0.2667 0.2667 2.3562 +-0.3000 0.3000 2.3562 +primID: 2 +startangle_c: 6 +endpose_c: 1 -1 6 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 2.3562 +0.0250 -0.0250 2.3562 +0.0500 -0.0500 2.3562 +primID: 3 +startangle_c: 6 +endpose_c: -5 3 7 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 2.3562 +-0.0266 0.0250 2.4053 +-0.0547 0.0484 2.4544 +-0.0842 0.0700 2.5035 +-0.1151 0.0898 2.5525 +-0.1472 0.1078 2.6016 +-0.1804 0.1239 2.6507 +-0.2147 0.1379 2.6998 +-0.2500 0.1500 2.7489 +primID: 4 +startangle_c: 6 +endpose_c: -3 5 5 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 2.3562 +-0.0250 0.0266 2.3071 +-0.0484 0.0547 2.2580 +-0.0700 0.0842 2.2089 +-0.0898 0.1151 2.1598 +-0.1078 0.1472 2.1108 +-0.1239 0.1804 2.0617 +-0.1379 0.2147 2.0126 +-0.1500 0.2500 1.9635 +primID: 5 +startangle_c: 6 +endpose_c: 0 0 7 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 2.3562 +0.0000 0.0000 2.4544 +0.0000 0.0000 2.5525 +0.0000 0.0000 2.6507 +0.0000 0.0000 2.7489 +primID: 6 +startangle_c: 6 +endpose_c: 0 0 5 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 2.3562 +0.0000 0.0000 2.2580 +0.0000 0.0000 2.1598 +0.0000 0.0000 2.0617 +0.0000 0.0000 1.9635 +primID: 0 +startangle_c: 7 +endpose_c: -2 1 7 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 2.7489 +-0.0333 0.0167 2.7489 +-0.0667 0.0333 2.7489 +-0.1000 0.0500 2.7489 +primID: 1 +startangle_c: 7 +endpose_c: -7 3 7 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 2.7489 +-0.0438 0.0188 2.7489 +-0.0875 0.0375 2.7489 +-0.1313 0.0563 2.7489 +-0.1750 0.0750 2.7489 +-0.2188 0.0938 2.7489 +-0.2625 0.1125 2.7489 +-0.3063 0.1313 2.7489 +-0.3500 0.1500 2.7489 +primID: 2 +startangle_c: 7 +endpose_c: 2 -1 7 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 2.7489 +0.0333 -0.0167 2.7489 +0.0667 -0.0333 2.7489 +0.1000 -0.0500 2.7489 +primID: 3 +startangle_c: 7 +endpose_c: -5 1 8 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 2.7489 +-0.0400 0.0151 2.8126 +-0.0809 0.0276 2.8784 +-0.1225 0.0374 2.9442 +-0.1647 0.0444 3.0100 +-0.2073 0.0486 3.0758 +-0.2500 0.0500 3.1416 +primID: 4 +startangle_c: 7 +endpose_c: -5 3 6 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 2.7489 +-0.0453 0.0188 2.7489 +-0.0903 0.0380 2.7039 +-0.1337 0.0607 2.6170 +-0.1750 0.0871 2.5300 +-0.2139 0.1169 2.4431 +-0.2500 0.1500 2.3562 +primID: 5 +startangle_c: 7 +endpose_c: 0 0 8 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 2.7489 +0.0000 0.0000 2.8471 +0.0000 0.0000 2.9452 +0.0000 0.0000 3.0434 +0.0000 0.0000 3.1416 +primID: 6 +startangle_c: 7 +endpose_c: 0 0 6 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 2.7489 +0.0000 0.0000 2.6507 +0.0000 0.0000 2.5525 +0.0000 0.0000 2.4544 +0.0000 0.0000 2.3562 +primID: 0 +startangle_c: 8 +endpose_c: -2 0 8 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 3.1416 +-0.0500 0.0000 3.1416 +-0.1000 0.0000 3.1416 +primID: 1 +startangle_c: 8 +endpose_c: -8 0 8 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 3.1416 +-0.0500 0.0000 3.1416 +-0.1000 0.0000 3.1416 +-0.1500 0.0000 3.1416 +-0.2000 0.0000 3.1416 +-0.2500 0.0000 3.1416 +-0.3000 0.0000 3.1416 +-0.3500 0.0000 3.1416 +-0.4000 0.0000 3.1416 +primID: 2 +startangle_c: 8 +endpose_c: 2 0 8 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 3.1416 +0.0500 0.0000 3.1416 +0.1000 0.0000 3.1416 +primID: 3 +startangle_c: 8 +endpose_c: -5 -1 9 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 3.1416 +-0.0427 -0.0014 3.2070 +-0.0853 -0.0056 3.2725 +-0.1275 -0.0126 3.3379 +-0.1691 -0.0224 3.4034 +-0.2100 -0.0349 3.4688 +-0.2500 -0.0500 3.5343 +primID: 4 +startangle_c: 8 +endpose_c: -5 1 7 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 3.1416 +-0.0427 0.0014 3.0761 +-0.0853 0.0056 3.0107 +-0.1275 0.0126 2.9452 +-0.1691 0.0224 2.8798 +-0.2100 0.0349 2.8143 +-0.2500 0.0500 2.7489 +primID: 5 +startangle_c: 8 +endpose_c: 0 0 9 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.1416 +0.0000 0.0000 3.2398 +0.0000 0.0000 3.3379 +0.0000 0.0000 3.4361 +0.0000 0.0000 3.5343 +primID: 6 +startangle_c: 8 +endpose_c: 0 0 7 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.1416 +0.0000 0.0000 3.0434 +0.0000 0.0000 2.9452 +0.0000 0.0000 2.8471 +0.0000 0.0000 2.7489 +primID: 0 +startangle_c: 9 +endpose_c: -2 -1 9 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 3.5343 +-0.0333 -0.0167 3.5343 +-0.0667 -0.0333 3.5343 +-0.1000 -0.0500 3.5343 +primID: 1 +startangle_c: 9 +endpose_c: -7 -3 9 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 3.5343 +-0.0438 -0.0188 3.5343 +-0.0875 -0.0375 3.5343 +-0.1313 -0.0563 3.5343 +-0.1750 -0.0750 3.5343 +-0.2188 -0.0938 3.5343 +-0.2625 -0.1125 3.5343 +-0.3063 -0.1313 3.5343 +-0.3500 -0.1500 3.5343 +primID: 2 +startangle_c: 9 +endpose_c: 2 1 9 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 3.5343 +0.0333 0.0167 3.5343 +0.0667 0.0333 3.5343 +0.1000 0.0500 3.5343 +primID: 3 +startangle_c: 9 +endpose_c: -5 -3 10 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 3.5343 +-0.0453 -0.0188 3.5343 +-0.0903 -0.0380 3.5793 +-0.1337 -0.0607 3.6662 +-0.1750 -0.0871 3.7531 +-0.2139 -0.1169 3.8401 +-0.2500 -0.1500 3.9270 +primID: 4 +startangle_c: 9 +endpose_c: -5 -1 8 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 3.5343 +-0.0400 -0.0151 3.4706 +-0.0809 -0.0276 3.4048 +-0.1225 -0.0374 3.3390 +-0.1647 -0.0444 3.2732 +-0.2073 -0.0486 3.2074 +-0.2500 -0.0500 3.1416 +primID: 5 +startangle_c: 9 +endpose_c: 0 0 10 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.5343 +0.0000 0.0000 3.6325 +0.0000 0.0000 3.7306 +0.0000 0.0000 3.8288 +0.0000 0.0000 3.9270 +primID: 6 +startangle_c: 9 +endpose_c: 0 0 8 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.5343 +0.0000 0.0000 3.4361 +0.0000 0.0000 3.3379 +0.0000 0.0000 3.2398 +0.0000 0.0000 3.1416 +primID: 0 +startangle_c: 10 +endpose_c: -1 -1 10 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 3.9270 +-0.0250 -0.0250 3.9270 +-0.0500 -0.0500 3.9270 +primID: 1 +startangle_c: 10 +endpose_c: -6 -6 10 +additionalactioncostmult: 1 +intermediateposes: 10 +0.0000 0.0000 3.9270 +-0.0333 -0.0333 3.9270 +-0.0667 -0.0667 3.9270 +-0.1000 -0.1000 3.9270 +-0.1333 -0.1333 3.9270 +-0.1667 -0.1667 3.9270 +-0.2000 -0.2000 3.9270 +-0.2333 -0.2333 3.9270 +-0.2667 -0.2667 3.9270 +-0.3000 -0.3000 3.9270 +primID: 2 +startangle_c: 10 +endpose_c: 1 1 10 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 3.9270 +0.0250 0.0250 3.9270 +0.0500 0.0500 3.9270 +primID: 3 +startangle_c: 10 +endpose_c: -3 -5 11 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 3.9270 +-0.0250 -0.0266 3.9761 +-0.0484 -0.0547 4.0252 +-0.0700 -0.0842 4.0743 +-0.0898 -0.1151 4.1233 +-0.1078 -0.1472 4.1724 +-0.1239 -0.1804 4.2215 +-0.1379 -0.2147 4.2706 +-0.1500 -0.2500 4.3197 +primID: 4 +startangle_c: 10 +endpose_c: -5 -3 9 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 3.9270 +-0.0266 -0.0250 3.8779 +-0.0547 -0.0484 3.8288 +-0.0842 -0.0700 3.7797 +-0.1151 -0.0898 3.7306 +-0.1472 -0.1078 3.6816 +-0.1804 -0.1239 3.6325 +-0.2147 -0.1379 3.5834 +-0.2500 -0.1500 3.5343 +primID: 5 +startangle_c: 10 +endpose_c: 0 0 11 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.9270 +0.0000 0.0000 4.0252 +0.0000 0.0000 4.1233 +0.0000 0.0000 4.2215 +0.0000 0.0000 4.3197 +primID: 6 +startangle_c: 10 +endpose_c: 0 0 9 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 3.9270 +0.0000 0.0000 3.8288 +0.0000 0.0000 3.7306 +0.0000 0.0000 3.6325 +0.0000 0.0000 3.5343 +primID: 0 +startangle_c: 11 +endpose_c: -1 -2 11 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 4.3197 +-0.0167 -0.0333 4.3197 +-0.0333 -0.0667 4.3197 +-0.0500 -0.1000 4.3197 +primID: 1 +startangle_c: 11 +endpose_c: -3 -7 11 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 4.3197 +-0.0188 -0.0438 4.3197 +-0.0375 -0.0875 4.3197 +-0.0563 -0.1313 4.3197 +-0.0750 -0.1750 4.3197 +-0.0938 -0.2188 4.3197 +-0.1125 -0.2625 4.3197 +-0.1313 -0.3063 4.3197 +-0.1500 -0.3500 4.3197 +primID: 2 +startangle_c: 11 +endpose_c: 1 2 11 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 4.3197 +0.0167 0.0333 4.3197 +0.0333 0.0667 4.3197 +0.0500 0.1000 4.3197 +primID: 3 +startangle_c: 11 +endpose_c: -1 -5 12 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 4.3197 +-0.0151 -0.0400 4.3834 +-0.0276 -0.0809 4.4492 +-0.0374 -0.1225 4.5150 +-0.0444 -0.1647 4.5808 +-0.0486 -0.2073 4.6466 +-0.0500 -0.2500 4.7124 +primID: 4 +startangle_c: 11 +endpose_c: -3 -5 10 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 4.3197 +-0.0188 -0.0453 4.3197 +-0.0380 -0.0903 4.2747 +-0.0607 -0.1337 4.1878 +-0.0871 -0.1750 4.1008 +-0.1169 -0.2139 4.0139 +-0.1500 -0.2500 3.9270 +primID: 5 +startangle_c: 11 +endpose_c: 0 0 12 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 4.3197 +0.0000 0.0000 4.4179 +0.0000 0.0000 4.5160 +0.0000 0.0000 4.6142 +0.0000 0.0000 4.7124 +primID: 6 +startangle_c: 11 +endpose_c: 0 0 10 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 4.3197 +0.0000 0.0000 4.2215 +0.0000 0.0000 4.1233 +0.0000 0.0000 4.0252 +0.0000 0.0000 3.9270 +primID: 0 +startangle_c: 12 +endpose_c: 0 -2 12 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 4.7124 +0.0000 -0.0500 4.7124 +0.0000 -0.1000 4.7124 +primID: 1 +startangle_c: 12 +endpose_c: 0 -8 12 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 4.7124 +0.0000 -0.0500 4.7124 +0.0000 -0.1000 4.7124 +0.0000 -0.1500 4.7124 +0.0000 -0.2000 4.7124 +0.0000 -0.2500 4.7124 +0.0000 -0.3000 4.7124 +0.0000 -0.3500 4.7124 +0.0000 -0.4000 4.7124 +primID: 2 +startangle_c: 12 +endpose_c: 0 2 12 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 4.7124 +0.0000 0.0500 4.7124 +0.0000 0.1000 4.7124 +primID: 3 +startangle_c: 12 +endpose_c: 1 -5 13 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 4.7124 +0.0014 -0.0427 4.7778 +0.0056 -0.0853 4.8433 +0.0126 -0.1275 4.9087 +0.0224 -0.1691 4.9742 +0.0349 -0.2100 5.0396 +0.0500 -0.2500 5.1051 +primID: 4 +startangle_c: 12 +endpose_c: -1 -5 11 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 4.7124 +-0.0014 -0.0427 4.6469 +-0.0056 -0.0853 4.5815 +-0.0126 -0.1275 4.5160 +-0.0224 -0.1691 4.4506 +-0.0349 -0.2100 4.3851 +-0.0500 -0.2500 4.3197 +primID: 5 +startangle_c: 12 +endpose_c: 0 0 13 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 4.7124 +0.0000 0.0000 4.8106 +0.0000 0.0000 4.9087 +0.0000 0.0000 5.0069 +0.0000 0.0000 5.1051 +primID: 6 +startangle_c: 12 +endpose_c: 0 0 11 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 4.7124 +0.0000 0.0000 4.6142 +0.0000 0.0000 4.5160 +0.0000 0.0000 4.4179 +0.0000 0.0000 4.3197 +primID: 0 +startangle_c: 13 +endpose_c: 1 -2 13 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 5.1051 +0.0167 -0.0333 5.1051 +0.0333 -0.0667 5.1051 +0.0500 -0.1000 5.1051 +primID: 1 +startangle_c: 13 +endpose_c: 3 -7 13 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 5.1051 +0.0188 -0.0438 5.1051 +0.0375 -0.0875 5.1051 +0.0563 -0.1313 5.1051 +0.0750 -0.1750 5.1051 +0.0938 -0.2188 5.1051 +0.1125 -0.2625 5.1051 +0.1313 -0.3063 5.1051 +0.1500 -0.3500 5.1051 +primID: 2 +startangle_c: 13 +endpose_c: -1 2 13 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 5.1051 +-0.0167 0.0333 5.1051 +-0.0333 0.0667 5.1051 +-0.0500 0.1000 5.1051 +primID: 3 +startangle_c: 13 +endpose_c: 3 -5 14 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 5.1051 +0.0188 -0.0453 5.1051 +0.0380 -0.0903 5.1501 +0.0607 -0.1337 5.2370 +0.0871 -0.1750 5.3239 +0.1169 -0.2139 5.4109 +0.1500 -0.2500 5.4978 +primID: 4 +startangle_c: 13 +endpose_c: 1 -5 12 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 5.1051 +0.0151 -0.0400 5.0414 +0.0276 -0.0809 4.9756 +0.0374 -0.1225 4.9098 +0.0444 -0.1647 4.8440 +0.0486 -0.2073 4.7782 +0.0500 -0.2500 4.7124 +primID: 5 +startangle_c: 13 +endpose_c: 0 0 14 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.1051 +0.0000 0.0000 5.2033 +0.0000 0.0000 5.3014 +0.0000 0.0000 5.3996 +0.0000 0.0000 5.4978 +primID: 6 +startangle_c: 13 +endpose_c: 0 0 12 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.1051 +0.0000 0.0000 5.0069 +0.0000 0.0000 4.9087 +0.0000 0.0000 4.8106 +0.0000 0.0000 4.7124 +primID: 0 +startangle_c: 14 +endpose_c: 1 -1 14 +additionalactioncostmult: 1 +intermediateposes: 3 +0.0000 0.0000 5.4978 +0.0250 -0.0250 5.4978 +0.0500 -0.0500 5.4978 +primID: 1 +startangle_c: 14 +endpose_c: 6 -6 14 +additionalactioncostmult: 1 +intermediateposes: 10 +0.0000 0.0000 5.4978 +0.0333 -0.0333 5.4978 +0.0667 -0.0667 5.4978 +0.1000 -0.1000 5.4978 +0.1333 -0.1333 5.4978 +0.1667 -0.1667 5.4978 +0.2000 -0.2000 5.4978 +0.2333 -0.2333 5.4978 +0.2667 -0.2667 5.4978 +0.3000 -0.3000 5.4978 +primID: 2 +startangle_c: 14 +endpose_c: -1 1 14 +additionalactioncostmult: 40 +intermediateposes: 3 +0.0000 0.0000 5.4978 +-0.0250 0.0250 5.4978 +-0.0500 0.0500 5.4978 +primID: 3 +startangle_c: 14 +endpose_c: 5 -3 15 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 5.4978 +0.0266 -0.0250 5.5469 +0.0547 -0.0484 5.5960 +0.0842 -0.0700 5.6450 +0.1151 -0.0898 5.6941 +0.1472 -0.1078 5.7432 +0.1804 -0.1239 5.7923 +0.2147 -0.1379 5.8414 +0.2500 -0.1500 5.8905 +primID: 4 +startangle_c: 14 +endpose_c: 3 -5 13 +additionalactioncostmult: 2 +intermediateposes: 9 +0.0000 0.0000 5.4978 +0.0250 -0.0266 5.4487 +0.0484 -0.0547 5.3996 +0.0700 -0.0842 5.3505 +0.0898 -0.1151 5.3014 +0.1078 -0.1472 5.2524 +0.1239 -0.1804 5.2033 +0.1379 -0.2147 5.1542 +0.1500 -0.2500 5.1051 +primID: 5 +startangle_c: 14 +endpose_c: 0 0 15 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.4978 +0.0000 0.0000 5.5960 +0.0000 0.0000 5.6941 +0.0000 0.0000 5.7923 +0.0000 0.0000 5.8905 +primID: 6 +startangle_c: 14 +endpose_c: 0 0 13 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.4978 +0.0000 0.0000 5.3996 +0.0000 0.0000 5.3014 +0.0000 0.0000 5.2033 +0.0000 0.0000 5.1051 +primID: 0 +startangle_c: 15 +endpose_c: 2 -1 15 +additionalactioncostmult: 1 +intermediateposes: 4 +0.0000 0.0000 5.8905 +0.0333 -0.0167 5.8905 +0.0667 -0.0333 5.8905 +0.1000 -0.0500 5.8905 +primID: 1 +startangle_c: 15 +endpose_c: 7 -3 15 +additionalactioncostmult: 1 +intermediateposes: 9 +0.0000 0.0000 5.8905 +0.0438 -0.0188 5.8905 +0.0875 -0.0375 5.8905 +0.1313 -0.0563 5.8905 +0.1750 -0.0750 5.8905 +0.2188 -0.0938 5.8905 +0.2625 -0.1125 5.8905 +0.3063 -0.1313 5.8905 +0.3500 -0.1500 5.8905 +primID: 2 +startangle_c: 15 +endpose_c: -2 1 15 +additionalactioncostmult: 40 +intermediateposes: 4 +0.0000 0.0000 5.8905 +-0.0333 0.0167 5.8905 +-0.0667 0.0333 5.8905 +-0.1000 0.0500 5.8905 +primID: 3 +startangle_c: 15 +endpose_c: 5 -1 0 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 5.8905 +0.0400 -0.0151 5.9542 +0.0809 -0.0276 6.0200 +0.1225 -0.0374 6.0858 +0.1647 -0.0444 6.1516 +0.2073 -0.0486 6.2174 +0.2500 -0.0500 6.2832 +primID: 4 +startangle_c: 15 +endpose_c: 5 -3 14 +additionalactioncostmult: 2 +intermediateposes: 7 +0.0000 0.0000 5.8905 +0.0453 -0.0188 5.8905 +0.0903 -0.0380 5.8455 +0.1337 -0.0607 5.7586 +0.1750 -0.0871 5.6716 +0.2139 -0.1169 5.5847 +0.2500 -0.1500 5.4978 +primID: 5 +startangle_c: 15 +endpose_c: 0 0 0 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.8905 +0.0000 0.0000 5.9887 +0.0000 0.0000 6.0868 +0.0000 0.0000 6.1850 +0.0000 0.0000 6.2832 +primID: 6 +startangle_c: 15 +endpose_c: 0 0 14 +additionalactioncostmult: 20 +intermediateposes: 5 +0.0000 0.0000 5.8905 +0.0000 0.0000 5.7923 +0.0000 0.0000 5.6941 +0.0000 0.0000 5.5960 +0.0000 0.0000 5.4978 diff --git a/diff_5cm_angle0.png b/diff_5cm_angle0.png new file mode 100644 index 0000000000000000000000000000000000000000..9fb95472de44e1ddfb3ed30d41496e2e7972ffe8 GIT binary patch literal 41404 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 w5U2Km^jeV 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 a^%c`Fqxk6dNR8k6Uo@+OUshZAp7}4NK8-v8 literal 0 HcmV?d00001 diff --git a/generate_mprim.py b/generate_mprim.py new file mode 100644 index 0000000..e3d9241 --- /dev/null +++ b/generate_mprim.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +""" +generate_mprim.py +------------------ +CLI để sinh file .mprim từ 1 file config trong configs/. + +Cách dùng: + python3 generate_mprim.py configs/differential.py + python3 generate_mprim.py configs/omnidirectional.py -o my_output.mprim + python3 generate_mprim.py configs/ackermann.py + +Muốn thêm robot mới: copy 1 file trong configs/, đổi số liệu, chạy lại. +KHÔNG cần sửa code trong mprim_core.py / kinematics.py. +""" + +import argparse +import importlib.util +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from mprim_config import KinematicType +from mprim_core import generate_mprim_file +from kinematics import unicycle_poses, holonomic_poses + +# differential và ackermann dùng chung interpolator: đường đi của xe bánh lái theo +# cung tròn trùng với nghiệm unicycle (đoạn thẳng + cung); khác nhau chỉ ở tập +# primitive và ràng buộc min_turning_radius (mprim_core xử lý). +KINEMATICS_MAP = { + KinematicType.DIFFERENTIAL: unicycle_poses, + KinematicType.OMNIDIRECTIONAL: holonomic_poses, + KinematicType.ACKERMANN: unicycle_poses, +} + + +def load_config(config_path: str): + spec = importlib.util.spec_from_file_location("robot_config_module", config_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + if not hasattr(module, "config"): + raise ValueError(f"{config_path} phải định nghĩa biến `config = RobotConfig(...)`") + return module.config + + +def main(): + parser = argparse.ArgumentParser(description="Sinh file .mprim từ config robot") + parser.add_argument("config", help="Đường dẫn tới file config Python (vd: configs/differential.py)") + parser.add_argument("-o", "--output", default=None, help="Đường dẫn file .mprim output") + args = parser.parse_args() + + cfg = load_config(args.config) + kinematics_fn = KINEMATICS_MAP[cfg.kinematic_type] + + output_path = args.output or f"{cfg.name}.mprim" + n, warnings = generate_mprim_file(cfg, kinematics_fn, output_path) + + print(f"[OK] {cfg.kinematic_type.value}: đã sinh {n} primitives -> {output_path}") + if warnings: + print(f"[WARN] {len(warnings)} cảnh báo:") + for w in warnings: + print(f" - {w}") + + +if __name__ == "__main__": + main() diff --git a/kinematics.py b/kinematics.py new file mode 100644 index 0000000..a859c76 --- /dev/null +++ b/kinematics.py @@ -0,0 +1,140 @@ +""" +kinematics.py +------------- +Mỗi loại drive có một cách nội suy quỹ đạo (intermediate poses) khác nhau giữa +điểm đầu và điểm cuối của 1 motion primitive. Đây là phần logic THỰC SỰ khác +nhau giữa các robot — phần còn lại (symmetry theo góc, ghi file...) dùng chung. + +Mọi interpolator đều nhận: + startpt = (x, y, theta) # luôn là (0, 0, currentangle) + endpt = (x, y, theta) # điểm kết thúc ĐÃ quantize về lưới (mét, rad) + dtheta_c = số bước góc rời rạc thay đổi (int, có dấu, +: CCW) + numberofangles = tổng số góc rời rạc + spacing_m = khoảng cách mong muốn giữa 2 pose liên tiếp [m] + spacing_rad = bước góc tối đa giữa 2 pose liên tiếp [rad] + +Trả về (poses, info): + poses: list (x, y, theta); pose đầu = startpt, pose cuối = ĐÚNG endpt, + heading cuối = ĐÚNG startangle + dtheta_c bước góc (SBPL yêu cầu để nạp file). + info: dict chẩn đoán {"radius_m": bán kính cung (None nếu thẳng), + "line_lead_m": đoạn thẳng dẫn vào của nghiệm line+arc} + — engine dùng để cảnh báo vi phạm min_turning_radius. + +Số sample tính THEO CHIỀU DÀI (n = ceil(L/spacing)+1) thay vì cố định — nhờ vậy +khoảng cách pose ~ đều nhau trên mọi primitive dài ngắn khác nhau, và path SBPL +ghép từ nhiều primitive cũng đều theo. +""" + +import numpy as np + + +def _num_samples(path_len_m, sweep_rad, spacing_m, spacing_rad): + """Số pose sao cho bước dài <= spacing_m VÀ bước góc <= spacing_rad.""" + n_lin = int(np.ceil(abs(path_len_m) / spacing_m)) if spacing_m > 0 else 0 + n_ang = int(np.ceil(abs(sweep_rad) / spacing_rad)) if spacing_rad > 0 else 0 + return max(2, n_lin + 1, n_ang + 1) + + +def _distribute_endpoint_error(poses, ex, ey): + """Dàn sai số điểm cuối tuyến tính dọc quỹ đạo để pose cuối khớp ĐÚNG lưới cell. + + Chỉ dịch (x, y), KHÔNG đụng theta — heading đã chính xác theo cấu trúc nghiệm, + dịch vị trí nhỏ (<= nửa cell) chỉ gây lệch tangent vài độ, chấp nhận được. + """ + errx = ex - poses[-1][0] + erry = ey - poses[-1][1] + n = len(poses) + return [(x + errx * i / (n - 1), y + erry * i / (n - 1), th) + for i, (x, y, th) in enumerate(poses)] + + +def unicycle_poses(startpt, endpt, dtheta_c, numberofangles, spacing_m, spacing_rad): + """Nội suy cho robot nonholonomic (differential VÀ ackermann — cùng hình học đường đi). + + Nghiệm "đoạn thẳng l + cung tròn bán kính k" giải từ hệ tuyến tính, y hệt + genmprim_unicycle*.m gốc. Khác bản gốc ở 2 điểm: + 1. Khi dấu l mâu thuẫn với chiều đi (nghiệm đòi lùi rồi mới tiến), bản gốc chỉ ép + l = 0 mà giữ nguyên rv -> heading cuối lệch tới ~10 độ so với endpose (bug F2). + Ở đây ép l = 0 RỒI TÍNH LẠI rv = rotation_angle -> heading cuối luôn chính xác, + sai số dồn hết vào vị trí và được _distribute_endpoint_error dàn đều. + 2. Sample theo chiều dài thay vì 10 điểm cố định. + """ + sx, sy, sth = startpt + ex, ey, _ = endpt + rotation_angle = dtheta_c * (2 * np.pi / numberofangles) + eth_cont = sth + rotation_angle # heading cuối liên tục (không wrap — sin/cos như nhau) + dist = np.hypot(ex - sx, ey - sy) + + # --- đi thẳng / lùi không đổi hướng --- + if dtheta_c == 0: + n = _num_samples(dist, 0.0, spacing_m, spacing_rad) + # theta giữ = heading; hướng vector đi có thể lệch heading vài độ do endpose đã + # quantize về cell — sai số lưới, không tránh được, giống bản MATLAB gốc. + poses = [(sx + (ex - sx) * t, sy + (ey - sy) * t, sth) + for t in np.linspace(0.0, 1.0, n)] + return poses, {"radius_m": None, "line_lead_m": None} + + # --- quay tại chỗ --- + if dist < 1e-9: + n = _num_samples(0.0, rotation_angle, spacing_m, spacing_rad) + poses = [(sx, sy, sth + rotation_angle * t) for t in np.linspace(0.0, 1.0, n)] + return poses, {"radius_m": None, "line_lead_m": None} + + # --- đoạn thẳng + cung tròn (unicycle, tv & rv hằng) --- + # [dx, dy]^T = [cos(sth), sin(eth)-sin(sth); sin(sth), -(cos(eth)-cos(sth))] @ [l, k]^T + # với l = chiều dài đoạn thẳng dẫn vào (có dấu), k = tv/rv = bán kính cung (có dấu). + R = np.array([ + [np.cos(sth), np.sin(eth_cont) - np.sin(sth)], + [np.sin(sth), -(np.cos(eth_cont) - np.cos(sth))], + ]) + b = np.array([ex - sx, ey - sy]) + S = np.linalg.pinv(R) @ b + l = float(S[0]) + k = float(S[1]) + if abs(k) < 1e-9: + # endpose thẳng hàng với heading nhưng vẫn phải đổi góc: cung suy biến thành + # "đi thẳng rồi xoay gắt ở cuối". Xảy ra khi turn_radius quá nhỏ so với cell + # (validate đã cảnh báo) — vẫn sinh ra pose hợp lệ, không chia cho 0. + k = 1e-9 if k >= 0 else -1e-9 + + rv = rotation_angle + l / k + tv = k * rv + if (l < 0 < tv) or (l > 0 > tv): + # Nghiệm đòi đảo chiều trong 1 primitive (do quantize đẩy endpose ra sau cung). + # Ép l = 0 và TÍNH LẠI rv để heading cuối = rotation_angle chính xác. + l = 0.0 + rv = rotation_angle + tv = k * rv + + arc_len = abs(k * rotation_angle) + n = _num_samples(abs(l) + arc_len, rotation_angle, spacing_m, spacing_rad) + poses = [] + for t in np.linspace(0.0, 1.0, n): + if tv != 0.0 and abs(t * tv) < abs(l): + # pha đi thẳng dọc heading ban đầu + poses.append((sx + t * tv * np.cos(sth), + sy + t * tv * np.sin(sth), + sth)) + else: + denom = tv if tv != 0.0 else 1e-9 + th = rv * (t - l / denom) + sth + poses.append((sx + l * np.cos(sth) + k * (np.sin(th) - np.sin(sth)), + sy + l * np.sin(sth) - k * (np.cos(th) - np.cos(sth)), + th)) + # chốt heading cuối đúng tuyệt đối (tránh sai số tích lũy float) + poses[-1] = (poses[-1][0], poses[-1][1], eth_cont) + poses = _distribute_endpoint_error(poses, ex, ey) + return poses, {"radius_m": abs(k), "line_lead_m": l} + + +def holonomic_poses(startpt, endpt, dtheta_c, numberofangles, spacing_m, spacing_rad): + """Holonomic (omni/mecanum/swerve): (x, y) độc lập với heading nên nội suy tuyến tính + cả vị trí lẫn theta — mô hình LINESEGMENT_MPRIMS của genmprim.m gốc.""" + sx, sy, sth = startpt + ex, ey, _ = endpt + rotation_angle = dtheta_c * (2 * np.pi / numberofangles) + dist = np.hypot(ex - sx, ey - sy) + n = _num_samples(dist, rotation_angle, spacing_m, spacing_rad) + poses = [(sx + (ex - sx) * t, sy + (ey - sy) * t, sth + rotation_angle * t) + for t in np.linspace(0.0, 1.0, n)] + return poses, {"radius_m": None, "line_lead_m": None} diff --git a/mprim_config.py b/mprim_config.py new file mode 100644 index 0000000..2234bf9 --- /dev/null +++ b/mprim_config.py @@ -0,0 +1,198 @@ +""" +mprim_config.py +---------------- +Cấu hình robot dùng chung cho mọi loại kinematic (differential / omnidirectional / ackermann). + +Chỉnh các thông số trong file configs/*.py theo robot thực tế của bạn. +File này KHÔNG chứa logic sinh primitive — chỉ định nghĩa dữ liệu + validate. + +Ràng buộc quan trọng của format SBPL (lý do cho các validate bên dưới): +- `additionalactioncostmult` được SBPL đọc bằng fscanf("%d") -> BẮT BUỘC là số nguyên, + giá trị lẻ (1.5, 2.5...) làm hỏng cả file ngay từ primitive đầu tiên chứa nó. +- Pose cuối của quỹ đạo phải khớp endpose sau khi rời rạc hóa, nếu lệch quá nửa bước góc + (= 180/numberofangles độ) SBPL từ chối nạp file. +- Khoảng cách giữa 2 intermediate pose liên tiếp không được vượt quá 1 cell, nếu không + bước quét footprint (get_2d_motion_cells) sẽ bỏ sót cell trên đường đi. +""" + +import math +from dataclasses import dataclass, field +from enum import Enum +from typing import Optional, Tuple + + +class KinematicType(str, Enum): + DIFFERENTIAL = "differential" # 2 bánh vi sai (unicycle model) + OMNIDIRECTIONAL = "omnidirectional" # mecanum / omni / swerve (holonomic) + ACKERMANN = "ackermann" # xe có bánh lái, bán kính quay tối thiểu + + +@dataclass +class RobotConfig: + # --- Thông tin chung --- + name: str # tên robot; dùng đặt tên file output .mprim + kinematic_type: KinematicType # differential / omnidirectional / ackermann — quyết + # định tập primitive và cách nội suy quỹ đạo + + # --- Lưới rời rạc hóa (phải khớp với costmap / planner config) --- + resolution_m: float = 0.05 # [m] kích thước 1 cell — PHẢI khớp costmap resolution, + # SBPL so sánh và từ chối file nếu lệch + numberofangles: int = 16 # số góc heading rời rạc; bội số của 8, khuyến nghị 16 + # (16 góc -> 1 bước góc = 22.5 độ) + + # --- Primitive đi thẳng --- + forward_short_m: float = 0.10 # [m] bước tiến ngắn, dùng để tinh chỉnh vị trí gần + # goal; >= 0.75*resolution để không quantize về (0,0) + forward_long_m: float = 0.40 # [m] bước tiến dài — quãng đường ~1 chu kỳ điều khiển + # ở tốc độ hành trình; quyết định tốc độ expand của A* + backward_m: float = 0.10 # [m] bước lùi thẳng (chỉ dùng khi has_backward_prims) + + # --- Primitive vừa đi vừa rẽ --- + # Rẽ được mô tả bằng CUNG TRÒN: turn_steps liệt kê số bước góc rời rạc mỗi primitive rẽ + # (1 = 22.5 độ với 16 góc), turn_radius_m là bán kính cung [m]. Điểm cuối được tự tính: + # dx = R*sin(dtheta), dy = R*(1 - cos(dtheta)) + # -> heading cuối LUÔN khớp góc rời rạc đích, không phải đoán độ lệch ngang bằng tay. + turn_steps: Tuple[int, ...] = (1,) # các mức rẽ; (1,) = chỉ rẽ 22.5 độ, (1, 2) thêm rẽ 45 + # độ — mỗi mức thêm 2 primitive/góc (CCW + CW), + # branching factor tăng -> planner chậm hơn + turn_radius_m: float = 0.70 # [m] bán kính cung rẽ. Nên >= max_vel/max_yawrate để + # local planner bám được không phải giảm tốc; và >= + # resolution/(1-cos(bước góc)) (~0.66m với lưới 5cm/16 + # góc) để quantize không làm méo cung. Ackermann: bắt + # buộc >= min_turning_radius_m. + + # --- Chỉ dùng cho OMNIDIRECTIONAL --- + sidestep_m: float = 0.10 # [m] bước đi ngang (trái/phải) giữ nguyên heading + diagonal_m: float = 0.10 # [m] chiều dài bước đi chéo 45 độ giữ nguyên heading + + # --- Chỉ dùng cho ACKERMANN --- + min_turning_radius_m: float = 0.35 # [m] bán kính quay tối thiểu VẬT LÝ (đo từ robot + # thật, không trượt bánh) — generator cảnh báo nếu + # cung sau quantize vi phạm giá trị này + + # --- Bật/tắt từng họ primitive --- + has_backward_prims: bool = True # cho phép lùi thẳng (tắt cho robot kéo hàng + # không được lùi kiểu noreverse_trolley) + has_backward_turn_prims: bool = False # cho phép lùi + rẽ; mặc định tắt vì AMR cảm biến + # chính hướng trước thì lùi-rẽ là hành động mù + has_turn_in_place_prims: bool = True # quay tại chỗ (ackermann thật thường phải tắt) + + # --- Hệ số chi phí, theo convention SBPL: cost thật = cost_thời_gian * costmult --- + # BẮT BUỘC số nguyên >= 1 (SBPL đọc bằng %d). Tham khảo bộ tuned unicycle_highcost: + # backward=40, turninplace=20, forwardandturn=2 — lùi/xoay tại chỗ đắt tới mức chỉ được + # chọn khi không còn đường nào khác. + forwardcostmult: int = 1 # tiến thẳng — mốc chuẩn, luôn để 1 + backwardcostmult: int = 40 # lùi thẳng; 40 nghĩa là lùi 0.1m "đắt" bằng tiến 4m + forwardandturncostmult: int = 2 # vừa tiến vừa rẽ; >1 để planner ưu tiên đường thẳng + backwardandturncostmult: int = 40 # lùi + rẽ (chỉ dùng khi has_backward_turn_prims) + turninplacecostmult: int = 20 # quay tại chỗ; giảm xuống nếu muốn robot xoay thay vì + # đi vòng, nhưng path sẽ xuất hiện cụm pose trùng điểm + sidestepcostmult: int = 10 # đi ngang (chỉ omni) + diagonalcostmult: int = 2 # đi chéo 45 độ (chỉ omni) + + # --- Mật độ intermediate poses (quyết định độ đều của path SBPL trả ra) --- + # Số sample được tính THEO CHIỀU DÀI: n = ceil(L / pose_spacing_m) + 1, nên mọi primitive + # dài ngắn khác nhau đều có khoảng cách pose ~ pose_spacing_m -> path ra đều nhau. + pose_spacing_m: float = 0.05 # [m] khoảng cách giữa 2 pose liên tiếp; phải <= + # resolution_m để footprint sweep không bỏ sót cell + pose_angular_spacing_deg: float = 5.625 # [deg] bước góc tối đa giữa 2 pose (quay tại chỗ + # và phần cung tròn; footprint sweep cần đủ mịn) + + # --- Giới hạn động học để sanity-check (tùy chọn, None = bỏ qua) --- + max_vel_mps: Optional[float] = None # [m/s] vận tốc dài tối đa của robot thật + max_yawrate_radps: Optional[float] = None # [rad/s] vận tốc xoay tối đa; cùng max_vel + # dùng để cảnh báo turn_radius quá gắt + + def validate(self): + """Raise ValueError nếu config sai; trả về list cảnh báo (không chặn generate).""" + errors = [] + warnings = [] + + if self.numberofangles % 8 != 0: + errors.append("numberofangles phải là bội số của 8 (khuyến nghị 16 hoặc 32).") + if self.resolution_m <= 0: + errors.append("resolution_m phải > 0.") + + # costmult: SBPL đọc bằng fscanf("%d") — số lẻ làm hỏng cả file + for f in ("forwardcostmult", "backwardcostmult", "forwardandturncostmult", + "backwardandturncostmult", "turninplacecostmult", + "sidestepcostmult", "diagonalcostmult"): + v = getattr(self, f) + if not isinstance(v, int) or isinstance(v, bool) or v < 1: + errors.append(f"{f} = {v!r}: costmult phải là SỐ NGUYÊN >= 1 " + "(SBPL đọc bằng fscanf %d, giá trị lẻ làm hỏng file).") + + # độ dài primitive thẳng: quá ngắn sẽ quantize về (0,0) ở góc 45 độ -> self-loop + min_len = self.resolution_m * 0.75 + for f in ("forward_short_m", "forward_long_m"): + if getattr(self, f) < min_len: + errors.append(f"{f} = {getattr(self, f)}: phải >= 0.75*resolution_m " + f"({min_len:.3f}m), nếu không endpose quantize về (0,0) ở góc chéo.") + elif getattr(self, f) < self.resolution_m: + warnings.append(f"{f} < resolution_m: primitive chỉ đi được ~1 cell, " + "cân nhắc tăng lên.") + if self.has_backward_prims and self.backward_m < min_len: + errors.append(f"backward_m = {self.backward_m}: phải >= {min_len:.3f}m " + "(hoặc tắt has_backward_prims).") + + # turn prims + if not self.turn_steps: + errors.append("turn_steps rỗng: cần ít nhất 1 bước góc (vd (1,)).") + for s in self.turn_steps: + if not isinstance(s, int) or s < 1 or s > self.numberofangles // 4: + errors.append(f"turn_steps chứa {s!r}: phải là int trong " + f"[1, {self.numberofangles // 4}] (tối đa 90 độ mỗi primitive).") + if len(set(self.turn_steps)) != len(self.turn_steps): + errors.append("turn_steps có phần tử trùng nhau.") + if self.turn_radius_m <= 0: + errors.append("turn_radius_m phải > 0.") + else: + step_rad = 2 * math.pi / self.numberofangles + min_step = min(self.turn_steps or [1]) + dy_min = self.turn_radius_m * (1 - math.cos(min_step * step_rad)) + if dy_min < self.resolution_m: + # dy < 1 cell nghĩa là sai số quantize cùng cỡ với chính hình dạng cung -> + # cung bị méo nặng, bán kính thực tế sau quantize có thể nhỏ hơn nhiều + r_need = self.resolution_m / (1 - math.cos(min_step * step_rad)) + warnings.append( + f"turn_radius_m = {self.turn_radius_m}: độ lệch ngang của cung rẽ nhỏ nhất " + f"({dy_min:.3f}m) < 1 cell -> quantize làm méo cung đáng kể. " + f"Khuyến nghị turn_radius_m >= {r_need:.2f}m với lưới hiện tại.") + + # sampling + if self.pose_spacing_m <= 0: + errors.append("pose_spacing_m phải > 0.") + elif self.pose_spacing_m > self.resolution_m: + errors.append(f"pose_spacing_m = {self.pose_spacing_m} > resolution_m: " + "footprint sweep của SBPL sẽ bỏ sót cell giữa 2 pose liên tiếp.") + if self.pose_angular_spacing_deg <= 0: + errors.append("pose_angular_spacing_deg phải > 0.") + + # giới hạn động học (tùy chọn) + if self.max_vel_mps is not None and self.max_yawrate_radps is not None: + r_min_kin = self.max_vel_mps / self.max_yawrate_radps + if self.turn_radius_m < r_min_kin: + warnings.append( + f"turn_radius_m = {self.turn_radius_m} < max_vel/max_yawrate = " + f"{r_min_kin:.3f}m: local planner phải giảm tốc mới bám được cung rẽ này.") + + # ackermann + if self.kinematic_type == KinematicType.ACKERMANN: + if self.min_turning_radius_m <= 0: + errors.append("Ackermann cần min_turning_radius_m > 0.") + elif self.turn_radius_m < self.min_turning_radius_m: + errors.append(f"turn_radius_m = {self.turn_radius_m} < min_turning_radius_m = " + f"{self.min_turning_radius_m}: primitive vi phạm giới hạn lái vật lý.") + if self.has_turn_in_place_prims: + warnings.append("Ackermann bật has_turn_in_place_prims: xe bánh lái thường " + "KHÔNG quay tại chỗ được — chỉ giữ nếu robot có bánh xoay đặc biệt.") + + # omni + if self.kinematic_type == KinematicType.OMNIDIRECTIONAL: + for f in ("sidestep_m", "diagonal_m"): + if getattr(self, f) < min_len: + errors.append(f"{f} = {getattr(self, f)}: phải >= {min_len:.3f}m.") + + if errors: + raise ValueError("Config không hợp lệ:\n - " + "\n - ".join(errors)) + return warnings diff --git a/mprim_core.py b/mprim_core.py new file mode 100644 index 0000000..ac19506 --- /dev/null +++ b/mprim_core.py @@ -0,0 +1,216 @@ +""" +mprim_core.py +------------- +Logic dùng CHUNG cho mọi robot: định nghĩa tập primitive theo hệ tọa độ gắn với +thân robot (body frame, heading = 0), tự động xoay sang từng góc rời rạc +(0..numberofangles-1), quantize về lưới cell, nội suy quỹ đạo và ghi file .mprim. + +Khác các file .m gốc (định nghĩa tay 3 bộ template cell riêng cho 0/45/22.5 độ), +ở đây primitive được định nghĩa 1 lần THEO NGHĨA VẬT LÝ (mét, bán kính cung) rồi +xoay + làm tròn cho từng góc. Đánh đổi là sai số lượng tử hóa nhỏ ở các góc lẻ +(< nửa cell, đã được dàn đều dọc quỹ đạo) — chấp nhận được với AMR thực tế. + +Sau khi sinh, engine TỰ KIỂM những ràng buộc mà SBPL sẽ enforce lúc nạp file +(xem ReadinMotionPrimitive trong environment_navxythetalat.cpp) để lỗi lộ ra +ngay lúc generate thay vì lúc khởi động planner trên robot. +""" + +import math +from dataclasses import dataclass +from typing import Callable, Dict, List, Tuple + +import numpy as np + +from mprim_config import RobotConfig, KinematicType + + +@dataclass +class Primitive: + dx_body_m: float # [m] dịch chuyển dọc trục robot (body frame) + dy_body_m: float # [m] dịch chuyển ngang (body frame, +: trái) + dtheta_c: int # số bước góc rời rạc thay đổi (+: CCW) + costmult: int # hệ số chi phí SBPL (số nguyên, đã validate) + label: str = "" + + +def _turn_endpoint(radius_m: float, steps: int, numberofangles: int) -> Tuple[float, float]: + """Điểm cuối body-frame của cung tròn bán kính radius_m quay steps bước góc. + + dx = R*sin(|dtheta|), dy = sign(steps)*R*(1-cos(|dtheta|)) — heading cuối trùng + khít góc rời rạc đích theo cấu trúc, không cần người dùng đoán độ lệch ngang. + """ + dtheta = abs(steps) * 2.0 * math.pi / numberofangles + return radius_m * math.sin(dtheta), math.copysign(radius_m * (1.0 - math.cos(dtheta)), steps) + + +def _nonholonomic_prims(cfg: RobotConfig, turn_radius_m: float) -> List[Primitive]: + """Tập primitive chung cho differential và ackermann (chỉ khác toggle/costmult).""" + prims = [ + Primitive(cfg.forward_short_m, 0.0, 0, cfg.forwardcostmult, "fwd_short"), + Primitive(cfg.forward_long_m, 0.0, 0, cfg.forwardcostmult, "fwd_long"), + ] + if cfg.has_backward_prims: + prims.append(Primitive(-cfg.backward_m, 0.0, 0, cfg.backwardcostmult, "bwd")) + for s in cfg.turn_steps: + dx, dy = _turn_endpoint(turn_radius_m, s, cfg.numberofangles) + prims += [ + Primitive(dx, dy, s, cfg.forwardandturncostmult, f"fwd_turn_ccw{s}"), + Primitive(dx, -dy, -s, cfg.forwardandturncostmult, f"fwd_turn_cw{s}"), + ] + if cfg.has_turn_in_place_prims: + prims += [ + Primitive(0.0, 0.0, 1, cfg.turninplacecostmult, "turn_in_place_ccw"), + Primitive(0.0, 0.0, -1, cfg.turninplacecostmult, "turn_in_place_cw"), + ] + if cfg.has_backward_turn_prims: + # lùi theo cung: heading quay CCW (+s) thì điểm cuối lệch về (-x, -y) — suy từ + # tích phân unicycle với tv < 0, rv > 0. + for s in cfg.turn_steps: + dx, dy = _turn_endpoint(turn_radius_m, s, cfg.numberofangles) + prims += [ + Primitive(-dx, -dy, s, cfg.backwardandturncostmult, f"bwd_turn_ccw{s}"), + Primitive(-dx, dy, -s, cfg.backwardandturncostmult, f"bwd_turn_cw{s}"), + ] + return prims + + +def build_primitive_set(cfg: RobotConfig) -> List[Primitive]: + """Sinh tập primitive body-frame theo loại kinematic.""" + if cfg.kinematic_type == KinematicType.DIFFERENTIAL: + return _nonholonomic_prims(cfg, cfg.turn_radius_m) + + if cfg.kinematic_type == KinematicType.ACKERMANN: + # validate() đã đảm bảo turn_radius_m >= min_turning_radius_m + return _nonholonomic_prims(cfg, cfg.turn_radius_m) + + if cfg.kinematic_type == KinematicType.OMNIDIRECTIONAL: + # kế thừa toàn bộ primitive nonholonomic (vẫn hợp lệ với omni)... + prims = _nonholonomic_prims(cfg, cfg.turn_radius_m) + # ...cộng khả năng đặc thù omni: đi ngang & đi chéo 45 độ giữ nguyên heading + diag = cfg.diagonal_m / math.sqrt(2.0) + prims += [ + Primitive(0.0, cfg.sidestep_m, 0, cfg.sidestepcostmult, "sidestep_left"), + Primitive(0.0, -cfg.sidestep_m, 0, cfg.sidestepcostmult, "sidestep_right"), + Primitive(diag, diag, 0, cfg.diagonalcostmult, "diag_fwd_left"), + Primitive(diag, -diag, 0, cfg.diagonalcostmult, "diag_fwd_right"), + ] + return prims + + raise ValueError(f"Chưa hỗ trợ kinematic_type={cfg.kinematic_type}") + + +# --- Tái hiện đúng phép rời rạc hóa của SBPL (environment_navxythetalat.cpp) --- + +def _contxy2disc(x: float, cellsize: float) -> int: + return int(math.floor(x / cellsize)) + + +def _conttheta2disc(theta: float, numberofangles: int) -> int: + step = 2.0 * math.pi / numberofangles + return int(round(((theta % (2.0 * math.pi)) / step))) % numberofangles + + +def _sbpl_endpose_check(poses, endcell, resolution: float, numberofangles: int): + """Đúng phép kiểm tra SBPL chạy khi nạp file: pose cuối (tính từ TÂM cell (0,0)) + phải rời rạc hóa ra đúng endcell — sai là SBPL từ chối cả file.""" + half = resolution / 2.0 + lx, ly, lth = poses[-1] + got = (_contxy2disc(half + lx, resolution), + _contxy2disc(half + ly, resolution), + _conttheta2disc(lth, numberofangles)) + return got == tuple(endcell), got + + +def generate_mprim_file(cfg: RobotConfig, kinematics_fn: Callable, output_path: str): + """Sinh file .mprim. Trả về (số primitive, list cảnh báo). + + kinematics_fn(startpt, endpt, dtheta_c, numberofangles, spacing_m, spacing_rad) + -> (List[(x, y, theta)], info_dict) + """ + warnings = list(cfg.validate()) + prims = build_primitive_set(cfg) + numberofangles = cfg.numberofangles + resolution = cfg.resolution_m + spacing_rad = math.radians(cfg.pose_angular_spacing_deg) + ang_step = 2.0 * math.pi / numberofangles + + lines = [ + f"resolution_m: {resolution:.6f}", + f"numberofangles: {numberofangles}", + f"totalnumberofprimitives: {len(prims) * numberofangles}", + ] + + worst_radius: Dict[str, float] = {} + for angleind in range(numberofangles): + currentangle = angleind * ang_step + endposes_seen: Dict[Tuple[int, int, int], str] = {} + for primid, p in enumerate(prims): + # xoay primitive (body frame) sang world frame tại góc hiện tại + dx_world = p.dx_body_m * math.cos(currentangle) - p.dy_body_m * math.sin(currentangle) + dy_world = p.dx_body_m * math.sin(currentangle) + p.dy_body_m * math.cos(currentangle) + + endx_c = int(round(dx_world / resolution)) + endy_c = int(round(dy_world / resolution)) + endtheta_c = (angleind + p.dtheta_c) % numberofangles + + # primitive suy biến: không đi, không xoay -> self-loop trong đồ thị SBPL + if endx_c == 0 and endy_c == 0 and p.dtheta_c == 0: + raise ValueError( + f"Primitive '{p.label}' quantize về (0,0,không đổi góc) tại " + f"startangle={angleind}: bước đi quá ngắn so với resolution.") + + key = (endx_c, endy_c, endtheta_c) + if key in endposes_seen: + warnings.append( + f"Trùng endpose {key} tại startangle={angleind}: '{p.label}' và " + f"'{endposes_seen[key]}' — successor thừa, tốn expansion vô ích.") + endposes_seen[key] = p.label + + startpt = (0.0, 0.0, currentangle) + endpt = (endx_c * resolution, endy_c * resolution, endtheta_c * ang_step) + poses, info = kinematics_fn(startpt, endpt, p.dtheta_c, numberofangles, + cfg.pose_spacing_m, spacing_rad) + + # --- tự kiểm các ràng buộc SBPL enforce lúc nạp file --- + heading_err = abs((poses[-1][2] - endtheta_c * ang_step + math.pi) + % (2.0 * math.pi) - math.pi) + if heading_err > 1e-6: + raise RuntimeError( + f"BUG nội bộ: heading cuối lệch {math.degrees(heading_err):.3f} độ tại " + f"'{p.label}' startangle={angleind} — interpolator phải trả heading chính xác.") + ok, got = _sbpl_endpose_check(poses, key, resolution, numberofangles) + if not ok: + raise RuntimeError( + f"BUG nội bộ: pose cuối rời rạc hóa ra {got} != endpose {key} tại " + f"'{p.label}' startangle={angleind} — SBPL sẽ từ chối file này.") + max_gap = max((math.hypot(x1 - x0, y1 - y0) + for (x0, y0, _), (x1, y1, _) in zip(poses, poses[1:])), + default=0.0) + if max_gap > resolution * 1.05: + warnings.append( + f"Bước giữa 2 pose = {max_gap:.3f}m > 1 cell tại '{p.label}' " + f"startangle={angleind}: footprint sweep có thể bỏ sót cell.") + if (cfg.kinematic_type == KinematicType.ACKERMANN + and info.get("radius_m") is not None + and info["radius_m"] < cfg.min_turning_radius_m * 0.999): + worst_radius[p.label] = min(worst_radius.get(p.label, math.inf), + info["radius_m"]) + + lines.append(f"primID: {primid}") + lines.append(f"startangle_c: {angleind}") + lines.append(f"endpose_c: {endx_c} {endy_c} {endtheta_c}") + lines.append(f"additionalactioncostmult: {p.costmult:d}") + lines.append(f"intermediateposes: {len(poses)}") + for (x, y, th) in poses: + lines.append(f"{x:.4f} {y:.4f} {th:.4f}") + + for label, r in sorted(worst_radius.items()): + warnings.append( + f"'{label}': bán kính cung sau quantize nhỏ nhất = {r:.3f}m < " + f"min_turning_radius_m = {cfg.min_turning_radius_m}m — xe thật không bám được, " + "tăng turn_radius_m để có dự phòng quantize.") + + with open(output_path, "w") as f: + f.write("\n".join(lines) + "\n") + + return len(prims) * numberofangles, warnings diff --git a/test_mprim_gen.py b/test_mprim_gen.py new file mode 100644 index 0000000..09594a4 --- /dev/null +++ b/test_mprim_gen.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +""" +test_mprim_gen.py +------------------ +Test tự động cho generator: sinh cả 3 config mẫu ra thư mục tạm rồi kiểm tra +output theo đúng các ràng buộc SBPL enforce lúc nạp file + chất lượng hình học. + +Chạy: + python3 test_mprim_gen.py + +Các kiểm tra: + 1. Header khớp số primitive thực tế; parse trọn vẹn không dư token. + 2. additionalactioncostmult là SỐ NGUYÊN (SBPL đọc fscanf %d). + 3. Pose cuối rời rạc hóa ra đúng endpose_c (đúng phép check của + ReadinMotionPrimitive — sai là SBPL từ chối cả file). + 4. Heading cuối khớp CHÍNH XÁC góc rời rạc đích (bug l=0 cũ gây lệch 10 độ). + 5. Khoảng cách 2 pose liên tiếp <= 1.05 cell (footprint sweep không sót cell) + và ĐỀU trong từng primitive (max/min <= 1.5). + 6. Vi phạm nonholonomic (góc lệch vector di chuyển vs heading) < 9 độ với + differential/ackermann — bắt lỗi kiểu cung lùi sai hình học (28 độ cũ). + 7. Không có primitive suy biến, không trùng endpose trong cùng start angle. + 8. Config sai (costmult lẻ, spacing > resolution, ackermann turn_radius < + min_turning_radius) phải bị validate() chặn. +""" + +import math +import os +import sys +import tempfile + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from mprim_config import RobotConfig, KinematicType +from mprim_core import generate_mprim_file +from generate_mprim import KINEMATICS_MAP, load_config + +CONFIG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "configs") +CHECKS = [] + + +def check(name, cond, detail=""): + CHECKS.append((name, cond, detail)) + status = "PASS" if cond else "FAIL" + print(f" [{status}] {name}" + (f" — {detail}" if detail and not cond else "")) + + +def parse(path): + toks = open(path).read().split() + it = iter(toks) + def nxt(): + return next(it) + assert nxt() == "resolution_m:" + res = float(nxt()) + assert nxt() == "numberofangles:" + nang = int(nxt()) + assert nxt() == "totalnumberofprimitives:" + ntot = int(nxt()) + prims = [] + while True: + try: + tag = nxt() + except StopIteration: + break + assert tag == "primID:" + pid = int(nxt()) + assert nxt() == "startangle_c:" + sa = int(nxt()) + assert nxt() == "endpose_c:" + end = (int(nxt()), int(nxt()), int(nxt())) + assert nxt() == "additionalactioncostmult:" + cm = nxt() + assert nxt() == "intermediateposes:" + n = int(nxt()) + poses = [(float(nxt()), float(nxt()), float(nxt())) for _ in range(n)] + prims.append(dict(pid=pid, sa=sa, end=end, cm=cm, poses=poses)) + return res, nang, ntot, prims + + +def ang_diff(a, b): + return (a - b + math.pi) % (2 * math.pi) - math.pi + + +def contxy2disc(x, cell): + return int(math.floor(x / cell)) + + +def verify_file(path, kinematic, min_turning_radius=None): + res, nang, ntot, prims = parse(path) + step = 2 * math.pi / nang + + check("header khớp số primitive parse được", ntot == len(prims), + f"header {ntot} != parsed {len(prims)}") + + bad_cm = [p["cm"] for p in prims if not p["cm"].lstrip("-").isdigit()] + check("costmult toàn số nguyên (fscanf %d)", not bad_cm, f"lẻ: {sorted(set(bad_cm))}") + + # phép check của SBPL ReadinMotionPrimitive + bad_end = 0 + for p in prims: + lx, ly, lth = p["poses"][-1] + got = (contxy2disc(res / 2 + lx, res), contxy2disc(res / 2 + ly, res), + int(round((lth % (2 * math.pi)) / step)) % nang) + if got != p["end"]: + bad_end += 1 + check("pose cuối rời rạc hóa đúng endpose_c (điều kiện SBPL nạp file)", bad_end == 0, + f"{bad_end} primitive sai") + + worst_heading = max(abs(ang_diff(p["poses"][-1][2], p["end"][2] * step)) for p in prims) + check("heading cuối khớp chính xác góc đích", worst_heading < 1e-4, + f"lệch tối đa {math.degrees(worst_heading):.2f} độ") + + worst_gap, worst_ratio = 0.0, 1.0 + for p in prims: + gaps = [math.hypot(x1 - x0, y1 - y0) + for (x0, y0, _), (x1, y1, _) in zip(p["poses"], p["poses"][1:])] + moving = [g for g in gaps if g > 1e-9] + if moving: + worst_gap = max(worst_gap, max(moving)) + worst_ratio = max(worst_ratio, max(moving) / min(moving)) + check("bước giữa 2 pose <= 1.05 cell (footprint sweep không sót)", + worst_gap <= res * 1.05, f"max {worst_gap:.4f}m") + check("spacing đều trong từng primitive (max/min <= 1.5)", worst_ratio <= 1.5, + f"tỉ lệ {worst_ratio:.2f}") + + if kinematic in (KinematicType.DIFFERENTIAL, KinematicType.ACKERMANN): + worst_nh = 0.0 + for p in prims: + if p["end"][0] == 0 and p["end"][1] == 0: + continue + for (x0, y0, t0), (x1, y1, t1) in zip(p["poses"], p["poses"][1:]): + dx, dy = x1 - x0, y1 - y0 + if math.hypot(dx, dy) < 1e-9: + continue + hd = math.atan2(dy, dx) + th = 0.5 * (t0 + t1) + worst_nh = max(worst_nh, min(abs(ang_diff(hd, th)), + abs(ang_diff(hd, th + math.pi)))) + check("vi phạm nonholonomic < 9 độ", math.degrees(worst_nh) < 9.0, + f"max {math.degrees(worst_nh):.2f} độ") + + degenerate = [p for p in prims + if p["end"] == (0, 0, p["sa"])] + check("không có primitive suy biến (0,0,không đổi góc)", not degenerate, + f"{len(degenerate)} cái") + + dup = 0 + for sa in range(nang): + seen = set() + for p in prims: + if p["sa"] != sa: + continue + if p["end"] in seen: + dup += 1 + seen.add(p["end"]) + check("không trùng endpose trong cùng start angle", dup == 0, f"{dup} cặp trùng") + + +def test_configs_generate(): + with tempfile.TemporaryDirectory() as tmp: + for fname in ("differential.py", "omnidirectional.py", "ackermann.py"): + cfg = load_config(os.path.join(CONFIG_DIR, fname)) + out = os.path.join(tmp, cfg.name + ".mprim") + n, warnings = generate_mprim_file(cfg, KINEMATICS_MAP[cfg.kinematic_type], out) + print(f"\n== {fname}: {n} primitives, {len(warnings)} cảnh báo ==") + for w in warnings: + print(f" (warn) {w}") + verify_file(out, cfg.kinematic_type, + min_turning_radius=getattr(cfg, "min_turning_radius_m", None)) + + +def test_validation_rejects_bad_configs(): + print("\n== validate() phải chặn config sai ==") + + def must_fail(name, **overrides): + base = dict(name="t", kinematic_type=KinematicType.DIFFERENTIAL) + base.update(overrides) + try: + RobotConfig(**base).validate() + check(name, False, "validate() đã KHÔNG chặn") + except ValueError: + check(name, True) + + must_fail("chặn costmult không nguyên", forwardandturncostmult=2.5) + must_fail("chặn costmult < 1", backwardcostmult=0) + must_fail("chặn pose_spacing_m > resolution", pose_spacing_m=0.08) + must_fail("chặn forward quá ngắn (quantize về 0,0)", forward_short_m=0.02) + must_fail("chặn turn_steps rỗng", turn_steps=()) + must_fail("chặn ackermann turn_radius < min_turning_radius", + kinematic_type=KinematicType.ACKERMANN, + min_turning_radius_m=0.45, turn_radius_m=0.30) + + +if __name__ == "__main__": + test_configs_generate() + test_validation_rejects_bad_configs() + failed = [c for c in CHECKS if not c[1]] + print(f"\n{'='*60}\nTổng: {len(CHECKS)} check, {len(failed)} FAIL") + sys.exit(1 if failed else 0) diff --git a/visualize_mprim.py b/visualize_mprim.py new file mode 100644 index 0000000..f9cf395 --- /dev/null +++ b/visualize_mprim.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +""" +visualize_mprim.py +------------------- +Vẽ nhanh tất cả primitive tại 1 góc bắt đầu (startangle) để kiểm tra bằng mắt +trước khi tích hợp vào planner thật. + +Cách dùng: + python3 visualize_mprim.py diff.mprim --angle 0 + python3 visualize_mprim.py omni.mprim --angle 4 +""" + +import argparse +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt + + +def parse_mprim(path): + with open(path) as f: + lines = [l.strip() for l in f.readlines()] + + i = 0 + resolution = float(lines[i].split(":")[1]); i += 1 + numberofangles = int(lines[i].split(":")[1]); i += 1 + total = int(lines[i].split(":")[1]); i += 1 + + prims = [] + while i < len(lines) and lines[i]: + primid = int(lines[i].split(":")[1]); i += 1 + startangle = int(lines[i].split(":")[1]); i += 1 + endpose = lines[i].split(":")[1].split(); i += 1 + costmult = float(lines[i].split(":")[1]); i += 1 + n = int(lines[i].split(":")[1]); i += 1 + poses = [] + for _ in range(n): + x, y, th = map(float, lines[i].split()) + poses.append((x, y, th)) + i += 1 + prims.append({ + "primid": primid, "startangle": startangle, + "endpose_c": [int(v) for v in endpose], + "costmult": costmult, "poses": poses + }) + return resolution, numberofangles, prims + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("mprim_file") + ap.add_argument("--angle", type=int, default=0, help="startangle_c cần vẽ") + ap.add_argument("-o", "--output", default=None, help="file ảnh output (mặc định: _angleN.png)") + args = ap.parse_args() + + resolution, numberofangles, prims = parse_mprim(args.mprim_file) + subset = [p for p in prims if p["startangle"] == args.angle] + + if not subset: + print(f"Không tìm thấy primitive nào với startangle_c={args.angle}") + return + + import math + + fig, ax = plt.subplots(figsize=(6, 6)) + tick = resolution * 0.4 # độ dài vạch heading tại mỗi pose + for p in subset: + xs = [pt[0] for pt in p["poses"]] + ys = [pt[1] for pt in p["poses"]] + line, = ax.plot(xs, ys, marker="o", markersize=2, + label=f'id{p["primid"]} (cost {p["costmult"]:g})') + # vạch heading: kiểm tra bằng mắt heading có bám tangent quỹ đạo không + # (bug kiểu "heading cuối lệch 10 độ" hiện rõ ở đây) + for (x, y, th) in p["poses"]: + ax.plot([x, x + tick * math.cos(th)], [y, y + tick * math.sin(th)], + color=line.get_color(), linewidth=0.6, alpha=0.6) + ax.annotate(str(p["primid"]), (xs[-1], ys[-1]), fontsize=8) + + ax.set_aspect("equal") + ax.grid(True) + ax.set_title(f"Primitives tại startangle_c={args.angle} / {numberofangles} (resolution={resolution}m)") + ax.set_xlabel("x (m)") + ax.set_ylabel("y (m)") + ax.legend(fontsize=7, loc="upper left", bbox_to_anchor=(1.02, 1)) + plt.tight_layout() + + out = args.output or args.mprim_file.replace(".mprim", f"_angle{args.angle}.png") + plt.savefig(out, dpi=130) + print(f"[OK] Đã lưu hình: {out}") + + +if __name__ == "__main__": + main() \ No newline at end of file