amino  1.0-beta2
Lightweight Robot Utility Library
Scene Graphs

\[ \newcommand{\normtwo}[1]{\left| #1 \right|} \newcommand{\MAT}[1]{\boldsymbol{#1}} \newcommand{\unitvec}[1]{\boldsymbol{\hat{#1}}} \newcommand{\ielt}[0]{\unitvec{\imath}} \newcommand{\jelt}[0]{\unitvec{\jmath}} \newcommand{\kelt}[0]{\unitvec{k}} \newcommand{\quat}[1]{\mathcal{#1}} \newcommand{\sequat}[0]{\quat{h}} \newcommand{\qmul}[0]{\otimes} \newcommand{\dotprod}{\boldsymbol{\cdot}} \newcommand{\tf}[3]{{^{#2}{#1}_{#3}}} \newcommand{\tfmat}[3]{{^{#2}\MAT{#1}_{#3}}} \newcommand{\tfquat}[3]{{^{#2}\quat{#1}_{#3}}} \newcommand{\qmul}[0]{\otimes} \newcommand{\dualelt}[0]{\boldsymbol{\varepsilon}} \newcommand{\setranssym}[0]{v} \newcommand{\setrans}[0]{\vec{\setranssym}} \newcommand{\setransvel}[0]{\dot{\setrans}} \newcommand{\confsym}[0]{\theta} \newcommand{\conf}[0]{\confsym} \newcommand{\confvec}[0]{\boldsymbol{\confsym}} \]

This tutorial introduces the scene graph data structure and Scene Window viewer.

Overview

The scene graph is a tree representing the local coordinate frames and objects in the scene. Each node in the scene graph represents a local coordinate frames. Edges in the scene graph indicate the relative transforms between local coordinate frames. Objects are represented in the scene graph as geometry, i.e., meshes or primitive shapes like boxes and spheres, attached to local coordinate frames. Both the environment and robot are represented in this same data structure. The frames for the environment will typically be fixed, while frames for robot joints will vary based on the robot's configuration.

Initial Example Scene

First, we will construct a simple scene containing a few boxes and a grid. The scene looks like the following:

Simple Scene in Scene Viewer

Structurally, we will arrange the local coordinate frames as shown in the following:

Frame Structure of the Simple Scene

There is a single root frame, labeled the code as empty string "". Child of the root is a frame for the grid. Red box A and blue box C are placed on the grid, so their frames are children of the grid. Green box B is placed on box A, so B is a child of A.

The following code constructs the scene and displays in the viewer:

#!/usr/bin/env python3
# File: t3-scene.py
# =================
#
# Create a scene with a grid and three boxes. Display the scene in
# the viewer window.
from amino import SceneWin, SceneGraph, Geom, QuatTrans
# Create a window
win = SceneWin(start=False)
# Create an (empty) scene graph
sg = SceneGraph()
# Draw a grid
sg.add_frame_fixed(
"",
"grid",
geom=Geom.grid({
"color": (0, 0, 1)
}, [1.0, 1.0], [.1, .1], .005))
# Draw some boxes
h = .1 # box height
dim = [h, h, h] # box dimension
sg.add_frame_fixed(
"grid",
"box-a",
tf=QuatTrans((1, (0, 0, h / 2))),
geom=Geom.box({
"color": (1, 0, 0)
}, dim))
sg.add_frame_fixed(
"box-a",
"box-b",
tf=QuatTrans((1, (0, 0, h))),
geom=Geom.box({
"color": (0, 1, 0)
}, dim))
sg.add_frame_fixed(
"grid",
"box-c",
tf=QuatTrans((1, (3 * h, 0, h / 2))),
geom=Geom.box({
"color": (0, 0, 1)
}, dim))
# Initalize the scene graph
sg.init()
# Pass the scenegraph to the window
win.scenegraph = sg
# Start the window in the current (main) thread
win.start(False)

Click and drag the mouse and to rotate the view, and zoom with the scroll wheel. Close the window the exit.

Robot Scene

Next, we will procedurally construct a serial robot. We model robots as chains or trees of local coordinate frames:

Robot Local Frames

Each joint corresponds to a frame whose transform is parameterized by a configuration variable (i.e., the joint angle). In other words, the joint angle tells us the relative transform between a joint's local coordinate frame and the joint's parent frame. For each rigid body or link in the robot, we represent the points of that link in the link's local coordinate frame.

Robot Joints

The most common types of joints are revolute (rotating) and prismatic (linear).

Prismatic Joints

Prismatic joints create a linear motion. We compute the relative translation along the axis of motion:

\[ \setrans = \ell \unitvec{u} \; . \]

Revolute Joints

Revolute joints create a rotating motion. We compute the relative rotation about the axis of motion from the quaternion exponential

\[ \sequat = \exp\left(\frac{\conf}{2} \unitvec{u}\right) \; . \]

Summary

The following table summarizes the relative rotation and translation for these joints. The joint frame is labeled c and its parent p. For the revolute frame, we compute the rotation quaternion via the exponential; the translation \( \tf{\vec{v}}{p}{c} \) is constant. For the prismatic frame, rotation \(\tf{\quat{h}}{p}{c}\) is constant and we compute translation along the joint axis \(\vec{u}\).

Type Rotation Translation Diagram
Revolute \(\exp \left( \frac{\theta}{2}\vec{u} \right)\) \( \tf{\vec{v}}{p}{c} \) Revolute Frame Diagram
Prismatic \(\tf{\quat{h}}{p}{c}\) \( \ell \vec{u} \) Prismatic Frame Diagram

Example Robot Construction

We will construct the following robot:

Procedurally constructed 4-DoF Robot

This robot has four joints: three at the shoulder and one at the elbow. For each joint, we create a "revolute" (rotating) frame and specify the joint's axis of rotation. To represent the robot's links, we attach geometry, i.e., a sphere, cylinders, and a cone, to various frames.

The following code constructs the robot, displays it in the viewer, and waves the elbow:

#!/usr/bin/env python3
# File: t3.1-robot.py
# ===================
#
# Procedural construction of a robot scene.
from math import pi, cos
from time import sleep
from amino import SceneWin, SceneGraph, Geom, GeomOpt, Vec3, YAngle, XAngle
def draw_robot(sg, parent):
"""Draw a robot in the scenegraph sg under parent"""
radius = .05 # link radius
len0 = 1 # upper arm length
len1 = 1 # forearm length
r = 1.618
joint_opts = GeomOpt({"color": (0, 0, 0), "specular": (.5, .5, .5)})
link_opts = GeomOpt({"color": (.5, .5, .5), "specular": (2, 2, 2)})
# Shoulder
sg.add_frame_revolute(
parent, "s0", axis=(1, 0, 0), geom=Geom.sphere(joint_opts, r * radius))
sg.add_frame_revolute("s0", "s1", axis=(0, 1, 0))
sg.add_frame_revolute("s1", "s2", axis=(1, 0, 0))
sg.add_frame_fixed(
"s2",
"upper-link",
tf=(YAngle(pi / 2), Vec3.identity()),
geom=Geom.cylinder(link_opts, len0, radius))
# Elbow
sg.add_frame_revolute("s2", "e", axis=(0, 1, 0), tf=(1, (len0, 0, 0)))
sg.add_frame_fixed(
"e",
"e-link",
tf=(XAngle(.5 * pi), (0, radius, 0)),
geom=Geom.cylinder(joint_opts, 2 * radius, r * radius))
sg.add_frame_fixed(
"e",
"lower-link",
tf=(YAngle(pi / 2), Vec3.identity()),
geom=Geom.cylinder(link_opts, len1, radius))
# "Hand"
sg.add_frame_fixed(
"e",
"hand",
tf=(YAngle(.5 * pi), (len1, 0, 0)),
geom=Geom.cone(joint_opts, 2 * (r**2) * radius, r * radius, 0))
sg.init()
# Create an (empty) scene graph
sg = SceneGraph()
# Draw a grid
sg.add_frame_fixed(
"",
"grid",
geom=Geom.grid({
"color": (0, 0, 1)
}, [1.0, 1.0], [.1, .1], .005))
# Draw the robot
draw_robot(sg, "grid")
# Initialize the scene graph
sg.init
# Create a window and pass the scenegraph
win = SceneWin(start=False)
win.scenegraph = sg
# Start the window in a background thread
win.start(True)
# Do a simple wave
dt = 1.0 / 60
t = 0
while win.is_runnining():
t += dt
e_angle = (120 + 15 * cos(t)) * (pi / 180)
win.config = {"s1": -.75 * pi, "e": e_angle}
sleep(dt)

Scene Compiler

Finally, we will use the scene compiler to generate a plugin (shared library) for a scene. The scene compilers supports definitions in a block-oriented (curly-braced) syntax and in URDF.

Scene Files

We will redefine our serial robot in a scene file, compile the scene, and load it in the viewer.

  1. Use the following scene file:

    /* example.roboray
    * ===============
    *
    * Serial robot example in a scene file.
    */
    /* Parameters for the robot */
    def rad 0.05; // Link radius
    def len0 1; // upper arm length
    def len1 1; // forearm length
    def r 1.618; // a ratio
    /* Define classes for robot parts */
    class link {
    shape cylinder;
    radius rad;
    color [.5, .5, .5];
    specular [2, 2, 2];
    }
    class joint_style {
    color [0, 0, 0];
    specular [.5, .5, .5];
    }
    /* Draw the grid */
    frame grid {
    geometry {
    shape grid;
    delta [.1, .1];
    thickness .005;
    dimension [1, 1];
    }
    }
    /* Draw the robot */
    // Shoulder
    frame s0 {
    type revolute; # frame type: revolute joint
    axis [1, 0, 0]; # joint axis
    geometry {
    isa joint_style; # instance of joint_style class
    shape sphere;
    radius r*rad;
    }
    }
    frame s1 {
    parent s0;
    type revolute;
    axis [0, 1, 0];
    }
    frame s2 {
    parent s1;
    type revolute;
    axis [1, 0, 0];
    // nested frame is a child of s2
    frame upper-link {
    rpy [0, pi/2, 0];
    geometry {
    isa link;
    height len0;
    }
    }
    }
    // Elbow
    frame e {
    parent s2;
    type revolute;
    axis [0,1,0];
    translation [len0, 0, 0];
    frame e-link {
    rpy [pi/2, 0, 0];
    translation [0, rad, 0];
    geometry {
    isa joint_style;
    shape cylinder;
    height 2*rad;
    radius r*rad;
    }
    }
    frame lower-link {
    rpy [0, pi/2, 0];
    geometry {
    isa link;
    height len1;
    }
    }
    // "Hand"
    frame hand {
    rpy [0, pi/2, 0];
    translation [len1, 0, 0];
    geometry {
    shape cone;
    height 2*r*r*rad;
    start_radius r*rad;
    end_radius 0;
    }
    }
    }

  2. Use the scene compiler to build the plugin. While you may call the scene compiler directly from the shell, it will be preferable to use a build script. The following CMake file will build a plugin for the serial robot:

    cmake_minimum_required (VERSION 2.6)
    project (AminoTutorials)
    # pkg-config setup
    find_package(PkgConfig)
    # Search for the pkg-config packages
    pkg_search_module(SDL2 REQUIRED sdl2)
    pkg_search_module(GL REQUIRED gl)
    pkg_search_module(AMINO REQUIRED amino)
    pkg_search_module(AMINO_GL REQUIRED amino-gl)
    # Compile scene graph to C
    add_custom_command(
    OUTPUT example-scene.c example-scene.c.h
    DEPENDS example.robray
    COMMAND aarxc "example.robray" -o example-scene.c -n "example"
    )
    # Compile plugin (shared library)
    add_library( scene MODULE example-scene.c )
    target_include_directories( scene PUBLIC
    ${AMINO_INCLUDE_DIRS}
    )
    target_link_libraries( scene
    ${AMINO_GL_LIBRARIES}
    ${AMINO_LIBRARIES}
    )
    amino namespace
    Definition: amino.hpp:60

    From the directory containing the CMakLists.txt, build the plugin by running:

    cmake .
    make

  3. Use the following code to load the scene file and display in the viewer:

    #!/usr/bin/env python3
    # File: t3.2-scenefile.py
    # =======================
    #
    # Load a compiled scene plugin
    from math import pi, cos
    from time import sleep
    import os
    from amino import SceneWin, SceneGraph
    # Scene Parameters
    scene_plugin = ("{}/../plugin/scenefile/libscene.so".format(os.path.dirname(__file__)))
    scene_name = "example"
    # Create an (empty) scene graph
    sg = SceneGraph()
    # Load the scene plugin
    sg.load(scene_plugin, scene_name)
    # Initialize the scene graph
    sg.init()
    # Create a window, pass the scenegraph, and start
    win = SceneWin(scenegraph=sg, start=True, background=True)
    # Do a simple wave
    dt = 1.0 / 60
    t = 0
    while win.is_runnining():
    t += dt
    e_angle = (120 + 15 * cos(t)) * (pi / 180)
    win.config = {"s1": -.75 * pi, "e": e_angle}
    sleep(dt)

  4. The resulting display should look identical to the procedurally-defined robot above.

URDF

We will compile the URDF file for the Baxter robot, load the plugin, and display the scene in the viewer.

Baxter in Viewer

  1. Obtain the baxter URDF, which describes the kinematics and geometry (meshes) of the robot.

    • If you already have an existing ROS installation, you can install the baxter_description ROS package. For example on ROS Indigo:

      sudo apt-get install ros-indigo-baxter-description
      export ROS_PACKAGE_PATH=/opt/ros/indigo/share

    • An existing ROS installation is not necessary, however, and you can install only the baxter URDF and meshes:

      mkdir -p ~/ros_ws/src
      git clone https://github.com/RethinkRobotics/baxter_common
      export ROS_PACKAGE_PATH=~/ros_ws/src/

  2. Now, check that you can load the Baxter model. The following command will visualize the Baxter in the viewer. Click and drag the mouse and to rotate the view, and zoom with the scroll wheel. Close the window the exit.

    aarxc --gui package://baxter_description/urdf/baxter.urdf

  3. Use the scene compiler to build the plugin. The following CMake file will build a plugin for the baxter:

    cmake_minimum_required (VERSION 2.6)
    project (AminoTutorials)
    # pkg-config setup
    find_package(PkgConfig)
    # Search for the pkg-config packages
    pkg_search_module(SDL2 REQUIRED sdl2)
    pkg_search_module(GL REQUIRED gl)
    pkg_search_module(AMINO REQUIRED amino)
    pkg_search_module(AMINO_GL REQUIRED amino-gl)
    # Scene graph compilation
    add_custom_command(
    OUTPUT baxter-model.c baxter-model.c.h
    COMMAND aarxc "package://baxter_description/urdf/baxter.urdf" -o baxter-model.c -n "baxter"
    )
    add_library( baxter-model MODULE baxter-model.c )
    target_include_directories( baxter-model PUBLIC
    ${AMINO_INCLUDE_DIRS}
    )
    target_link_libraries( baxter-model
    ${AMINO_GL_LIBRARIES}
    ${AMINO_LIBRARIES}
    )

    From the directory containing the CMakLists.txt, build the plugin by running:

    cmake .
    make

    The command will generate a C file baxter-model.c from the URDF. Then, it will compile the baxter model into the plugin libbaxter-model.so which describes the kinematic layout and all meshes of the robot.

  4. Load the plugin using the following code:

    #!/usr/bin/env python3
    # File: t3.2-urdf.py
    # ===================
    #
    # Loading a compiled scene plugin
    import os
    from amino import SceneWin, SceneGraph, Geom
    # Scene Parameters
    scene_plugin = ("{}/../plugin/urdf/libbaxter-model.so".format(os.path.dirname(__file__)))
    scene_name = "baxter"
    # Create an (empty) scene graph
    sg = SceneGraph()
    # Draw a grid
    sg.add_frame_fixed(
    "",
    "grid",
    tf=(1, (0, 0, -.9)),
    geom=Geom.grid({
    "color": (0, 0, 1)
    }, [1.0, 1.0], [.1, .1], .005))
    # Load the scene plugin
    sg.load(scene_plugin, scene_name)
    # Initialize the scene graph
    sg.init()
    # Create a window and pass the scenegraph
    win = SceneWin(start=False)
    win.scenegraph = sg
    # Start the window in a background thread
    win.start(False)

See Also

Python

C

Other Documentation