Iray Programmer's Manual

Implementation of an exporter

This topic introduces:

  1. Basic concepts about the implementation and usage of custom exporters used in conjunction with the Iray API:
  2. 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:

  1. Implementing the mi::neuraylib::IImpexp_state interface
  2. Implementing the mi::neuraylib::IExporter interface
  3. 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:

  1. Creating the export result object
  2. Setting up some data structures
  3. 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 }