Clarisse 5.0 SP8 SDK  5.0.5.8.0
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
API changes from 4.0 to 5.0

Table of Contents

New C++ Object Framework API

With the new scene assembly system introduced in Clarisse BUiLDER, a lot of changes have been introduced in the object framework API, mainly to support deduplication of items. Indeed, one item may now exist in multiple scene assembly nodes. Even though this concept is not used in Clarisse iFX, the part of the API which has changed may affect your existing C++ code. Keep in mind that everything has been done so the modifications needed to move from Clarisse 4.0 to Clarisse 5.0 are kept minimal.

New Item Handles

The classes OfItem, OfObject and OfContext describe still the same thing as in Clarisse 4.0, they are just representation of an item inside a unique context in a Clarisse application. One instance of OfItem lives inside one and only one instance of OfContext.

However, in Clarisse BUiLDER, an item exists at least in its owning context, but it can also exist in multiple other contexts, often referred as scene assembly nodes. To resolve item deduplication in multiple contexts, new classes allowing to associate an item to another context have been introduced: OfItemHandle, OfObjectHandle and OfContextHandle. Each handle class contains all the public methods of the corresponding class, and all the methods having a behavior depending on the associated context have been re-implemented. These new classes are not persistent in memory, they can be seen as empty shells that must be used whenever it's important to consider the path of the item. You should always prefer using handles when dealing with code supposed to work in a Clarisse BUiLDER environment.

OfPlugHandle, OfAttrHandle and OfOutputHandle should also be used instead of OfPlug, OfAttr and OfOutput respectively.

To ensure the const-correctness of the underlying data of the handle, you can use the corresponding const handle class: OfConstItemHandle, OfConstObjectHandle, etc. All the non-const methods called from an instance of one of these classes won't compile.

New Item Proxies

Handles are just empty shells that don't persist in memory and with no intelligence. For example, if the scene assembly is updated after getting an object handle, the underlying data of the handle is not ensured to be valid anymore. If you still want to work with handles and store them anywhere, with the underlying data being updated after any scene assembly changes, you can use OfItemProxy. Just keep in mind that its usage must be restricted as they are internally using events to be synchronized with the current scene state, so you may have memory and speed impact if you're using too much of it. There is a proxy class for each one of the handle classes, including OfItemProxy, OfObjectProxy, OfContextProxy, OfPlugProxy, OfAttrProxy and OfOutputProxy.

OfObject

No modifications breaking existing code have been done in OfObject class. You just have to know that OfObject::get_context() returns the context to which this object belongs. It doesn't change anything in Clarisse iFX as there is a one-to-one correspondence between objects and their parent context. However, in Clarisse BUiLDER, it's important to be aware that it may be dangerous to work directly with it, as there is no such correspondence. If you're working with an object in a scene assembly context, and if this object was not created inside it, OfObject::get_context() won't return this scene assembly context, but the other context having generated this object (typically a read node or a standard iFX context). That's why if your code deals with Clarisse BUiLDER scene assembly, you should always prefer working with OfObjectHandle.

OfContext

Methods allowing to get an item inside a context by specifying its relative path have been removed. This includes the following methods:

OfItem * get_item(const CoreString& name) const
OfItem * item_exists(const CoreString& name) const
OfContext * get_context(const CoreString& name) const
OfContext * context_exists(const CoreString& name) const
OfObject * get_object(const CoreString& object_name) const
OfObject * object_exists(const CoreString& object_name) const

The reason why is because calling one of these methods could be very ambiguous in Clarisse BUiLDER. Indeed, an instance of OfObject represents an object in the context owning it, and the context caller is not necessarily the context owning the asked item. For example, most scene assembly nodes do not create any objects, they are just able to give the object associated to its parent context in the scene assembly node through an instance of OfObjectHandle. Thus it is still possible to get an instance of OfObjectHandle from a relative path, but you will have to call the corresponding method from an instance of OfContextHandle.

OfItemHandle get_item(const CoreString& name) const
OfObjectHandle get_object(const CoreString& object_name) const
OfObjectHandle object_exists(const CoreString& object_name) const
OfContextHandle get_context(const CoreString& name) const
OfContextHandle context_exists(const CoreString& name) const

For the same reason the method in OfContext

OfAttr * find_attribute(const CoreString& path) const

has been removed, and the equivalent method can be found in OfContextHandle

OfAttrHandle find_attribute(const CoreString& path) const

The behavior of the following methods in OfContext (and all the other variations of these methods) has not changed in Clarisse iFX, but you should change the code calling it if it is intended to work with Clarisse BUiLDER scene assembly:

void get_objects(const CoreString& class_name, CoreArray<OfObject *>& objects) const
void get_all_objects(const CoreString& class_name, CoreArray<OfObject *>& objects) const
void get_items(CoreVector<OfItem *>& items ) const
void get_all_items(CoreSet<OfItem *>& items) const

They return the list of items that are actually created and owned by the context. Most scene assembly nodes do not own their children, this will result in an empty result list. You should call the corresponding method through an instance of OfContextHandle instead, returning the list of item handles, including items not created by the context caller with their associated parent context.

void get_objects(const CoreString& class_name, CoreArray<OfObjectHandle>& objects) const
void get_all_objects(const CoreString& class_name, CoreArray<OfObjectHandle>& objects) const
void get_items(CoreVector<OfItemHandle>& items ) const
void get_all_items(CoreSet<OfItemHandle>& items) const

OfAttr

In Clarisse 5.0, attributes can now reference contexts in addition to objects. For example, the Group module now has a new attribute Input Context allowing to specify in which context the rules must be applied. The method OfAttr::get_context() which initially returned the parent context of the object owner has been renamed to OfAttr::get_parent_context() to remove any ambiguity. The method OfAttr::get_object_filters() has also been removed and replaced by OfAttr::get_item_filters(), it now specifies the type of contexts supported by the attribute if it is of type reference, in addition to the supported object types.

Like OfObject and OfContext, some methods become ambiguous when called from C++ code. As OfItemHandle has not been introduced in the OfAttr API. You should be aware that calling

OfObject * get_object(unsigned int index)
OfObject * get_parent_object()

will always return the object in the context owning it. There is no ambiguity in code working exclusively in a Clarisse iFX environment, but when called in a Clarisse BUiLDER environment, you should clearly use the equivalent method returning the object associated with its parent context, available in OfAttrHandle.

OfObjectHandle get_object(unsigned int index)
OfObjectHandle get_parent_object()

Consider the following C++ code as an example of how it could be dangerous to not use handles in Clarisse BUiLDER. Let's say a lighting artist has created a standard context build://lights in which a set of lights has been created or imported from a Clarisse iFX project. Assuming this context is connected to the scene assembly node build://merge, please note that light_path is equal to build://lights/light as light corresponds to the object in its owning context build://lights

OfAttr *attr = factory.find_attribute("build://merge/light.color");
OfObject *light = attr->get_parent_object();
CoreString light_path = light->get_full_name();

Here, light_path is equal to the expected value build://merge/light, as light associates correctly the real object build://lights/light to the context build://merge.

OfAttrHandle attr = factory.find_attribute("build://merge/light.color");
OfObjectHandle light = attr.get_parent_object();
CoreString light_path = light.get_full_name();

New Project Structure

New Build Root

In Clarisse iFX 4.0 and previous versions, the application root context was project:/. Since Clarisse 5.0, the application root context is now build:/. Absolute paths returned by OfItem::get_full_name() are now always prefixed by build:/ instead of project:/ (except for items in special roots, see the next section). For some C++ code and Python scripts, unfortunately this could imply to change locally the code assuming that OfItem::get_full_name() always returns strings starting with project:/.

In Clarisse iFX 5.0, project:/ doesn't exist anymore, it is now replaced by the context build://project. Backward compatibility with old projects is still ensured. The conversion to the new hierarchy is done automatically during the project deserialization.

OfObjectFactory::get_root() now returns the build root and not the project root anymore. It could also imply to update any C++ code or python script accordingly.

New Special Roots

The special context project://default is not inside the project anymore. It defines its own root, outside the build: default:/. Any code assuming that the path of the default context is project://default must replace it by default:/.

Widgets objects are not hidden objects inside project:/ anymore. There are all inside the new root widgets:/. Any code assuming that the path of the widgets start with project:/ must replace it by widgets:/.

Tools objects are not hidden objects inside project:/ anymore. There are all inside the new root tools:/. Any code assuming that the path of the tools start with project:/ must replace it by tools:/.

Vizroot

The new concept of vizroot introduced by Clarisse 5.0 BUiLDER may impact some of your existing code. More information about it can be found in the Vizroot page in the Reference Guide.

Any C++ code and python scripts supposed to work only in a Clarisse iFX environment will still behave as in the previous versions, as the vizroot is internally set on the context build://project. However, any scripts handling Clarisse BUiLDER scene assembly should take into account that OfItem::is_editable() now returns false if the item is not inside the current vizroot. This allows to not edit any items that are not inside the current vizroot of the application.

How to Find Items by Paths

All functions to get an item by a path in OfObjectFactory now return an item handle. It makes it possible to always get the item associated with the correct context in the path, even if it is an item inside a scene assembly node which does not own the item. For example, the signatures of the following functions in OfObjectFactory:

OfObject * get_object(const CoreString& object_name) const
OfObject * object_exists(const CoreString& object_name) const

have been replaced by

OfObjectHandle get_object(const CoreString& object_name) const
OfObjectHandle object_exists(const CoreString& object_name) const

This won't break your C++ code using it, as implicit conversion from an object handle to an object is not considered as a compile time error (whereas the inverse conversion is). So you should be careful, and check if it makes sense to update the code. In such a case, you will have to change the code locally and work with handles. Notice the pointer of the underlying item can always be accessed with OfItemHandle::get_ptr().

In Clarisse 5.0, there are now two ways of finding an item given its path. You can give the absolute build path which is always valid whatever the application state. For example:

OfObjectHandle obj = get_factory().get_object("build://my_context/object")

Otherwise, you can also give the project path. Project paths have the same syntax as in previous versions, it makes it possible to be compatible with any previous code. For example:

OfObjectHandle obj = get_factory().get_object("project://my_object")

The keyword project:/ refers to the vizroot context of the application. In Clarisse iFX, it always corresponds to build://project (equivalent to project:/ in previous versions) as the vizroot is always set on it. In Clarisse BUiLDER, project:/ can refer to different contexts depending on which context the vizroot is set on. So any code using project paths is dangerous to use in a Clarisse BUiLDER environment as it is application state dependent. The project path of an item can always be retrieved by calling the method OfItem::get_project_full_name().

All these getters also support kinematic paths starting with the world:/ keyword, and any paths in special root (like default://material).

How to Find Items by Rules

Searching for one or multiple items matching a rule is a common thing done in scripts. This is often done by using the method OfApp::get_matching_objects(). The behavior of this method has been changed. For example, if using the overloaded method

void get_matching_objects(CoreVector<OfObjectHandle>& result, const CoreString& rule)

First, notice the method now returns a list of object handles. Also, please note the rule will be processed by default in the root context build:/ as no base context is specified, even in Clarisse iFX. It means your code will be broken as rules processed in the build root are particular rules that do not search recursively in children contexts. For example, the following script will always return 0 objects as the rule * cannot be processed from the build root. Indeed, it makes no sense to get all the versions of all the objects in all the scene assembly context in Clarisse BUiLDER.

1 lights = ix.api.OfObjectVector()
2 ix.application.get_matching_objects(lights, '*', 'Light')

And even the following script, which is valid in Clarisse BUiLDER, won't return any object in Clarisse iFX as it is not possible to create an object in the build from a Clarisse iFX session. In Clarisse BUiLDER, it will return all the lights inside the build:/ context, but not inside the children context of the build.

1 # rules processed in the build root must always be declared explicitly
2 # by starting with 'build:/'
3 lights = ix.api.OfObjectVector()
4 ix.application.get_matching_objects(lights, 'build:/*', 'Light')

That's why now you should always use the other overloaded method if it's not already the case

void get_matching_objects(CoreVector<OfObjectHandle>& result, const CoreString& rule, const OfContext& base_context)

You can specify the base context from which the rules must be processed. To fix the script above so that it returns all the lights in the project root of a Clarisse iFX session, you will have to change the script like that:

1 lights = ix.api.OfObjectVector()
2 project_root = ix.application.get_factory().get_project()
3 ix.application.get_matching_objects(lights, '*', project_root, 'Light')

It is important to note that OfApp::get_matching_objects() only works with relative rules (starting by ./) and absolute project path rule (starting with project:/). The only case where it is possible to target an item by its build path is when searching for items inside the first level of the build in Clarisse BUiLDER, as shown in the little Python sample above.

Application

How To Know the Current License in Use

If you have generic code handling both Clarisse iFX and Clarisse BUiLDER environment, you will have to use the method AppBase::get_flavor() returning at any time an enum value specifying the current running license. If the license Clarisse iFX is used, it will return AppBase::FLAVOR_IFX, otherwise AppBase::FLAVOR_BUILDER.

In addition, AppBase::get_file_extension() can be used to get the default project file extension depending on the flavor of the current running license, and AppBase::get_project_extension_name() to get the allowed file extension for the current running license including PLE extensions.

Opening a New Build or a Project

In Clarisse 4.0, there was only one method to create a new empty project: AppObject::new_project(). In Clarisse 5.0, if your code is license-agnostic and you attempt to create a new Clarisse scene, you should use AppObject::new_workspace(). If you explicitly want to create a new empty project or a new empty build, you should use AppObject::new_project() or AppObject::new_build() respectively.

Loading a Build or a Project

In Clarisse 4.0, there was only one method to load a project file: AppObject::load_project(). In Clarisse 5.0, if your code is license-agnostic and you attempt to load a Clarisse file, you should use AppObject::load(). If you explicitly want to load a project file or a build file, you should use AppObject::load_project() or AppObject::load_build() respectively.

Saving a Build or a Project

In Clarisse 4.0, there was only one method to save a project file: AppObject::save_project(). In Clarisse 5.0, if your code is license-agnostic and you attempt to save a Clarisse file, you should use AppObject::save(). If you explicitly want to save a project file or a build file, you should use AppObject::save_project() or AppObject::save_build() respectively.

One thing to mention is that calling AppObject::save_project() while the running license is BUiLDER will save the current context set as vizroot in a project file. It is valid only if the vizroot is set on a standard project context.

Selection

In Clarisse BUiLDER, the selection mechanism now works with OfItemHandle instead of OfItem as it is possible to select any item in scene assembly nodes. Even if it won't change anything for Python scripting, however C++ code will have to be updated. For example, the following methods of AppSelection:

OfItem * get_item(const unsigned int& index) const
void remove_item(OfItem& item)
const CoreVector<OfItem *>& get_items(const CoreString& group) const

have been replaced by:

OfItemHandle get_item(const unsigned int& index) const
void remove_item(OfItemHandle item)
const CoreVector<OfItemHandle> get_items(const CoreString& group) const

As a consequence, the methods of GuiWidget:

bool initate_dnd_event(const CoreVector<CoreBaseObject *>& data)
static const CoreVector<CoreBaseObject *>& get_dnd_event_data()

must not be used in the same way as before when drag-and-dropping a selection of items to or from a custom widget. While in Clarisse 4.0, code handling drag-and-drop of items would look like this:

// Drag and drop starting from my custom widget
bool
MyCustomWidget::do_initiate_dnd_event()
{
// ...
CoreVector<OfObject *> objects_to_drag = get_objects_to_drag();
for (OfObject *object : objects_to_drag) {
dnd_data.add(object);
}
initiate_dnd_event(dnd_data);
return true;
}
// Drag and drop ending in my custom widget
int
MyCustomWidget::on_dnd_release()
{
const CoreVector<CoreBaseObject *>& dnd_data = get_dnd_event_data();
for (CoreBaseObject *dnd_item : dnd_data) {
if (dnd_item->is_kindof(OfObject::class_info())) {
OfObject *dnd_object = static_cast<OfObject *>(dnd_item);
// Add the object to the widget ...
} else {
// ...
}
}
return true;
}

In Clarisse 5.0, you have to use an intermediary class OfHandle, and it would now look like this:

// Drag and drop starting from my custom widget
bool
MyCustomWidget::do_initiate_dnd_event()
{
// ...
CoreVector<OfObjectHandle> objects_to_drag = get_objects_to_drag();
for (OfObjectHandle object : objects_to_drag) {
// implicit conversion from OfObjectHandle to OfHandle
dnd_data.add(object);
}
initiate_dnd_event(dnd_data);
return true;
}
// Drag and drop ending in my custom widget
int
MyCustomWidget::on_dnd_release()
{
const CoreVector<CoreBaseObject *>& dnd_data = get_dnd_event_data();
for (CoreBaseObject *dnd_item : dnd_data) {
if (dnd_item->is_kindof(OfHandle::class_info())) {
OfHandle *dnd_handle = static_cast<OfHandle *>(dnd_item);
if (dnd_handle->is_object()) {
OfConstObjectHandle dnd_object = of_handle;
// Add the object to the widget ...
} else {
// ...
}
} else {
// ...
}
}
return true;
}

Context Engine

The structure OfContextEngine::Descriptor defines new member attributes. If you have implemented your own context engine, you will have to take it into consideration in your implementation of the virtual method OfContextEngine::get_descriptor().

Module

If you have implemented your own module and overridden one of the following callbacks: cb_create_module, cb_destroy_module, cb_module_constructor and cb_module_destructor, you will have to update their signatures to not have compilation error. You just have to change the class ModuleObject by OfModule. It should have no impact on your implementation of the callback.

Also, notice that OfObject::get_module() now returns an OfModule pointer instead of a ModuleObject.

Core

The template container class CoreOrderedDictionary<T> has been removed. If you were using it, you can change it by a CoreString specialization of the new container class CoreOrderedHashTable<CoreString, T>.

Python

What's New?

We know how it can be painful to update Python code from one version to another. That's why we did everything to ensure that you have as little as possible to change in your existing scripts. As you may have noticed if you read the sections above, C++ code working with the Object Framework will have to be updated in several cases. However, in Python, we have introduced wrappers that are transparent for SDK users. These wrappers encapsulate both item and attribute handles, and thanks to it, the syntax of the scripts can stay the same as before when you have to work with the Object Framework. The only changes you will have to do only concern new naming or behavior of methods, listed in the above sections. Thus, you should not get any surprise runtime error due to syntax issues. The wrapper PyOfObject previously used to encapsulate a pointer of an OfObject now encapsulates an instance of an OfObjectProxy. It means all your existing code working with PyOfObject will automatically work with object handles without modifying the behavior of your program.

We also introduced other wrappers that didn't exist before for all the other Object Framework types that are commonly used: PyOfItem, PyOfContext and PyOfAttr. For example, the simple script:

1 item = ix.application.get_factory().get_item("project://scene")
2 print(item.get_full_name())

will behave exactly as before, but the type of the returned item is not the automatic SWIG generated wrapper OfContext, but our PyOfContext wrapper holding the correct handle for the context. It makes it possible to work properly and transparently with the new item handles framework.

The other major change that should be transparent for users is that OfItemVector, OfObjectVector, and OfContextVector (and other type of containers) which previously store a SWIG generated wrapper per element now store a PyOfItem/PyOfObject/PyOfContext per element respectively.

What's Not Working Anymore?

Even though we did everything we could to ensure that you have as little changes as possible, we have identified some very specific cases that won't work anymore in Python. Even if you probably don't have any of these specificities implemented, here is the list of what is currently known as not working anymore or with different behavior:

1 object_1 = ix.application.get_factory().get_object("project://scene/light")
2 object_2 = ix.application.get_factory().get_object("project://scene/light")
3 # Change it by: is_equal = (object_1 == object_2)
4 is_equal = (object_1.this == object_2.this)

Finally, for the SDK users of Olympus R1, be aware that the containers OfObjectHandleArray, OfObjectHandleVector, OfObjectHandleSet and OfObjectHandleBasicArray have been removed. You have to switch back to the old container classes instead: OfObjectArray, OfObjectVector, OfObjectSet and OfObjectBasicArray. These containers were just temporary classes used to not break scripts before we finalized the new Python API.