SCENE C++ API  2.1.0
General Documentation

Smart Pointers

In the SCENE API, smart pointers are data structures which act similar to pointers in C++ but work on special reference counted objects in order to provide an automatic memory management. All reference counted objects in the API are derived from the LSRefType baseclass, while the smart pointers are implemented by the ref_ptr template class. LSRefType instances are automatically destroyed once the last ref_ptr which references the instance goes out of scope. Smart pointers can therefore be used for the management of certain object instances which are shared between apps and the SCENE platform. The following code snippet illustrates how you can work with the smart pointer template provided by the SCENE platform.

#include <core_api/ref_ptr.h>

using namespace SCENE_API;

// Create a smart pointer which references a new object instance
ref_ptr<some_ref_type> ptr1(new some_ref_type);

// Create another smart pointer that references the same instance
ref_ptr<some_ref_type> ptr2(ptr1);

// Access the smart pointer just as you would access a C++ pointer
ptr1->some_ref_type_method();

// Reset the first smart pointer. This will give up the reference to the 
// object instance but will not destroy the instance as ptr2 still 
// holds a reference to it.
ptr1.reset();

// Reset the second smart pointer. This will also the destroy the
// referenced object instance as this is the last pointer that holds a
// references to it.
ptr2.reset();

Note: While smart pointers guarantee that the referenced object instance is not destroyed during their lifetime, they do not prevent the object from changing its state. Some objects like workspaces might not produce valid results anymore after they changed their state, e.g. after they have been closed. Therefore make sure to check the state of these objects before accessing their functionality.

Attributes and Attribute Containers

Within the SCENE API, attributes and attribute containers are used as generic serializable data structures to store various types of associated information elements. Attributes represent single data elements and may contain different data types such as a string or an integer value whereas attribute containers form a collection of attributes as well as other attribute containers.

Attributes

Attributes are used in the API to provide a generic interface to access and store objects of different supported data types. Each attribute instance represents a single object of a specific type and provides functionality to query both the objects type and value. Attributes are implemented by the LSAttribute class.

#include <core_api/lsattribute.h>

Creating and Assigning Attributes

Attributes can be created by just passing a value of a supported type to the attribute constructor. Using the default constructor, an empty/invalid attribute will be created which doesn't have a specified type or value. Any attribute can be assigned to any other attribute, assigning an attribute of a different type or value will change both the type and value of the attribute to which the attribute is assigned.

using namespace SCENE_API;

// Create an "invalid" attribute with no specified type or value
LSAttribute attribute0;

// Create a new attribute containing an integer value
LSAttribute attribute1(0);

// Create another attribute containing a boolean value
LSAttribute attribute2(true);

// All attributes now have the same type and value
attribute0 = attribute1 = attribute2;

Getting the Attribute Type

The type of an attribute can be acquired either as a string value representing the name of the type (e.g. "bool") or by explicitly testing against a specified type.

using namespace SCENE_API;

// Create a new attribute containing a boolean value
LSAttribute attribute(true);

// Get the attribute type as a string 
LSString typename = attribute.getType(); // will return "bool".

// Check whether the attribute is of a certain type
bool test1 = attribute.hasType<bool>(); // will return true
bool test2 = attribute.hasType(L"bool"); // will return true
bool test3 = attribute.hasType<int>(); // will return false
bool test4 = attribute.hasType(L"int"); // will return false

Getting and Setting the Attribute Value

Similar to the attribute type, the attribute value can both be gotten and set using either a string representation of the value or explicitly using a value of the appropriate type.

using namespace SCENE_API;

// Create a new attribute containing a boolean value
LSAttribute attribute(true);

// Get the attribute value as a string 
LSString valueAsString = attribute.toString(); // will return "true".

// Set the attribute value as a string
attribute.fromString("false");

// Get the attribute value explicitly
bool value = attribute.getValue(); // will return false

// Set the attribute value explicitly
attribute.setValue(true);

Attribute Containers

Attribute containers represent generic collections of named data elements. Besides ordinary attributes, these data elements can also take the form of other attribute containers - in which case they are called "fields" - e.g. to create a hierarchical collection of associated data elements. All attribute containers in the SCENE API are instances of the LSAttributeContainer class.

#include <core_api/lsattributecontainer.h>

Attribute Container Types

Attribute containers have a set of pre-defined attributes and fields which cannot be changed. The number and type of these attributes and fields varies depending on the type of the attribute container itself. The SCENE platform uses a whole range of different attribute container types to represent different types of objects and their properties. In order to determine whether an attribute container is of a certain type you can use the getTypeName() function of the LSAttributeContainer class.

Accessing Attributes

Each attribute within an attribute container can be addressed either by its index within the attribute collection or by an associated name which is unique for each attribute in the collection. The attribute type of the attributes of an attribute container - contrary to attributes in general - is fixed and cannot be changed. Therefore, you need to make sure that the attribute types are compatible when you are setting the attribute within an attribute container.

using namespace SCENE_API;

// Get the number of attributes from a given attribute container
unsigned int numAttributes = container->getNumAttributes();

// Get an attribute based on its index within the attribute collection
LSAttribute attribute1 = container->getAttribute(0);

// Set an attribute based on its index within the attribute collection. The
// attribute type of the attribute to be set has to be compatible to the type
// of the attribute passed to the function.
LSResult::Result result = container->setAttribute(0, LSAttribute(true));
assert(result == LSResult::Ok); // Ok if the attribute could be set

// Get an attribute based on its name within the attribute collection
LSAttribute attribute2 = container->getAttribute(L"AttrName");

// Set an attribute based on its index within the attribute collection. The
// attribute type of the attribute to be set has to be compatible to the type
// of the attribute passed to the function.
LSResult::Result result = container->setAttribute(L"AttrName", LSAttribute(true));
assert(result == LSResult::Ok); // Ok if the attribute could be set

Additionally to the fixed attributes which are defined by the attribute container type, an attribute container may have additional custom attributes. These attributes can be added and removed at any time and can be used to store additional information for a certain container.

using namespace SCENE_API;

// Add a new custom attribute to the container. The attribute name has to be
// unique within the attribute collection.
container->addCustomAttribute(L"AttrName", LSAttribute(0));

// Remove the custom attribute from the container.
container->removeCustomAttribute(L"AttrName");

Accessing Fields

Similarly to the attributes of an attribute container, the fields of the container can generally be split into two categories: The fixed fields which are defined by the container type and the custom fields which can be added and removed on demand. Fields are always attribute containers themselves and can be used to store associated information in a hierarchical manner.

using namespace SCENE_API;

// Get the number of fields from a given attribute container
unsigned int numFields = container->getNumFields();

// Get a field based on its index within the field collection
ref_ptr<LSAttributeContainer> field1 = container->getField(0);

// Get a field based on its name within the field collection
ref_ptr<LSAttributeContainer> field1 = container->getField(L"FieldName");

// Add a new custom field to the container. The field name has to be
// unique within the field collection. 
ref_ptr<LSAttributeContainer> addedField;
container->addField(L"FieldName", "FieldType", addedField);

// Remove the custom field from the container.
container->rmField(L"FieldName");

Objects

Objects are special attribute containers which are used to build up a hierarchical tree structure with a single object on top of the hierarchy and subtrees of objects below. This structure is formed by implementing a special relationship between the objects. Every object may only have a direct relationship to a single object in the hierarchy above - called the parent object - but a relationship to an arbitrary number of objects in the hierarchy below - called the child objects. The single object on top of the structure is called the root object, while the structure itself is called the object tree. Contrary to the fields of the attribute container class which are used to hierarchically store associated information related to a single object, the object tree is used for a higher order relationship where multiple object are related to each other. Within the SCENE API, the object data structure is implemented within the LSObject class which provides numerous functions to modify the object tree.

#include <core_api/lsobject.h>

Traversing the Object Tree

You can manually traverse the object tree by just using the getNumChildren() and getChild() methods provided by the LSObject class:

using namespace SCENE_API;

enum visitOrder {PRE_ORDER, POST_ORDER};

void depth_first_traversal(ref_ptr<LSObject> object, visitOrder order)
{
    if (order == PRE_ORDER)
    {
        // Depth-First Pre-Order visit of the object
    }

    for (int i = 0; i < object->getNumChildren(); ++i)
        depth_first_traversal(object->getChild(i), order);

    if (order == POST_ORDER)
    {
        // Depth-First Post-Order visit of the object
    }
}

void breadth_first_traversal(ref_ptr<LSObject> object)
{
    std::queue<ref_ptr<LSObject> > q;
    q.push(object);

    while(!q.empty())
    {
        // Add all children of the current object to the visitor queue
        for (int i = 0; i < object->getNumChildren(); ++i)
            q.push(object->getChild(i));

        // Breadth first visit of q.front();

        q.pop();
    }
}

Alternatively, you can also use an iterator to run over the objects in the object tree. The iterator will always do a breadth-first traversal of the object tree:

using namespace SCENE_API;

void iterator_traversal(ref_ptr<LSObject> object)
{
    LSIterator<ref_ptr<LSObject> > iterator = object->getChildIterator();

    while(iterator.moveNext())
    {
        // Breadth-first visit of iterator.getCurrent();
    }
}

Modifying the Object Tree

The object tree can be modified by adding or removing objects from the tree. Existing child objects can be removed by passing either their name or index within the child list to the rmChild() method of an LSObject instance. New child objects may be added to an existing object by calling the addChild() method with a supported object type and an object name. Note that it is only possible to add new objects to the object tree, adding existing object instances is not supported.

Tasks

Tasks are specialized function objects which are used to encapsulate certain functionality. All tasks are derived from the LSTask interface provided by the API. The SCENE platform provides a set of pre-defined tasks which can be retrieved using the getRegisteredTasks() method from the application context of your app. You can execute these tasks by just calling the perform() method of a specific task instance.

#include <core_api/lstask.h>

Besides using the pre-defined tasks, apps can also make use of the task data structure by defining their own tasks in order to provide certain functionality to the SCENE platform. You can define your own user defined task by deriving you own task class from the LSTask class and implementing the abstract perform() method. User defined tasks can be registered with the application context and then be bound to certain events like the triggering of a UI control. When registering a task with the application context, a name for the task has to be supplied. SCENE will add a "Apps:AppName:" (where AppName is the name of your app) prefix to that task name to create a taskID which is unique across the SCENE platform and which can be used to reference the task later on:

using namespace SCENE_API;

class SpecificTask : public LSTask
{
    virtual void perform()
    {
            // Provide an implementation
    }
}

void registerTask(ref_ptr<LSAppContext> appContext)
{
    appContext->registerTask(L"TaskName", new SpecificTask);
}

Signals

Signals provide an implementation of the observer pattern to enable easy communication between API objects and apps. They are used to signal certain events associated to an object and provide apps with the opportunity to react to these events by calling registered handlers with the appropriate event information. The signals of an API object are always public member attributes of the object class:

using namespace SCENE_API;

// An example API object which just contains a single signal
class API_Object 
{
public:

    // A signal which provides a smart pointer to an LSObject instance as
        // its event information when calling a registered handler.
    LSSignal<ref_ptr<LSObject> > signal;

};

Using static functions as signal handlers

Both free static functions and static member functions can be added as signal handlers when they match the function signature required by a specific signal. In general, a signal handler function needs to have a void return type and a single parameter that matches the template argument of the signal to which it should be connected:

using namespace SCENE_API;

// A free static function that can be used as a signal handler
void handler1(ref_ptr<LSObject> param) {}


class SomeClass 
{
public:
    // A static member function that can be used as a signal handler
    static void handler(ref_ptr<LSObject> param) {}
};

void connect_handlers(API_Object& object)
{
        // Connect a global static function
        object.signal.connect(&handler1);

        // Connect a static member function
        object.signal.connect(&SomeClass::handler);    
}

Using non-static member functions as signal handlers

Additionally to static functions, you can also use member functions as signal handlers. In order to be able to use a member function of a class as a signal handler, the class needs to be derived from the LSTrackable class provided by the SCENE API. The LSTrackable class makes sure that signal connection is automatically removed once the signal or the instance that provides the handler is destroyed.

using namespace SCENE_API;

class SomeClass : public LSTrackable
{
public:
    // A non-static handler function which is part of a user defined class
    void handler(ref_ptr<LSObject> param) {}
};

void connect_handler(API_Object& object, SomeClass& trackable)
{      
        // Connect a non-static member function
        object.signal.connect(trackable, &SomeClass::handler);
}

Projects and Workspaces

Projects and workspaces are an integral part of the SCENE platform. They are used to store and manage associated objects such as scans, point clouds and matched geometry. Therefore they act as a general starting point from which all of these objects may be accessed.

Workspaces

Workspaces represent a certain view at the files and objects that are part of a project. They manage the contained objects within a hierarchical data structure called the object tree. Within the SCENE API, workspaces can be accessed using the LSWorkspace interface. Workspaces are always associated to a certain project and can either exist as project workspaces representing the project view itself or as local workspaces representing a custom view on the project.

#include <core_api/lsworkspace.h>

Using the LSWorkspace interface you can directly access the object tree of the workspace:

using namespace SCENE_API;

void accessObjectTree(ref_ptr<LSWorkspace> workspace)
{
    // Get the root object of the workspace object tree
    ref_ptr<LSObject> rootObject = workspace->getRootObject();

    // Use an iterator to run over all objects that are part of the workspace
    LSIterator<ref_ptr<LSObject> > objectIterator =
        rootObject->getChildIterator();

    while (objectIterator.moveNext())
    {
        ref_ptr<LSObject> currentObject = objectIterator.getCurrent();
    }
}

The workspace interface also provides access to additional elements like the project point cloud:

using namespace SCENE_API;

void accessPointCloud(ref_ptr<LSWorkspace> workspace)
{
    // Get the project point cloud of the workspace
    ref_ptr<LSPointCloud> pointCloud = workspace->getPointCloud();

    // Use an iterator to run over all points that are part of the point cloud
    LSIterator<LSPointXYZRGB> pointIterator =
        pointCloud->getPointCloudIterator();

    while (pointIterator.moveNext())
    {
        LSPointXYZRGB currentPoint = pointIterator.getCurrent();
    }
}

Projects

Projects consist of both the files related to the project workspace and the workspace itself. They may contain several consecutive revisions, each one represents a certain state within the project history and each with its own project workspace and all the files that were added in that revision. When loading a project per default always the latest revision is loaded. To create a project or to load and save revisions the API provides the LSProject interface. The interface is derived from the LSWorkspace interface and thus also provides direct access to the loaded project workspace.

#include <core_api/lsproject.h>

In order to create an new project in the file system, you can use the static createProject() method provided by the LSProject interface:

using namespace SCENE_API;

// Create a new project in the file system
// projectPath: The file system path at which the files for the new project
//              will be stored.
// projectName: The name that will be used for the new project. The name of
//              a project is always defined by the name of the project file 
//              (*.lsproj).
LSResult::Result res = LSProject::createProject(projectPath, projectName);

// Check whether the project could be created
if (res != LSResult::Ok)
{
    // The project could not be created. Check the result code for more 
    // information about the error.
}

Using the loadProject() and saveRevision() methods, you can also load and save revisions for an existing project:

using namespace SCENE_API;

ref_ptr<LSProject> project;

// Load a project from the file system
// fsPath: The file system path at which the project is located (may either be
//         the project directory or a path to the project file itself).
LSResult::Result res = LSProject::loadProject(fsPath, project);

// Check whether the project could be loaded
if (res != LSResult::Ok)
{
    // The project could not be loaded. Check the result code for more 
    // information about the error.
    return;
}

// Save a new revision for the loaded project
res = project->saveRevision();

// Check whether the revision could be saved
if (res != LSResult::Ok)
{
    // The revision could not be saved. Check the result code for more 
    // information about the error.
}

Local Workspaces

Local workspaces are always associated to a certain project and define a custom view on that project. They may consist of less objects than the associated project but may also contain additional files that are only part of the local workspace. In order to load or save a local workspace, the API provides the LSLocalWorkspace interface which is - similarly to the LSProject interface - also derived from the LSWorkspace interface to offer direct access to the workspace structure.

#include <core_api/lslocalworkspace.h>

Similarly to the LSProject interface, the LSLocalWorkspace interface may be used to load and save local workspaces using the loadWorkspace() and saveWorkspace() methods:

using namespace SCENE_API;

ref_ptr<LSLocalWorkspace> workspace;

// Load a local workspace from the file system
// fsPath: The file system path at which the workspace file of the local
//         workspace is located.
LSResult::Result res = LSLocalWorkspace::loadWorkspace(fsPath, workspace);

// Check whether the workspace could be loaded
if (res != LSResult::Ok)
{
    // The workspace could not be loaded. Check the result code for more 
    // information about the error.
    return;
}

// Save the local workspace. This will overwrite the previously saved workspace
// with the current state of the workspace.
res = workspace->saveWorkspace();

// Check whether the workspace could be saved
if (res != LSResult::Ok)
{
    // The workspace could not be saved. Check the result code for more 
    // information about the error.
}