Implementation of an exporter
This topic introduces:
- Basic concepts about the implementation and usage of custom exporters used in conjunction with the Iray API:
- An example exporter program, consisting of vanilla_exporter.h and example_exporter.cpp. The exporter is called Vanilla. For simplicity, it is defined in and used by the main application.
Implementing an exporter
The implementation of the Vanilla exporter in the example source is structured in three parts:
- Implementing the mi::neuraylib::IImpexp_state interface
- Implementing the mi::neuraylib::IExporter interface
- Implementing the mi::neuraylib::IExporter::export_scene() and the mi::neuraylib::IExporter::export_elements() methods
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 example_plugins.cpp).
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 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 the extension .vnl and .van. It does not require specific capabilities of the writer to handle these formats.
The actual work of the Vanilla exporter occurs in the export_scene() and export_elements() methods. It is split into three parts:
- Creating the export result object
- Setting up some data structures
- Traversing 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, how errors can be reported, 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.
Registering an exporter
Registering exporters is similar to registering user-defined classes. However, since exporters are different from regular classes (for example, 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 for import. The exporter will create a file called test3.vnl, which contains the types and names of all elements in the scene.
vanilla_exporter.h
001 /****************************************************************************** 002 * © 1986, 2016 NVIDIA Corporation. All rights reserved. 003 *****************************************************************************/ 004 005 #include <mi/neuraylib.h> 006 007 #include <string> 008 #include <list> 009 #include <set> 010 #include <map> 011 #include <utility> // for std::pair 012 #include <sstream> 013 014 // Support function to handle mi::base::Message_severity to std::string conversion 015 static std::string enum_to_str( mi::base::Message_severity severity) 016 { 017 switch( severity) { 018 case mi::base::MESSAGE_SEVERITY_FATAL: return "fatal"; 019 case mi::base::MESSAGE_SEVERITY_ERROR: return "error"; 020 case mi::base::MESSAGE_SEVERITY_WARNING: return "warning"; 021 case mi::base::MESSAGE_SEVERITY_INFO: return "information"; 022 case mi::base::MESSAGE_SEVERITY_VERBOSE: return "verbose"; 023 case mi::base::MESSAGE_SEVERITY_DEBUG: return "debug"; 024 default: return "unknown" ; 025 } 026 } 027 028 // Define exporter state, which is used in the exporter function to carry additional data around for 029 // possible recursive invocations of exporters. It contains a URI for the exported resource, a name 030 // space, a line number, and a pointer to a parent state supporting recursive exports. 031 class Vanilla_export_state 032 : public mi::base::Interface_implement< mi::neuraylib::IImpexp_state> 033 { 034 public: 035 // Constructor. 036 Vanilla_export_state( const char* uri, const mi::neuraylib::IImpexp_state* parent_state) 037 : m_line_number( 1), 038 m_uri( uri ? uri : ""), 039 m_parent_state( parent_state, mi::base::DUP_INTERFACE) { } 040 041 // Definition of all interface functions. 042 043 const char* get_uri() const { return m_uri.empty() ? 0 : m_uri.c_str(); } 044 045 mi::Uint32 get_line_number() const { return m_line_number; } 046 047 void set_line_number( mi::Uint32 number) { m_line_number = number; } 048 049 void incr_line_number() { ++m_line_number; } 050 051 const mi::neuraylib::IImpexp_state* get_parent_state() const 052 { 053 if( m_parent_state) 054 m_parent_state->retain(); 055 return m_parent_state.get(); 056 } 057 058 private: 059 mi::Uint32 m_line_number; 060 std::string m_uri; 061 mi::base::Handle<const mi::neuraylib::IImpexp_state> m_parent_state; 062 }; 063 064 // Define exporter. It defines all meta information, for example, author, version numbers, which 065 // formats it supports etc. The longer format detection functions and export_elements() function are 066 // implemented outside of the class body. 067 class Vanilla_exporter 068 : public mi::base::Interface_implement< mi::neuraylib::IExporter> 069 { 070 public: 071 // Definition of all interface functions. 072 073 mi::neuraylib::IImpexp_state* create_impexp_state( 074 const char* uri, const mi::neuraylib::IImpexp_state* parent_state) const 075 { 076 return new Vanilla_export_state( uri, parent_state); 077 } 078 079 // This exporter supports the file name extensions ".vnl" and ".van". 080 const char* get_supported_extensions( mi::Uint32 i) const 081 { 082 switch( i) { 083 case 0: return ".vnl"; 084 case 1: return ".van"; 085 default: return 0; 086 } 087 } 088 089 mi::neuraylib::Impexp_priority get_priority() const 090 { 091 return mi::neuraylib::IMPEXP_PRIORITY_WELL_DEFINED; 092 } 093 094 const char* get_name() const { return "NVIDIA example vanilla (v1) exporter"; } 095 096 const char* get_author() const { return "NVIDIA ARC GmbH, Berlin, Germany"; } 097 098 mi::base::Uuid get_uuid() const 099 { 100 mi::base::Uuid uuid; 101 uuid.m_id1 = 0x7b0a3b59; 102 uuid.m_id2 = 0xbed44329; 103 uuid.m_id3 = 0xad1a2353; 104 uuid.m_id4 = 0xb0ab89a8; 105 return uuid; 106 } 107 108 mi::Uint32 get_major_version() const { return 1; } 109 110 mi::Uint32 get_minor_version() const { return 0; } 111 112 bool test_file_type( const char* extension) const; 113 114 bool test_file_type( const char* extension, const mi::neuraylib::IWriter* writer) const; 115 116 mi::neuraylib::IExport_result* export_scene( 117 mi::neuraylib::ITransaction* transaction, 118 const char* extension, 119 mi::neuraylib::IWriter* writer, 120 const char* rootgroup, 121 const char* caminst, 122 const char* options, 123 const mi::IMap* exporter_options, 124 mi::neuraylib::IImpexp_state* export_state) const; 125 126 mi::neuraylib::IExport_result* export_elements( 127 mi::neuraylib::ITransaction* transaction, 128 const char* extension, 129 mi::neuraylib::IWriter* writer, 130 const mi::IArray* elements, 131 const mi::IMap* exporter_options, 132 mi::neuraylib::IImpexp_state* export_state) const; 133 134 private: 135 // Definition of support functions. They are not part of the interface. 136 137 // Formats message with context and appends it to the messages in the result. 138 static mi::neuraylib::IExport_result_ext* report_message( 139 mi::neuraylib::IExport_result_ext* result, 140 mi::Uint32 message_number, 141 mi::base::Message_severity message_severity, 142 std::string message, 143 const mi::neuraylib::IImpexp_state* export_state) 144 { 145 std::ostringstream s; 146 const char* uri = export_state->get_uri(); 147 s << (uri ? uri : "(no URI)") 148 << ":" << export_state->get_line_number() << ": " 149 << "Vanilla exporter message " << message_number << ", " 150 << "severity " << enum_to_str( message_severity) << ": " 151 << message; 152 // Report context of all parent export states from recursive invocations of 153 // export_elements() in their own lines with indentation. 154 mi::base::Handle<const mi::neuraylib::IImpexp_state> parent_state( 155 export_state->get_parent_state()); 156 while( parent_state.is_valid_interface()) { 157 s << "\n included from: " << parent_state->get_uri() 158 << ":" << parent_state->get_line_number(); 159 parent_state = parent_state->get_parent_state(); 160 } 161 result->message_push_back( message_number, message_severity, s.str().c_str()); 162 return result; 163 } 164 165 // Writes name to writer, writes the name without the leading prefix if it is equal to the 166 // prefix parameter. 167 static void write_name( 168 const std::string& name, const std::string& prefix, mi::neuraylib::IWriter* writer) 169 { 170 if( prefix.size() > 0 && 0 == name.compare( 0, prefix.size(), prefix)) 171 writer->writeline( name.c_str() + prefix.size()); 172 else 173 writer->writeline( name.c_str()); 174 } 175 176 // Returns the element type of an element. 177 static std::string get_element_type( const mi::base::IInterface* interface) 178 { 179 mi::base::Handle<const mi::neuraylib::IGroup> group( 180 interface->get_interface<mi::neuraylib::IGroup>()); 181 if( group.is_valid_interface()) 182 return "Group"; 183 mi::base::Handle<const mi::neuraylib::IInstance> instance( 184 interface->get_interface<mi::neuraylib::IInstance>()); 185 if( instance.is_valid_interface()) 186 return "Instance"; 187 return "Unknown"; 188 } 189 }; 190 191 bool Vanilla_exporter::test_file_type( const char* extension) const 192 { 193 // This exporter supports the file name extensions ".vnl" and ".van". 194 mi::Size len = std::strlen( extension); 195 return (len > 3) 196 && (( 0 == strcmp( extension + len - 4, ".vnl")) 197 || ( 0 == strcmp( extension + len - 4, ".van"))); 198 } 199 200 bool Vanilla_exporter::test_file_type( 201 const char* extension, const mi::neuraylib::IWriter*) const 202 { 203 // The writer capabilities do not matter for this simple format. More involved formats might 204 // require random access from the writer. 205 return test_file_type( extension); 206 } 207 208 mi::neuraylib::IExport_result* Vanilla_exporter::export_scene( 209 mi::neuraylib::ITransaction* transaction, 210 const char* /*extension*/, 211 mi::neuraylib::IWriter* writer, 212 const char* rootgroup, 213 const char* caminst, 214 const char* options, 215 const mi::IMap* exporter_options, 216 mi::neuraylib::IImpexp_state* export_state) const 217 { 218 // Create the exporter result instance for the return value. If that fails something is really 219 // wrong and we return 0. 220 mi::neuraylib::IExport_result_ext* result 221 = transaction->create<mi::neuraylib::IExport_result_ext>( "Export_result_ext"); 222 if( !result) 223 return 0; 224 225 // Get the 'strip_prefix' option. 226 std::string strip_prefix; 227 if( exporter_options && exporter_options->has_key( "strip_prefix")) { 228 mi::base::Handle<const mi::IString> option( 229 exporter_options->get_value<mi::IString>( "strip_prefix")); 230 if( !option.is_valid_interface()) 231 return report_message( 232 result, 6, mi::base::MESSAGE_SEVERITY_ERROR, 233 "The option 'strip_prefix' has an invalid type.", export_state); 234 strip_prefix = option->get_c_str(); 235 } 236 237 // Two data structures maintain the information during export. The elements list keeps the names 238 // of all elements that we want to export and a bit if they have been expanded already. The 239 // order of elements follows the .mi requirements; elements that are referenced are first in the 240 // list and exported before the elements that reference them. The elements_exported map 241 // maintains a list of all exported elements. That allows us to handle objects that are 242 // referenced multiple times and export them only once. 243 std::list< std::pair< std::string, bool> > elements; 244 std::set< std::string> elements_exported; 245 246 // Initialize the elements list with the three input parameters. 247 if( options && options[0] != '\0') 248 elements.push_back( std::make_pair( std::string( options), false)); 249 if( caminst && caminst[0] != '\0') 250 elements.push_back( std::make_pair( std::string( caminst), false)); 251 elements.push_back( std::make_pair( std::string( rootgroup), false)); 252 253 // Start file with magic header and use Windows line-ending convention with CR LF pairs. 254 writer->writeline( "VANILLA\r\n"); 255 256 // Main loop through all elements 257 // This is a simplified recursive directed acyclic graph traversal on the scene graph that 258 // performs a depth first search. The traversal is implemented as an iterative loop and a stack 259 // on the elements list data structure. The boolean value of entries in the elements list 260 // encodes whether we are descending or are ascending in the graph. This flag determines whether 261 // we need to expand into the element and put all its children on the stack, or whether we are 262 // done with the element and can write it out to file. Other exporters might need to manage more 263 // data during the traversal, such as a transformation stack. 264 while( (0 == writer->get_error_number()) && (!elements.empty())) { 265 if( elements.front().second) { 266 267 // Traversal is ascending in the scene graph 268 // Keep element name and remove it from list 269 std::string name = elements.front().first; 270 elements.pop_front(); 271 // Check if element has not been written yet 272 if( elements_exported.find( name) == elements_exported.end()) { 273 // Element can be written to file, mark it as written 274 elements_exported.insert( name); 275 // Access the element in the DB 276 mi::base::Handle<const mi::base::IInterface> element( 277 transaction->access( name.c_str())); 278 if( !element.is_valid_interface()) { 279 // The element is not in the DB. Export fails with customized message. 280 std::string message( "Element '"); 281 message += name + "' does not exist in database, export failed."; 282 // Error numbers from 6000 to 7999 are reserved for custom 283 // exporter messages like this one 284 return report_message( result, 6001, mi::base::MESSAGE_SEVERITY_ERROR, 285 message, export_state); 286 } 287 writer->writeline( get_element_type( element.get()).c_str()); 288 writer->writeline( " \""); 289 write_name( name, strip_prefix, writer); 290 writer->writeline( "\"\r\n"); 291 } 292 293 } else { 294 295 // Traversal is descending in the scene graph, mark element as expanded. 296 elements.front().second = true; 297 // Expand front element, but keep it in the list. 298 std::string name = elements.front().first; 299 // Access the element in the DB. 300 mi::base::Handle<const mi::base::IInterface> element( 301 transaction->access( name.c_str())); 302 if( !element.is_valid_interface()) { 303 // The element is not in the DB. Export fails with customized message. 304 std::string message( "Element '"); 305 message += name + "' does not exist in database, export failed."; 306 return report_message( result, 6002, mi::base::MESSAGE_SEVERITY_ERROR, 307 message, export_state); 308 } 309 // Dispatch on the type name of the element. 310 std::string element_type = get_element_type( element.get()); 311 if( element_type == "Group") { 312 313 mi::base::Handle<const mi::neuraylib::IGroup> group( 314 element->get_interface<mi::neuraylib::IGroup>()); 315 // Enumerate all elements in the group and push them in reverse order on the 316 // elements list front. 317 mi::Uint32 group_size = group->get_length(); 318 for( mi::Uint32 i = 0; i != group_size; ++i) { 319 const char* element_name = group->get_element( group_size - i -1); 320 // Optimization: put name only in the elements list if it has not been exported 321 // yet. 322 if( elements_exported.find( element_name) == elements_exported.end()) 323 elements.push_front( std::make_pair( std::string( element_name), false)); 324 } 325 326 } else if( element_type == "Instance") { 327 328 mi::base::Handle<const mi::neuraylib::IInstance> instance( 329 element->get_interface<mi::neuraylib::IInstance>()); 330 // Get element in the instance and push it on the elements list front. 331 const char* element_name = instance->get_item(); 332 // Optimization: put name only in the elements list if it has not been exported yet. 333 if( elements_exported.find( element_name) == elements_exported.end()) 334 elements.push_front( std::make_pair( std::string( element_name), false)); 335 336 } 337 } 338 } 339 340 // Report message condition for a possibly failed writer call 341 if( writer->get_error_number() != 0) 342 return report_message( 343 result, 344 static_cast<mi::Uint32>( writer->get_error_number()), 345 mi::base::MESSAGE_SEVERITY_ERROR, 346 writer->get_error_message() ? writer->get_error_message() : "", 347 export_state); 348 349 return result; 350 } 351 352 // Exports all scene elements mentioned in the elements array through the writer. 353 mi::neuraylib::IExport_result* Vanilla_exporter::export_elements( 354 mi::neuraylib::ITransaction* transaction, 355 const char* /*extension*/, 356 mi::neuraylib::IWriter* writer, 357 const mi::IArray* elements, 358 const mi::IMap* exporter_options, 359 mi::neuraylib::IImpexp_state* export_state) const 360 { 361 // Create the exporter result instance for the return value. 362 // If that fails something is really wrong and we return 0. 363 mi::neuraylib::IExport_result_ext* result 364 = transaction->create<mi::neuraylib::IExport_result_ext>( "Export_result_ext"); 365 if( !result) 366 return 0; 367 368 // Get the 'strip_prefix' option. 369 std::string strip_prefix; 370 if( exporter_options && exporter_options->has_key( "strip_prefix")) { 371 mi::base::Handle<const mi::IString> option( 372 exporter_options->get_value<mi::IString>( "strip_prefix")); 373 if( !option.is_valid_interface()) 374 return report_message( 375 result, 6, mi::base::MESSAGE_SEVERITY_ERROR, 376 "The option 'strip_prefix' has an invalid type.", export_state); 377 strip_prefix = option->get_c_str(); 378 } 379 380 // Start file with magic header and use Windows line-ending convention with CR LF pairs. 381 writer->writeline( "VANILLA\x0D\x0A"); 382 383 // Iterate through the string array of element names 384 mi::Size size = elements->get_length(); 385 for( mi::Size i = 0; (0 == writer->get_error_number()) && i < size; ++i) { 386 387 // Get string for element i from the array 388 mi::base::Handle<const mi::IString> name( elements->get_element<mi::IString>( i)); 389 if( !name.is_valid_interface()) { 390 return report_message( result, 6007, mi::base::MESSAGE_SEVERITY_ERROR, 391 "element array contains an invalid object", export_state); 392 } 393 const char* element_name = name->get_c_str(); 394 395 // Access the element in the DB 396 mi::base::Handle<const mi::base::IInterface> element( transaction->access( element_name)); 397 if( !element.is_valid_interface()) { 398 std::string message( "Element '"); 399 message += element_name; 400 message += "' does not exist in database, export failed."; 401 return report_message( result, 6008, mi::base::MESSAGE_SEVERITY_ERROR, 402 message, export_state); 403 } 404 405 // Write element to file 406 writer->writeline( get_element_type( element.get()).c_str()); 407 writer->writeline( " \""); 408 write_name( element_name, strip_prefix, writer); 409 writer->writeline( "\"\x0D\x0A"); 410 } 411 412 return result; 413 }
example_exporter.cpp
001 /****************************************************************************** 002 * © 1986, 2016 NVIDIA Corporation. All rights reserved. 003 *****************************************************************************/ 004 005 // examples/example_exporter.cpp 006 // 007 // Demonstrates the implementation of custom exporters 008 // 009 // The example expects the following command line arguments: 010 // 011 // example_exporter <scene_file> <mdl_path> 012 // 013 // scene_file some scene file, e.g., main.mi 014 // mdl_path path to the MDL modules, e.g., iray-<version>/mdl 015 016 #include <iostream> 017 018 #include <mi/neuraylib.h> 019 020 // Include code shared by all examples. 021 #include "example_shared.h" 022 023 // Include header file for the Vanilla exporter. 024 #include "vanilla_exporter.h" 025 026 // The exporter. 027 mi::base::Handle<mi::neuraylib::IExporter> exporter; 028 029 void configuration( mi::base::Handle<mi::neuraylib::INeuray> neuray, const char* mdl_path) 030 { 031 // Configure the neuray library. Here we set the search path for .mdl files. 032 mi::base::Handle<mi::neuraylib::IRendering_configuration> rendering_configuration( 033 neuray->get_api_component<mi::neuraylib::IRendering_configuration>()); 034 check_success( rendering_configuration.is_valid_interface()); 035 check_success( rendering_configuration->add_mdl_path( mdl_path) == 0); 036 037 // Register the Vanilla exporter. 038 mi::base::Handle<mi::neuraylib::IExtension_api> extension_api( 039 neuray->get_api_component<mi::neuraylib::IExtension_api>()); 040 check_success( extension_api.is_valid_interface()); 041 exporter = new Vanilla_exporter; 042 check_success( extension_api->register_exporter( exporter.get()) == 0); 043 044 // Load the .mi importer plugin. 045 mi::base::Handle<mi::neuraylib::IPlugin_configuration> plugin_configuration( 046 neuray->get_api_component<mi::neuraylib::IPlugin_configuration>()); 047 #ifndef MI_PLATFORM_WINDOWS 048 check_success( plugin_configuration->load_plugin_library( "mi_importer.so") == 0); 049 #else 050 check_success( plugin_configuration->load_plugin_library( "mi_importer.dll") == 0); 051 #endif 052 } 053 054 void test_exporter( mi::base::Handle<mi::neuraylib::INeuray> neuray, const char* scene_file) 055 { 056 // Get the database, the global scope of the database, and create a transaction in the global 057 // scope. 058 mi::base::Handle<mi::neuraylib::IDatabase> database( 059 neuray->get_api_component<mi::neuraylib::IDatabase>()); 060 check_success( database.is_valid_interface()); 061 mi::base::Handle<mi::neuraylib::IScope> scope( 062 database->get_global_scope()); 063 mi::base::Handle<mi::neuraylib::ITransaction> transaction( 064 scope->create_transaction()); 065 check_success( transaction.is_valid_interface()); 066 067 // Import the scene file 068 mi::base::Handle<mi::neuraylib::IImport_api> import_api( 069 neuray->get_api_component<mi::neuraylib::IImport_api>()); 070 check_success( import_api.is_valid_interface()); 071 mi::base::Handle<const mi::IString> uri( import_api->convert_filename_to_uri( scene_file)); 072 mi::base::Handle<const mi::neuraylib::IImport_result> import_result( 073 import_api->import_elements( transaction.get(), uri->get_c_str())); 074 check_success( import_result->get_error_number() == 0); 075 const char* root_group = import_result->get_rootgroup(); 076 077 // Export the scene to a file test3.vnl (implicitly using the Vanilla exporter). 078 mi::base::Handle<mi::neuraylib::IExport_api> export_api( 079 neuray->get_api_component<mi::neuraylib::IExport_api>()); 080 check_success( export_api.is_valid_interface()); 081 mi::base::Handle<const mi::neuraylib::IExport_result> export_result( 082 export_api->export_scene( transaction.get(), "file:test3.vnl", root_group)); 083 check_success( export_result.is_valid_interface()); 084 085 // Print all messages 086 for( mi::Size i = 0; i < export_result->get_messages_length(); ++i) 087 std::cout << export_result->get_message( i) << std::endl; 088 089 check_success( export_result->get_error_number() == 0); 090 transaction->commit(); 091 } 092 093 int main( int argc, char* argv[]) 094 { 095 // Collect command line parameters 096 if( argc != 3) { 097 std::cerr << "Usage: example_exporter <scene_file> <mdl_path>" << std::endl; 098 keep_console_open(); 099 return EXIT_FAILURE; 100 } 101 const char* scene_file = argv[1]; 102 const char* mdl_path = argv[2]; 103 104 // Access the neuray library 105 mi::base::Handle<mi::neuraylib::INeuray> neuray( load_and_get_ineuray()); 106 check_success( neuray.is_valid_interface()); 107 108 // Configure the neuray library 109 configuration( neuray, mdl_path); 110 111 // Start the neuray library 112 mi::Sint32 result = neuray->start(); 113 check_start_success( result); 114 115 // Test the Vanilla exporter 116 test_exporter( neuray, scene_file); 117 118 // Shut down the neuray library 119 check_success( neuray->shutdown() == 0); 120 121 // Unregister the Vanilla exporter. 122 mi::base::Handle<mi::neuraylib::IExtension_api> extension_api( 123 neuray->get_api_component<mi::neuraylib::IExtension_api>()); 124 check_success( extension_api->unregister_exporter( exporter.get()) == 0); 125 exporter = 0; 126 extension_api = 0; 127 neuray = 0; 128 129 // Unload the neuray library 130 check_success( unload()); 131 132 keep_console_open(); 133 return EXIT_SUCCESS; 134 }