Example for Exporters
This example demonstrates the implementation and usage of custom exporters to be used in conjunction with the neuray 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.
Detailed Description
- Implementation of an exporter
-
The implementation of the Vanilla exporter in the example source in structured in three parts:
-
Implementation of the INVALID_DOXYREFmi::IImpexp_state interface
-
Implementation of the mi::neuraylib::IExporter interface
-
Implementation of the mi::neuraylib::IExporter::export_scene() and Implementation of the mi::neuraylib::IExporter::export_elements() methods
Instances of INVALID_DOXYREFmi::IImpexp_state are used to pass information about the current importer state, for example to recursive calls of importers. 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 neuray API. Most of the methods implemented here are actually defined in the base interface INVALID_DOXYREFmi::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 Group and Instance 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 an 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 1986, 2011 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"; 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::IImpexp_state> { // Member variables to keep all necessary data std::string m_uri; mi::Uint32 m_line_number; const INVALID_DOXYREFmi::IImpexp_state* m_parent_state; public: // 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 INVALID_DOXYREFmi::IImpexp_state* get_parent_state() const { return m_parent_state; } // Definition of corresponding setters / constructors. They are not part of the interface. // Default constructor, initializes line number to 1. Vanilla_export_state() : m_line_number( 1), m_parent_state( 0) {} void set_uri( const char* uri) { m_uri = uri ? uri : ""; } void set_parent_state( const INVALID_DOXYREFmi::IImpexp_state* parent_state) { m_parent_state = parent_state; } }; // 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> { // A map that maps type names to ID for efficient dispatch. // It lists only those types that reference other types and need to be expanded during export. // The map is built once at construction time. std::map< std::string, mi::Uint32> map_type_name_to_id; typedef std::map< std::string, mi::Uint32>::const_iterator map_iterator; public: // Returns a state suitable for passing it to an export call. // The parameters are used to initialize the corresponding properties of the state. // The line number is set to 1. INVALID_DOXYREFmi::IImpexp_state* create_impexp_state ( const char* uri, const INVALID_DOXYREFmi::IImpexp_state* parent_state) const { Vanilla_export_state* export_state = new Vanilla_export_state(); export_state->set_uri( uri); export_state->set_parent_state( parent_state); return export_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; } } // Returns the confidence of the exporter that its test_file_type() can identify the file and // that the file format is fully supported. INVALID_DOXYREFmi::Impexp_priority get_priority () const { return INVALID_DOXYREFmi::IMPEXP_PRIORITY_WELL_DEFINED; } // Returns a concise single-line clear text description of the exporter. const char* get_name () const { return "mental images example vanilla (v1) exporter"; } // Returns a concise single-line clear text description of the author of this exporter. const char* get_author () const { return "mental images GmbH, Berlin, Germany"; } // Returns the unique identifier for the exporter. mi::base::Uuid get_uuid() const { mi::base::Uuid uuid; uuid.m_id1 = 0x7b0a3b59; uuid.m_id2 = 0xbed44329; uuid.m_id3 = 0xad1a2353; uuid.m_id4 = 0xb0ab89a8; return uuid; } // Returns the major version number of the exporter. mi::Uint32 get_major_version() const { return 1; } // Returns the minor version number of the exporter. mi::Uint32 get_minor_version() const { return 0; } // Returns true if the exporter can handle the file type determined by the file name extension. bool test_file_type ( const char* extension ) const; // Returns true if the exporter can handle the file type determined by the file name extension // and if the writer has sufficient capabilities to export successfully. bool test_file_type( const char* extension, const INVALID_DOXYREFmi::IWriter* writer ) const; // Exports the scene identified by the rootgroup, camera instance, and options // through the writer. INVALID_DOXYREFmi::IExport_result* export_scene ( mi::neuraylib::ITransaction* transaction, const char* extension, INVALID_DOXYREFmi::IWriter* writer, const char* rootgroup, const char* caminst, const char* options, const mi::IMap* exporter_options, INVALID_DOXYREFmi::IImpexp_state* export_state) const; // Exports all scene elements contained in the elements array through the writer. INVALID_DOXYREFmi::IExport_result* export_elements ( mi::neuraylib::ITransaction* transaction, const char* extension, INVALID_DOXYREFmi::IWriter* writer, const mi::IArray* elements, const mi::IMap* exporter_options, INVALID_DOXYREFmi::IImpexp_state* export_state) const; // Definition of constructors and support functions. They are not part of the interface. // Default constructor. Vanilla_exporter() { // Build the map mapping type names to IDs. This example uses literal numbers as IDs, // a more involved exporter uses symbolic constants. map_type_name_to_id[ "Group"] = 1; map_type_name_to_id[ "Instance"] = 2; } // 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, INVALID_DOXYREFmi::IWriter* writer) { if ( prefix.size() > 0 && 0 == name.compare( 0, prefix.size(), prefix)) writer->INVALID_DOXYREF( name.c_str() + prefix.size()); else writer->INVALID_DOXYREF( name.c_str()); } // Formats error message with context and appends it to the error messages in the result. static INVALID_DOXYREFmi::IExport_result_ext* report_error( mi::neuraylib::ITransaction* transaction, INVALID_DOXYREFmi::IExport_result_ext* result, mi::Sint32 error_number, mi::base::Message_severity error_severity, std::string error_message, const INVALID_DOXYREFmi::IImpexp_state* export_state) // not 0 { std::ostringstream message; message << export_state->INVALID_DOXYREF() << ":" << export_state->INVALID_DOXYREF() << ": " << "Vanilla exporter error " << error_number << ", " << "severity " << enum_to_str( error_severity) << ": " << error_message; // Report context of all parent export states from recursive // invocations of export_elements in their own lines with indentation. export_state = export_state->INVALID_DOXYREF(); while( export_state) { message << "\n included from: " << export_state->INVALID_DOXYREF() << ":" << export_state->INVALID_DOXYREF(); export_state = export_state->INVALID_DOXYREF(); } result->INVALID_DOXYREF( error_number, error_severity, message.str().c_str()); return result; } // Returns the element type of an element static std::string get_element_type( const mi::base::IInterface* interface) { mi::base::Handle< const mi::IGroup> group( interface->get_interface<INVALID_DOXYREFmi::IGroup>()); if( group.is_valid_interface()) return "Group"; mi::base::Handle< const mi::IInstance> instance( interface->get_interface<INVALID_DOXYREFmi::IInstance>()); if( instance.is_valid_interface()) return "Instance"; return "Unknown"; } }; // Returns true if the exporter can handle the file type determined by the file name extension. 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"))); } // Returns true if the exporter can handle the file type determined by the file name extension // and if the writer has sufficient capabilities to export successfully. bool Vanilla_exporter::test_file_type( const char* extension, const INVALID_DOXYREFmi::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); } // Exports the scene identified by the rootgroup, camera inst, and options through the writer. INVALID_DOXYREFmi::IExport_result* Vanilla_exporter::export_scene ( mi::neuraylib::ITransaction* transaction, const char*, // extension INVALID_DOXYREFmi::IWriter* writer, // not 0 const char* rootgroup, // not 0 const char* caminst, const char* options, const mi::IMap* exporter_options, INVALID_DOXYREFmi::IImpexp_state* export_state) const // not 0 { // Create the exporter result instance for the return value. // If that fails something is really wrong and we return NULL. INVALID_DOXYREFmi::IExport_result_ext* result = transaction->create<INVALID_DOXYREFmi::IExport_result_ext>( "Export_result_ext"); if ( ! result) return NULL; // Get the 'strip_prefix' option. MISTD::string strip_prefix; if( exporter_options && exporter_options->has_key( "strip_prefix")) { mi::base::Handle< const mi::IString> option( exporter_options->get_value<mi::IString>( "strip_prefix")); if( !option.is_valid_interface()) return report_error( transaction, result, 6, mi::base::MESSAGE_SEVERITY_ERROR, "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->INVALID_DOXYREF( "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->INVALID_DOXYREF()) && (! 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 mi::base::Handle< const mi::base::IInterface> element( transaction->access( name.c_str())); if ( ! element.is_valid_interface()) { // The element is not in the DB. Export fails with customized error 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_error( transaction, result, 6001, mi::base::MESSAGE_SEVERITY_ERROR, message.c_str(), export_state); } writer->INVALID_DOXYREF( get_element_type( element.get()).c_str()); writer->INVALID_DOXYREF( " \""); write_name( name, strip_prefix, writer); writer->INVALID_DOXYREF( "\"\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 mi::base::Handle< const mi::base::IInterface> element( transaction->access( name.c_str())); if ( ! element.is_valid_interface()) { // The element is not in the DB. Export fails with customized error message. std::string message( "Element '"); message += name + "' does not exist in database, export failed."; return report_error( transaction, result, 6002, mi::base::MESSAGE_SEVERITY_ERROR, message.c_str(), export_state); } // Dispatch on the type name of the element. mi::Uint32 id = 0; map_iterator iter = map_type_name_to_id.find( get_element_type( element.get())); if ( iter != map_type_name_to_id.end()) id = iter->second; switch ( id) { case 1: // Group { mi::base::Handle< const mi::IGroup> group( element->get_interface<INVALID_DOXYREFmi::IGroup>()); // Enumerate all elements in the group and push them in reverse order on the // elements list front mi::Size group_size = group->get_length(); for ( mi::Size i = 0; i != group_size; ++i) { mi::base::Handle< const mi::base::IInterface> interface( group->access_element( group_size - i -1)); // Sanity check if item exists if ( ! interface.is_valid_interface()) { std::ostringstream message; message << "Item " << group_size - i -1 << " of group '" << name << "' does not exist in the database, export failed."; return report_error( transaction, result, 6003, mi::base::MESSAGE_SEVERITY_ERROR, message.str().c_str(), export_state); } const char* item_name = transaction->name_of( interface.get()); // Sanity check if item has proper name in database if ( 0 == item_name ) { std::ostringstream message; message << "Item " << group_size - i -1 << " of group '" << name << "'does not have a proper name in the database, export failed."; return report_error( transaction, result, 6004, mi::base::MESSAGE_SEVERITY_ERROR, message.str().c_str(), export_state); } // Optimization: put name only in the elements list if it has not been exported // yet. if ( elements_exported.find( item_name) == elements_exported.end()) elements.push_front( std::make_pair( std::string( item_name), false)); } break; } case 2: // Instance { mi::base::Handle< const mi::IInstance> instance( element->get_interface<INVALID_DOXYREFmi::IInstance>()); // Get item in the instance and push it on the elements list front. mi::base::Handle< const mi::base::IInterface> interface( instance->access_item()); // Sanity check if item exists if ( ! interface.is_valid_interface()) { std::string message( "Item of instance '"); message += name + " does not exist in the database, export failed."; return report_error( transaction, result, 6005, mi::base::MESSAGE_SEVERITY_ERROR, message.c_str(), export_state); } const char* item_name = transaction->name_of( interface.get()); // Sanity check if item has proper name in database if ( 0 == item_name ) { std::string message( "Item of instance '"); message += name; message += " does not have a proper name in the database, export failed."; return report_error( transaction, result, 6006, mi::base::MESSAGE_SEVERITY_ERROR, message.c_str(), export_state); } // Optimization: put name only in the elements list // if it has not been exported yet. if ( elements_exported.find( item_name) == elements_exported.end()) elements.push_front( std::make_pair( std::string( item_name), false)); break; } default: break; } } } // Report error condition for a possibly failed writer call if ( writer->INVALID_DOXYREF() != 0) return report_error( transaction, result, writer->INVALID_DOXYREF(), mi::base::MESSAGE_SEVERITY_ERROR, writer->INVALID_DOXYREF() ? writer->INVALID_DOXYREF() : "", export_state); return result; } // Exports all scene elements mentioned in the elements array through the writer. INVALID_DOXYREFmi::IExport_result* Vanilla_exporter::export_elements ( mi::neuraylib::ITransaction* transaction, const char*, // extension INVALID_DOXYREFmi::IWriter* writer, // not 0 const mi::IArray* elements, // not 0 const mi::IMap* exporter_options, INVALID_DOXYREFmi::IImpexp_state* export_state) const // not 0 { // Create the exporter result instance for the return value. // If that fails something is really wrong and we return NULL. INVALID_DOXYREFmi::IExport_result_ext* result = transaction->create<INVALID_DOXYREFmi::IExport_result_ext>( "Export_result_ext"); if ( ! result) return NULL; // Get the 'strip_prefix' option. MISTD::string strip_prefix; if( exporter_options && exporter_options->has_key( "strip_prefix")) { mi::base::Handle< const mi::IString> option( exporter_options->get_value<mi::IString>( "strip_prefix")); if( !option.is_valid_interface()) return report_error( transaction, result, 6, mi::base::MESSAGE_SEVERITY_ERROR, "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->INVALID_DOXYREF( "VANILLA\x0D\x0A"); // Iterate through the string array of element names mi::Size size = elements->get_length(); for ( mi::Size i = 0; (0 == writer->INVALID_DOXYREF()) && i < size; ++i) { // Get string for element i from the array mi::base::Handle< const mi::IString> name( elements->get_element<mi::IString>( i)); if ( ! name.is_valid_interface()) { return report_error( transaction, 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_error( transaction, result, 6008, mi::base::MESSAGE_SEVERITY_ERROR, message.c_str(), export_state); } // Write element to file writer->INVALID_DOXYREF( get_element_type( element.get()).c_str()); writer->INVALID_DOXYREF( " \""); write_name( element_name, strip_prefix, writer); writer->INVALID_DOXYREF( "\"\x0D\x0A"); } return result; }
Source Code Location: examples/example_exporter.cpp
/****************************************************************************** * Copyright 1986, 2011 NVIDIA Corporation. All rights reserved. *****************************************************************************/ // examples/example_exporter.cpp // // Demonstrates the implementation of custom exporters #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. mi::base::Handle< mi::neuraylib::IExporter> exporter; void configuration( mi::base::Handle< mi::neuraylib::INeuray> neuray, const char* shader_path) { // Configure the neuray library. Here we set some paths needed by the renderer. mi::base::Handle< mi::neuraylib::IRendering_configuration> rendering_configuration( neuray->get_api_component<mi::neuraylib::IRendering_configuration>()); check_success( rendering_configuration.is_valid_interface()); check_success( rendering_configuration->add_shader_path( shader_path) == 0); // Register the Vanilla exporter. mi::base::Handle< mi::neuraylib::IExtension_api> extension_api( neuray->get_api_component<mi::neuraylib::IExtension_api>()); check_success( extension_api.is_valid_interface()); exporter = new Vanilla_exporter; check_success( extension_api->register_exporter( exporter.get()) == 0); } void test_exporter( mi::base::Handle< mi::neuraylib::INeuray> neuray, const char* scene_file) { // Get the database, the global scope, which is the root for all transactions, // and create a transaction. mi::base::Handle< mi::neuraylib::IDatabase> database( neuray->get_api_component<mi::neuraylib::IDatabase>()); check_success( database.is_valid_interface()); mi::base::Handle< mi::neuraylib::IScope> scope( database->get_global_scope()); mi::base::Handle< mi::neuraylib::ITransaction> transaction( scope->create_transaction()); check_success( transaction.is_valid_interface()); // Import the scene file mi::base::Handle< mi::neuraylib::IImport_api> import_api( neuray->get_api_component<mi::neuraylib::IImport_api>()); check_success( import_api.is_valid_interface()); mi::base::Handle< const mi::IImport_result> import_result( import_api->import_elements( transaction.get(), scene_file)); 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). mi::base::Handle< mi::neuraylib::IExport_api> export_api( neuray->get_api_component<mi::neuraylib::IExport_api>()); check_success( export_api.is_valid_interface()); mi::base::Handle< const mi::IExport_result> export_result( export_api->export_scene( transaction.get(), "test3.vnl", root_group)); check_success( export_result.is_valid_interface()); check_success( export_result->get_error_number() == 0); // All transactions need to get committed or aborted, not really important in this example. transaction->commit(); } // The example takes the following command line arguments: // // example_exporter <scene_file> <shader_path> // // scene_file some scene file // shader_path path to the shaders, e.g., neuray-<version>/shaders // int main( int argc, char* argv[]) { // Collect command line parameters if( argc != 3) { std::cerr << "Usage: example_exporter <scene_file> <shader_path>" << std::endl; return EXIT_FAILURE; } const char* scene_file = argv[1]; const char* shader_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, shader_path); // Start the neuray library check_success( neuray->start( true) == 0); // Test the Vanilla exporter test_exporter( neuray, scene_file); // Shut down the neuray library check_success( neuray->shutdown() == 0); // Unregister the Vanilla exporter. mi::base::Handle< mi::neuraylib::IExtension_api> extension_api( neuray->get_api_component<mi::neuraylib::IExtension_api>()); check_success( extension_api->unregister_exporter( exporter.get()) == 0); exporter = 0; extension_api = 0; neuray = 0; // Unload the neuray library check_success( unload()); return EXIT_SUCCESS; }