Skip to content

Commit 3159a56

Browse files
committed
improvement: map URDF <mimic> joints to BB.Sensor.Mimic
`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.
1 parent 1092ec7 commit 3159a56

6 files changed

Lines changed: 82 additions & 16 deletions

File tree

lib/bb/urdf/importer.ex

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,31 @@ if Code.ensure_loaded?(Sourceror) do
249249
render_axis(joint) ++
250250
render_limit(joint) ++
251251
render_dynamics(joint.dynamics) ++
252+
render_mimic(joint) ++
252253
[render_link(child_link, links_by_name, joints_by_parent)]
253254

254255
call(:joint, [atom_name(joint.name)], body)
255256
end
256257

258+
defp render_mimic(%{mimic: nil}), do: []
259+
260+
defp render_mimic(%{name: joint_name, mimic: mimic}) do
261+
sensor_name = String.to_atom("#{joint_name}_mimic")
262+
263+
opts =
264+
[source: atom_name(mimic.joint)]
265+
|> append_if(mimic.multiplier != 1.0, multiplier: mimic.multiplier)
266+
|> append_if(mimic.offset != 0.0, offset: mimic.offset)
267+
268+
mimic_module = {:__aliases__, [], [:BB, :Sensor, :Mimic]}
269+
child_spec = {mimic_module, opts}
270+
271+
[call(:sensor, [sensor_name, child_spec])]
272+
end
273+
274+
defp append_if(list, false, _kv), do: list
275+
defp append_if(list, true, kv), do: list ++ kv
276+
257277
defp render_axis(%{type: :fixed}), do: []
258278
defp render_axis(%{axis: nil}), do: []
259279

@@ -462,6 +482,8 @@ if Code.ensure_loaded?(Sourceror) do
462482
red: 1,
463483
roll: 1,
464484
scale: 1,
485+
sensor: 2,
486+
sensor: 3,
465487
settings: 1,
466488
sphere: 0,
467489
sphere: 1,

lib/bb/urdf/parser.ex

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ defmodule BB.Urdf.Parser do
325325
axis = parse_axis(first_by_name(children, :axis))
326326
limit = parse_limit(first_by_name(children, :limit))
327327
dynamics = parse_dynamics(first_by_name(children, :dynamics))
328-
{mimic, mimic_warnings} = parse_mimic(first_by_name(children, :mimic), to_string(name))
328+
mimic = parse_mimic(first_by_name(children, :mimic))
329329

330330
safety_warnings =
331331
case first_by_name(children, :safety_controller) do
@@ -343,7 +343,7 @@ defmodule BB.Urdf.Parser do
343343
limit: limit,
344344
dynamics: dynamics,
345345
mimic: mimic
346-
}, mimic_warnings ++ safety_warnings}
346+
}, safety_warnings}
347347
end
348348

349349
defp parse_origin(nil), do: nil
@@ -380,19 +380,14 @@ defmodule BB.Urdf.Parser do
380380
}
381381
end
382382

383-
defp parse_mimic(nil, _joint_name), do: {nil, []}
383+
defp parse_mimic(nil), do: nil
384384

385-
defp parse_mimic(xml_element() = element, joint_name) do
386-
mimic = %{
385+
defp parse_mimic(xml_element() = element) do
386+
%{
387387
joint: element |> attr(:joint) |> to_string(),
388388
multiplier: element |> attr(:multiplier, "1") |> parse_float(),
389389
offset: element |> attr(:offset, "0") |> parse_float()
390390
}
391-
392-
warning =
393-
"joint #{inspect(joint_name)}: <mimic> is not supported by bb (mirrors #{inspect(mimic.joint)})"
394-
395-
{mimic, [warning]}
396391
end
397392

398393
defp parse_geometry(nil), do: nil

lib/mix/tasks/bb.from_urdf.ex

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@ if Code.ensure_loaded?(Igniter) do
2323
* `--module`, `-m` - The module name for the generated robot.
2424
Defaults to `{AppPrefix}.Robot`.
2525
26-
## Limitations
26+
## URDF feature support
2727
28-
Some URDF features have no direct BB equivalent and are skipped with a
28+
`<mimic>` joints are emitted as a `BB.Sensor.Mimic` attached to the
29+
mimicking joint — BB's sensor implements the same
30+
`position * multiplier + offset` semantics as URDF.
31+
32+
These URDF features have no direct BB equivalent and are skipped with a
2933
warning rather than failing the import:
3034
31-
* `<mimic>` joints
3235
* `<safety_controller>` blocks
3336
* `<transmission>` blocks
3437
* `<gazebo>` extensions

test/bb/urdf/importer_test.exs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,51 @@ defmodule BB.Urdf.ImporterTest do
9090
test "passes through parser warnings" do
9191
{_source, warnings} = import_fixture("mimic_and_transmission.urdf")
9292

93-
assert Enum.any?(warnings, &(&1 =~ "<mimic>"))
9493
assert Enum.any?(warnings, &(&1 =~ "<safety_controller>"))
9594
assert Enum.any?(warnings, &(&1 =~ "<transmission>"))
9695
end
9796

97+
test "emits a BB.Sensor.Mimic for URDF <mimic> joints" do
98+
{source, _warnings} = import_fixture("mimic_and_transmission.urdf", MyApp.MimicGen)
99+
100+
assert source =~ "sensor :right_finger_joint_mimic"
101+
assert source =~ "BB.Sensor.Mimic"
102+
assert source =~ "source: :left_finger_joint"
103+
assert source =~ "multiplier: -1.0"
104+
assert [{MyApp.MimicGen, _}] = Code.compile_string(source)
105+
end
106+
107+
test "omits default multiplier and offset on emitted mimic sensors" do
108+
xml = """
109+
<?xml version="1.0"?>
110+
<robot name="t">
111+
<link name="a"/>
112+
<link name="b"/>
113+
<link name="c"/>
114+
<joint name="j1" type="prismatic">
115+
<parent link="a"/>
116+
<child link="b"/>
117+
<axis xyz="1 0 0"/>
118+
<limit lower="0" upper="1" effort="1" velocity="1"/>
119+
</joint>
120+
<joint name="j2" type="prismatic">
121+
<parent link="b"/>
122+
<child link="c"/>
123+
<axis xyz="1 0 0"/>
124+
<limit lower="0" upper="1" effort="1" velocity="1"/>
125+
<mimic joint="j1"/>
126+
</joint>
127+
</robot>
128+
"""
129+
130+
{:ok, parsed} = Parser.parse_string(xml)
131+
{:ok, source, _} = Importer.to_source(parsed, MyApp.DefaultMimic)
132+
133+
assert source =~ ~r/source: :j1[^,}]*\}/
134+
refute source =~ "multiplier:"
135+
refute source =~ "offset:"
136+
end
137+
98138
test "dedupes named materials so multiple visuals can share a URDF material" do
99139
{source, _warnings} = import_fixture("shared_materials.urdf", MyApp.SharedGen)
100140

test/bb/urdf/parser_test.exs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,18 @@ defmodule BB.Urdf.ParserTest do
4545
test "collects warnings for unsupported features" do
4646
{:ok, robot} = Parser.parse_file(Path.join(@fixture_dir, "mimic_and_transmission.urdf"))
4747

48-
assert Enum.any?(robot.warnings, &(&1 =~ "<mimic>"))
4948
assert Enum.any?(robot.warnings, &(&1 =~ "<safety_controller>"))
5049
assert Enum.any?(robot.warnings, &(&1 =~ "<transmission>"))
5150
assert Enum.any?(robot.warnings, &(&1 =~ "<gazebo>"))
5251
end
5352

53+
test "parses <mimic> into a per-joint mimic struct" do
54+
{:ok, robot} = Parser.parse_file(Path.join(@fixture_dir, "mimic_and_transmission.urdf"))
55+
56+
right = Enum.find(robot.joints, &(&1.name == "right_finger_joint"))
57+
assert right.mimic == %{joint: "left_finger_joint", multiplier: -1.0, offset: 0.0}
58+
end
59+
5460
test "resolves top-level material references inside visuals" do
5561
{:ok, robot} = Parser.parse_file(Path.join(@fixture_dir, "two_link_arm.urdf"))
5662

test/mix/tasks/bb.from_urdf_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ defmodule Mix.Tasks.Bb.FromUrdfTest do
3838
test_project()
3939
|> Igniter.compose_task("bb.from_urdf", [urdf, "--module", "Test.Robot"])
4040

41-
assert Enum.any?(igniter.warnings, &(&1 =~ "<mimic>"))
41+
assert Enum.any?(igniter.warnings, &(&1 =~ "<safety_controller>"))
4242
assert Enum.any?(igniter.warnings, &(&1 =~ "<transmission>"))
4343
end
4444

0 commit comments

Comments
 (0)