batches 0.11.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- batches-0.11.0/.gitignore +8 -0
- batches-0.11.0/AGENTS.md +137 -0
- batches-0.11.0/CHANGELOG.md +452 -0
- batches-0.11.0/CONTRIBUTING.md +130 -0
- batches-0.11.0/Cargo.lock +2293 -0
- batches-0.11.0/Cargo.toml +92 -0
- batches-0.11.0/LICENSE-APACHE +202 -0
- batches-0.11.0/LICENSE-MIT +21 -0
- batches-0.11.0/PKG-INFO +34 -0
- batches-0.11.0/PYPI_README.md +20 -0
- batches-0.11.0/README.md +332 -0
- batches-0.11.0/assets/README.md +35 -0
- batches-0.11.0/assets/fitting/stand.urdf +9 -0
- batches-0.11.0/assets/franka/LICENSE +178 -0
- batches-0.11.0/assets/franka/franka_panda.urdf +318 -0
- batches-0.11.0/assets/franka/meshes/collision/finger.obj +152 -0
- batches-0.11.0/assets/franka/meshes/collision/hand.obj +622 -0
- batches-0.11.0/assets/franka/meshes/collision/link0.obj +568 -0
- batches-0.11.0/assets/franka/meshes/collision/link1.obj +712 -0
- batches-0.11.0/assets/franka/meshes/collision/link2.obj +716 -0
- batches-0.11.0/assets/franka/meshes/collision/link3.obj +702 -0
- batches-0.11.0/assets/franka/meshes/collision/link4.obj +702 -0
- batches-0.11.0/assets/franka/meshes/collision/link5.obj +728 -0
- batches-0.11.0/assets/franka/meshes/collision/link6.obj +502 -0
- batches-0.11.0/assets/franka/meshes/collision/link7.obj +576 -0
- batches-0.11.0/assets/franka/panda_collision.json +464 -0
- batches-0.11.0/assets/franka/usd/Payload/Contents.usda +20 -0
- batches-0.11.0/assets/franka/usd/Payload/Geometry.usda +408 -0
- batches-0.11.0/assets/franka/usd/Payload/GeometryLibrary.usdc +0 -0
- batches-0.11.0/assets/franka/usd/Payload/Physics.usda +450 -0
- batches-0.11.0/assets/franka/usd/panda.usda +30 -0
- batches-0.11.0/assets/menagerie/franka_emika_panda/LICENSE +176 -0
- batches-0.11.0/assets/menagerie/franka_emika_panda/panda.xml +287 -0
- batches-0.11.0/assets/menagerie/franka_emika_panda/scene.xml +21 -0
- batches-0.11.0/assets/menagerie/robotiq_2f85/2f85.xml +192 -0
- batches-0.11.0/assets/menagerie/robotiq_2f85/LICENSE +23 -0
- batches-0.11.0/assets/menagerie/universal_robots_ur5e/LICENSE +26 -0
- batches-0.11.0/assets/menagerie/universal_robots_ur5e/ur5e.xml +136 -0
- batches-0.11.0/assets/newton/robotiq_2f85_v4/LICENSE +23 -0
- batches-0.11.0/assets/newton/robotiq_2f85_v4/usd_structured/Dual_wrist_camera.usda +29 -0
- batches-0.11.0/assets/newton/robotiq_2f85_v4/usd_structured/Payload/Contents.usda +20 -0
- batches-0.11.0/assets/newton/robotiq_2f85_v4/usd_structured/Payload/Geometry.usda +662 -0
- batches-0.11.0/assets/newton/robotiq_2f85_v4/usd_structured/Payload/GeometryLibrary.usdc +0 -0
- batches-0.11.0/assets/newton/robotiq_2f85_v4/usd_structured/Payload/Materials.usda +150 -0
- batches-0.11.0/assets/newton/robotiq_2f85_v4/usd_structured/Payload/MaterialsLibrary.usdc +0 -0
- batches-0.11.0/assets/newton/robotiq_2f85_v4/usd_structured/Payload/Physics.usda +593 -0
- batches-0.11.0/assets/newton/universal_robots_ur5e/LICENSE +26 -0
- batches-0.11.0/assets/newton/universal_robots_ur5e/usd_structured/Payload/Contents.usda +20 -0
- batches-0.11.0/assets/newton/universal_robots_ur5e/usd_structured/Payload/Geometry.usda +687 -0
- batches-0.11.0/assets/newton/universal_robots_ur5e/usd_structured/Payload/Materials.usda +205 -0
- batches-0.11.0/assets/newton/universal_robots_ur5e/usd_structured/Payload/Physics.usda +405 -0
- batches-0.11.0/assets/newton/universal_robots_ur5e/usd_structured/ur5e.usda +29 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/LICENSE +29 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/left_finger.stl +0 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/left_finger_tip.stl +0 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/left_inner_knuckle.stl +0 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/left_knuckle.stl +0 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/right_finger.stl +0 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/right_finger_tip.stl +0 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/right_inner_knuckle.stl +0 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/right_knuckle.stl +0 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/robotiq_base.stl +0 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/ur_to_robotiq_adapter.stl +0 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/package.xml +34 -0
- batches-0.11.0/assets/robotiq_2f85/robotiq_description/urdf/robotiq_2f_85.urdf +302 -0
- batches-0.11.0/assets/so101/LICENSE +201 -0
- batches-0.11.0/assets/so101/assets/base_motor_holder_so101_v1_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/base_so101_v2_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/motor_holder_so101_base_v1_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/motor_holder_so101_wrist_v1_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/moving_jaw_so101_v1_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/rotation_pitch_so101_v1_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/sts3215_03a_no_horn_v1_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/sts3215_03a_v1_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/under_arm_so101_v1_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/upper_arm_so101_v1_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/waveshare_mounting_plate_so101_v2_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/wrist_roll_follower_so101_v1_hull.stl +0 -0
- batches-0.11.0/assets/so101/assets/wrist_roll_pitch_so101_v2_hull.stl +0 -0
- batches-0.11.0/assets/so101/so101_new_calib.urdf +453 -0
- batches-0.11.0/assets/ur5e/ur_description/LICENSE +25 -0
- batches-0.11.0/assets/ur5e/ur_description/meshes/ur5e/collision/base.stl +0 -0
- batches-0.11.0/assets/ur5e/ur_description/meshes/ur5e/collision/forearm.stl +0 -0
- batches-0.11.0/assets/ur5e/ur_description/meshes/ur5e/collision/shoulder.stl +0 -0
- batches-0.11.0/assets/ur5e/ur_description/meshes/ur5e/collision/upperarm.stl +0 -0
- batches-0.11.0/assets/ur5e/ur_description/meshes/ur5e/collision/wrist1.stl +0 -0
- batches-0.11.0/assets/ur5e/ur_description/meshes/ur5e/collision/wrist2.stl +0 -0
- batches-0.11.0/assets/ur5e/ur_description/meshes/ur5e/collision/wrist3.stl +0 -0
- batches-0.11.0/assets/ur5e/ur_description/package.xml +61 -0
- batches-0.11.0/assets/ur5e/ur_description/urdf/ur5e.urdf +297 -0
- batches-0.11.0/assets/usd/arm.usda +28 -0
- batches-0.11.0/assets/usd/arm_parts.usda +138 -0
- batches-0.11.0/assets/usd/scene.usda +48 -0
- batches-0.11.0/batchplan/__init__.py +155 -0
- batches-0.11.0/clippy.toml +6 -0
- batches-0.11.0/docs/README.md +26 -0
- batches-0.11.0/docs/api.md +148 -0
- batches-0.11.0/docs/architecture.md +154 -0
- batches-0.11.0/docs/benchmarks.md +475 -0
- batches-0.11.0/docs/devices.md +70 -0
- batches-0.11.0/docs/perception.md +153 -0
- batches-0.11.0/docs/planning.md +534 -0
- batches-0.11.0/docs/python.md +112 -0
- batches-0.11.0/docs/robots.md +172 -0
- batches-0.11.0/docs/training-data.md +227 -0
- batches-0.11.0/docs/verification.md +166 -0
- batches-0.11.0/examples/bench.rs +86 -0
- batches-0.11.0/examples/benchmark.rs +862 -0
- batches-0.11.0/examples/common/mod.rs +514 -0
- batches-0.11.0/examples/compare.rs +719 -0
- batches-0.11.0/examples/constraints.rs +185 -0
- batches-0.11.0/examples/control_loop.rs +120 -0
- batches-0.11.0/examples/datagen.rs +133 -0
- batches-0.11.0/examples/depth.rs +151 -0
- batches-0.11.0/examples/fitting.rs +109 -0
- batches-0.11.0/examples/handover/main.rs +206 -0
- batches-0.11.0/examples/handover/rebot.rs +265 -0
- batches-0.11.0/examples/join.rs +172 -0
- batches-0.11.0/examples/latency.rs +277 -0
- batches-0.11.0/examples/so101_tracking.rs +233 -0
- batches-0.11.0/examples/tasks.rs +184 -0
- batches-0.11.0/examples/timing.rs +157 -0
- batches-0.11.0/examples/tool_paths.rs +87 -0
- batches-0.11.0/examples/usd_fk.rs +61 -0
- batches-0.11.0/licenses/LICENSE-APACHE +202 -0
- batches-0.11.0/licenses/LICENSE-MIT +21 -0
- batches-0.11.0/pyproject.toml +53 -0
- batches-0.11.0/python/Cargo.lock +1789 -0
- batches-0.11.0/python/Cargo.toml +50 -0
- batches-0.11.0/python/PYPI_README.md +20 -0
- batches-0.11.0/python/licenses/LICENSE-APACHE +202 -0
- batches-0.11.0/python/licenses/LICENSE-MIT +21 -0
- batches-0.11.0/python/src/device.rs +224 -0
- batches-0.11.0/python/src/error.rs +84 -0
- batches-0.11.0/python/src/field.rs +361 -0
- batches-0.11.0/python/src/held.rs +139 -0
- batches-0.11.0/python/src/ik.rs +348 -0
- batches-0.11.0/python/src/lib.rs +46 -0
- batches-0.11.0/python/src/plan.rs +470 -0
- batches-0.11.0/python/src/robot.rs +486 -0
- batches-0.11.0/python/src/solved.rs +48 -0
- batches-0.11.0/python/src/task.rs +670 -0
- batches-0.11.0/python/src/timing.rs +363 -0
- batches-0.11.0/python/src/tool_path.rs +252 -0
- batches-0.11.0/python/src/world.rs +218 -0
- batches-0.11.0/python/tests/conftest.py +63 -0
- batches-0.11.0/python/tests/test_docs.py +27 -0
- batches-0.11.0/python/tests/test_planning.py +625 -0
- batches-0.11.0/python/tests/test_tasks.py +305 -0
- batches-0.11.0/python/tests/test_values.py +136 -0
- batches-0.11.0/python/uv.lock +436 -0
- batches-0.11.0/rustfmt.toml +4 -0
- batches-0.11.0/scripts/bench_model.py +364 -0
- batches-0.11.0/scripts/build_compare_cpp.sh +27 -0
- batches-0.11.0/scripts/check.sh +29 -0
- batches-0.11.0/scripts/compare.py +596 -0
- batches-0.11.0/scripts/compare_cpp.cc +378 -0
- batches-0.11.0/scripts/compare_planners.py +564 -0
- batches-0.11.0/scripts/cpp_planners.cc +298 -0
- batches-0.11.0/scripts/fetch_benchmark.sh +36 -0
- batches-0.11.0/scripts/fetch_panda_meshes.sh +46 -0
- batches-0.11.0/scripts/fetch_rebot.sh +86 -0
- batches-0.11.0/scripts/parity.py +334 -0
- batches-0.11.0/scripts/replay_mujoco.py +207 -0
- batches-0.11.0/scripts/report.py +219 -0
- batches-0.11.0/scripts/report_suite.sh +27 -0
- batches-0.11.0/scripts/robokit/batchplan_ik.py +138 -0
- batches-0.11.0/scripts/robokit/batchplan_mp.py +57 -0
- batches-0.11.0/scripts/robokit/batchplan_plugin.py +145 -0
- batches-0.11.0/scripts/run_comparison.sh +68 -0
- batches-0.11.0/scripts/run_suite.sh +29 -0
- batches-0.11.0/scripts/so101_tracking.py +153 -0
- batches-0.11.0/scripts/sync.sh +15 -0
- batches-0.11.0/scripts/test_bench.py +190 -0
- batches-0.11.0/scripts/validate.py +197 -0
- batches-0.11.0/scripts/validate_lerobot.py +68 -0
- batches-0.11.0/scripts/validate_usd.py +199 -0
- batches-0.11.0/src/constraint.rs +995 -0
- batches-0.11.0/src/cpu.rs +2456 -0
- batches-0.11.0/src/datagen.rs +1550 -0
- batches-0.11.0/src/description.rs +577 -0
- batches-0.11.0/src/device.rs +659 -0
- batches-0.11.0/src/edges.rs +619 -0
- batches-0.11.0/src/error.rs +70 -0
- batches-0.11.0/src/gpu.rs +3099 -0
- batches-0.11.0/src/grid.wgsl +44 -0
- batches-0.11.0/src/grids.wgsl +352 -0
- batches-0.11.0/src/handover.rs +408 -0
- batches-0.11.0/src/held.rs +490 -0
- batches-0.11.0/src/ik.rs +555 -0
- batches-0.11.0/src/join.rs +834 -0
- batches-0.11.0/src/kernels.wgsl +1151 -0
- batches-0.11.0/src/lanes.rs +1490 -0
- batches-0.11.0/src/lerobot.rs +866 -0
- batches-0.11.0/src/lib.rs +122 -0
- batches-0.11.0/src/live.rs +932 -0
- batches-0.11.0/src/loops.rs +351 -0
- batches-0.11.0/src/meshes.rs +360 -0
- batches-0.11.0/src/mjcf.rs +658 -0
- batches-0.11.0/src/npy.rs +213 -0
- batches-0.11.0/src/rng.rs +51 -0
- batches-0.11.0/src/robot.rs +2158 -0
- batches-0.11.0/src/rrt/ahead.rs +1203 -0
- batches-0.11.0/src/rrt/connect.rs +1628 -0
- batches-0.11.0/src/rrt.rs +26 -0
- batches-0.11.0/src/sdf.rs +1501 -0
- batches-0.11.0/src/shortcut.rs +606 -0
- batches-0.11.0/src/spheres.rs +1098 -0
- batches-0.11.0/src/spline.rs +236 -0
- batches-0.11.0/src/task.rs +1836 -0
- batches-0.11.0/src/timing.rs +926 -0
- batches-0.11.0/src/tool_path.rs +824 -0
- batches-0.11.0/src/topp.rs +927 -0
- batches-0.11.0/src/trajopt.rs +2282 -0
- batches-0.11.0/src/types.rs +155 -0
- batches-0.11.0/src/urdf.rs +213 -0
- batches-0.11.0/src/usd.rs +812 -0
- batches-0.11.0/src/world.rs +717 -0
- batches-0.11.0/tests/allocations.rs +131 -0
- batches-0.11.0/tests/attach.rs +430 -0
- batches-0.11.0/tests/budget.rs +151 -0
- batches-0.11.0/tests/constraints.rs +820 -0
- batches-0.11.0/tests/cpu.rs +297 -0
- batches-0.11.0/tests/device.rs +1927 -0
- batches-0.11.0/tests/export.rs +638 -0
- batches-0.11.0/tests/failures.rs +224 -0
- batches-0.11.0/tests/goals.rs +410 -0
- batches-0.11.0/tests/gpu.rs +1146 -0
- batches-0.11.0/tests/handover.rs +467 -0
- batches-0.11.0/tests/hands.rs +607 -0
- batches-0.11.0/tests/held.rs +707 -0
- batches-0.11.0/tests/join.rs +822 -0
- batches-0.11.0/tests/live.rs +1859 -0
- batches-0.11.0/tests/meshes.rs +419 -0
- batches-0.11.0/tests/mjcf.rs +478 -0
- batches-0.11.0/tests/pick_place.rs +1014 -0
- batches-0.11.0/tests/robot.rs +1850 -0
- batches-0.11.0/tests/rrt.rs +792 -0
- batches-0.11.0/tests/sdf.rs +1586 -0
- batches-0.11.0/tests/start_limits.rs +523 -0
- batches-0.11.0/tests/task.rs +2035 -0
- batches-0.11.0/tests/threads.rs +105 -0
- batches-0.11.0/tests/tool_path.rs +665 -0
- batches-0.11.0/tests/touch.rs +414 -0
- batches-0.11.0/tests/trajectory.rs +1502 -0
- batches-0.11.0/tests/usd.rs +383 -0
batches-0.11.0/AGENTS.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Agent instructions
|
|
2
|
+
|
|
3
|
+
This file is for coding agents that use batchplan in a program. To change batchplan itself, read [`CLAUDE.md`](CLAUDE.md): its design rules, invariants and working rules. The guides under [`docs/`](docs/README.md) explain each topic in full; this page gathers what an agent needs first and links to them. From Python, the `batchplan` package makes the same calls by the same names; [Python](docs/python.md) says how they map and what it binds so far.
|
|
4
|
+
|
|
5
|
+
## Use batchplan: make once, warm up, reuse
|
|
6
|
+
|
|
7
|
+
Build the robot and its device once, warm the device, upload each scene once, and reuse all three for every request. Results are per problem: read each one's answer or its reason, never assume a batch succeeded together.
|
|
8
|
+
|
|
9
|
+
```rust,no_run
|
|
10
|
+
use batchplan::glam::{Quat, Vec3};
|
|
11
|
+
use batchplan::{
|
|
12
|
+
Device, Error, Obstacle, PlanFailure, PlanOptions, PlanProblem, Pose, Robot, RobotOptions,
|
|
13
|
+
Trajectory, World,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
fn main() -> Result<(), Error> {
|
|
17
|
+
// Once per robot: load it, check its spheres, make its device and warm it up.
|
|
18
|
+
let options = RobotOptions {
|
|
19
|
+
ee_links: vec!["tool0".into()],
|
|
20
|
+
..Default::default()
|
|
21
|
+
};
|
|
22
|
+
let robot = Robot::load("assets/ur5e/ur_description/urdf/ur5e.urdf", &options)?;
|
|
23
|
+
for link in robot.qualify()?.links {
|
|
24
|
+
println!("{}: spheres reach {:.1} mm past it", link.link, link.overhang * 1e3);
|
|
25
|
+
}
|
|
26
|
+
let device = Device::gpu(&robot).or_else(|_| Device::cpu(&robot))?;
|
|
27
|
+
device.warmup()?;
|
|
28
|
+
|
|
29
|
+
// Once per scene, and again whenever it changes: obstacles in the robot's frame, with the
|
|
30
|
+
// links that always touch the table it stands on allowed to.
|
|
31
|
+
let table = Obstacle::cuboid(Vec3::new(0.3, 0.0, -0.02), Vec3::new(0.6, 0.6, 0.02));
|
|
32
|
+
let mut world = World::new(vec![table]);
|
|
33
|
+
let mounted = world.always_touching(&robot);
|
|
34
|
+
world.touch.extend(mounted);
|
|
35
|
+
let worlds = device.upload(&[world])?;
|
|
36
|
+
|
|
37
|
+
// Per request: a pose in the robot's frame (quaternions x, y, z, w), from the measured state
|
|
38
|
+
// in `robot.joint_names()` order.
|
|
39
|
+
let target = Pose::new(Vec3::new(0.4, 0.2, 0.3), Quat::from_xyzw(1.0, 0.0, 0.0, 0.0));
|
|
40
|
+
let start = robot.default_q();
|
|
41
|
+
// IK from the start finds where the path ends: the nearest collision-free solution.
|
|
42
|
+
let problem = PlanProblem::new(0, start, target);
|
|
43
|
+
let plans = batchplan::plan(&device, &worlds, &[problem], &PlanOptions::default())?;
|
|
44
|
+
match (plans.best(0), &plans.failures[0]) {
|
|
45
|
+
(Some(path), _) => {
|
|
46
|
+
let trajectory = Trajectory::try_new(&robot, path, 1.0)?;
|
|
47
|
+
trajectory.check(&robot)?;
|
|
48
|
+
let samples = trajectory.sample(100.0)?; // positions, velocities, accelerations
|
|
49
|
+
println!("{} samples over {:.2} s", samples.len(), trajectory.duration());
|
|
50
|
+
}
|
|
51
|
+
(None, Some(PlanFailure::StartCollides { contacts })) => {
|
|
52
|
+
println!("the start collides: {contacts:?}");
|
|
53
|
+
}
|
|
54
|
+
(None, Some(PlanFailure::GoalUnreachable)) => println!("no IK seed reaches the pose"),
|
|
55
|
+
(None, why) => println!("no plan: {why:?}"),
|
|
56
|
+
}
|
|
57
|
+
Ok(())
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Conventions
|
|
62
|
+
|
|
63
|
+
- **Units.** Meters, radians (meters for prismatic joints), seconds, `f32` throughout.
|
|
64
|
+
- **Poses** are `Pose { position, rotation }` with [glam](https://docs.rs/glam) types, re-exported as `batchplan::glam`. glam's quaternions are x, y, z, w (`Quat::from_xyzw`), as ROS messages are; MuJoCo and Eigen's constructor are w, x, y, z. Rotations must be unit quaternions.
|
|
65
|
+
- **Frames.** Targets, obstacles and depth-camera poses are all in the robot's frame: its description's root link, or `world` for a robot made with `Robot::assemble`. Transform camera and map data into it first.
|
|
66
|
+
- **Joint order** is `robot.joint_names()`: the actuated joints after locking, numbered breadth first, so two arms' joints interleave. Map a controller's names to it; never assume the description's order. Planar, ball and floating joints become one-axis joints named `<joint>_x`, `_theta` and so on ([robots](docs/robots.md#kinematics)).
|
|
67
|
+
- **Hands** are the IK frames of `RobotOptions::ee_links`, numbered in that order. For a gripper, use a frame between the fingertips, z out of the hand and y along the closing direction: what `grasps` assumes.
|
|
68
|
+
|
|
69
|
+
### Lifecycle
|
|
70
|
+
|
|
71
|
+
- **Robot.** `Robot::load` fits collision spheres unless given a model: a fraction of a second for an arm at the default budget, seconds for thousands of spheres; save `robot.collision_model()` and pass it back as `RobotOptions::collision_model` to load the same spheres every time.
|
|
72
|
+
- **Device.** One per robot (`Device::gpu`, falling back to `Device::cpu`), made once: never inside a control loop or per request. `device.warmup()` before timing anything ([devices](docs/devices.md#make-once-warm-up-reuse)).
|
|
73
|
+
- **Worlds.** `device.upload(&worlds)` once per scene. Uploads are cheap (0.1–0.2 ms for 512 tabletop worlds), so a scene that changes is uploaded again; there is no partial update ([changing a scene](docs/perception.md#changing-a-scene)).
|
|
74
|
+
- **Locked joints that move**, as a torso or head the robot plans without: `robot.relock(&values)` then `device.with_robot(&relocked)`, which compiles nothing and keeps the uploaded worlds. Fit the collision model with those joints free and load with it: a model records the joints it was fitted with locked, whose pairs that meet only at other values would go unchecked, and `relock` refuses to move them ([robots](docs/robots.md#kinematics)).
|
|
75
|
+
- **Held objects.** `holding: Some(held)` on a `PlanProblem`, `IkProblem`, `ToolPath` or `Sequence`, with the device without the object: one call takes problems holding different objects or grasps, a batch per hold ([holding objects](docs/robots.md#holding-objects)). On a GPU, warm up a device holding any object in each hand you will hold in: the first call holding there builds its kernels. For a device that holds one, `robot.attach(&AttachedObject)` then `device.with_robot(&held)`: the same GPU or threads, and the uploaded worlds still work, unless they name what a hand comes to hold (`Touch::held`). The held robot's GPU kernels are its own: warm its device up too before timing it. `robot.detach(name)` reverses it. Attachment is collision only: there is no payload mass or inertia, so timing a heavy carry ignores the load; slow it with a speed scale ([trajectories](docs/planning.md#executable-trajectories)).
|
|
76
|
+
- **Finger openings.** `Pick::locked` and `Held::locked` hold the robot's locked joints (the fingers) at given values for that task or hold, many openings in one call; empty is as loaded ([tasks](docs/planning.md#picking-up-and-setting-down)). Don't build a relocked device per opening for calls that take them.
|
|
77
|
+
- **Live loops** call `Follower::tick` or `servo` each period and nothing that builds: pass the measured state to `tick` so a robot held back or pushed is followed from where it is (held there, `Blocked`, with `FollowerOptions::replan` off). A controller in another process takes each replan from `FollowStatus::Replanned { at, trajectory }`, with `FollowerOptions::lead` for the time sending it takes. `Stopping { contacts }` and `Collides { at, contacts }` name what is in the way, links and obstacles as `PlanFailure::StartCollides` names them ([live motion](docs/planning.md#live-motion)).
|
|
78
|
+
|
|
79
|
+
### Choose the call
|
|
80
|
+
|
|
81
|
+
| Task | Call | Guide |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| Forward kinematics | `robot.ee_pose(q, hand)`, `robot.link_pose(q, link)`, `robot.spheres(q)` | [robots](docs/robots.md) |
|
|
84
|
+
| Configurations for hand poses | `solve_ik`; `ik::failures` for why one has none | [IK](docs/planning.md#inverse-kinematics) |
|
|
85
|
+
| A collision-free path to a configuration or a hand pose | `plan`, then `time` (one path: `Trajectory::try_new`) | [planning](docs/planning.md#planning-a-path) |
|
|
86
|
+
| The hand along straight lines | `follow` | [tool paths](docs/planning.md#moving-the-hand-along-a-path) |
|
|
87
|
+
| A hand kept upright, in a box or relative to another hand | `HandConstraint` on the problem | [constraints](docs/planning.md#constraints-along-the-path) |
|
|
88
|
+
| Picking up and setting down | `grasps`, `pick`, `place` | [tasks](docs/planning.md#picking-up-and-setting-down) |
|
|
89
|
+
| Passing an object from one hand to another | `pick` with `GraspOptions::along` (toward an end), then `handover` | [tasks](docs/planning.md#picking-up-and-setting-down) |
|
|
90
|
+
| A program of picks and places planned together | `pick` and `place` with `transit: false`, then one `plan` and `join` | [tasks](docs/planning.md#picking-up-and-setting-down) |
|
|
91
|
+
| Motions run without stopping between them | `join` | [joins](docs/planning.md#joining-motions) |
|
|
92
|
+
| A motion that comes fast and arrives slowly | `Trajectory::with_hand_speed` with a cap near the grasp (`HandSpeed::near`); many paths: `time`; in `pick`/`place`: `TaskOptions::tool_speed_near` | [timing](docs/planning.md#executable-trajectories) |
|
|
93
|
+
| Running a motion while the world changes | `Follower`, `servo` | [live motion](docs/planning.md#live-motion) |
|
|
94
|
+
| Obstacles from depth images or point clouds | `SdfGrid`, `OccupancyMap`, `DepthImage::robot_mask` | [perception](docs/perception.md) |
|
|
95
|
+
| Training data | `datagen`, `npy::export`, `lerobot::export` | [training data](docs/training-data.md) |
|
|
96
|
+
|
|
97
|
+
IK finds configurations; a plan finds the path to one, or to a pose, solving IK from the start itself. Solve IK first only to choose among its solutions. Every call takes many problems, each naming its world: batch them rather than calling once per problem, since each GPU call has a fixed cost.
|
|
98
|
+
|
|
99
|
+
### Qualify the robot
|
|
100
|
+
|
|
101
|
+
A robot that loads may still have spheres unfit to plan with. Before planning with a new robot or a hand-tuned collision model ([qualifying a robot](docs/robots.md#qualifying-a-robot)):
|
|
102
|
+
|
|
103
|
+
1. `robot.qualify()`: each link's `overhang` past its geometry, the `uncovered` share of its surface and its `thickness`, and each hand's `tips`. Fingers whose overhang or tip is a large part of the smallest object you will grasp will collide with what it stands on; give them a tighter tolerance (`RobotOptions::sphere_tolerances`) or a finer `SphereOptions::voxel_size`.
|
|
104
|
+
2. `world.always_touching(&robot)` with the table the robot stands on: links that touch it in every configuration need a touch, or every plan's start collides.
|
|
105
|
+
3. Fingers that are part of the palm's link, as the SO-101's fixed jaw: name them in `RobotOptions::finger_links`, or grasps where they meet the object fail.
|
|
106
|
+
4. `device.contacts(&worlds, &item_world, &q, 0.0)` at the default configuration, the SRDF's named states and a few others: each should collide with nothing.
|
|
107
|
+
|
|
108
|
+
### Read results
|
|
109
|
+
|
|
110
|
+
- `ik.best(i)`, `plans.best(i)` and `solved()` return only valid answers; `IkResult::success` and `PlanResult::valid` are per seed, problem-major (`problem * seeds + seed`).
|
|
111
|
+
- A plan is B-spline control points, not a trajectory: time a call's paths together with `time`, or one with `Trajectory::try_new` (`Trajectory::moving` from a moving start), sample it with `at` or `sample`, and `check` it before it runs on hardware. Never send control points to a controller.
|
|
112
|
+
- With `start_tolerance`, use the corrected start retained in the result’s problem; goals and start motion remain as given ([measured starts](docs/planning.md#measured-starts-at-joint-limits)).
|
|
113
|
+
- Plans do not depend on the batch or the device; with a `time_budget`, they depend on the machine's speed. IK starts every problem from the same seeds in any batch, but a GPU rounds differently from the CPU, and differently on either side of where it switches how many invocations share a configuration (4,096 configurations, more for robots with many spheres), so solutions there can differ slightly, and so can plans to poses, which end where IK solves them.
|
|
114
|
+
|
|
115
|
+
### Diagnose a failed request, in order
|
|
116
|
+
|
|
117
|
+
Check these before raising budgets:
|
|
118
|
+
|
|
119
|
+
1. **The input.** An `Error::Input` names what was refused and the value given: shapes, joint order, unit quaternions, joint limits (strict by default; [measured starts can opt into correction](docs/planning.md#measured-starts-at-joint-limits)), frames.
|
|
120
|
+
2. **The start.** `PlanFailure::StartCollides { contacts }` names each link and obstacle, or pair of links. A link touching the table in every configuration needs a touch (`always_touching`); a start inside a held object's spheres needs the object attached.
|
|
121
|
+
3. **The goal.** `PlanFailure::GoalCollides`, and for a pose goal `GoalUnreachable`, which more `PlanOptions::goal_ik` iterations may fix, or more seeds `kept` past its screening (`screening: 0` gives every seed every iteration); for IK, `ik::failures` tells `Unreachable` from `Collides { contacts }`.
|
|
122
|
+
4. **The search.** `NoPath`: more `rrt.max_iterations`, or the way is blocked. `NoSpline`: more `control_points`, or a slower start motion. `OutOfTime`: a longer `time_budget`.
|
|
123
|
+
5. **Tasks.** A `TaskFailure` prints where and why every candidate stopped; each `CandidateStop` has the step, the reason and the contacts, whose obstacles are numbered as in the task's world; in a place, `world.obstacles.len()` is the object where it is set down, and in a handover, the object where it changes hands. `grasps` says why it found none. Grasps near a support need `GraspOptions::tip` and `floor`, which keeps the open fingers over it too; from above, they keep at least `overlap` of the fingers (1 cm) in the object, so an object whose top is less than that over the floor has none.
|
|
124
|
+
|
|
125
|
+
### Footguns
|
|
126
|
+
|
|
127
|
+
- glam's quaternion order is x, y, z, w; a w-first source passed straight through turns the hand.
|
|
128
|
+
- `PlanOptions::seeds` is 0: no trajectory optimization unless asked for.
|
|
129
|
+
- A world's touches name links; what a problem's `holding` may touch is named with `Touch::held(obstacle, hand)`, one hand's per upload, on the device that holds nothing.
|
|
130
|
+
- `holding` takes the device without the object: one already holding a `Held` in that hand is an `Error::Input`.
|
|
131
|
+
- `grasps` returns poses in the object's frame; with a `floor`, pass the obstacle as it stands in the world.
|
|
132
|
+
- A task's world holds for all its steps unless `transit_world` or `depart_world` name others: what the hand may touch at the grasp, it may touch all the way from the start.
|
|
133
|
+
- `TaskPlan::transit` is `None` for a task with `transit: false`, whose start is a seed and is not checked.
|
|
134
|
+
- `Robot::lower` and `upper` are ranges; continuous joints turn without limit and wrap.
|
|
135
|
+
- `Follower::tick` with `None` trusts the trajectory; on hardware, pass the measured state.
|
|
136
|
+
- `FollowStatus::Stopping { contacts }` numbers obstacles as in the worlds of the tick the stop began at, on every tick of the stop: map them through those worlds, not the current ones.
|
|
137
|
+
- A `Follower` on a tool path (`follow`) needs `FollowerOptions::replan` off: a replan leaves the path for a free-space motion to its end. Off, a collision ahead stops it on the path and a robot past `tolerance` is held where it is, both `Blocked` at rest ([live motion](docs/planning.md#live-motion)). On a way joined into a move in, give it a `Detour` instead: it replans only the way, to where the move in starts, and takes the move in up from rest there (`Resumed`).
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
What changed in each release, for people upgrading, in the format of
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow
|
|
5
|
+
[Cargo's semver](https://doc.rust-lang.org/cargo/reference/semver.html): before 1.0, a new minor
|
|
6
|
+
version (0.9 → 0.10) may break the interface, and a patch (0.9.0 → 0.9.1) breaks nothing.
|
|
7
|
+
[Contributing](CONTRIBUTING.md#releases) says how releases are made.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [0.11.0] - 2026-11-14
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Breaking:** `PlanOptions::start_tolerance` and `ToolPathOptions::start_tolerance` optionally
|
|
16
|
+
clamp measured starts just past a joint limit before the normal collision and constraint checks.
|
|
17
|
+
Both default to zero, preserving strict validation. Larger or nonfinite violations are input
|
|
18
|
+
errors; continuous joints, goals and start velocities are unchanged. `pick`, `place` and
|
|
19
|
+
`handover` use `TaskOptions::plan.start_tolerance` for their initial states. Results retain the
|
|
20
|
+
corrected starts. Exact options literals need the new field; Python options expose it too.
|
|
21
|
+
- **Breaking:** picks and holds say where the fingers are: `Pick::locked` and `Held::locked` hold
|
|
22
|
+
the robot's locked joints (as `Robot::relock` takes them) at those values for that task, or
|
|
23
|
+
while the object is held, so a rim between walls can be picked with the fingers part open and
|
|
24
|
+
a hold checked where they closed. One call takes tasks and holds at many openings, a relocked
|
|
25
|
+
robot for each group; empty, the default, is as loaded, to the bit. A `Pick` or `Held` literal
|
|
26
|
+
needs `locked: HashMap::new()`. In Python, `Pick(..., locked={...})`, `Held(..., locked={...})`.
|
|
27
|
+
- **Breaking:** `Occlusion::Band(distance)`, for `SdfGrid::from_depth` and `OccupancyMap::grid`:
|
|
28
|
+
solid within `distance` behind an observed surface, as seen from the camera, and free past it,
|
|
29
|
+
in the robot's shadow and along pixels without a reading. In a map, unobserved space within
|
|
30
|
+
`distance` of an occupied voxel is solid, in any direction, and the robot's shadow is free. A
|
|
31
|
+
band that is not finite or is negative is an input error. `Occlusion` is no longer `Eq` (the
|
|
32
|
+
band is an `f32`); from depth images, the other rules build the same grids as before.
|
|
33
|
+
- One hand can pass what it holds to another: `handover(&device, &worlds, &[Handover], &TaskOptions)`,
|
|
34
|
+
with `Handover::new(&robot, world, start, held, to)`. Candidates are each meeting with each of the
|
|
35
|
+
taker's grasps; both hands' IK solves at the meeting with the object standing there, each hand's
|
|
36
|
+
straight move holds the other arm still, and the way to the meeting holds the object in the
|
|
37
|
+
giver's hand. Every candidate that gets through comes back quickest first, naming its `meeting`
|
|
38
|
+
(`TaskPlan::meeting`) and the taker's `held`. `handover_poses` proposes meetings where both arms
|
|
39
|
+
reach and `handover_grasps` the taker's grasps at the object's other end. In Python, the same
|
|
40
|
+
names.
|
|
41
|
+
- **Breaking:** `grasps` can hold an object toward its ends: `GraspOptions::along`, a fraction of
|
|
42
|
+
half the side the fingers close on (0, the middle, as before), for a pick whose object one hand
|
|
43
|
+
passes to another. A `GraspOptions` literal needs the new field (`along: 0.0`); literals with
|
|
44
|
+
`..GraspOptions::default()` are unaffected. In Python, `GraspOptions(along=0.75)`.
|
|
45
|
+
- **Breaking:** a tool path can hold joints still: `ToolPath::hold` lists joints that stay where
|
|
46
|
+
`start` has them along the whole path, as `PlanProblem::hold` and `IkProblem::hold` do, for a
|
|
47
|
+
hand whose arm shares joints (a torso) with one that holds what it reaches for. The hand follows
|
|
48
|
+
with the joints left, and stops where they cannot keep it on the path. A `ToolPath` literal
|
|
49
|
+
needs the new field (`hold: vec![]`). In Python, `ToolPath(..., hold=[0, 1])`.
|
|
50
|
+
- **Breaking:** the straight moves of `pick` and `place` can be capped near the grasp or release
|
|
51
|
+
alone: `TaskOptions::tool_speed_near` (meters) holds `tool_speed` within that distance of where
|
|
52
|
+
the hand grasps or lets go, which the move in ends at and the move away starts from, and the
|
|
53
|
+
rest of each move runs as fast as the joints allow. The handovers of `datagen::handover` take
|
|
54
|
+
it too. `None`, the default, caps both moves all along, as before, to the bit. A `TaskOptions`
|
|
55
|
+
literal needs the new field (`tool_speed_near: None`). In Python,
|
|
56
|
+
`TaskOptions(tool_speed_near=0.02)`.
|
|
57
|
+
- **Breaking:** a `Follower` can follow a motion that joins a way to a move in, replanning only
|
|
58
|
+
the way: `FollowerOptions::detour` takes a `Detour { from, until, then }`. The follower keeps
|
|
59
|
+
the path before `from` and after `until`. Where the rest of the motion collides within that
|
|
60
|
+
window, it plans to `then`'s first state (the move in as planned apart), from no sooner than
|
|
61
|
+
`from`, then takes up `then` from rest there and reports `FollowStatus::Resumed { at,
|
|
62
|
+
trajectory }`; along `then` it keeps the path. `None`, the default, changes nothing. A
|
|
63
|
+
`FollowerOptions` literal needs the new field (`detour: None`).
|
|
64
|
+
|
|
65
|
+
- `CollisionModel::to_json` and `CollisionModel::from_json` give and take the JSON text `save`
|
|
66
|
+
and `load` write and read, for a model sent where no file goes. In Python, `model.to_json()`
|
|
67
|
+
and `CollisionModel.from_json(text)`.
|
|
68
|
+
|
|
69
|
+
- **Breaking:** a `Pick` or `Place` can leave its transit out and run each step in a world of its
|
|
70
|
+
own. With `transit: false` a task solves its grasps or releases and its two straight moves
|
|
71
|
+
alone, without checking its start, and keeps every candidate that passes them, so a program of
|
|
72
|
+
tasks solves all its moves in two calls and the ways between them in one `plan`
|
|
73
|
+
(`TaskPlan::begin` and `end` are where a way ends and the next begins), then joins them.
|
|
74
|
+
`transit_world` and `depart_world` name the worlds of the start and the planned path and of the
|
|
75
|
+
move away where they are not the task's `world`: the same obstacles, shaped or touched
|
|
76
|
+
otherwise, as a transit that touches nothing the grasp may. `TaskPlan::transit` is an `Option`,
|
|
77
|
+
`None` for a task without one, and `TaskPlan::motions` gives a plan's motions in order. Code
|
|
78
|
+
reading `plan.transit` unwraps it or reads `plan.motions()`, and a struct literal that writes
|
|
79
|
+
out every field of `Pick` or `Place` needs the three new ones. In Python, `Pick` and `Place`
|
|
80
|
+
take `transit`, `transit_world` and `depart_world`, and `TaskPlan` has `motions` and `begin`.
|
|
81
|
+
- **Breaking:** `join` checks a sequence's blends with what a hand holds: `Sequence::holding`, a
|
|
82
|
+
pick's `Held`, as `PlanProblem::holding`, so a carry joins into the move in of its place on the
|
|
83
|
+
device without the object. A `Sequence` literal needs the new field (`holding: None`).
|
|
84
|
+
- A world can let what a hand comes to hold touch an obstacle: `Touch::held(obstacle, hand)`, for
|
|
85
|
+
one hand in an upload, on the device that holds nothing. `solve_ik`, `follow` and `plan`
|
|
86
|
+
problems with `holding` then rest their objects on what they are set down on in one call
|
|
87
|
+
against one upload, as a device holding the object does with the world uploaded to it;
|
|
88
|
+
problems that hold nothing are checked as without it. In Python, `Touch.held`.
|
|
89
|
+
- `time(&device, &paths, speed_scale, &hand_speeds)` times many planned paths in one call on the
|
|
90
|
+
device's threads, each as `Trajectory::try_new` times one, or under its own list of hand speed
|
|
91
|
+
caps (`&[]` caps none), and gives each path's trajectory or error in order. In Python,
|
|
92
|
+
`time(device, paths, speed_scale=1.0, hand_speeds=None)`.
|
|
93
|
+
- **Breaking:** a hand speed cap can hold on part of a path: `HandSpeed::near`
|
|
94
|
+
(`Near { point, radius }`) caps the hand only while its frame is within `radius` of `point`,
|
|
95
|
+
so a way joined into the move to a grasp comes as fast as the joints allow and arrives slowly,
|
|
96
|
+
and `Trajectory::with_hand_speed` takes several caps, each holding where it is. A `HandSpeed`
|
|
97
|
+
literal needs `near: None`, and `with_hand_speed` takes a slice (`&[speed]`). A 15 cm way
|
|
98
|
+
joined into a 6 cm move capped at 5 cm/s takes 2.21 s where the two take 2.26 s one after the
|
|
99
|
+
other and the joined path 4.41 s capped throughout (a Panda, `tests/join.rs`). In Python,
|
|
100
|
+
`HandSpeed(hand, linear, angular, near=Near(point, radius))`, and `with_hand_speed` takes one
|
|
101
|
+
cap or a list.
|
|
102
|
+
|
|
103
|
+
- Publish the Python bindings as `batches` on PyPI (`import batchplan`). Version tags publish
|
|
104
|
+
the Rust crate and Python wheels and source together, using trusted publishing.
|
|
105
|
+
|
|
106
|
+
- **Breaking:** `plan` takes the hands' poses as a goal: `PlanProblem::goal` is a `Goal`, a
|
|
107
|
+
configuration (`Goal::Joints`) or a pose or `None` per hand (`Goal::Poses`), and
|
|
108
|
+
`PlanProblem::new` takes either, a `Pose` for a one-handed robot. `plan` solves IK for every
|
|
109
|
+
pose goal of the call in one batch, seeded from each start and keeping its held joints, what it
|
|
110
|
+
holds and its constraints, and plans to the collision-free solution nearest the start up to
|
|
111
|
+
whole turns. Poses no seed reaches collision-free are solved again with four times the seeds, up
|
|
112
|
+
to sixteen times, while a `time_budget` lasts; those still unreached fail as
|
|
113
|
+
`PlanFailure::GoalUnreachable`, or `GoalCollides` where IK reaches them only in collision.
|
|
114
|
+
`PlanOptions::goal_ik` sets that IK. `PlanProblem::new` takes a configuration as a `Vec<f32>`,
|
|
115
|
+
`&[f32]`, `&Vec<f32>`, `[f32; N]` or `&[f32; N]`; convert any other form with `to_vec()`. Code
|
|
116
|
+
reading a goal as a slice reads `goal.joints()`, a struct literal wraps a configuration in
|
|
117
|
+
`Goal::Joints`, and code that writes out every field of `PlanOptions` needs the new one. The benchmark's ik+plan mode plans to the
|
|
118
|
+
goal pose this way. In Python, `PlanProblem` takes a `Pose`, or a `Pose` or `None` per hand, as
|
|
119
|
+
its goal, beside `PlanOptions.goal_ik` and `PlanFailure.GoalUnreachable`.
|
|
120
|
+
- Python bindings, in `python/`: the `batchplan` package, built with maturin
|
|
121
|
+
([Python](docs/python.md)). Robots, assembled ones among them, obstacles and worlds, devices, IK
|
|
122
|
+
(`solve_ik`, `ik_failures`), planning with hand constraints (`plan`), timing (`Trajectory`),
|
|
123
|
+
tool paths (`follow`), whole tasks (`pick`, `place`, `grasps`, `place_poses`) and held objects,
|
|
124
|
+
with NumPy arrays in and out, `batchplan::Error`'s kinds as exception classes, and the
|
|
125
|
+
interpreter released while each call works. Values are frozen and changed with
|
|
126
|
+
`replace(**changes)`, as Rust's struct update syntax does. Install it from the repository with
|
|
127
|
+
`pip install "git+https://github.com/natsuku-lab/batchplan#subdirectory=python"`, which needs a
|
|
128
|
+
Rust toolchain.
|
|
129
|
+
- **Breaking:** `PlanProblem::holding`, `IkProblem::holding` and `ToolPath::holding`: an object a
|
|
130
|
+
hand holds (a `Held`, as `TaskPlan::held` gives it), per problem. One `plan`, `solve_ik` or
|
|
131
|
+
`follow` call takes problems holding nothing, different objects, or one object by different
|
|
132
|
+
grasps, and gives each what a device holding its object alone gives: it groups them by what
|
|
133
|
+
they hold and runs a batch per group, in parallel, on the device's GPU or threads. Holding per
|
|
134
|
+
problem took a device and a call per hold. Pass the device without the object; one already
|
|
135
|
+
holding a `Held` in that hand is an `Error::Input`. Code that writes out every field of
|
|
136
|
+
`IkProblem`, `PlanProblem` or `ToolPath` needs the new one (`holding: None`).
|
|
137
|
+
- `SphereOptions` implements `PartialEq`.
|
|
138
|
+
- **Breaking:** `GraspOptions::overlap`, 1 cm by default: the least the fingers reach into the
|
|
139
|
+
object in a grasp from above over a `floor`. An object flatter than `tip` and the floor's
|
|
140
|
+
clearance is now held from above with the fingertips at the floor and the frame over its top,
|
|
141
|
+
as long as its top is at least `overlap` over the floor. `grasps` left out every grasp from
|
|
142
|
+
above of such an object: an 8.8 x 4.8 x 1.85 cm package got only grasps from the sides, which
|
|
143
|
+
put the hand into the table. Every grasp from above over a floor now keeps at least `overlap`
|
|
144
|
+
of the fingers in the object, where it kept the frame inside it, so with `tip` under 1 cm (the
|
|
145
|
+
default 0 among them) some it returned are left out. Code that writes out every field of
|
|
146
|
+
`GraspOptions` needs the new one.
|
|
147
|
+
- **Breaking:** `FollowerOptions::replan`, on by default. Off, a `Follower` keeps its
|
|
148
|
+
trajectory's path, as a motion along a tool path must: where the rest of the motion collides,
|
|
149
|
+
it stops on the path, and where the robot is measured past `tolerance`, it holds it there,
|
|
150
|
+
`Blocked`. In either case it used to plan a free-space motion to the goal, off the path. Code
|
|
151
|
+
that writes out every field of `FollowerOptions` needs the new one.
|
|
152
|
+
|
|
153
|
+
### Changed
|
|
154
|
+
|
|
155
|
+
- Faster sphere fitting, with the same fitted collision models. Dense surface checks test four
|
|
156
|
+
spheres at once: on one thread, fitting the stand with a 512-sphere budget takes 19% less time
|
|
157
|
+
on an M4 Pro and 30% less on a Ryzen AI Max+ 395; the Panda with a 1,200-sphere budget takes
|
|
158
|
+
4% and 11% less, respectively.
|
|
159
|
+
These isolate fitting from self-collision analysis. The
|
|
160
|
+
[benchmark guide](docs/benchmarks.md#fitting-collision-spheres) gives the runs and
|
|
161
|
+
`examples/fitting.rs` reproduces the fixtures.
|
|
162
|
+
- Faster CPU collision checks, contact queries, IK, planning, tasks, depth-grid construction and
|
|
163
|
+
occupancy updates, with results the same to the bit. In six alternating runs, the best CPU
|
|
164
|
+
times for 1,000 IK targets among clutter fell 9% on a Ryzen AI Max+ 395 and 4% on an M4 Pro;
|
|
165
|
+
256 plans with four optimized seeds took 22% and 6% less time, respectively. The
|
|
166
|
+
[benchmark guide](docs/benchmarks.md#how-the-speed-came) gives the times, lower quartiles and
|
|
167
|
+
build configurations. This workspace builds batchplan as one
|
|
168
|
+
codegen unit (`[profile.release.package.batchplan] codegen-units = 1`), so a change measured here
|
|
169
|
+
no longer moves with how rustc splits the crate; a crate that depends on batchplan can do the same.
|
|
170
|
+
- Published crates exclude development agent instructions and skills; the library-user
|
|
171
|
+
`AGENTS.md` guide remains included.
|
|
172
|
+
- A world with a distance grid uploads without checking the grid's every value again: a grid is
|
|
173
|
+
checked when it is made, and nothing changes one after. `pick` and `place` upload their worlds
|
|
174
|
+
by the dozen, so a grid in the world cost each call that many passes over its values. The
|
|
175
|
+
searches' and tool paths' collision checks and the CPU device's `clearance` also look a grid
|
|
176
|
+
up without its gradient, which they never read.
|
|
177
|
+
- IK on the CPU device, and trajectory optimization's repairs on either, skip the self-collision
|
|
178
|
+
sphere pairs that cannot add to the cost: a sphere past the margin from another link's
|
|
179
|
+
bounding sphere is not checked against that link's spheres. Results are the same to the bit.
|
|
180
|
+
A robot fitted with many spheres gains most: a `place` of four holds on a Panda of 512 spheres
|
|
181
|
+
takes 11 ms on an M4 Pro where it took 18 ([Benchmarks](docs/benchmarks.md#how-the-speed-came)).
|
|
182
|
+
- **Breaking:** IK screens its seeds. Every seed takes `IkOptions::screening` steps (8 by
|
|
183
|
+
default), then only `IkOptions::kept` of each problem's (4) take the rest of `iterations`: those
|
|
184
|
+
not yet at their targets, the nearest first, then seeds that have arrived. Where the steps push
|
|
185
|
+
away from collisions (`collision_step` above 0, for a robot with collision spheres),
|
|
186
|
+
collision-free seeds go before the others; elsewhere whether a seed collides after the
|
|
187
|
+
screening says little about where it ends, and they go by their errors alone. The seeds left behind are returned as the screening left
|
|
188
|
+
them. Code that writes out every field of `IkOptions` needs the two new ones, and
|
|
189
|
+
`screening: 0` is the IK of before, as are problems of more than 32,768 seeds. On a GPU the
|
|
190
|
+
screening, the choice and the remaining steps are one round trip, a small call is one
|
|
191
|
+
submission, and its buffers are kept for the next. On an M4 Pro, 1,000 targets for a Panda
|
|
192
|
+
with collision spheres take 36–39 ms where they took 68–82, and its CPU threads 95 ms where
|
|
193
|
+
212, with 945 solved where 944; one target and ten take no longer than before, with the push
|
|
194
|
+
or without. In RoboKit's IK benchmark on an RTX 4090 (a Panda without spheres), 1,000 targets
|
|
195
|
+
take 3.1–3.3 ms where they took 3.6–3.7 and 2,000 take 6.1–6.3 ms where 7.2; 10 took
|
|
196
|
+
0.77–0.80 ms where 0.71 before the changes for small calls, which are not measured there
|
|
197
|
+
([Historical benchmark][historical-robokit]). The 2,600 pose-goal problems solve as
|
|
198
|
+
before (98.3%). In Python, `IkOptions.screening` and `IkOptions.kept`.
|
|
199
|
+
- Batched IK spends less time around the solve: a problem's seeds share its targets instead
|
|
200
|
+
of each carrying a copy, large batches are seeded on the device's threads, buffers go to the
|
|
201
|
+
GPU without an extra copy, and a robot without collision spheres skips the collision push,
|
|
202
|
+
which is zero for it. The same results, bit for bit. In RoboKit's IK benchmark on an RTX 4090
|
|
203
|
+
(a Panda, collision off, 64 seeds of 30 steps), 1,000 targets take 4.5 ms where they took
|
|
204
|
+
10.8, 2,000 take 9.1 ms where 23.2, and one takes 0.9 ms where 1.26
|
|
205
|
+
([Historical benchmark][historical-robokit]).
|
|
206
|
+
- **Breaking:** `IkOptions::collision_step` must be finite: an infinite or NaN one is an input
|
|
207
|
+
error. NaN and negative infinity used to mean no push, and positive infinity solved nothing
|
|
208
|
+
(the CPU left every seed where it started).
|
|
209
|
+
- **Breaking:** `Device::gpu` no longer chooses DX12's software renderer (WARP, "Microsoft Basic
|
|
210
|
+
Render Driver"), which every Windows machine has: it fails as it does where there is no
|
|
211
|
+
adapter, naming the renderer it left out, so `Device::gpu(..).or_else(|_| Device::cpu(..))`
|
|
212
|
+
falls to the CPU device. On a four-core machine that renderer had not finished one pick after
|
|
213
|
+
82 minutes, where the CPU device takes 16 ms. `Device::gpu_named` still takes it by name.
|
|
214
|
+
- A distance grid costs a link nothing while the link keeps away from the grid's box: the
|
|
215
|
+
collision checks rule the grid out by its box and the least value on the box's faces, as they
|
|
216
|
+
rule other obstacles out by their distance, where every sphere used to be looked up in every
|
|
217
|
+
grid. Among cluttered tabletops, IK for 1,000 targets with a grid 2 m from the arm in every
|
|
218
|
+
world takes 28.8 ms on an M4 Pro's GPU where it took 35.7, and 67 ms on its CPU where 107;
|
|
219
|
+
with the grid beside the table, 31.7 ms where 35.7, and 72 ms where 108
|
|
220
|
+
(`examples/latency.rs`). `world_clearance` for a link past the margin from a grid is a bound
|
|
221
|
+
from below, as it is for other obstacles. Uploads without a grid run the code they ran: the
|
|
222
|
+
rule is in kernels, and in copies of the CPU's checks, that only worlds uploaded with a grid
|
|
223
|
+
run ([Benchmarks](docs/benchmarks.md#how-the-speed-came)).
|
|
224
|
+
- A GPU device is made sooner: its kernels' pipelines compile each on a thread of its own. With
|
|
225
|
+
the driver's shader cache empty, `Device::gpu` takes 1.4 s on an M4 Pro where it took 2.6,
|
|
226
|
+
though it now builds the kernels of uploads with grids as well, `Device::warmup` 0.7 s where
|
|
227
|
+
2.0, and `Device::with_robot` for a robot holding an object 1.7 s where 3.0. The second set
|
|
228
|
+
costs memory: a GPU device keeps 66 to 88 MB after it is made where it kept 35 to 37, and
|
|
229
|
+
about 20 MB for each held shape where 9.
|
|
230
|
+
- GPU kernels leave naga fewer loops to count, and trajectory optimization on a GPU leaves out
|
|
231
|
+
paths that are done. naga counts the rounds of every shader loop to rule out endless ones,
|
|
232
|
+
which cost IK's short loops more than their work. One hand's damped least squares is now
|
|
233
|
+
written out term by term for each robot, as its kinematics are, and the kernels' loops of a
|
|
234
|
+
known count (over the joints, the hands, the line search's steps) are written out when the
|
|
235
|
+
kernels are built. A path whose optimization is over, a steepest-descent search having found
|
|
236
|
+
nothing better, takes no part in later rounds, as the CPU's already stopped. Of the lanes a
|
|
237
|
+
small batch gives each configuration, one works out the IK step and the others take the
|
|
238
|
+
configuration from it, and a robot's own link of 24 spheres or more takes them two to a round.
|
|
239
|
+
On an M4 Pro, one target's default IK takes 0.34 ms where it took 1.14 for a Panda without
|
|
240
|
+
collision spheres, and 0.92 ms where 2.03 with them; 1,000 targets take 1.7 and 22.1 ms where
|
|
241
|
+
3.8 and 30.0; 256 plans with 4 optimized seeds take 83 ms where 94, and 16 take 15.9 ms where
|
|
242
|
+
23.7 (`examples/latency.rs`). On a Radeon 8060S, before the last two of these changes, one
|
|
243
|
+
target took 0.18 ms where 1.12, and 1.10 ms where 2.70. In RoboKit's IK benchmark on an RTX
|
|
244
|
+
4090 one target takes 0.33–0.42 ms and ten 0.35–0.43, where RoboKit takes 0.57–0.59 and
|
|
245
|
+
0.55–0.56 ([Historical benchmark][historical-robokit]). The crate has no `unsafe`: the
|
|
246
|
+
kernels are built with every check naga makes, the loop counters among them, which on an M4
|
|
247
|
+
Pro make optimized plans 4–7% slower and IK for hands that share joints 13–20%. Three kinds of
|
|
248
|
+
call are slower than before: IK with the push for a robot of several hundred spheres (4–5%),
|
|
249
|
+
plans to goals past an obstacle (5%) and `evaluate` (1.5–4%)
|
|
250
|
+
([Benchmarks](docs/benchmarks.md#how-the-speed-came)). A GPU's results move in their last
|
|
251
|
+
places, IK's and trajectory optimization's alike: a seed in thousands arrives or does not.
|
|
252
|
+
- Trajectories are sooner: time-optimal timing uses the full velocity and acceleration limits,
|
|
253
|
+
and where its smoothed time map overshoots a limit, slows the profile there instead of
|
|
254
|
+
stretching the whole motion for it. Planned Panda motions take 6.6% less time on average, the
|
|
255
|
+
hand's straight moves 3.0%, replans from a moving start 5.7%, and the 2,600 benchmark
|
|
256
|
+
problems' motions 5.0%, within the same limits (`Trajectory::check`); more joins are worth
|
|
257
|
+
blending. Timing costs less than it did (a Ryzen thread): a path from rest 9% less, and one
|
|
258
|
+
from a moving start 59% less (475 µs where 1,172), timed once with its start at the full
|
|
259
|
+
limits, its candidate time maps turned down at a quarter of the checking density, and each
|
|
260
|
+
map measured over the joints in contiguous rows. A moving start's timing and a stop's are
|
|
261
|
+
also held to their limits either side of where they cross the path's knots, between the
|
|
262
|
+
samples checked, where jerk could pass its limit by up to 12%, and in a stop acceleration
|
|
263
|
+
by up to 20% and jerk by 19%: of 960 stops asked early in planned Panda motions 52 are
|
|
264
|
+
refused where 40 were. Replans from a moving start take 11% less time one at a time on a
|
|
265
|
+
Panda and 70% less on an SO-101; a batch of 96 Panda replans takes 65 ms where it took 61
|
|
266
|
+
(within the runs' spread), since a moving start no timing fits costs more to turn down
|
|
267
|
+
(2.0 ms where 1.6). A spline that cannot follow the start motion is then tried again,
|
|
268
|
+
stopping at every waypoint, with less of that timing, which takes a further quarter off a
|
|
269
|
+
Panda's replans one at a time and a sixth off the SO-101's (an M4 Pro)
|
|
270
|
+
([Benchmarks](docs/benchmarks.md#timing-up-to-the-limits)).
|
|
271
|
+
- **Breaking:** `IkOptions::default()` is 64 seeds of 30 steps of at most 1 rad, where it was 32
|
|
272
|
+
seeds of 60 steps of at most 0.3 rad: at 0.3 rad, about half the seeds ran out of steps before
|
|
273
|
+
converging. The same problems solve. Results carry 64 seeds per problem, and `TaskOptions`,
|
|
274
|
+
`DemoOptions`, `PickPlaceOptions` and `PlanOptions::goal_ik` inherit the new IK; pass the old
|
|
275
|
+
values to keep the old results.
|
|
276
|
+
- **Breaking:** An IK step that would move no joint by more than 1e-6 rad is not taken, on the
|
|
277
|
+
CPU and the GPU alike: a seed that has come to rest stays exactly there, and CPU threads skip
|
|
278
|
+
its remaining steps, which makes their IK about a fifth faster. Seeds can differ from before
|
|
279
|
+
by a few microradians. Together with the new defaults, the 2,600 benchmark problems plan to
|
|
280
|
+
pose goals 1.8 times as fast in batches on a Radeon 8060S (6,225 against 3,489 per second, and
|
|
281
|
+
in 5 ms instead of 9 for one) and 1.18 times as fast on 32 CPU threads (3,344 against 2,836);
|
|
282
|
+
IK for 512 tabletop targets takes 71 ms instead of 84 on those threads.
|
|
283
|
+
- `solve_ik` on a GPU checks the solved configurations' clearances in the same submission, from
|
|
284
|
+
the configurations already on the GPU, and reads everything back at once: one wait per 65,536
|
|
285
|
+
seeds instead of three, about 0.4 ms less per call.
|
|
286
|
+
- **Breaking:** `Robot::load`, `Robot::from_urdf` and `Robot::assemble` refuse a collision
|
|
287
|
+
model whose spheres have a non-finite center or radius or a negative one, or whose
|
|
288
|
+
self-collision buffer for a link is not finite or would shrink one of its spheres below nothing,
|
|
289
|
+
naming the link and the value (`Error::Load`; `Error::Input` from `from_urdf`). Spheres fitted to
|
|
290
|
+
geometry that is not finite, or so large it overflows, are refused alike. Such models loaded
|
|
291
|
+
before, and a sphere smaller than nothing checked as farther from what it touched than it was.
|
|
292
|
+
- **Breaking:** `Held` is `batchplan::Held`, no longer also `batchplan::task::Held`.
|
|
293
|
+
- A device keeps the spheres it fits to held objects, the latest 64, and shares them with the
|
|
294
|
+
devices `with_robot` makes from it. `pick`, `place` and every call holding a cylinder or a
|
|
295
|
+
capsule fitted it again, which takes milliseconds; a box or a ball is covered in microseconds.
|
|
296
|
+
- **Breaking:** `FollowStatus::Stopping { contacts }` and `FollowStatus::Collides { at, contacts }`
|
|
297
|
+
name what is in the way, as `Device::contacts` lists it: what the rest of the motion collided
|
|
298
|
+
with where it was first found to, numbered as in the worlds of the tick the stop began at and
|
|
299
|
+
repeated on every tick of the stop while it is clear, and what the robot collides with at
|
|
300
|
+
`at`. A graze a GPU device's check finds just outside what its CPU threads list names the
|
|
301
|
+
nearest within 0.1 mm. They said only that something collided. `Stopping` was a unit variant.
|
|
302
|
+
- **Breaking:** `grasps` keeps the open fingers above `GraspOptions::floor` too, `opening` apart
|
|
303
|
+
and reaching `tip` past the frame, where it kept only the frame and the point between the
|
|
304
|
+
fingertips. A grasp tilted about a bar over a support, as a drawer's handle, swung one open
|
|
305
|
+
finger into it: of a bar whose top is 3.7 cm over a plate, every one of the 12 grasps tilted
|
|
306
|
+
from above at 16 turns put a Panda's finger into the plate. They are now held as far out as
|
|
307
|
+
keeps the finger over the floor, and left out where that holds less than `overlap`: the 4 left
|
|
308
|
+
all pick it. Grasps from the side that close upright on an object standing on the table, their
|
|
309
|
+
lower finger below the table's top, are left out too, so an object only those fit, such as a
|
|
310
|
+
box too wide across its top to grasp from above, gets an `Error::Input` where it got them. An
|
|
311
|
+
infinite `opening` is an `Error::Input`.
|
|
312
|
+
|
|
313
|
+
### Fixed
|
|
314
|
+
|
|
315
|
+
- A map no longer frees what another surface hides because the arm once hid it too: a voxel no
|
|
316
|
+
image has observed is the robot's shadow only while nothing else has hidden it, whichever image
|
|
317
|
+
came first. Under `Occlusion::OccupiedExceptRobotShadow` such a voxel is solid, where it read
|
|
318
|
+
free.
|
|
319
|
+
- An obstacle whose rotation is a little off a unit quaternion (up to a thousandth is accepted)
|
|
320
|
+
is turned, not stretched. Its frame was built from the quaternion as given, which scaled it
|
|
321
|
+
by up to 0.4%: a 1.2 m box reached 2 mm past its ends, and a search, which first rules
|
|
322
|
+
obstacles out by bounding spheres of their stated size, could pass a configuration that
|
|
323
|
+
`evaluate` found 2 mm inside one. Frames are now built from the rotation made unit, on both
|
|
324
|
+
devices and in `Obstacle::distance`; a rotation that is unit to rounding is used as it is, so
|
|
325
|
+
results for those do not move. The same holds for a held object's shapes (`Robot::attach`
|
|
326
|
+
and a problem's `holding`), whose spheres sat up to 0.2% nearer or farther along the object,
|
|
327
|
+
for the obstacles `MeshModel::clearance` checks, and for a depth image's camera, whose
|
|
328
|
+
readings were placed 0.2% nearer or farther (5 mm at 3 m) in `SdfGrid::from_depth`,
|
|
329
|
+
`OccupancyMap::integrate` and `DepthImage::robot_mask`. A hold's grasp places its object by
|
|
330
|
+
the grasp made unit, and `Robot::assemble` a part by its base made unit. A camera's pose and
|
|
331
|
+
a part's base are now checked as other poses are: one that is not finite, or whose rotation
|
|
332
|
+
is more than a thousandth off unit, is an `Error::Input`, where it built a wrong grid or
|
|
333
|
+
robot without a word.
|
|
334
|
+
|
|
335
|
+
- GPU devices on Windows using the FXC DX12 shader compiler can build the hand-frame kernels.
|
|
336
|
+
|
|
337
|
+
- A plan from a moving start is optimized (`PlanOptions::seeds`) unless its path is within 2% of
|
|
338
|
+
the straight line from the start: the line was measured from the path's first control point,
|
|
339
|
+
which lies behind a moving start, so paths that bent by half as much again were left
|
|
340
|
+
unoptimized.
|
|
341
|
+
- `Trajectory::stop` stops a motion from more of the states it passes through, and sooner. It
|
|
342
|
+
refused where a joint ran within 0.6% of its velocity limit, since a stop's velocity was held
|
|
343
|
+
to the margin under the limit even where it starts, and often while a motion still gathered
|
|
344
|
+
speed, since braking at once breaks the jerk limit there, or stopped only by creeping to the
|
|
345
|
+
path's end. It now also tries carrying on for one span with the motion's jerk, half of it or
|
|
346
|
+
none before braking, and keeps the stop that ends soonest along the path. Trajectories timed
|
|
347
|
+
up to the limits pass through such states more often. Through the first fifth of 48 planned
|
|
348
|
+
Panda motions, 52 of 960 stops are refused where 153 were and one takes longer than going on
|
|
349
|
+
where 66 did; of 19 states at a velocity limit, 9 stop where none did. A quarter of the way
|
|
350
|
+
along 438 motions, stops last 0.633 s where they lasted 0.646, and take 0.62 ms to find where
|
|
351
|
+
they took 0.73 (`examples/timing.rs`, which now times them, on an M4 Pro). A `Follower` that
|
|
352
|
+
must stop where none is found keeps to its trajectory, which comes to rest at the path's end.
|
|
353
|
+
- Optimized seeds (`PlanOptions::seeds`) are validated as searched splines are checked: at samples no
|
|
354
|
+
farther apart than `shortcut.resolution`, keeping the margin from the world searched splines keep
|
|
355
|
+
away from their ends (2 mm, or `shortcut.clearance` if less). Validation kept no margin and
|
|
356
|
+
sampled each span `validate_substeps` times however far it moved, so optimization, which pulls
|
|
357
|
+
paths taut against obstacles, returned valid paths that collided between samples, as on picks
|
|
358
|
+
under a table. Their lengths, which `best` compares, are measured at those samples too.
|
|
359
|
+
- `plan` refuses a start or a joint goal that is not finite. A continuous joint's range has no
|
|
360
|
+
limits, so a start at infinity there passed the limit check and planned a path of infinite
|
|
361
|
+
control points marked valid.
|
|
362
|
+
- `Trajectory::at` and `Trajectory::sample` keep each position between the control points it
|
|
363
|
+
blends, so a path that runs along a joint limit never samples past it. Rounding left samples a
|
|
364
|
+
unit or two in the last place past (up to 4.8e-7 rad), which limit checks with no tolerance
|
|
365
|
+
refuse.
|
|
366
|
+
- `pick` builds each holding device where it moves with it again, so building one overlaps the
|
|
367
|
+
other grasps' moves. 0.10.0 built them all first, which cost picks on a GPU device about 1.5%:
|
|
368
|
+
64 picks of the tasks example (`cargo run --release --example tasks -- 64`) took 185.5 ms on
|
|
369
|
+
an M4 Pro and take 183.2 ms now, against 181.5 ms in 0.9.0 (medians of seven alternated runs).
|
|
370
|
+
The rest of that gap is in grasping and moving in, whose code did not change.
|
|
371
|
+
- `grasps` refuses an object whose size is negative or not finite, or whose rotation is not a
|
|
372
|
+
unit quaternion, naming it, as uploading a world does.
|
|
373
|
+
|
|
374
|
+
## [0.10.0] - 2026-11-07
|
|
375
|
+
|
|
376
|
+
### Changed
|
|
377
|
+
|
|
378
|
+
- **Breaking:** `Robot::attach` refuses a shape whose size is negative or not finite, or whose
|
|
379
|
+
rotation is not a unit quaternion to within 1e-3, as uploading a world does, naming the shape.
|
|
380
|
+
0.9.0 attached such shapes with spheres that did not cover them. Normalize a rotation read
|
|
381
|
+
with few digits, such as (0, 0, 0.71, 0.71), before attaching it.
|
|
382
|
+
- The model's `source` also gives the largest of each link's reach over its own tolerance.
|
|
383
|
+
- `Robot::attach` fits a single shape in its own frame and places its spheres by the shape's
|
|
384
|
+
pose, so where it sits on its link no longer changes them, and `pick`, `place` and attaching
|
|
385
|
+
`Held::attachment` give a held object the same spheres.
|
|
386
|
+
- `pick` and `place` fit each held object once and place its spheres by each grasp, where they
|
|
387
|
+
attached it at each grasp and fitted it again: picks of cylinders and capsules spend less of
|
|
388
|
+
their time attaching.
|
|
389
|
+
|
|
390
|
+
### Fixed
|
|
391
|
+
|
|
392
|
+
- `place` refuses a held object whose size is not finite, as an input error naming its task.
|
|
393
|
+
- A pick's contacts where it lifts the object name obstacles as the task's world numbers them:
|
|
394
|
+
every obstacle after the picked one came back one index too low.
|
|
395
|
+
- Where the budget reaches every tolerance but cannot hold each link within its own, what it has
|
|
396
|
+
left holds the spheres tighter. It used to go unspent, with spheres reaching up to twice their
|
|
397
|
+
link's tolerance. A stand of two boxes held to 2 cm (`tests/robot.rs`) reached 36.6 mm with 379
|
|
398
|
+
of 512 spheres, and now 24.1 mm with 502; the Panda at 1,200 spheres reached 9.5 mm against
|
|
399
|
+
5 mm, and now 5.8 mm. The search for the least growth makes such fits two to three times as
|
|
400
|
+
long: the Panda's at 1,200 spheres takes 0.20 s, where it took 0.10 s, and the stand's at 512
|
|
401
|
+
0.13 s, where it took 0.05 s.
|
|
402
|
+
- Covers are refined until they reach every surface sample, where they stopped after 8 rounds,
|
|
403
|
+
so large boxes keep to their tolerance where the budget allows: the stand reached 25.7 mm at
|
|
404
|
+
1,024 spheres, and now 20.0 mm.
|
|
405
|
+
|
|
406
|
+
## [0.9.0] - 2026-11-06
|
|
407
|
+
|
|
408
|
+
### Changed
|
|
409
|
+
|
|
410
|
+
- Fitted spheres reach less far past thin parts. Each link's spheres reach at most twice its
|
|
411
|
+
tolerance, scaled by what the budget allows, and less as the budget grows. Where the budget
|
|
412
|
+
affords it, each closed link is held within its own tolerance. Parts thinner than 0.1 mm and
|
|
413
|
+
vertices whose averaged normal leads out of the geometry may reach a little farther: at 2,048
|
|
414
|
+
spheres the Panda's base reaches 5.18 mm against 5 mm. The model's `source` names the scale.
|
|
415
|
+
Fitted models change, and saved models load as before.
|
|
416
|
+
- Spheres held that tight leave slivers of surface about a millimeter deep outside them:
|
|
417
|
+
`LinkQualification::uncovered` reaches about 2% of a link, where it was a fraction of a
|
|
418
|
+
percent.
|
|
419
|
+
- `Robot::qualify` measures on samples the fit did not draw.
|
|
420
|
+
|
|
421
|
+
## [0.8.0] - 2026-11-06
|
|
422
|
+
|
|
423
|
+
### Added
|
|
424
|
+
|
|
425
|
+
- `Robot::relock` holds joints locked at load at new values, and `Device::with_robot` takes the
|
|
426
|
+
relocked robot without compiling kernels or uploading worlds again.
|
|
427
|
+
- `FollowerOptions::lead`: time after the planning budget for a replan to reach the robot.
|
|
428
|
+
|
|
429
|
+
### Changed
|
|
430
|
+
|
|
431
|
+
- **Breaking:** `FollowStatus::Replanned` carries the new trajectory: `Replanned { at, trajectory }`.
|
|
432
|
+
- **Breaking:** `FollowerOptions` gains `lead`, and `CollisionModel` gains `locked_joints`, the
|
|
433
|
+
joints locked while fitting. A model fitted with locked joints must be loaded with them locked
|
|
434
|
+
at the same values, and `relock` refuses to move them. Models saved before 0.8.0 load as before.
|
|
435
|
+
|
|
436
|
+
### Fixed
|
|
437
|
+
|
|
438
|
+
- `Device::evaluate` refuses configurations that are not finite, and `Robot::load` lock values
|
|
439
|
+
that are not finite.
|
|
440
|
+
- GPU picks at small batches are as fast as in 0.6.1 again: IK that does not push away from
|
|
441
|
+
obstacles no longer splits each configuration's work across GPU invocations, and robots with
|
|
442
|
+
many spheres keep the split past 4,096 configurations.
|
|
443
|
+
|
|
444
|
+
Earlier releases have no notes beyond their commits.
|
|
445
|
+
|
|
446
|
+
[Unreleased]: https://github.com/natsuku-lab/batchplan/compare/v0.11.0...HEAD
|
|
447
|
+
[0.11.0]: https://github.com/natsuku-lab/batchplan/compare/v0.10.0...v0.11.0
|
|
448
|
+
[0.10.0]: https://github.com/natsuku-lab/batchplan/compare/v0.9.0...v0.10.0
|
|
449
|
+
[0.9.0]: https://github.com/natsuku-lab/batchplan/compare/v0.8.0...v0.9.0
|
|
450
|
+
[0.8.0]: https://github.com/natsuku-lab/batchplan/compare/v0.7.0...v0.8.0
|
|
451
|
+
|
|
452
|
+
[historical-robokit]: https://github.com/natsuku-lab/batchplan/blob/9d7823a53148a3f571fd5615d0ebbd5818d8f1d4/docs/benchmarks.md#against-robokit
|