Setting Up a Scene

This guide builds a scene with objects and robot systems. It follows examples/python/scene_example.py (and its C++ twin examples/cpp/src/scene_example.cpp).

Prerequisite: an active workcell (Workcells).

Objects

An Object (flexiv.pdk.scene.object_pb2) is a rigid body in the scene.

Field

Type

Description

header

ConfigHeader

Required. Name, parent and pose.

visual

repeated Visual

What the object looks like. Use CAD (.stp, .step) for precise tool-path generation, or a mesh (.dae, .obj, .stl) for scanned parts.

collision

repeated Collision

Geometry used for collision checking. Empty means the object never collides.

inertial

physics.Inertial

Mass, centre of mass and inertia (optional).

reference_only

bool

Keep as a template for patterns or feeders; do not add to the scene.

Visual and Collision each have an origin (SE3Pose relative to the object) and a Shape, which is one of box, sphere, cylinder, capsule, mesh, convex_mesh or octomap.

The simplest object has only a header (it becomes a bare frame):

obj1 = Object(header=make_header("box_1", px=0.5, py=0.0, pz=0.0))
try:
    client.config.stub.Save(SaveConfigRequest(object=obj1))
except grpc.RpcError:
    pass

Primitive shapes

def make_box(name, parent="world", px=0.0, py=0.0, pz=0.0, size_x=0.1, size_y=0.1, size_z=0.1):
    obj = Object(header=make_header(name, parent=parent, px=px, py=py, pz=pz))

    visual = obj.visual.add()
    visual.origin.position.x = 0.0
    visual.origin.position.y = 0.0
    visual.origin.position.z = 0.0
    visual.origin.rotation.x = 0.0
    visual.origin.rotation.y = 0.0
    visual.origin.rotation.z = 0.0
    visual.origin.rotation.w = 1.0
    visual.shape.box.size.extend([size_x, size_y, size_z])

    collision = obj.collision.add()
    collision.origin.position.x = 0.0
    collision.origin.position.y = 0.0
    collision.origin.position.z = 0.0
    collision.origin.rotation.x = 0.0
    collision.origin.rotation.y = 0.0
    collision.origin.rotation.z = 0.0
    collision.origin.rotation.w = 1.0

    collision.shape.box.size.extend([size_x, size_y, size_z])
    return obj

Mesh and CAD files

Mesh.filename is a path on the server machine. The server must be able to read it; with a containerized server, put files under the server’s user_data folder and use the path as the server sees it. Mesh.scale is required: use (1, 1, 1) for no scaling.

def make_mesh_visual(origin, mesh_path, scale=1.0):
    return Visual(
        origin=SE3Pose(
            position=Vector3(x=origin[0], y=origin[1], z=origin[2]),
            rotation=Quaternion(x=0.0, y=0.0, z=0.0, w=1.0)
        ),
        shape=Shape(
            mesh=Mesh(
                filename=mesh_path,
                scale=Vector3(x=scale, y=scale, z=scale)
            )
        )
    )

The examples use a 1 m × 1 m plane mesh shipped in examples/assets/plane_1m_10x10.obj.

Robot systems

A System (flexiv.pdk.system.system_pb2) describes one robot and everything mounted on it. The minimum is a header, the control box serial number and a manipulator:

sys1 = System(
    header=make_header("system_1", px=-0.7, py=0.0, pz=0.0),
    control_box_serial_number="CX01-01-T1-000001",
    manipulator=Manipulator(
        name="arm",
        arm_serial_number="A02L-00-M3-000001"
    )
)

The second system in the example is rotated 180° about Z with the quaternion (x, y, z, w) = (0, 0, 1, 0) so that the two robots face each other.

See Robot Systems and Components for mounts, tools, external axes, and adding a system by connecting to a real robot.

Cameras

A Camera places a camera frame and its view frustum in the scene:

Field

Description

header

Pose of the camera. Parent to world for a fixed camera or to a robot link (such as system_1.arm.flange) for a wrist camera.

far_width, far_height

Size of the far plane of the frustum (m).

far_distance, near_distance

Far and near clipping distances (m).

depth_scale

depth [m] = depth [raw] / depth_scale (e.g. 1000 for millimetres).

camera_id

Device ID of the physical camera.

print("Saving camera to the scene...")
camera_name = "camera_1"
camera = Camera(
    header=make_header(camera_name, parent="system_1.arm.flange", px=-0.1, py=0.0, pz=0.0, rw=0.707107, rx=0.0, ry=0.0, rz=-0.707107),
    far_width=1.0,
    far_height=1.0,
    far_distance=2.0,
    near_distance=0.1,
    depth_scale=1000.0,
    camera_id=9
)

Capturing images is covered in Cameras and 3D Reconstruction.

Coordinates

A Coordinate is a named frame, like a URDF link without geometry, useful for fixtures and reference points. scale is the display size of the axes. The same message type is used for saved object poses (object_pose) and grasp poses (grasp_pose).

from flexiv.pdk.config_service_pb2 import SaveConfigRequest
from flexiv.pdk.scene.coordinate_pb2 import Coordinate

# make_header() is the helper shown in "The Scene Model"
fixture = Coordinate(header=make_header("fixture", px=0.4, pz=0.1))
client.config.stub.Save(SaveConfigRequest(coordinate=fixture))

Inspecting and editing the scene

from google.protobuf.empty_pb2 import Empty
from flexiv.pdk.config_service_pb2 import (
    ConfigType, ListConfigRequest, SetPoseRequest, TypeNameConfigRequest)
from flexiv.pdk.spatial.geometry_pb2 import Quaternion, SE3Pose, Vector3

# List all objects
print(client.config.stub.List(ListConfigRequest(type=ConfigType.CONFIG_TYPE_OBJECT)).names)

# Move an object (persisted)
client.config.stub.SetPose(SetPoseRequest(
    type=ConfigType.CONFIG_TYPE_OBJECT, name="box_1",
    pose=SE3Pose(position=Vector3(x=0.6), rotation=Quaternion(w=1.0))))

# Remove it
client.config.stub.Remove(TypeNameConfigRequest(type=ConfigType.CONFIG_TYPE_OBJECT, name="box_1"))

# Export the whole scene as URDF
print(client.config.stub.GetUrdf(Empty()).urdf_content)

The pdk-scene CLI does the same from a shell; see The pdk-scene Command-Line Tool.

Complete example

Note

The examples catch grpc.RpcError and print it, and they do not check every status.code. Production code should check both; see Error Handling.

Python:

"""
Example: Create a simple scene using the Flexiv PDK.

This script demonstrates how to:
  1. Create a new workcell
  2. Add multiple objects to the scene
  3. Add two robot systems to the scene, separated by 1.4 meters and facing each other
"""
import os
import grpc

from flexiv.pdk import Client
from flexiv.pdk.workcell.workcell_service_pb2 import CreateRequest, NameRequest
from flexiv.pdk.config_service_pb2 import SaveConfigRequest
from flexiv.pdk.scene.object_pb2 import Object
from flexiv.pdk.system.system_pb2 import System
from flexiv.pdk.system.components.manipulator_pb2 import Manipulator
from flexiv.pdk.config_header_pb2 import ConfigHeader
from flexiv.pdk.spatial.geometry_pb2 import SE3Pose, Vector3, Quaternion


def main():
    target = os.environ.get("PDK_SERVER_ADDR", "localhost:50052")
    print(f"Connecting to the PDK server at {target}...")

    # We use insecure channel, wrapped in our Client
    with Client(target) as client:
        # 1. Create a workcell
        workcell_name = "example_workcell"
        print(f"Removing any existing workcell with name: {workcell_name}")
        try:
            client.workcell.stub.Remove(NameRequest(name=workcell_name))
        except grpc.RpcError as e:
            print(f"Remove workcell failed or not present: {e}")

        print(f"Creating workcell: {workcell_name}")
        req = CreateRequest(name=workcell_name, description="A dual-robot setup with objects")
        try:
            client.workcell.stub.Create(req)
            client.workcell.stub.Load(NameRequest(name=workcell_name))
        except grpc.RpcError as e:
            print(f"Workcell operation failed or environment not running a server yet: {e}")

        # Helper to construct header
        def make_header(name, parent="world", px=0.0, py=0.0, pz=0.0, rw=1.0, rx=0.0, ry=0.0, rz=0.0):
            return ConfigHeader(
                name=name,
                parent=parent,
                pose_in_parent=SE3Pose(
                    position=Vector3(x=px, y=py, z=pz),
                    rotation=Quaternion(x=rx, y=ry, z=rz, w=rw)
                )
            )

        # 2. Add objects to the scene
        print("Saving objects to the scene...")

        # Required fields for Object: header
        # (header requires: name, parent, pose_in_parent)
        # Optional Object fields: inertial, collision, reference_only, visual
        
        # Object 1
        obj1 = Object(header=make_header("box_1", px=0.5, py=0.0, pz=0.0))
        try:
            client.config.stub.Save(SaveConfigRequest(object=obj1))
        except grpc.RpcError:
            pass
        
        # Object 2
        obj2 = Object(header=make_header("box_2", px=0.5, py=0.5, pz=0.0))
        try:
            client.config.stub.Save(SaveConfigRequest(object=obj2))
        except grpc.RpcError:
            pass

        # Object 3
        obj3 = Object(header=make_header("box_3", px=0.5, py=1.0, pz=0.0))
        try:
            client.config.stub.Save(SaveConfigRequest(object=obj3))
        except grpc.RpcError:
            pass

        # 3. Add two robot systems to the scene
        print("Saving robot systems to the scene...")

        # System 1: Placed at x=-0.7, facing +x (rz=0)
        sys1 = System(
            header=make_header("system_1", px=-0.7, py=0.0, pz=0.0),
            control_box_serial_number="CX01-01-T1-000001",
            manipulator=Manipulator(
                name="arm",
                arm_serial_number="A02L-00-M3-000001"
            )
        )
        try:
            client.config.stub.Save(SaveConfigRequest(system=sys1))
        except grpc.RpcError:
            pass

        # System 2: Placed at x=0.7, facing -x (rz=1, rw=0 -> 180 degrees around Z)
        sys2 = System(
            header=make_header("system_2", px=0.7, py=0.0, pz=0.0, rw=0.0, rz=1.0),
            control_box_serial_number="CX01-01-T1-000002",
            manipulator=Manipulator(
                name="arm",
                arm_serial_number="A02L-00-M3-000002"
            )
        )
        try:
            client.config.stub.Save(SaveConfigRequest(system=sys2))
        except grpc.RpcError:
            pass
        
        print("Commands executed to create workcell, 3 objects, and 2 systems in the scene!")


if __name__ == "__main__":
    main()

C++:

/**
 * @file scene_example.cpp
 * @brief Example: Create a simple scene using the Flexiv PDK.
 *
 * This program demonstrates how to:
 *   1. Create a new workcell
 *   2. Add multiple objects to the scene
 *   3. Add two robot systems to the scene, separated by 1.4 meters and facing each other
 */

#include <flexiv/pdk/client.hpp>
#include <flexiv/pdk/config_header.pb.h>
#include <flexiv/pdk/config_service.pb.h>
#include <flexiv/pdk/scene/object.pb.h>
#include <flexiv/pdk/system/components/manipulator.pb.h>
#include <flexiv/pdk/system/system.pb.h>
#include <flexiv/pdk/workcell/workcell_service.pb.h>

#include <cstdlib>
#include <iostream>
#include <string>

using flexiv::pdk::Client;
using flexiv::pdk::ConfigHeader;

namespace {

std::string ServerAddress()
{
  const char* address = std::getenv("PDK_SERVER_ADDR");
  return address != nullptr ? address : "localhost:50052";
}

ConfigHeader MakeHeader(const std::string& name, const std::string& parent = "world",
    double px = 0.0, double py = 0.0, double pz = 0.0, double rw = 1.0, double rx = 0.0,
    double ry = 0.0, double rz = 0.0)
{
  ConfigHeader header;
  header.set_name(name);
  header.set_parent(parent);
  auto* pose = header.mutable_pose_in_parent();
  pose->mutable_position()->set_x(px);
  pose->mutable_position()->set_y(py);
  pose->mutable_position()->set_z(pz);
  pose->mutable_rotation()->set_w(rw);
  pose->mutable_rotation()->set_x(rx);
  pose->mutable_rotation()->set_y(ry);
  pose->mutable_rotation()->set_z(rz);
  return header;
}

void SaveObject(Client& client, const ConfigHeader& header)
{
  // Required fields for Object: header (name, parent, pose_in_parent)
  // Optional Object fields: inertial, collision, reference_only, visual
  flexiv::pdk::SaveConfigRequest request;
  *request.mutable_object()->mutable_header() = header;
  grpc::ClientContext context;
  flexiv::pdk::SaveConfigResponse response;
  client.config().stub->Save(&context, request, &response);
}

void SaveSystem(Client& client, const ConfigHeader& header,
    const std::string& control_box_serial_number, const std::string& arm_serial_number)
{
  flexiv::pdk::SaveConfigRequest request;
  auto* system = request.mutable_system();
  *system->mutable_header() = header;
  system->set_control_box_serial_number(control_box_serial_number);
  system->mutable_manipulator()->set_name("arm");
  system->mutable_manipulator()->set_arm_serial_number(arm_serial_number);
  grpc::ClientContext context;
  flexiv::pdk::SaveConfigResponse response;
  client.config().stub->Save(&context, request, &response);
}

} // namespace

int main()
{
  const std::string target = ServerAddress();
  std::cout << "Connecting to the PDK server at " << target << "..." << std::endl;

  Client client(target);

  // 1. Create a workcell
  const std::string workcell_name = "example_workcell";
  std::cout << "Removing any existing workcell with name: " << workcell_name << std::endl;
  {
    grpc::ClientContext context;
    flexiv::pdk::workcell::NameRequest request;
    flexiv::pdk::workcell::RemoveResponse response;
    request.set_name(workcell_name);
    const grpc::Status status = client.workcell().stub->Remove(&context, request, &response);
    if (!status.ok()) {
      std::cerr << "Remove workcell failed or not present: " << status.error_message() << std::endl;
    }
  }

  std::cout << "Creating workcell: " << workcell_name << std::endl;
  {
    grpc::ClientContext create_context;
    flexiv::pdk::workcell::CreateRequest create_request;
    flexiv::pdk::workcell::CreateResponse create_response;
    create_request.set_name(workcell_name);
    create_request.set_description("A dual-robot setup with objects");
    grpc::Status status
        = client.workcell().stub->Create(&create_context, create_request, &create_response);
    if (status.ok()) {
      grpc::ClientContext load_context;
      flexiv::pdk::workcell::NameRequest load_request;
      flexiv::pdk::workcell::WorkcellResponse load_response;
      load_request.set_name(workcell_name);
      status = client.workcell().stub->Load(&load_context, load_request, &load_response);
    }
    if (!status.ok()) {
      std::cerr << "Workcell operation failed or environment not running a server yet: "
                << status.error_message() << std::endl;
    }
  }

  // 2. Add objects to the scene
  std::cout << "Saving objects to the scene..." << std::endl;
  SaveObject(client, MakeHeader("box_1", "world", 0.5, 0.0, 0.0));
  SaveObject(client, MakeHeader("box_2", "world", 0.5, 0.5, 0.0));
  SaveObject(client, MakeHeader("box_3", "world", 0.5, 1.0, 0.0));

  // 3. Add two robot systems to the scene
  std::cout << "Saving robot systems to the scene..." << std::endl;

  // System 1: Placed at x=-0.7, facing +x (rz=0)
  SaveSystem(client, MakeHeader("system_1", "world", -0.7, 0.0, 0.0), "CX01-01-T1-000001",
      "A02L-00-M3-000001");

  // System 2: Placed at x=0.7, facing -x (rz=1, rw=0 -> 180 degrees around Z)
  SaveSystem(client, MakeHeader("system_2", "world", 0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0),
      "CX01-01-T1-000002", "A02L-00-M3-000002");

  std::cout << "Commands executed to create workcell, 3 objects, and 2 systems in the scene!"
            << std::endl;

  return 0;
}