Iray SDK API nvidia_logo_transpbg.gif Up
Example for Exporters
[Previous] [Next] [Up]

This example demonstrates the implementation and usage of custom exporters to be used in conjunction with the Iray SDK API. In this example the new exporter is defined in and used by the main application for simplicity. Note that it is possible to provide custom exporters via plugins, as for any other user defined class, see Plugins.

A simple exporter called "Vanilla exporter" will be used in this example to demonstrate the basic steps. This exporter is an illustrative skeleton that implements all interfaces but does not actually write elements out, it just writes their types and names.

New Topics

  • Implementation of an exporter
  • Registration of an exporter

Detailed Description

Implementation of an exporter


The implementation of the Vanilla exporter in the example source in structured in three parts:

Instances of mi::neuraylib::IImpexp_state are used to pass information about the current exporter state, for example to recursive calls of exporters. The Vanilla exporter does not need to carry around any additional information besides what is required by the interface, therefore this simple implementation is fine. With the exception of the element list flag (which is not needed for exporters), it is the same implementation as for the Vanilla importer. Note that the simple derivation from mi::base::Interface_implement suffices here (in contrast to user-defined classes).

The Vanilla exporter is given in the implementation of the mi::neuraylib::IExporter interface. Later, an instance of this class will be registered as exporter with the Iray SDK API. Most of the methods implemented here are actually defined in the base interface mi::neuraylib::IImpexp_state, as they are common for importers and exporters. The Vanilla exporter claims to handle files with extension ".vnl" and ".van". It does not require specific capabilities of the writer to handle these formats.

The actual work of the Vanilla exporter happens in the export_scene() and export_elements() methods. It is split into three parts:

  • creation of the export result object
  • setup of some data structures
  • traversal of the scene graph and writing the file element by element

The map is used to perform the depth-first traversal of the scene graphs. As an example, the loop expands elements of type mi::neuraylib::IGroup and mi::neuraylib::IInstance and follows to the elements mentioned as their items. Other scene elements that need traversal are not handled in this example.

While performing these tasks the example demonstrates what type of errors to detect, a way to report the errors, and how to implement a depth-first traversal of the scene graph.

The export_elements() member function uses the same code fragments as the export_scene() member function above. In its overall structure, the export_elements() member function is just simpler in that it does not need to recursively follow any elements. It just exports all elements given in its parameter.

Registration of an exporter


The registration of an exporter is similar to the registration of user-defined classes. However, since exporters are different from regular classes (e.g., you cannot create instances of them using mi::neuraylib::ITransaction::create()) you need to use a registration method specific to exporters. This registration method expects a pointer to an instance of the custom exporter.

To run the example, you need to call it with an existing scene file to import. The exporter will create a file called test3.vnl which contains the types and names of all elements in the scene.

Source Code Location: example/vanilla_exporter.h

/******************************************************************************
* Copyright 2023 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
#include <mi/neuraylib.h>
#include <string>
#include <list>
#include <set>
#include <map>
#include <utility> // for std::pair
#include <sstream>
// Support function to handle mi::base::Message_severity to std::string conversion
static std::string enum_to_str( mi::base::Message_severity severity)
{
switch( severity) {
case mi::base::MESSAGE_SEVERITY_FATAL: return "fatal";
case mi::base::MESSAGE_SEVERITY_ERROR: return "error";
case mi::base::MESSAGE_SEVERITY_WARNING: return "warning";
case mi::base::MESSAGE_SEVERITY_INFO: return "information";
case mi::base::MESSAGE_SEVERITY_VERBOSE: return "verbose";
case mi::base::MESSAGE_SEVERITY_DEBUG: return "debug";
default: return "unknown" ;
}
}
// Define exporter state, which is used in the exporter function to carry additional data around for
// possible recursive invocations of exporters. It contains a URI for the exported resource, a name
// space, a line number, and a pointer to a parent state supporting recursive exports.
class Vanilla_export_state
: public mi::base::Interface_implement< mi::neuraylib::IImpexp_state>
{
public:
// Constructor.
Vanilla_export_state( const char* uri, const mi::neuraylib::IImpexp_state* parent_state)
: m_line_number( 1),
m_uri( uri ? uri : ""),
m_parent_state( parent_state, mi::base::DUP_INTERFACE) { }
// Definition of all interface functions.
const char* get_uri() const { return m_uri.empty() ? 0 : m_uri.c_str(); }
mi::Uint32 get_line_number() const { return m_line_number; }
void set_line_number( mi::Uint32 number) { m_line_number = number; }
void incr_line_number() { ++m_line_number; }
const mi::neuraylib::IImpexp_state* get_parent_state() const
{
if( m_parent_state)
m_parent_state->retain();
return m_parent_state.get();
}
private:
mi::Uint32 m_line_number;
std::string m_uri;
};
// Define exporter. It defines all meta information, for example, author, version numbers, which
// formats it supports etc. The longer format detection functions and export_elements() function are
// implemented outside of the class body.
class Vanilla_exporter
: public mi::base::Interface_implement< mi::neuraylib::IExporter>
{
public:
// Definition of all interface functions.
mi::neuraylib::IImpexp_state* create_impexp_state(
const char* uri, const mi::neuraylib::IImpexp_state* parent_state) const
{
return new Vanilla_export_state( uri, parent_state);
}
// This exporter supports the file name extensions ".vnl" and ".van".
const char* get_supported_extensions( mi::Uint32 i) const
{
switch( i) {
case 0: return ".vnl";
case 1: return ".van";
default: return 0;
}
}
mi::neuraylib::Impexp_priority get_priority() const
{
}
const char* get_name() const { return "NVIDIA example vanilla (v1) exporter"; }
const char* get_author() const { return "NVIDIA Corporation, Berlin, Germany"; }
mi::base::Uuid get_uuid() const
{
uuid.m_id1 = 0x7b0a3b59;
uuid.m_id2 = 0xbed44329;
uuid.m_id3 = 0xad1a2353;
uuid.m_id4 = 0xb0ab89a8;
return uuid;
}
mi::Uint32 get_major_version() const { return 1; }
mi::Uint32 get_minor_version() const { return 0; }
bool test_file_type( const char* extension) const;
bool test_file_type( const char* extension, const mi::neuraylib::IWriter* writer) const;
const char* extension,
const char* rootgroup,
const char* caminst,
const char* options,
const mi::IMap* exporter_options,
mi::neuraylib::IImpexp_state* export_state) const;
const char* extension,
const mi::IArray* elements,
const mi::IMap* exporter_options,
mi::neuraylib::IImpexp_state* export_state) const;
private:
// Definition of support functions. They are not part of the interface.
// Formats message with context and appends it to the messages in the result.
static mi::neuraylib::IExport_result_ext* report_message(
mi::Uint32 message_number,
mi::base::Message_severity message_severity,
std::string message,
const mi::neuraylib::IImpexp_state* export_state)
{
std::ostringstream s;
const char* uri = export_state->get_uri();
s << (uri ? uri : "(no URI)")
<< ":" << export_state->get_line_number() << ": "
<< "Vanilla exporter message " << message_number << ", "
<< "severity " << enum_to_str( message_severity) << ": "
<< message;
// Report context of all parent export states from recursive invocations of
// export_elements() in their own lines with indentation.
export_state->get_parent_state());
while( parent_state.is_valid_interface()) {
s << "\n included from: " << parent_state->get_uri()
<< ":" << parent_state->get_line_number();
parent_state = parent_state->get_parent_state();
}
result->message_push_back( message_number, message_severity, s.str().c_str());
return result;
}
// Writes name to writer, writes the name without the leading prefix if it is equal to the
// prefix parameter.
static void write_name(
const std::string& name, const std::string& prefix, mi::neuraylib::IWriter* writer)
{
if( prefix.size() > 0 && 0 == name.compare( 0, prefix.size(), prefix))
writer->writeline( name.c_str() + prefix.size());
else
writer->writeline( name.c_str());
}
// Returns the element type of an element.
static std::string get_element_type( const mi::base::IInterface* interface)
{
if( group.is_valid_interface())
return "Group";
if( instance.is_valid_interface())
return "Instance";
return "Unknown";
}
};
bool Vanilla_exporter::test_file_type( const char* extension) const
{
// This exporter supports the file name extensions ".vnl" and ".van".
mi::Size len = std::strlen( extension);
return (len > 3)
&& (( 0 == strcmp( extension + len - 4, ".vnl"))
|| ( 0 == strcmp( extension + len - 4, ".van")));
}
bool Vanilla_exporter::test_file_type(
const char* extension, const mi::neuraylib::IWriter*) const
{
// The writer capabilities do not matter for this simple format. More involved formats might
// require random access from the writer.
return test_file_type( extension);
}
mi::neuraylib::IExport_result* Vanilla_exporter::export_scene(
const char* /*extension*/,
const char* rootgroup,
const char* caminst,
const char* options,
const mi::IMap* exporter_options,
mi::neuraylib::IImpexp_state* export_state) const
{
// Create the exporter result instance for the return value. If that fails something is really
// wrong and we return 0.
= transaction->create<mi::neuraylib::IExport_result_ext>( "Export_result_ext");
if( !result)
return 0;
// Get the 'strip_prefix' option.
std::string strip_prefix;
if( exporter_options && exporter_options->has_key( "strip_prefix")) {
exporter_options->get_value<mi::IString>( "strip_prefix"));
if( !option.is_valid_interface())
return report_message(
"The option 'strip_prefix' has an invalid type.", export_state);
strip_prefix = option->get_c_str();
}
// Two data structures maintain the information during export. The elements list keeps the names
// of all elements that we want to export and a bit if they have been expanded already. The
// order of elements follows the .mi requirements; elements that are referenced are first in the
// list and exported before the elements that reference them. The elements_exported map
// maintains a list of all exported elements. That allows us to handle objects that are
// referenced multiple times and export them only once.
std::list< std::pair< std::string, bool> > elements;
std::set< std::string> elements_exported;
// Initialize the elements list with the three input parameters.
if( options && options[0] != '\0')
elements.push_back( std::make_pair( std::string( options), false));
if( caminst && caminst[0] != '\0')
elements.push_back( std::make_pair( std::string( caminst), false));
elements.push_back( std::make_pair( std::string( rootgroup), false));
// Start file with magic header and use Windows line-ending convention with CR LF pairs.
writer->writeline( "VANILLA\r\n");
// Main loop through all elements
// This is a simplified recursive directed acyclic graph traversal on the scene graph that
// performs a depth first search. The traversal is implemented as an iterative loop and a stack
// on the elements list data structure. The boolean value of entries in the elements list
// encodes whether we are descending or are ascending in the graph. This flag determines whether
// we need to expand into the element and put all its children on the stack, or whether we are
// done with the element and can write it out to file. Other exporters might need to manage more
// data during the traversal, such as a transformation stack.
while( (0 == writer->get_error_number()) && (!elements.empty())) {
if( elements.front().second) {
// Traversal is ascending in the scene graph
// Keep element name and remove it from list
std::string name = elements.front().first;
elements.pop_front();
// Check if element has not been written yet
if( elements_exported.find( name) == elements_exported.end()) {
// Element can be written to file, mark it as written
elements_exported.insert( name);
// Access the element in the DB
transaction->access( name.c_str()));
if( !element.is_valid_interface()) {
// The element is not in the DB. Export fails with customized message.
std::string message( "Element '");
message += name + "' does not exist in database, export failed.";
// Error numbers from 6000 to 7999 are reserved for custom
// exporter messages like this one
return report_message( result, 6001, mi::base::MESSAGE_SEVERITY_ERROR,
message, export_state);
}
writer->writeline( get_element_type( element.get()).c_str());
writer->writeline( " \"");
write_name( name, strip_prefix, writer);
writer->writeline( "\"\r\n");
}
} else {
// Traversal is descending in the scene graph, mark element as expanded.
elements.front().second = true;
// Expand front element, but keep it in the list.
std::string name = elements.front().first;
// Access the element in the DB.
transaction->access( name.c_str()));
if( !element.is_valid_interface()) {
// The element is not in the DB. Export fails with customized message.
std::string message( "Element '");
message += name + "' does not exist in database, export failed.";
return report_message( result, 6002, mi::base::MESSAGE_SEVERITY_ERROR,
message, export_state);
}
// Dispatch on the type name of the element.
std::string element_type = get_element_type( element.get());
if( element_type == "Group") {
element->get_interface<mi::neuraylib::IGroup>());
// Enumerate all elements in the group and push them in reverse order on the
// elements list front.
mi::Uint32 group_size = group->get_length();
for( mi::Uint32 i = 0; i != group_size; ++i) {
const char* element_name = group->get_element( group_size - i -1);
// Optimization: put name only in the elements list if it has not been exported
// yet.
if( elements_exported.find( element_name) == elements_exported.end())
elements.push_front( std::make_pair( std::string( element_name), false));
}
} else if( element_type == "Instance") {
element->get_interface<mi::neuraylib::IInstance>());
// Get element in the instance and push it on the elements list front.
const char* element_name = instance->get_item();
// Optimization: put name only in the elements list if it has not been exported yet.
if( elements_exported.find( element_name) == elements_exported.end())
elements.push_front( std::make_pair( std::string( element_name), false));
}
}
}
// Report message condition for a possibly failed writer call
if( writer->get_error_number() != 0)
return report_message(
result,
static_cast<mi::Uint32>( writer->get_error_number()),
writer->get_error_message() ? writer->get_error_message() : "",
export_state);
return result;
}
// Exports all scene elements mentioned in the elements array through the writer.
mi::neuraylib::IExport_result* Vanilla_exporter::export_elements(
const char* /*extension*/,
const mi::IArray* elements,
const mi::IMap* exporter_options,
mi::neuraylib::IImpexp_state* export_state) const
{
// Create the exporter result instance for the return value.
// If that fails something is really wrong and we return 0.
= transaction->create<mi::neuraylib::IExport_result_ext>( "Export_result_ext");
if( !result)
return 0;
// Get the 'strip_prefix' option.
std::string strip_prefix;
if( exporter_options && exporter_options->has_key( "strip_prefix")) {
exporter_options->get_value<mi::IString>( "strip_prefix"));
if( !option.is_valid_interface())
return report_message(
"The option 'strip_prefix' has an invalid type.", export_state);
strip_prefix = option->get_c_str();
}
// Start file with magic header and use Windows line-ending convention with CR LF pairs.
writer->writeline( "VANILLA\x0D\x0A");
// Iterate through the string array of element names
mi::Size size = elements->get_length();
for( mi::Size i = 0; (0 == writer->get_error_number()) && i < size; ++i) {
// Get string for element i from the array
if( !name.is_valid_interface()) {
return report_message( result, 6007, mi::base::MESSAGE_SEVERITY_ERROR,
"element array contains an invalid object", export_state);
}
const char* element_name = name->get_c_str();
// Access the element in the DB
mi::base::Handle<const mi::base::IInterface> element( transaction->access( element_name));
if( !element.is_valid_interface()) {
std::string message( "Element '");
message += element_name;
message += "' does not exist in database, export failed.";
return report_message( result, 6008, mi::base::MESSAGE_SEVERITY_ERROR,
message, export_state);
}
// Write element to file
writer->writeline( get_element_type( element.get()).c_str());
writer->writeline( " \"");
write_name( element_name, strip_prefix, writer);
writer->writeline( "\"\x0D\x0A");
}
return result;
}
This interface represents static arrays, i.e., arrays with a fixed number of elements.
Definition: iarray.h:37
virtual const base::IInterface * get_element(Size index) const =0
Returns the index -th element of the array.
virtual Size get_length() const =0
Returns the size of the array.
virtual const base::IInterface * get_value(const char *key) const =0
Returns the value for key key.
virtual bool has_key(const char *key) const =0
Indicates whether the key key exists or not.
This interface represents maps, i.e., a key-value based data structure.
Definition: imap.h:41
A simple string class.
Definition: istring.h:22
Handle class template for interfaces, automatizing the lifetime control via reference counting.
Definition: handle.h:113
The basic extensible interface.
Definition: iinterface.h:103
Mixin class template for deriving interface implementations.
Definition: interface_implement.h:41
This interface represents the result of an export operation.
Definition: iexport_result.h:107
This interface represents the result of an export operation.
Definition: iexport_result.h:44
A group is a container for other scene elements.
Definition: igroup.h:39
This interface represents states that are passed to recursive calls of importers and exporters.
Definition: iimpexp_state.h:36
An instance is a scene element that adds a transformation and attributes to another scene element.
Definition: iinstance.h:117
virtual Sint32 get_error_number() const =0
Returns the error number of the last error that happened in this reader or writer,...
virtual const char * get_error_message() const =0
Returns the error message of the last error that happened in this reader or writer.
A transaction provides a consistent view on the database.
Definition: itransaction.h:81
virtual const base::IInterface * access(const char *name)=0
Retrieves an element from the database.
virtual base::IInterface * create(const char *type_name, Uint32 argc=0, const base::IInterface *argv[]=0)=0
Creates an object of the type type_name.
A writer supports binary block writes and string-oriented line writes that accept a zero-terminated s...
Definition: iwriter.h:27
virtual bool writeline(const char *str)=0
Writes a zero-terminated string to the stream.
virtual const IInterface * get_interface(const Uuid &interface_id) const =0
Acquires a const interface from another.
virtual Uint32 retain() const =0
Increments the reference count.
static const Dup_interface DUP_INTERFACE
Symbolic constant to trigger a special constructor in the Handle class.
Definition: handle.h:37
Uint32 m_id1
First value.
Definition: uuid.h:27
Uint32 m_id2
Second value.
Definition: uuid.h:28
Uint32 m_id3
Third value.
Definition: uuid.h:29
Uint32 m_id4
Fourth value.
Definition: uuid.h:30
Message_severity
Constants for possible message severities.
Definition: enums.h:31
@ MESSAGE_SEVERITY_FATAL
A fatal error has occurred.
Definition: enums.h:33
@ MESSAGE_SEVERITY_DEBUG
This is debug message.
Definition: enums.h:43
@ MESSAGE_SEVERITY_WARNING
A warning has occurred.
Definition: enums.h:37
@ MESSAGE_SEVERITY_INFO
This is a normal operational message.
Definition: enums.h:39
@ MESSAGE_SEVERITY_VERBOSE
This is a more verbose message.
Definition: enums.h:41
@ MESSAGE_SEVERITY_ERROR
An error has occurred.
Definition: enums.h:35
unsigned int Uint32
32-bit unsigned integer.
Definition: types.h:49
Uint64 Size
Unsigned integral type that is large enough to hold the size of all types.
Definition: types.h:112
virtual const IImpexp_state * get_parent_state() const =0
Returns the state of the parent importer or exporter.
virtual const char * get_uri() const =0
Returns the URI for this file.
virtual Sint32 message_push_back(Uint32 number, base::Message_severity severity, const char *message)=0
Appends a message number, severity, and message to the array of recorded message numbers,...
Impexp_priority
Confidence in capabilities of an importer or exporter.
Definition: iimpexp_base.h:31
virtual Uint32 get_line_number() const =0
Returns the line number after the last read or write operation.
@ IMPEXP_PRIORITY_WELL_DEFINED
The highest possible priority for importers or exporters in plugins provided in the Iray SDK.
Definition: iimpexp_base.h:37
Common namespace for APIs of NVIDIA Advanced Rendering Center GmbH.
Definition: neuraylib.h:179
Iray SDK API.
A 128 bit representation of a universally unique identifier (UUID or GUID).
Definition: uuid.h:26

Source Code Location: examples/example_exporter.cpp

/******************************************************************************
* Copyright 2023 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/example_exporter.cpp
//
// Demonstrates the implementation of custom exporters
//
// The example expects the following command line arguments:
//
// example_exporter <scene_file> <mdl_path>
//
// scene_file some scene file, e.g., main.mi
// mdl_path path to the MDL modules, e.g., iray-<version>/mdl
#include <iostream>
#include <mi/neuraylib.h>
// Include code shared by all examples.
#include "example_shared.h"
// Include header file for the Vanilla exporter.
#include "vanilla_exporter.h"
// The exporter.
void configuration( mi::neuraylib::INeuray* neuray, const char* mdl_path)
{
// Configure the neuray library. Here we set the search path for .mdl files.
check_success( rendering_configuration->add_mdl_path( mdl_path) == 0);
check_success( rendering_configuration->add_mdl_path( ".") == 0);
// Register the Vanilla exporter.
check_success( extension_api.is_valid_interface());
exporter = new Vanilla_exporter;
check_success( extension_api->register_exporter( exporter.get()) == 0);
// Load the .mi importer plugin.
check_success( plugin_configuration->load_plugin_library(
"mi_importer" MI_BASE_DLL_FILE_EXT) == 0);
}
void test_exporter( mi::neuraylib::INeuray* neuray, const char* scene_file)
{
// Get the database, the global scope of the database, and create a transaction in the global
// scope.
check_success( database.is_valid_interface());
database->get_global_scope());
scope->create_transaction());
check_success( transaction.is_valid_interface());
// Import the scene file
check_success( import_api.is_valid_interface());
mi::base::Handle<const mi::IString> uri( import_api->convert_filename_to_uri( scene_file));
import_api->import_elements( transaction.get(), uri->get_c_str()));
check_success( import_result->get_error_number() == 0);
const char* root_group = import_result->get_rootgroup();
// Export the scene to a file test3.vnl (implicitly using the Vanilla exporter).
check_success( export_api.is_valid_interface());
export_api->export_scene( transaction.get(), "file:test3.vnl", root_group));
check_success( export_result.is_valid_interface());
// Print all messages
for( mi::Size i = 0; i < export_result->get_messages_length(); ++i)
std::cout << export_result->get_message( i) << std::endl;
check_success( export_result->get_error_number() == 0);
transaction->commit();
}
int main( int argc, char* argv[])
{
// Collect command line parameters
if( argc != 3) {
std::cerr << "Usage: example_exporter <scene_file> <mdl_path>" << std::endl;
keep_console_open();
return EXIT_FAILURE;
}
const char* scene_file = argv[1];
const char* mdl_path = argv[2];
// Access the neuray library
mi::base::Handle<mi::neuraylib::INeuray> neuray( load_and_get_ineuray());
check_success( neuray.is_valid_interface());
// Configure the neuray library
configuration( neuray.get(), mdl_path);
// Start the neuray library
mi::Sint32 result = neuray->start();
check_start_success( result);
// Test the Vanilla exporter
test_exporter( neuray.get(), scene_file);
// Shut down the neuray library
check_success( neuray->shutdown() == 0);
// Unregister the Vanilla exporter.
check_success( extension_api->unregister_exporter( exporter.get()) == 0);
exporter = 0;
extension_api = 0;
neuray = 0;
// Unload the neuray library
check_success( unload());
keep_console_open();
return EXIT_SUCCESS;
}
This interface is used to interact with the distributed database.
Definition: idatabase.h:293
This interface is used to export files.
Definition: iexport_api.h:38
This interface is used to extent the Iray SDK API.
Definition: iextension_api.h:32
This interface is used to import files.
Definition: iimport_api.h:100
This is an object representing the Iray library.
Definition: ineuray.h:44
virtual Sint32 shutdown(bool blocking=true)=0
Shuts down the library.
virtual base::IInterface * get_api_component(const base::Uuid &uuid) const =0
Returns an API component from the Iray SDK API.
virtual Sint32 start(bool blocking=true)=0
Starts the operation of the Iray library.
This interface is used to load plugins and to query information about loaded plugins.
Definition: iplugin_configuration.h:24
This interface is used to query and change the rendering configuration.
Definition: irendering_configuration.h:109
#define MI_BASE_DLL_FILE_EXT
The operating system specific default filename extension for shared libraries (DLLs)
Definition: config.h:340
Interface * get() const
Access to the interface. Returns 0 for an invalid interface.
Definition: handle.h:294
signed int Sint32
32-bit signed integer.
Definition: types.h:46
[Previous] [Next] [Up]