diff --git a/README.md b/README.md index 15c33176..65483f8c 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ The default implementations are `openarm_describe_config_component` and `openarm Interface files declare macros; implementation and third-party model includes belong inside those macros. Each interface is loaded in a local scope, so its definitions do not affect other components. Presets need no implementation selector or product registry. +Component topology uses stable `link1`, `link2`, ... and `joint1`, `joint2`, ... keys. An optional `name` on each topology entry defines its local URDF name; otherwise the key is used. The instance prefix is added once. Geometry, inertia, joint parameters, parent/child links, mimic targets, references, reflection selections and overrides use the stable keys. Changing `name` preserves these config references. Component descriptors expose resolved `link_name_map` and `joint_name_map` entries for emitters and control generation. + ## Related links - 📚 Read the [documentation](https://docs.openarm.dev/software/description) @@ -49,3 +51,5 @@ Copyright 2025 Enactic, Inc. All participation in the OpenArm project is governed by our [Code of Conduct](CODE_OF_CONDUCT.md). + +Control partitions are declared under the preset's top-level `systems` mapping. Each system's `joints` maps component instance names to stable local joint keys with an `idx`. Each active joint requires a unique non-negative integer `idx` within its system; gaps are allowed. Joints are emitted in index order, with `idx` included as a joint parameter for runtime adapters. Disabled components are skipped, and final joint names come from the resolved component descriptions. Standard OpenArm hardware defaults are supplied by the model entry. Per-system hardware_plugin and driver parameters can override those defaults, and CAN launch arguments take precedence. `use_fake_hardware:=true` selects `mock_components/GenericSystem`. Optional `command_interfaces` and `state_interfaces` default to `[position, velocity, effort]`. diff --git a/assets/body/v2.0/config/inertials/nominal.yaml b/assets/body/v2.0/config/inertials/nominal.yaml index 75949159..52fdec86 100644 --- a/assets/body/v2.0/config/inertials/nominal.yaml +++ b/assets/body/v2.0/config/inertials/nominal.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -body_link0: +link1: origin: x: -0.01627169562 y: 0.0 diff --git a/assets/body/v2.0/config/link/collisions.yaml b/assets/body/v2.0/config/link/collisions.yaml index 46fb7013..7e1d776d 100644 --- a/assets/body/v2.0/config/link/collisions.yaml +++ b/assets/body/v2.0/config/link/collisions.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -body_link0: +link1: origin: x: 0.0 y: 0.0 diff --git a/assets/body/v2.0/config/link/visuals.yaml b/assets/body/v2.0/config/link/visuals.yaml index f1fa731c..474ec659 100644 --- a/assets/body/v2.0/config/link/visuals.yaml +++ b/assets/body/v2.0/config/link/visuals.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -body_link0: +link1: origin: x: 0.0 y: 0.0 diff --git a/assets/body/v2.0/config/struct/reference_points.yaml b/assets/body/v2.0/config/struct/reference_points.yaml index bf695c6c..38e13a41 100644 --- a/assets/body/v2.0/config/struct/reference_points.yaml +++ b/assets/body/v2.0/config/struct/reference_points.yaml @@ -13,7 +13,7 @@ # limitations under the License. left_arm_mount_point: - parent: body_link0 + parent: link1 origin: x: 0.0 y: 0.030 @@ -29,13 +29,13 @@ left_arm_mount_point: type: fixed parent_link: original_name: body_link0 - canonical_name: body_link0 + canonical_name: link1 child_link: original_name: body_left_arm_mount_point canonical_name: null right_arm_mount_point: - parent: body_link0 + parent: link1 origin: x: 0.0 y: -0.030 @@ -51,13 +51,13 @@ right_arm_mount_point: type: fixed parent_link: original_name: body_link0 - canonical_name: body_link0 + canonical_name: link1 child_link: original_name: body_right_arm_mount_point canonical_name: null arm_origin: - parent: body_link0 + parent: link1 emit: true origin: x: 0.0 diff --git a/assets/body/v2.0/config/struct/topology.yaml b/assets/body/v2.0/config/struct/topology.yaml index 59402442..97b46d61 100644 --- a/assets/body/v2.0/config/struct/topology.yaml +++ b/assets/body/v2.0/config/struct/topology.yaml @@ -13,5 +13,6 @@ # limitations under the License. links: - - body_link0 + link1: + name: body_link0 joints: {} diff --git a/assets/common/assembly.xacro b/assets/common/assembly.xacro index c67a42ac..1398db64 100644 --- a/assets/common/assembly.xacro +++ b/assets/common/assembly.xacro @@ -16,23 +16,21 @@ limitations under the License. - + - - - - + + - + + params="component_descriptions component_name components robot_preset config_root root_frame component_cfg component_desc parent_desc:=none emit_grasp_frame:=false out_name"> @@ -96,7 +94,7 @@ limitations under the License. - + @@ -111,9 +109,9 @@ limitations under the License. - + @@ -123,7 +121,7 @@ limitations under the License. name="attached_children" value="${[name for name in components if name in robot_preset and robot_preset[name]['enabled'] and 'connection' in robot_preset[name] and 'attach_to' in robot_preset[name]['connection'] and robot_preset[name]['connection']['attach_to'] == component_name]}" lazy_eval="false" /> - + - + @@ -169,20 +168,12 @@ limitations under the License. + params="component_api component_descriptions component_name components robot_preset config_root root_frame parent_desc:=none emit_grasp_frame:=false"> - - + - - - - - - + params="component_api component_descriptions index components parent_component_name robot_preset config_root root_frame parent_desc:=none emit_grasp_frame:=false"> @@ -228,22 +217,19 @@ limitations under the License. - - - - + params="component_api config_root robot_preset emit_grasp_frame:=false out_name:=component_descriptions"> @@ -268,9 +252,14 @@ limitations under the License. components="${components}" robot_preset="${robot_preset}" /> + + + + - - - - - + + + + + + params="index joint_names topology prefix link_name_map joint_name_map joint_origins joint_axes joint_limits joint_mimics inertials visuals collisions reflect_x reflect_y reflect_z reflect_include_links reflect_include_joints origin_override_joints limit_override_joints tip_collapse_source_link tip_collapse_child_link base_frame_shift"> @@ -26,20 +26,21 @@ limitations under the License. - - + frame_shift="${base_frame_shift if joint['parent'] == list(topology['links'])[0] else 'none'}" /> + params="connected_to mount_origin topology prefix link_name_map joint_name_map joint_origins joint_axes joint_limits joint_mimics:=${dict()} inertials visuals collisions reflect_x:=1 reflect_y:=1 reflect_z:=1 reflect_include_links:=${[]} reflect_include_joints:=${[]} origin_override_joints:=${dict()} limit_override_joints:=${dict()} tip_collapse_source_link:=none tip_collapse_child_link:=none skip_mount_joint:=${False} base_frame_shift:=none"> - + - - + - + - + diff --git a/assets/common/ros2_control.xacro b/assets/common/ros2_control.xacro new file mode 100644 index 00000000..3d56459a --- /dev/null +++ b/assets/common/ros2_control.xacro @@ -0,0 +1,129 @@ + + + + + + + + + + ${values[keys[index]]} + + + + + + + + + + 0.0 + + + + + + + + + + ${joint_indices[index]} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${plugin} + + + + + + + + + diff --git a/assets/common/validation.xacro b/assets/common/validation.xacro index e772915c..5bcee7d5 100644 --- a/assets/common/validation.xacro +++ b/assets/common/validation.xacro @@ -137,7 +137,7 @@ limitations under the License. - + @@ -146,10 +146,16 @@ limitations under the License. - + - + + + + + diff --git a/assets/component_loader.xacro b/assets/component_loader.xacro index a73afbdc..f4536023 100644 --- a/assets/component_loader.xacro +++ b/assets/component_loader.xacro @@ -47,13 +47,12 @@ limitations under the License. - + component_cfg="${component_cfg}" component_desc="${component_desc}" component_descriptions="${dict(component=component_desc)}" out_name="component_runtime" /> diff --git a/assets/end_effector/pinch_gripper/config/inertials/nominal.yaml b/assets/end_effector/pinch_gripper/config/inertials/nominal.yaml index 6bfd2f4b..05ebfae2 100644 --- a/assets/end_effector/pinch_gripper/config/inertials/nominal.yaml +++ b/assets/end_effector/pinch_gripper/config/inertials/nominal.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -ee_base_link: +link1: origin: x: -0.0067098 y: 0.008407299999999999 @@ -28,7 +28,7 @@ ee_base_link: yy: 0.00015627 yz: -1.5124e-06 zz: 0.0001659 -ee_link1: +link2: origin: x: -0.0124990911725494 y: 0.00176592789915655 @@ -44,7 +44,7 @@ ee_link1: yy: 3.62137424319396e-05 yz: -3.60343788693423e-06 zz: 1.74958961632907e-05 -ee_link2: +link3: origin: x: -0.012796 y: -0.0020183 diff --git a/assets/end_effector/pinch_gripper/config/joint/joint_axes.yaml b/assets/end_effector/pinch_gripper/config/joint/joint_axes.yaml index 13cc0ae1..c8d738fa 100644 --- a/assets/end_effector/pinch_gripper/config/joint/joint_axes.yaml +++ b/assets/end_effector/pinch_gripper/config/joint/joint_axes.yaml @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -finger_joint1: +joint1: axis: x: -1 y: 0 z: 0 -finger_joint2: +joint2: axis: x: 1 y: 0 diff --git a/assets/end_effector/pinch_gripper/config/joint/joint_limits.yaml b/assets/end_effector/pinch_gripper/config/joint/joint_limits.yaml index 19942211..8dfc86de 100644 --- a/assets/end_effector/pinch_gripper/config/joint/joint_limits.yaml +++ b/assets/end_effector/pinch_gripper/config/joint/joint_limits.yaml @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -finger_joint1: +joint1: limit: lower: -0.7854 upper: 0 effort: 7 velocity: 5 -finger_joint2: +joint2: limit: lower: -0.7854 upper: 0 diff --git a/assets/end_effector/pinch_gripper/config/joint/joint_mimics.yaml b/assets/end_effector/pinch_gripper/config/joint/joint_mimics.yaml index 1c0f9656..48434cde 100644 --- a/assets/end_effector/pinch_gripper/config/joint/joint_mimics.yaml +++ b/assets/end_effector/pinch_gripper/config/joint/joint_mimics.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -finger_joint2: - joint: finger_joint1 +joint2: + joint: joint1 multiplier: 1 offset: 0 diff --git a/assets/end_effector/pinch_gripper/config/joint/joint_origins.yaml b/assets/end_effector/pinch_gripper/config/joint/joint_origins.yaml index cc5b65bf..da58c50f 100644 --- a/assets/end_effector/pinch_gripper/config/joint/joint_origins.yaml +++ b/assets/end_effector/pinch_gripper/config/joint/joint_origins.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -finger_joint1: +joint1: origin: x: -0.0219300000000005 y: 0.0180000000000009 @@ -20,7 +20,7 @@ finger_joint1: roll: 0 pitch: -0 yaw: 0 -finger_joint2: +joint2: origin: x: -0.0219300000000005 y: -0.0180000000000012 diff --git a/assets/end_effector/pinch_gripper/config/link/collisions.yaml b/assets/end_effector/pinch_gripper/config/link/collisions.yaml index 2dfa0c8e..1f4d4c1b 100644 --- a/assets/end_effector/pinch_gripper/config/link/collisions.yaml +++ b/assets/end_effector/pinch_gripper/config/link/collisions.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -ee_base_link: +link1: origin: x: 0 y: 0 @@ -26,7 +26,7 @@ ee_base_link: x: 1 y: 1 z: 1 -ee_link1: +link2: origin: x: 0 y: 0 @@ -40,7 +40,7 @@ ee_link1: x: 1 y: 1 z: 1 -ee_link2: +link3: origin: x: 0 y: 0 diff --git a/assets/end_effector/pinch_gripper/config/link/visuals.yaml b/assets/end_effector/pinch_gripper/config/link/visuals.yaml index d6669034..144aca0d 100644 --- a/assets/end_effector/pinch_gripper/config/link/visuals.yaml +++ b/assets/end_effector/pinch_gripper/config/link/visuals.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -ee_base_link: +link1: origin: x: 0 y: 0 @@ -26,7 +26,7 @@ ee_base_link: x: 1 y: 1 z: 1 -ee_link1: +link2: origin: x: 0 y: 0 @@ -40,7 +40,7 @@ ee_link1: x: 1 y: 1 z: 1 -ee_link2: +link3: origin: x: 0 y: 0 diff --git a/assets/end_effector/pinch_gripper/config/struct/name_mapping.yaml b/assets/end_effector/pinch_gripper/config/struct/name_mapping.yaml index 84555f5c..b6300337 100644 --- a/assets/end_effector/pinch_gripper/config/struct/name_mapping.yaml +++ b/assets/end_effector/pinch_gripper/config/struct/name_mapping.yaml @@ -14,17 +14,17 @@ links: canonical_to_original: - ee_base_link: ee_base_link - ee_link1: finger_inner - ee_link2: finger_outer + link1: ee_base_link + link2: finger_inner + link3: finger_outer original_to_canonical: - ee_base_link: ee_base_link - finger_inner: ee_link1 - finger_outer: ee_link2 + ee_base_link: link1 + finger_inner: link2 + finger_outer: link3 joints: canonical_to_original: - finger_joint1: finger_inner_joint - finger_joint2: finger_outer_joint + joint1: finger_inner_joint + joint2: finger_outer_joint original_to_canonical: - finger_inner_joint: finger_joint1 - finger_outer_joint: finger_joint2 + finger_inner_joint: joint1 + finger_outer_joint: joint2 diff --git a/assets/end_effector/pinch_gripper/config/struct/reference_points.yaml b/assets/end_effector/pinch_gripper/config/struct/reference_points.yaml index e3cbf358..51344d31 100644 --- a/assets/end_effector/pinch_gripper/config/struct/reference_points.yaml +++ b/assets/end_effector/pinch_gripper/config/struct/reference_points.yaml @@ -13,7 +13,7 @@ # limitations under the License. grasp_frame: - parent: ee_base_link + parent: link1 origin: x: -0.0219300000000009 y: 0 @@ -29,13 +29,13 @@ grasp_frame: type: fixed parent_link: original_name: ee_base_link - canonical_name: ee_base_link + canonical_name: link1 child_link: original_name: grasp_frame canonical_name: null camera_view_frame: - parent: ee_base_link + parent: link1 emit: true origin: x: 0.0013353 diff --git a/assets/end_effector/pinch_gripper/config/struct/topology.yaml b/assets/end_effector/pinch_gripper/config/struct/topology.yaml index 9b3784e2..ff2863cb 100644 --- a/assets/end_effector/pinch_gripper/config/struct/topology.yaml +++ b/assets/end_effector/pinch_gripper/config/struct/topology.yaml @@ -13,15 +13,20 @@ # limitations under the License. links: -- ee_base_link -- ee_link1 -- ee_link2 + link1: + name: ee_base_link + link2: + name: ee_link1 + link3: + name: ee_link2 joints: - finger_joint1: + joint1: + name: finger_joint1 type: revolute - parent: ee_base_link - child: ee_link1 - finger_joint2: + parent: link1 + child: link2 + joint2: + name: finger_joint2 type: revolute - parent: ee_base_link - child: ee_link2 + parent: link1 + child: link3 diff --git a/assets/end_effector/pinch_gripper/urdf/component.xacro b/assets/end_effector/pinch_gripper/urdf/component.xacro index 4c130677..62f9ea4a 100644 --- a/assets/end_effector/pinch_gripper/urdf/component.xacro +++ b/assets/end_effector/pinch_gripper/urdf/component.xacro @@ -28,6 +28,8 @@ limitations under the License. + params="prefix connected_to mount_origin:=none topology link_name_map joint_name_map joint_origins joint_axes joint_limits joint_mimics inertials visuals collisions reflect_x:=1 reflect_y:=1 reflect_z:=1 component_reflects:=false reflect_include_links:=^ reflect_include_joints:=^ reflect_include_reference_points:=^ origin_override_joints:=${dict()} limit_override_joints:=^ ee_reference_points:=^ emit_grasp_frame:=false grasp_reference_point:=grasp_frame skip_mount_joint:=false base_frame_shift:=none"> - + - + - - - - + + + + @@ -44,8 +44,8 @@ limitations under the License. - - + + - + - - + + @@ -139,15 +139,15 @@ limitations under the License. - + - - + + @@ -164,6 +164,7 @@ limitations under the License. reference_points="${ee_reference_points}" force_emit_reference="${grasp_reference_point if emit_grasp_frame else ''}" prefix="${prefix}" + link_name_map="${link_name_map}" base_link_name="${base_link_name}" reflect_x="${reflect_x if component_reflects else 1}" reflect_y="${reflect_y if component_reflects else 1}" diff --git a/assets/robot/openarm_v2.0/config/arm/inertials/nominal.yaml b/assets/robot/openarm_v2.0/config/arm/inertials/nominal.yaml index a6f5f7ec..6d7df71d 100644 --- a/assets/robot/openarm_v2.0/config/arm/inertials/nominal.yaml +++ b/assets/robot/openarm_v2.0/config/arm/inertials/nominal.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -base_link: +link1: origin: x: 0.00253123748061566 y: -0.0258185540330169 @@ -28,7 +28,7 @@ base_link: yy: 0.00159935208714031 yz: 2.7521421737084e-08 zz: 0.000750541980863571 -link1: +link2: origin: x: 0.000900350355624244 y: -0.0444659716892874 @@ -44,7 +44,7 @@ link1: yy: 0.000759199728161993 yz: 6.66755698859637e-08 zz: 0.000670659201019629 -link2: +link3: origin: x: -0.0247184924346635 y: 7.60578399428002e-08 @@ -60,7 +60,7 @@ link2: yy: 0.000113062180225725 yz: 3.75816911469744e-10 zz: 8.22999351863579e-05 -link3: +link4: origin: x: -0.00300861804638783 y: -0.000514813312550808 @@ -76,7 +76,7 @@ link3: yy: 0.00114018992179734 yz: -1.06260802872572e-05 zz: 0.000559765478676303 -link4: +link5: origin: x: -0.00310326243752606 y: -0.00136538979048502 @@ -92,7 +92,7 @@ link4: yy: 0.000433926270522842 yz: 3.41757797773822e-06 zz: 0.000245211429759716 -link5: +link6: origin: x: -0.000737188580130584 y: -0.000412406544351845 @@ -108,7 +108,7 @@ link5: yy: 0.000334599623120095 yz: -1.49934409279756e-07 zz: 0.000176662464388138 -link6: +link7: origin: x: 2.27438423593244e-05 y: 0.00068649602098908 diff --git a/assets/robot/openarm_v2.0/config/arm/link/collisions.yaml b/assets/robot/openarm_v2.0/config/arm/link/collisions.yaml index 288633ba..4980bbc4 100644 --- a/assets/robot/openarm_v2.0/config/arm/link/collisions.yaml +++ b/assets/robot/openarm_v2.0/config/arm/link/collisions.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -base_link: +link1: origin: x: 0 y: 0 @@ -26,7 +26,7 @@ base_link: x: 1 y: 1 z: 1 -link1: +link2: origin: x: 0 y: 0 @@ -40,7 +40,7 @@ link1: x: 1 y: 1 z: 1 -link2: +link3: origin: x: 0 y: 0 @@ -54,7 +54,7 @@ link2: x: 1 y: 1 z: 1 -link3: +link4: origin: x: 0 y: 0 @@ -68,7 +68,7 @@ link3: x: 1 y: 1 z: 1 -link4: +link5: origin: x: 0 y: 0 @@ -82,7 +82,7 @@ link4: x: 1 y: 1 z: 1 -link5: +link6: origin: x: 0 y: 0 @@ -96,7 +96,7 @@ link5: x: 1 y: 1 z: 1 -link6: +link7: origin: x: 0 y: 0 diff --git a/assets/robot/openarm_v2.0/config/arm/link/visuals.yaml b/assets/robot/openarm_v2.0/config/arm/link/visuals.yaml index 6fcf1682..9e6a8467 100644 --- a/assets/robot/openarm_v2.0/config/arm/link/visuals.yaml +++ b/assets/robot/openarm_v2.0/config/arm/link/visuals.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -base_link: +link1: origin: x: 0 y: 0 @@ -26,7 +26,7 @@ base_link: x: 1 y: 1 z: 1 -link1: +link2: origin: x: 0 y: 0 @@ -40,7 +40,7 @@ link1: x: 1 y: 1 z: 1 -link2: +link3: origin: x: 0 y: 0 @@ -54,7 +54,7 @@ link2: x: 1 y: 1 z: 1 -link3: +link4: origin: x: 0 y: 0 @@ -68,7 +68,7 @@ link3: x: 1 y: 1 z: 1 -link4: +link5: origin: x: 0 y: 0 @@ -82,7 +82,7 @@ link4: x: 1 y: 1 z: 1 -link5: +link6: origin: x: 0 y: 0 @@ -96,7 +96,7 @@ link5: x: 1 y: 1 z: 1 -link6: +link7: origin: x: 0 y: 0 diff --git a/assets/robot/openarm_v2.0/config/arm/struct/name_mapping.yaml b/assets/robot/openarm_v2.0/config/arm/struct/name_mapping.yaml index 98d02fd9..dbabebb3 100644 --- a/assets/robot/openarm_v2.0/config/arm/struct/name_mapping.yaml +++ b/assets/robot/openarm_v2.0/config/arm/struct/name_mapping.yaml @@ -14,23 +14,23 @@ links: canonical_to_original: - base_link: base_link - link1: link1 - link2: link2 - link3: link3 - link4: link4 - link5: link5 - link6: link6 - link7: empty_link_for_J7 + link1: base_link + link2: link1 + link3: link2 + link4: link3 + link5: link4 + link6: link5 + link7: link6 + link8: empty_link_for_J7 original_to_canonical: - base_link: base_link - link1: link1 - link2: link2 - link3: link3 - link4: link4 - link5: link5 - link6: link6 - empty_link_for_J7: link7 + base_link: link1 + link1: link2 + link2: link3 + link3: link4 + link4: link5 + link5: link6 + link6: link7 + empty_link_for_J7: link8 joints: canonical_to_original: joint1: joint1 diff --git a/assets/robot/openarm_v2.0/config/arm/struct/reference_points.yaml b/assets/robot/openarm_v2.0/config/arm/struct/reference_points.yaml index 2f524870..1cd09d60 100644 --- a/assets/robot/openarm_v2.0/config/arm/struct/reference_points.yaml +++ b/assets/robot/openarm_v2.0/config/arm/struct/reference_points.yaml @@ -13,7 +13,7 @@ # limitations under the License. ee_mount_point: - parent: link7 + parent: link8 origin: x: 0.0205 y: 0 @@ -29,7 +29,7 @@ ee_mount_point: type: fixed parent_link: original_name: empty_link_for_J7 - canonical_name: link7 + canonical_name: link8 child_link: original_name: empty_link_for_ee_mounting canonical_name: null diff --git a/assets/robot/openarm_v2.0/config/arm/struct/topology.yaml b/assets/robot/openarm_v2.0/config/arm/struct/topology.yaml index acd91f4b..f2f248e3 100644 --- a/assets/robot/openarm_v2.0/config/arm/struct/topology.yaml +++ b/assets/robot/openarm_v2.0/config/arm/struct/topology.yaml @@ -13,42 +13,49 @@ # limitations under the License. collapse_empty_end: true - links: - - base_link - - link1 - - link2 - - link3 - - link4 - - link5 - - link6 - - link7 + link1: + name: base_link + link2: + name: link1 + link3: + name: link2 + link4: + name: link3 + link5: + name: link4 + link6: + name: link5 + link7: + name: link6 + link8: + name: link7 joints: joint1: - type: revolute - parent: base_link - child: link1 - joint2: type: revolute parent: link1 child: link2 - joint3: + joint2: type: revolute parent: link2 child: link3 - joint4: + joint3: type: revolute parent: link3 child: link4 - joint5: + joint4: type: revolute parent: link4 child: link5 - joint6: + joint5: type: revolute parent: link5 child: link6 - joint7: + joint6: type: revolute parent: link6 child: link7 + joint7: + type: revolute + parent: link7 + child: link8 diff --git a/assets/robot/openarm_v2.0/config/robot_presets/default_bimanual.yaml b/assets/robot/openarm_v2.0/config/robot_presets/default_bimanual.yaml index 3771f40b..559c7625 100644 --- a/assets/robot/openarm_v2.0/config/robot_presets/default_bimanual.yaml +++ b/assets/robot/openarm_v2.0/config/robot_presets/default_bimanual.yaml @@ -60,7 +60,7 @@ primary_arm: y: -1 z: 1 include: - links: [base_link, link1, link2, link5, link6, link7] + links: [link1, link2, link3, link6, link7, link8] joints: [joint1, joint2, joint3, joint5, joint6, joint7] reference_points: [ee_mount_point] limit_override_joints: {} @@ -118,3 +118,29 @@ secondary_arm_end_effector: y: 1 z: 1 limit_override_joints: {} + +systems: + openarm_left_hardware_interface: + joints: + primary_arm: + joint1: {idx: 1} + joint2: {idx: 2} + joint3: {idx: 3} + joint4: {idx: 4} + joint5: {idx: 5} + joint6: {idx: 6} + joint7: {idx: 7} + primary_arm_end_effector: + joint1: {idx: 8} + openarm_right_hardware_interface: + joints: + secondary_arm: + joint1: {idx: 1} + joint2: {idx: 2} + joint3: {idx: 3} + joint4: {idx: 4} + joint5: {idx: 5} + joint6: {idx: 6} + joint7: {idx: 7} + secondary_arm_end_effector: + joint1: {idx: 8} diff --git a/assets/robot/openarm_v2.0/config/robot_presets/left_arm.yaml b/assets/robot/openarm_v2.0/config/robot_presets/left_arm.yaml index 2b94a8ae..b4debe9d 100644 --- a/assets/robot/openarm_v2.0/config/robot_presets/left_arm.yaml +++ b/assets/robot/openarm_v2.0/config/robot_presets/left_arm.yaml @@ -60,7 +60,7 @@ primary_arm: y: -1 z: 1 include: - links: [base_link, link1, link2, link5, link6, link7] + links: [link1, link2, link3, link6, link7, link8] joints: [joint1, joint2, joint3, joint5, joint6, joint7] reference_points: [ee_mount_point] limit_override_joints: {} @@ -122,3 +122,15 @@ secondary_arm_end_effector: y: 1 z: 1 limit_override_joints: {} + +systems: + openarm_left_hardware_interface: + joints: + primary_arm: + joint1: {idx: 1} + joint2: {idx: 2} + joint3: {idx: 3} + joint4: {idx: 4} + joint5: {idx: 5} + joint6: {idx: 6} + joint7: {idx: 7} diff --git a/assets/robot/openarm_v2.0/config/robot_presets/left_arm_with_pinch_gripper.yaml b/assets/robot/openarm_v2.0/config/robot_presets/left_arm_with_pinch_gripper.yaml index fbfa0663..18fb9066 100644 --- a/assets/robot/openarm_v2.0/config/robot_presets/left_arm_with_pinch_gripper.yaml +++ b/assets/robot/openarm_v2.0/config/robot_presets/left_arm_with_pinch_gripper.yaml @@ -61,7 +61,7 @@ primary_arm: y: -1 z: 1 include: - links: [base_link, link1, link2, link5, link6, link7] + links: [link1, link2, link3, link6, link7, link8] joints: [joint1, joint2, joint3, joint5, joint6, joint7] reference_points: [ee_mount_point] limit_override_joints: {} @@ -123,3 +123,17 @@ secondary_arm_end_effector: y: 1 z: 1 limit_override_joints: {} + +systems: + openarm_left_hardware_interface: + joints: + primary_arm: + joint1: {idx: 1} + joint2: {idx: 2} + joint3: {idx: 3} + joint4: {idx: 4} + joint5: {idx: 5} + joint6: {idx: 6} + joint7: {idx: 7} + primary_arm_end_effector: + joint1: {idx: 8} diff --git a/assets/robot/openarm_v2.0/config/robot_presets/right_arm.yaml b/assets/robot/openarm_v2.0/config/robot_presets/right_arm.yaml index fe26dd88..d62cd67c 100644 --- a/assets/robot/openarm_v2.0/config/robot_presets/right_arm.yaml +++ b/assets/robot/openarm_v2.0/config/robot_presets/right_arm.yaml @@ -122,3 +122,15 @@ secondary_arm_end_effector: y: -1 z: 1 limit_override_joints: {} + +systems: + openarm_right_hardware_interface: + joints: + primary_arm: + joint1: {idx: 1} + joint2: {idx: 2} + joint3: {idx: 3} + joint4: {idx: 4} + joint5: {idx: 5} + joint6: {idx: 6} + joint7: {idx: 7} diff --git a/assets/robot/openarm_v2.0/config/robot_presets/right_arm_with_pinch_gripper.yaml b/assets/robot/openarm_v2.0/config/robot_presets/right_arm_with_pinch_gripper.yaml index f5e60d0e..06936a6b 100644 --- a/assets/robot/openarm_v2.0/config/robot_presets/right_arm_with_pinch_gripper.yaml +++ b/assets/robot/openarm_v2.0/config/robot_presets/right_arm_with_pinch_gripper.yaml @@ -119,3 +119,17 @@ secondary_arm_end_effector: y: -1 z: 1 limit_override_joints: {} + +systems: + openarm_right_hardware_interface: + joints: + primary_arm: + joint1: {idx: 1} + joint2: {idx: 2} + joint3: {idx: 3} + joint4: {idx: 4} + joint5: {idx: 5} + joint6: {idx: 6} + joint7: {idx: 7} + primary_arm_end_effector: + joint1: {idx: 8} diff --git a/assets/robot/openarm_v2.0/urdf/example/v2.urdf b/assets/robot/openarm_v2.0/urdf/example/v2.urdf index 39ad0c70..9ae8fcba 100644 --- a/assets/robot/openarm_v2.0/urdf/example/v2.urdf +++ b/assets/robot/openarm_v2.0/urdf/example/v2.urdf @@ -711,14 +711,6 @@ limitations under the License. 0.0 - @@ -839,13 +831,5 @@ limitations under the License. 0.0 - diff --git a/assets/robot/openarm_v2.0/urdf/openarm_v20.urdf.xacro b/assets/robot/openarm_v2.0/urdf/openarm_v20.urdf.xacro index 351b2678..dee35f42 100644 --- a/assets/robot/openarm_v2.0/urdf/openarm_v20.urdf.xacro +++ b/assets/robot/openarm_v2.0/urdf/openarm_v20.urdf.xacro @@ -16,42 +16,42 @@ limitations under the License. - + + + - - - + + + - - - - - - - - + + + + + + + + + diff --git a/assets/robot/openarm_v2.0/urdf/ros2_control/openarm.bimanual.ros2_control.xacro b/assets/robot/openarm_v2.0/urdf/ros2_control/openarm.bimanual.ros2_control.xacro deleted file mode 100644 index e7b25ad8..00000000 --- a/assets/robot/openarm_v2.0/urdf/ros2_control/openarm.bimanual.ros2_control.xacro +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - - ${arm_type} - - mock_components/GenericSystem - ${fake_sensor_commands} - 0.0 - - - openarm_hardware/OpenArmHW - ${left_can_interface} - ${left_arm_prefix} - ${can_fd} - ${ee_type} - ${control_gains['joint1']['kp']} - ${control_gains['joint2']['kp']} - ${control_gains['joint3']['kp']} - ${control_gains['joint4']['kp']} - ${control_gains['joint5']['kp']} - ${control_gains['joint6']['kp']} - ${control_gains['joint7']['kp']} - ${control_gains['joint1']['kd']} - ${control_gains['joint2']['kd']} - ${control_gains['joint3']['kd']} - ${control_gains['joint4']['kd']} - ${control_gains['joint5']['kd']} - ${control_gains['joint6']['kd']} - ${control_gains['joint7']['kd']} - - - - - - - - - - - ${initial_position} - - - - 0.0 - - - - 0.0 - - - - - - - - - - - - - - - - - - - - - ${arm_type} - - mock_components/GenericSystem - ${fake_sensor_commands} - 0.0 - - - openarm_hardware/OpenArmHW - ${right_can_interface} - ${right_arm_prefix} - ${can_fd} - ${ee_type} - ${control_gains['joint1']['kp']} - ${control_gains['joint2']['kp']} - ${control_gains['joint3']['kp']} - ${control_gains['joint4']['kp']} - ${control_gains['joint5']['kp']} - ${control_gains['joint6']['kp']} - ${control_gains['joint7']['kp']} - ${control_gains['joint1']['kd']} - ${control_gains['joint2']['kd']} - ${control_gains['joint3']['kd']} - ${control_gains['joint4']['kd']} - ${control_gains['joint5']['kd']} - ${control_gains['joint6']['kd']} - ${control_gains['joint7']['kd']} - - - - - - - - - - - - - - - - - - - - diff --git a/assets/robot/openarm_v2.0/urdf/ros2_control/openarm.ros2_control.xacro b/assets/robot/openarm_v2.0/urdf/ros2_control/openarm.ros2_control.xacro deleted file mode 100644 index 59f182b2..00000000 --- a/assets/robot/openarm_v2.0/urdf/ros2_control/openarm.ros2_control.xacro +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - - - - ${arm_type} - ${arm_prefix} - - mock_components/GenericSystem - ${fake_sensor_commands} - 0.0 - - - openarm_hardware/OpenArmHW - ${can_interface} - ${arm_prefix} - ${hand} - ${can_fd} - ${ee_type} - ${control_gains['joint1']['kp']} - ${control_gains['joint2']['kp']} - ${control_gains['joint3']['kp']} - ${control_gains['joint4']['kp']} - ${control_gains['joint5']['kp']} - ${control_gains['joint6']['kp']} - ${control_gains['joint7']['kp']} - ${control_gains['joint1']['kd']} - ${control_gains['joint2']['kd']} - ${control_gains['joint3']['kd']} - ${control_gains['joint4']['kd']} - ${control_gains['joint5']['kd']} - ${control_gains['joint6']['kd']} - ${control_gains['joint7']['kd']} - - - - - - - - - - - - - ${initial_position} - - - - 0.0 - - - - 0.0 - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/sensor/zed/urdf/component.xacro b/assets/sensor/zed/urdf/component.xacro index 554ded90..c44546ed 100644 --- a/assets/sensor/zed/urdf/component.xacro +++ b/assets/sensor/zed/urdf/component.xacro @@ -35,7 +35,7 @@ limitations under the License. prefix=component_prefix, root_link_actual=sensor_root_link, - topology=dict(links=[sensor_root_link], joints=dict()), + topology=dict(links=dict(link1=dict(name=sensor_root_link)), joints=dict()), reference_points=empty_dict, joint_origins=empty_dict, joint_axes=empty_dict, @@ -44,7 +44,8 @@ limitations under the License. inertials=empty_dict, visuals=empty_dict, collisions=empty_dict, - link_name_map=empty_dict, + link_name_map=dict(link1=sensor_root_link), + joint_name_map=empty_dict, default_mount_parent=sensor_root_link, collapse_empty_end=False, supports_frame_shift=False, diff --git a/launch/display_openarm.launch.py b/launch/display_openarm.launch.py index 3174b273..4471cff7 100644 --- a/launch/display_openarm.launch.py +++ b/launch/display_openarm.launch.py @@ -83,6 +83,9 @@ def robot_state_publisher_spawner( "arm_type": arm_type_str, "robot_preset": context.perform_substitution(robot_preset), "emit_grasp_frame": context.perform_substitution(emit_grasp_frame), + "ros2_control": context.perform_substitution(ros2_control), + "use_fake_hardware": context.perform_substitution(use_fake_hardware), + "fake_sensor_commands": context.perform_substitution(fake_sensor_commands), } robot_description = xacro.process_file( @@ -110,7 +113,7 @@ def generate_launch_description(): robot_preset_arg = DeclareLaunchArgument( "robot_preset", default_value="default_bimanual", - description="Robot preset for v2.0 (e.g. default_bimanual, right_arm, left_arm, right_arm_with_pinch_gripper, left_arm_with_pinch_gripper).", + description="Robot preset for v2.0 (e.g. default_bimanual, right_arm, left_arm).", ) rviz_config_arg = DeclareLaunchArgument( "rviz_config", diff --git a/test/support.py b/test/support.py index 870485e4..f40ae610 100644 --- a/test/support.py +++ b/test/support.py @@ -106,17 +106,6 @@ def package_share_at(path): def expand(files=None, version='v2.0', entry=None, **arguments): - if entry is None: - files = dict(files or {}) - files['fixture.urdf.xacro'] = ''' - - - - - -''' - entry = 'assets/fixture.urdf.xacro' entry = entry or f'assets/robot/openarm_{version}/urdf/openarm_{"v10" if version == "v1.0" else "v20"}.urdf.xacro' arguments = {k: str(v).lower() if isinstance(v, bool) else str(v) for k, v in arguments.items()} @@ -143,7 +132,7 @@ def expand(files=None, version='v2.0', entry=None, **arguments): def model(links=('root',), joints=None): joints = joints or {} - return {'struct/topology.yaml': dict(links=list(links), joints=deepcopy(joints)), + return {'struct/topology.yaml': dict(links={name: {} for name in links}, joints=deepcopy(joints)), 'struct/reference_points.yaml': {'port': dict(parent=links[-1], emit=False, origin=pose(.1, .2, .3, .2, -.1, .3))}, 'joint/joint_origins.yaml': {j: dict(origin=pose(z=.1)) for j in joints}, 'joint/joint_axes.yaml': {j: dict(axis=dict(x=0, y=0, z=1)) for j in joints}, @@ -161,13 +150,16 @@ def chain(): def pinch(): - data = model(('ee_base_link', 'ee_link1', 'ee_link2'), { - 'finger_joint1': dict(type='revolute', parent='ee_base_link', child='ee_link1'), - 'finger_joint2': dict(type='revolute', parent='ee_base_link', child='ee_link2')}) - data['joint/joint_mimics.yaml'] = {'finger_joint2': dict( - joint='finger_joint1', multiplier=-1, offset=.1)} + data = model(('link1', 'link2', 'link3'), { + 'joint1': dict(name='finger_joint1', type='revolute', parent='link1', child='link2'), + 'joint2': dict(name='finger_joint2', type='revolute', parent='link1', child='link3')}) + data['struct/topology.yaml']['links'] = { + 'link1': dict(name='ee_base_link'), + 'link2': dict(name='ee_link1'), 'link3': dict(name='ee_link2')} + data['joint/joint_mimics.yaml'] = {'joint2': dict( + joint='joint1', multiplier=-1, offset=.1)} data['struct/reference_points.yaml'] = {'grasp_frame': dict( - parent='ee_base_link', emit=False, origin=pose(z=.12))} + parent='link1', emit=False, origin=pose(z=.12))} return data diff --git a/test/test_assembly.py b/test/test_assembly.py index c7cb6c41..a5cbc8d2 100644 --- a/test/test_assembly.py +++ b/test/test_assembly.py @@ -54,6 +54,12 @@ def test_topology_and_values_come_from_configuration(kind): @pytest.mark.parametrize('product', ['fixture_tool', 'pinch_gripper']) def test_names_and_mimics_follow_configuration(product): data = pinch() + topology = data['struct/topology.yaml'] + topology['joints']['joint1']['name'] = 'drive' + topology['joints']['joint2']['name'] = 'follower' + topology['links']['link1']['name'] = 'tool_base' + topology['links']['link2']['name'] = 'moving_finger' + topology['links']['link3'].pop('name') for item in data['struct/topology.yaml']['joints'].values(): item['type'] = 'prismatic' for joint in data['joint/joint_axes.yaml'].values(): @@ -62,23 +68,29 @@ def test_names_and_mimics_follow_configuration(product): b=component('end_effector', product, 'beta_', connection=dict(attach_to='a'), reflect=dict(x=1, y=-1, z=1))) robot = assemble(spec, {'a': data, 'b': data}) - mimic = data['joint/joint_mimics.yaml']['finger_joint2'] + names = {key: cfg.get('name', key) for key, cfg in + data['struct/topology.yaml']['joints'].items()} + assert {link.get('name') for link in robot.findall('link')} == {'world'} | { + prefix + cfg.get('name', key) for prefix in ('alpha_', 'beta_') + for key, cfg in topology['links'].items()} + mimic = data['joint/joint_mimics.yaml']['joint2'] for prefix in ('alpha_', 'beta_'): - joint = robot.find(f"joint[@name='{prefix}finger_joint2']") + joint = robot.find(f"joint[@name='{prefix}follower']") assert joint.get('type') == 'prismatic' for name, configured in data['joint/joint_limits.yaml'].items(): - limit = robot.find(f"joint[@name='{prefix}{name}']/limit") + limit = robot.find(f"joint[@name='{prefix}{names[name]}']/limit") for key in ('lower', 'upper'): assert float(limit.get(key)) == configured['limit'][key] - assert joint.find('mimic').get('joint') == prefix + mimic['joint'] + assert joint.find('mimic').get( + 'joint') == prefix + names[mimic['joint']] for key in ('multiplier', 'offset'): assert float(joint.find('mimic').get(key)) == mimic[key] reflection = [[float(i == j) * (1, -1, 1, 1)[i] for j in range(4)] for i in range(4)] for name in data['struct/topology.yaml']['joints']: - right = robot.find(f"joint[@name='alpha_{name}']") - left = robot.find(f"joint[@name='beta_{name}']") + right = robot.find(f"joint[@name='alpha_{names[name]}']") + left = robot.find(f"joint[@name='beta_{names[name]}']") for endpoint in ['lower', 'upper']: position = float(right.find('limit').get(endpoint)) motions = [] @@ -126,7 +138,7 @@ def test_grasp_emission_combines_configuration_and_argument(): @pytest.mark.parametrize('tool', [False, True]) def test_overrides_are_applied_before_reflection(tool): data = pinch() if tool else chain() - name = 'finger_joint1' if tool else 'pivot' + name = 'joint1' if tool else 'pivot' origin = pose(.03, .04, .05, .2, -.3, .4) limit = dict(lower=-.2, upper=.7, effort=12, velocity=4) spec = preset(unit=component('end_effector' if tool else 'robot', @@ -134,7 +146,9 @@ def test_overrides_are_applied_before_reflection(tool): reflect=dict(x=1, y=-1, z=1), origin_override_joints={name: origin}, limit_override_joints={name: limit})) robot = assemble(spec, {'unit': data}) - joint = robot.find(f"joint[@name='unit_{name}']") + emitted_name = data['struct/topology.yaml']['joints'][name].get( + 'name', name) + joint = robot.find(f"joint[@name='unit_{emitted_name}']") expected = deepcopy(origin) for key in ('y', 'roll', 'yaw'): expected[key] *= -1 @@ -151,10 +165,10 @@ def test_reflection_selection_preserves_unselected_joints(product): for joint in data['joint/joint_axes.yaml'].values(): joint['axis'].update(x=.6, y=.8, z=0.) spec = preset(tool=component('end_effector', product, 'tool_', - reflect=dict(x=1, y=-1, z=1, include=dict(links=['ee_base_link'], joints=[], reference_points=[])))) + reflect=dict(x=1, y=-1, z=1, include=dict(links=['link1'], joints=[], reference_points=[])))) robot = assemble(spec, {'tool': data}) base = robot.find("link[@name='tool_ee_base_link']/inertial") - source = data['inertials/nominal.yaml']['ee_base_link'] + source = data['inertials/nominal.yaml']['link1'] assert float(base.find('mass').get('value')) == source['mass'] expected = deepcopy(source['origin']) expected['y'] *= -1 @@ -163,7 +177,9 @@ def test_reflection_selection_preserves_unselected_joints(product): sign = -1 if key in ('xy', 'yz') else 1 assert float(base.find('inertia').get('i'+key)) == sign * value for name in data['struct/topology.yaml']['joints']: - joint = robot.find(f"joint[@name='tool_{name}']") + emitted_name = data['struct/topology.yaml']['joints'][name].get( + 'name', name) + joint = robot.find(f"joint[@name='tool_{emitted_name}']") near(transform(joint.find('origin')), transform( data['joint/joint_origins.yaml'][name]['origin'])) assert [float(v) for v in joint.find('axis').get('xyz').split()] == list( @@ -181,24 +197,24 @@ def test_folding_preserves_physical_properties_and_frames(): assert robot.find("link[@name='arm_tip']") is not None spec, data = installed_tool(False) - data['tool']['struct/topology.yaml']['joints']['finger_joint2']['parent'] = 'ee_link1' + data['tool']['struct/topology.yaml']['joints']['joint2']['parent'] = 'link2' data['arm']['struct/reference_points.yaml']['port']['emit'] = True geometry = dict(mesh='package://fixture_meshes/shape.stl', scale=dict(x=1, y=1, z=1)) shape = dict(origin=pose(.02, -.04, .01, .2, .1, -.3), geometry=geometry) - data['tool']['link/visuals.yaml']['ee_base_link'] = deepcopy(shape) - data['tool']['link/collisions.yaml']['ee_base_link'] = deepcopy(shape) + data['tool']['link/visuals.yaml']['link1'] = deepcopy(shape) + data['tool']['link/collisions.yaml']['link1'] = deepcopy(shape) spec['tool']['reflect'] = dict(x=1, y=-1, z=1, include=dict( - links=['ee_base_link'], joints=[], reference_points=[])) + links=['link1'], joints=[], reference_points=[])) spec['tool']['origin_override_joints'] = { - 'finger_joint1': pose(.04, .03, .02, .3, .2, .1)} + 'joint1': pose(.04, .03, .02, .3, .2, .1)} first = assemble(spec, data, emit_grasp_frame=True) data['arm']['struct/topology.yaml']['collapse_empty_end'] = True second = assemble(spec, data, emit_grasp_frame=True) physical_equal(first, second) for frame in ('arm_port', 'tool_grasp_frame'): near(poses(first)[frame], poses(second)[frame]) - mass = data['tool']['inertials/nominal.yaml']['ee_base_link']['mass'] + mass = data['tool']['inertials/nominal.yaml']['link1']['mass'] assert float(second.find( "link[@name='tool_ee_base_link']/inertial/mass").get('value')) == mass diff --git a/test/test_entrypoints.py b/test/test_entrypoints.py index 3cfa0134..4fb33c37 100644 --- a/test/test_entrypoints.py +++ b/test/test_entrypoints.py @@ -28,7 +28,7 @@ def write_configuration(directory, data): def test_pinch_entry_forwards_reference_options(tmp_path): data = pinch() - reference = dict(parent='ee_base_link', emit=True, + reference = dict(parent='link1', emit=True, origin=pose(.01, .02, .03, .2, -.1, .3)) data['struct/reference_points.yaml']['probe'] = reference write_configuration(tmp_path, data) @@ -44,10 +44,10 @@ def test_pinch_entry_forwards_reference_options(tmp_path): data = pinch() joints = data['struct/topology.yaml']['joints'] if invalid == 'unknown link': - joints['finger_joint1']['parent'] = 'missing' + joints['joint1']['parent'] = 'missing' else: - joints['finger_joint1']['parent'] = joints['finger_joint2']['child'] - joints['finger_joint2']['parent'] = joints['finger_joint1']['child'] + joints['joint1']['parent'] = joints['joint2']['child'] + joints['joint2']['parent'] = joints['joint1']['child'] write_configuration(tmp_path, data) with pytest.raises(xacro.XacroException, match=invalid): expand(entry='assets/end_effector/pinch_gripper/urdf/pinch_gripper_standalone.urdf.xacro', diff --git a/test/test_interfaces.py b/test/test_interfaces.py index 64d7ff1a..5d79f5d1 100644 --- a/test/test_interfaces.py +++ b/test/test_interfaces.py @@ -67,3 +67,164 @@ def test_broken_component_hook_does_not_use_fallback(interface): files['body/fixture_a/urdf/component.xacro'] = interface with pytest.raises((xacro.XacroException, ExpatError)): support.expand(files, robot_preset='fixture', ros2_control=False) + + +def control_input(): + names = ['root'] + [f'link{i}' for i in range(1, 8)] + data = support.model(names, {f'joint{i}': dict( + type='revolute', parent=names[i-1], child=names[i]) for i in range(1, 8)}) + data['control/control_gains.yaml'] = { + f'joint{i}': dict(kp=10+i, kd=i/10) for i in range(1, 8)} + spec = support.preset(arm=support.component('robot', 'fixture_control', 'openarm_right_'), + tool=support.component('end_effector', 'pinch_gripper', 'openarm_right_', connection=dict(attach_to='arm'))) + spec['systems'] = {'fixture_system': dict( + hardware_plugin='openarm_hardware/OpenArmHW', can_interface='can_test', can_fd=True, + joints=dict(arm={name: dict(idx=i) for i, name in enumerate(data['struct/topology.yaml']['joints'])}, + tool=dict(joint1=dict(idx=8))))} + return spec, {'arm': data, 'tool': support.pinch()} + + +def test_control_joints_follow_model_names_and_enabled_components(): + spec, data = control_input() + spec['arm']['prefix'], spec['tool']['prefix'] = 'drive_', 'tool_' + arm_joints = data['arm']['struct/topology.yaml']['joints'] + arm_joints['joint7']['name'] = 'wrist' + selection = spec['systems']['fixture_system']['joints']['arm'] + spec['systems']['fixture_system']['joints']['arm'] = dict( + reversed(list(selection.items()))) + assert not support.assemble( + spec, data, ros2_control=False).findall('ros2_control') + for tool_enabled in (False, True): + spec['tool']['enabled'] = tool_enabled + robot = support.assemble( + spec, data, ros2_control=True, use_fake_hardware=True) + expected = {spec['arm']['prefix'] + cfg.get('name', name) + for name, cfg in arm_joints.items()} + if tool_enabled: + expected.update(spec['tool']['prefix'] + cfg.get('name', name) + for name, cfg in data['tool']['struct/topology.yaml']['joints'].items() + if name not in data['tool']['joint/joint_mimics.yaml']) + active = {joint.get('name') for joint in robot.findall('joint') + if joint.get('type') != 'fixed' and joint.find('mimic') is None} + assert active == expected + assert {joint.get('name') + for joint in robot.findall('ros2_control/joint')} == active + expected_indices = {spec[component]['prefix'] + data[component]['struct/topology.yaml']['joints'][name].get('name', name): cfg['idx'] + for component, joints in spec['systems']['fixture_system']['joints'].items() + if spec[component]['enabled'] for name, cfg in joints.items()} + actual_indices = {joint.get('name'): int(joint.find("param[@name='idx']").text) + for joint in robot.findall('ros2_control/joint')} + assert actual_indices == expected_indices + assert list(actual_indices.values()) == sorted( + expected_indices.values()) + + data['arm']['struct/topology.yaml']['collapse_empty_end'] = True + spec['tool']['connection']['parent_reference_point'] = 'port' + spec['systems']['fixture_system']['joints']['tool']['joint2'] = dict( + idx=9) + files = support.files_for(spec, data) + files['end_effector/pinch_gripper/urdf/component.xacro'] = ''' + + + + + ''' + robot = support.expand(files, robot_preset='fixture', + ros2_control=True, use_fake_hardware=True) + assert robot.find("joint[@name='tool_finger_joint2']/mimic") is None + expected.add('tool_finger_joint2') + active = {joint.get('name') for joint in robot.findall('joint') + if joint.get('type') != 'fixed' and joint.find('mimic') is None} + assert active == expected + assert {joint.get('name') + for joint in robot.findall('ros2_control/joint')} == active + + spec, data = control_input() + joints = list(data['arm']['struct/topology.yaml']['joints']) + spec['systems'] = { + 'shoulder': dict(joints=dict(arm={name: dict(idx=2*i-1) for i, name in enumerate(joints[:4], 1)})), + 'wrist': dict(joints=dict(arm={name: dict(idx=2*i-1) for i, name in enumerate(joints[4:], 1)}, + tool=dict(joint1=dict(idx=7))))} + robot = support.assemble( + spec, data, ros2_control=True, use_fake_hardware=True) + for name, cfg in spec['systems'].items(): + expected = {spec[component]['prefix'] + data[component]['struct/topology.yaml']['joints'][joint].get('name', joint) for component, + names in cfg['joints'].items() for joint in names} + assert {joint.get('name') for joint in robot.find( + f"ros2_control[@name='{name}']").findall('joint')} == expected + spec['systems']['shoulder'].update( + hardware_plugin='openarm_hardware/OpenArmHW', can_interface='can_test') + with pytest.raises(xacro.XacroException, match='seven arm joints'): + support.assemble(spec, data, ros2_control=True, + use_fake_hardware=False) + spec['systems']['wrist']['joints']['arm'][joints[0]] = dict(idx=9) + with pytest.raises(xacro.XacroException, match='duplicate joint assignment'): + support.assemble(spec, data, ros2_control=True, use_fake_hardware=True) + spec['systems']['wrist']['joints']['arm']['missing'] = spec['systems']['wrist']['joints']['arm'].pop( + joints[0]) + with pytest.raises(xacro.XacroException, match='unknown joint'): + support.assemble(spec, data, ros2_control=True, use_fake_hardware=True) + del spec['systems']['wrist']['joints']['arm']['missing'] + for invalid_idx in (-1, True, 1.5): + spec['systems']['wrist']['joints']['tool']['joint1']['idx'] = invalid_idx + with pytest.raises(xacro.XacroException, match='non-negative integer idx'): + support.assemble(spec, data, ros2_control=True, + use_fake_hardware=True) + spec['systems']['wrist']['joints']['tool']['joint1']['idx'] = 1 + with pytest.raises(xacro.XacroException, match='duplicate idx'): + support.assemble(spec, data, ros2_control=True, use_fake_hardware=True) + + +def test_hardware_parameters_follow_input(): + spec, data = control_input() + control = spec['systems']['fixture_system'] + for prefix, can_interface in [('openarm_left_', 'can_left_test'), + ('openarm_right_', 'can_right_test')]: + control.update(can_interface=can_interface, can_fd=False) + for name in ('arm', 'tool'): + spec[name]['prefix'] = prefix + robot = support.assemble(spec, data, ros2_control=True, + use_fake_hardware=False) + hardware = robot.find('ros2_control/hardware') + assert hardware.find('plugin').text == control['hardware_plugin'] + params = {p.get('name'): p.text for p in hardware.findall('param')} + assert len(params) == len(hardware.findall('param')) + assert params['can_interface'] == control['can_interface'] + assert params['can_fd'].lower() == 'false' + assert params['arm_prefix'] == prefix.removeprefix('openarm_') + assert params['ee_type'] == spec['tool']['product'] + for name, gains in data['arm']['control/control_gains.yaml'].items(): + for key, value in gains.items(): + assert float(params[key+name.removeprefix('joint')]) == value + + for side in ('left', 'right'): + spec['systems'] = {f'openarm_{side}_hardware_interface': control} + control.update(can_fd=True) + overrides = dict(left_can_interface='runtime_left', + right_can_interface='runtime_right', can_fd=False) + robot = support.assemble(spec, data, ros2_control=True, + use_fake_hardware=False, **overrides) + hardware = robot.find('ros2_control/hardware') + params = {p.get('name'): p.text for p in hardware.findall('param')} + assert params['can_interface'] == overrides[side + '_can_interface'] + assert params['can_fd'].lower() == 'false' + + robot = support.assemble(spec, data, ros2_control=True, + use_fake_hardware=True, fake_sensor_commands=True) + hardware = robot.find('ros2_control/hardware') + assert hardware.find('plugin').text == 'mock_components/GenericSystem' + params = {p.get('name'): p.text for p in hardware.findall('param')} + assert params['mock_sensor_commands'].lower() == 'true' + assert float(params['position_state_following_offset']) == 0.0 + assert 'fake_sensor_commands' not in params and 'state_following_offset' not in params + + control.update(command_interfaces=[ + 'position'], state_interfaces=['position']) + robot = support.assemble( + spec, data, ros2_control=True, use_fake_hardware=True) + for joint in robot.findall('ros2_control/joint'): + assert [i.get('name') for i in joint.findall( + 'command_interface')] == ['position'] + assert [i.get('name') + for i in joint.findall('state_interface')] == ['position'] diff --git a/test/test_models.py b/test/test_models.py new file mode 100644 index 00000000..57716a88 --- /dev/null +++ b/test/test_models.py @@ -0,0 +1,43 @@ +# Copyright 2026 Enactic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from urllib.parse import urlparse + +from support import ROOT, expand, packages + + +def test_shipped_presets_expand_and_resolve_resources(): + robots = [] + for version in ('v2.0',): + folder = ROOT/f'assets/robot/openarm_{version}/config/robot_presets' + robots.append((version, '', expand(version=version, ros2_control=True, + use_fake_hardware=True, emit_grasp_frame=True))) + for path in sorted(folder.glob('*.yaml')): + robot = expand(version=version, robot_preset=path.stem, ros2_control=True, + use_fake_hardware=True, emit_grasp_frame=True) + robots.append((version, path.stem, robot)) + for path in sorted((ROOT/'assets/end_effector').glob('pinch_gripper/urdf/*_standalone.urdf.xacro')): + robots.append(('standalone', path.name, expand( + entry=str(path.relative_to(ROOT))))) + for version, name, robot in robots: + for mesh in robot.findall('.//geometry/mesh'): + uri = urlparse(mesh.get('filename')) + if uri.scheme == 'package': + share = ROOT if uri.netloc == 'openarm_description' else Path( + packages.get_package_share_directory(uri.netloc)) + assert (share/uri.path.lstrip('/') + ).is_file(), (version, name, mesh.attrib) + elif uri.scheme in ('', 'file'): + assert Path(uri.path).is_file(), (version, name, mesh.attrib)