skrobot.assembly.RobotAssembly

class skrobot.assembly.RobotAssembly(name: str)[source]

Manages the assembly state of multiple robot modules.

This class represents the undirected graph of module connections that the user has assembled through the UI. It does NOT represent the final kinematic tree - that is generated by the build() method.

The assembly graph is stored as: - A dictionary of module instances (nodes) - A list of connections (edges)

name

Name of the assembled robot.

Type:

str

instances

Dictionary mapping instance_id to ModuleInstance.

Type:

Dict[str, ModuleInstance]

connections

List of all connections between module ports.

Type:

List[Connection]

root_instance

The instance ID that will become the root of the final URDF tree.

Type:

Optional[str]

root_port

The port on root_instance that will become the root link.

Type:

Optional[str]

loop_closures

After a build: the closure config the loop connections produced (the loop_closures.yaml content), ready to hand to skrobot.kinematics.LoopClosureSolver; None when the assembly declares no loop.

Type:

Optional[dict]

Examples

>>> # Create module definitions
>>> hinge = RobotModule.from_urdf("hinge", "screw_hinge_module.urdf")
>>> branch = RobotModule.from_urdf("branch", "screw_branch_module.urdf")
>>>
>>> # Create assembly
>>> assembly = RobotAssembly("my_robot")
>>> assembly.add_module_instance("base", branch)
>>> assembly.add_module_instance("arm1", hinge)
>>> assembly.add_module_instance("arm2", hinge)
>>>
>>> # Connect modules
>>> assembly.connect("base", "dummy_link1", "arm1", "base_link")
>>> assembly.connect("base", "dummy_link2", "arm2", "base_link")
>>>
>>> # Set root and build
>>> assembly.set_root("base", "base_link")
>>> combined_urdf = assembly.build()

Methods

add_module_instance(instance_id: str, module: RobotModule) None[source]

Add a module instance to the assembly.

Parameters:
  • instance_id (str) – Unique identifier for this instance.

  • module (RobotModule) – The module definition to instantiate.

Raises:

ValueError – If instance_id already exists.

build(output_path: str | None = None) str[source]

Build a combined URDF from the assembly.

Traverses the connection graph as a forest rooted at root_instance, inserts a world link as the absolute root and attaches every module through a fixed joint from its parent’s port to its own root link, with all element names prefixed by the instance id. Modules keep their original kinematic structure (no chain reversal).

Connections declared with loop=True are cut edges: they never become joints. When any exist, a loop_closures.yaml sidecar (the runtime-IK relay config) is written next to the output URDF, and a parallelogram four-bar loop additionally gets exact <mimic> tags on its dependent joints.

Parameters:

output_path (str, optional) – Path to save the combined URDF. If None, uses a temporary file.

Returns:

Path to the generated combined URDF file.

Return type:

str

Raises:

ValueError – If the assembly is empty or contains a cycle.

build_robot_model()[source]

Assemble directly into a skrobot.model.RobotModel.

Composes the combined URDF in memory and loads it without writing the result to disk (re-rooted modules still go through temporary files). Mesh resolution follows the same rules as loading the built URDF file would.

Returns:

The assembled robot.

Return type:

skrobot.model.RobotModel

connect(module_a: str, port_a: str, module_b: str, port_b: str, x: float = 0.0, y: float = 0.0, z: float = 0.0, roll: float = 0.0, pitch: float = 0.0, yaw: float = 0.0, mate: bool = False, attach: str = 'root', loop: bool = False, dependent: Tuple[str, ...] | None = None, joint_type: str = 'fixed', axis: Tuple[float, float, float] = (0.0, 0.0, 1.0), lower: float | None = None, upper: float | None = None, effort: float = 100.0, velocity: float = 1.0) None[source]

Create a connection between two module ports.

Parameters:
  • module_a (str) – Instance ID of the first module.

  • port_a (str) – Port name on module_a.

  • module_b (str) – Instance ID of the second module.

  • port_b (str) – Port name on module_b.

  • x (float, optional) – X position offset from port_a to port_b in meters (default: 0.0).

  • y (float, optional) – Y position offset from port_a to port_b in meters (default: 0.0).

  • z (float, optional) – Z position offset from port_a to port_b in meters (default: 0.0).

  • roll (float, optional) – Roll (rotation around X axis) offset in radians (default: 0.0).

  • pitch (float, optional) – Pitch (rotation around Y axis) offset in radians (default: 0.0).

  • yaw (float, optional) – Yaw (rotation around Z axis) offset in radians (default: 0.0).

Raises:

ValueError – If either module instance doesn’t exist, or if ports are invalid.

Examples

>>> assembly.connect('h1', 'dummy_link', 'p1', 'base_link')
>>> assembly.connect('h1', 'dummy_link', 'p2', 'base_link', yaw=1.57)
mate : bool
    Derive the transform so the child port seats onto the parent
    port (Z axes opposed); only ``yaw`` may be combined with it.
attach : str
    ``'root'`` (default) attaches the child-side module through its
    own root link, treating its declared port as placement metadata.
    ``'port'`` re-roots the child-side module at its declared port
    during the build so that port becomes the real attachment link.
    Applies to whichever endpoint becomes the child once the tree
    is rooted.
loop : bool
    ``True`` declares this connection as a loop closure -- the cut
    edge of a closed linkage (e.g. a four-bar).  It never becomes a
    joint in the built URDF; the two ports must already coincide at
    the zero pose of the rest of the assembly, and their common Z
    axis is the passive hinge axis.  ``build()`` exports the
    closure to a ``loop_closures.yaml`` sidecar for runtime IK and,
    for a parallelogram four-bar, writes exact ``<mimic>`` tags.
    A loop connection carries no transform, so the offset, ``mate``
    and ``attach`` arguments cannot be combined with it.
dependent : Tuple[str, ...], optional
    Only with ``loop=True``: the final (prefixed, e.g.
    ``'arm1_elbow'``) joint names this closure solves; the other
    movable joints on the loop ring stay driven.  Validated at
    build time against the actual ring.  Default: the ring joint
    nearest the root drives, the rest are solved -- right for a
    1-DOF loop, but a multi-DOF loop (five-bar and up) needs the
    solved joints named explicitly.
joint_type : str
    ``'fixed'`` (default) welds the connection; ``'revolute'``,
    ``'continuous'`` or ``'prismatic'`` turn the mated port pair
    itself into an articulation.  A movable connection must be
    declared parent-side first (``module_a`` ends up the parent
    once the tree is rooted): reversing a movable joint would
    relocate its frame, which the build refuses rather than
    approximates.
axis : Tuple[float, float, float]
    Joint axis of a movable connection, expressed in the child
    attachment link's frame (in URDF the joint frame IS the
    child frame): the module root frame under ``attach='root'``,
    the declared port frame under ``attach='port'`` -- combine
    with ``attach='port'``/``mate`` to hinge about the port Z
    of the keyed-mate convention.  Default Z.
lower, upper : float, optional
    Joint limits in radians / metres; required for
    ``'revolute'`` and ``'prismatic'``.
effort, velocity : float
    Limit attributes required by URDF alongside lower/upper.
disconnect(module_a: str, port_a: str, module_b: str, port_b: str) None[source]

Remove the connection between two ports.

The connection is matched in either declaration order (the graph is undirected).

Raises:

ValueError – If no such connection exists.

classmethod from_dict(data: dict) RobotAssembly[source]

Rebuild an assembly from to_dict() output.

Modules are re-parsed from the recorded urdf_path``s, so those files must still exist -- unless the dict was made with ``to_dict(embed=True), in which case the embedded URDF content is materialized into a temporary directory (the builds read the module files from disk) and the original paths are not needed.

get_adjacency_list() Dict[str, List[Tuple[str, str, str, Tuple, Tuple]]][source]

Get the assembly as an adjacency list.

Returns:

Maps instance_id to list of (connected_instance_id, this_port, other_port, xyz, rpy). When traversed from module_b to module_a, the connection transform is inverted (rigid inverse, not a component-wise negation – the latter is only correct for single-axis rotations). Loop closures (loop=True) are cut edges, not tree edges, so they do not appear here.

Return type:

Dict[str, List[Tuple[str, str, str, Tuple, Tuple]]]

remove_module_instance(instance_id: str) None[source]

Remove an instance and every connection that involves it.

When the removed instance was the root, the root falls back to the first remaining instance (through its module’s root link), or clears entirely on an empty assembly.

set_root(instance_id: str, port_name: str) None[source]

Set the root link for the assembled robot.

The specified port will become the root of the final kinematic tree.

Parameters:
  • instance_id (str) – Instance ID of the module containing the root.

  • port_name (str) – Port (link) name to use as root.

to_dict(embed: bool = False) dict[source]

Serialize the assembly to a dictionary.

Useful for saving/loading assembly state or sending to frontend.

Parameters:

embed (bool) – Also embed each module’s URDF XML (urdf_content), so from_dict() can rebuild the assembly without the original files – e.g. across machines or through a storage backend.

__eq__(value, /)

Return self==value.

__ne__(value, /)

Return self!=value.

__lt__(value, /)

Return self<value.

__le__(value, /)

Return self<=value.

__gt__(value, /)

Return self>value.

__ge__(value, /)

Return self>=value.