feat: add mix bb.from_urdf URDF importer#107
Conversation
…101) Adds three pieces: - `BB.Urdf.Parser` — parses URDF XML into a plain-map representation via `:xmerl_scan`. Collects warnings for `<mimic>`, `<safety_controller>`, `<transmission>`, and `<gazebo>` blocks rather than failing. - `BB.Urdf.Importer` — converts the parsed representation into a quoted BB DSL module and formats it via Sourceror, with `~u` sigil literals for unit-bearing values and joints nested under their parent links in topology order. Available when Sourceror is loaded (i.e. via igniter). - `mix bb.from_urdf path/to/robot.urdf --module MyApp.Robot` — Igniter task that wraps the parser and importer and writes the module via `Igniter.Project.Module.create_module`. Mesh files are referenced as-is (no `package://` rewriting in v1).
Tested the importer against UR5, Franka Panda, ANYmal B, and Allegro Hand URDFs from public ROS packages. Two issues fell out: - URDF allows many visuals to reference one named `<material>`. BB's DSL requires globally-unique entity names, so the generated module failed to compile. Keep the URDF name on the first visual that uses each material; strip it on later occurrences (BB's `material` entity auto-generates an identifier when `name` is absent). - `BB.Dsl.Mesh.scale` defaults to the integer `1`, and the URDF exporter calls `:erlang.float_to_binary/2` on it, which crashes. Always emit `scale` as a float so the resulting module round-trips through `mix bb.to_urdf` without hitting that bug. After these fixes, all four corpus URDFs parse, generate, compile, and round-trip through the exporter with the same link/joint counts as the inputs.
Tested against real-world URDFsRan the importer against four publicly-available URDFs to shake out edge cases:
Round-trip = parse → generate DSL → compile → Two real issues surfaced and are fixed in 1092ec7:
Parser warnings observed on the corpus all look correct: |
`BB.Sensor.Mimic` already implements URDF mimic semantics — same `position * multiplier + offset` formula, even documents the URDF equivalent in its docstring. So instead of skipping `<mimic>` with a warning, the importer now attaches a `BB.Sensor.Mimic` sensor to the mimicking joint pointing back at the source joint, with multiplier and offset only emitted when they differ from URDF's defaults of 1.0 and 0.0. Confirmed on the Franka Panda fixture (`panda_finger_joint2` mimics `panda_finger_joint1`): the generated module now wires up the mimic sensor automatically instead of leaving the user to add it by hand.
|
Good catch — For example, the Panda's joint :panda_finger_joint2 do
type :prismatic
...
sensor :panda_finger_joint2_mimic,
{BB.Sensor.Mimic, source: :panda_finger_joint1}
link :panda_rightfinger
endMultiplier and offset are omitted when they match URDF's defaults of 1.0 and 0.0. Updated tests on the existing |
Two failures that fell out of testing against a wider URDF corpus (Bullet's minitaur, the so101 from onshape-to-robot, drake's acrobot): - Many URDFs anchor the base link to the world frame with a fixed joint whose parent is a synthetic `world` link that's never defined. BB has no concept of a world frame — the topology root is the robot. Drop such joints in a pre-pass, leaving the child unparented so the root-finder picks it up. A warning records what was dropped. - URDF has separate namespaces for link and joint names, but BB requires global uniqueness across both. Several real URDFs use the same string for a link and a joint (so101's `gripper`, minitaur's `motor_back_rightL_link`). Rename colliding joints with a `_joint` suffix (incrementing if that's also taken) and rewrite `<mimic>` source references to match. With these fixes, eleven public URDFs now compile cleanly: Allegro Hand, ANYmal B, Bullet cartpole, Drake acrobot/pendulum, KUKA iiwa, Bullet minitaur, Franka Panda, SO-101, UR5, and WidowX (xacro-free portions).
Expanded corpusRan against eleven public URDFs spanning industrial arms, quadrupeds, mobile robots, and simple physics models. Two new failure modes surfaced (fixed in ecc341c) before everything compiled and round-tripped:
The two new pre-passes:
Regression tests added with synthetic fixtures for both cases. |
Closes #101.
Summary
Adds a
mix bb.from_urdfIgniter task that reads a URDF XML file and generates an equivalent BB DSL module:Three pieces:
BB.Urdf.Parser— parses URDF XML into a plain-map representation via:xmerl_scan(no extra deps). Collects warnings for<mimic>,<safety_controller>,<transmission>, and<gazebo>blocks rather than failing the import, so partial fidelity is preferred over rejecting real-world URDFs.BB.Urdf.Importer— converts parsed URDF into a quoted BB DSL module and formats it via Sourceror with~usigil literals for unit-bearing values. Joints are nested under their parent links in topology order; the root link is identified as the one never appearing as a<child>. Cardinal axis vectors map to BB'saxis do roll/pitch endEuler form (0 1 0→roll ~u(-90 degree), etc.); arbitrary vectors get rotated to Z via computed Euler angles.mix bb.from_urdf— Igniter task that wraps the two, writes the module viaIgniter.Project.Module.create_module, and surfaces parser warnings viaIgniter.add_warning/2.What's deliberately not in this PR
filenameattributes (includingpackage://URIs) are passed through verbatim. The user copies meshes and rewrites paths themselves; that felt like a separate concern from generation.<mimic>/<safety_controller>/<transmission>translation. No clean BB analogue — surfaced as warnings instead.07-urdf-import.mdfrom the issue's "Related" section. The existing07-parameters.mdmakes the numbering awkward; happy to add this as a follow-up once we decide on a slot (e.g. renumber, or move todocumentation/how-to/).bb_example_wx200's definition from a vendor URDF as a smoke test, per the issue's open question. Round-trip works on the test fixture (URDF → DSL → compile → URDF), but adding the example regen feels like a separate PR.Test plan
mix test test/bb/urdf/parser_test.exs(8 tests)mix test test/bb/urdf/importer_test.exs(10 tests, including a generated-source-compiles check)mix test test/mix/tasks/bb.from_urdf_test.exs(4 Igniter tests)mix test(938 tests, no regressions)mix format --check-formattedmix credo --strictmix dialyzer(clean)two_link_arm.urdf: parse → generate DSL → compile → export → same structure