Iray Programmer's Manual

Implementation of an importer

This topic introduces:

  1. Basic concepts about the implementation and usage of custom importers used in conjunction with the Iray API:
  2. An example importer program consisting of vanilla_importer.h and example_importer.cpp. The importer is called Vanilla. For simplicity, it is defined in and used by the main application. The importer is intended as an illustrative skeleton: It implements all interfaces but does not parse the file content in a meaningful way.

Implementing an importer

The implementation of the Vanilla importer is structured in three parts:

Instances of mi::IImpexp_state are used to pass information about the current importer state, for example to recursive calls of importers. The Vanilla importer does not need to carry around any additional information besides what is required by the interface, therefore this simple implementation is fine. Note that the simple derivation from mi::base::Interface_implement suffices here (in contrast to example_plugins.cpp).

The Vanilla importer is given in the implementation of the mi::neuraylib::IImporter interface. Later, an instance of this class is registered as an importer with the Iray API. Most of the methods implemented here are defined in the base interface mi::IImpexp_state, as they are common for importers and exporters. The Vanilla importer claims to handle files with the extension .vnl and .van. It does not require specific capabilities of the reader to handle these formats. However, if the reader supports the lookahead capability, it will use a magic header check instead of relying on file name extensions.

The actual work of the Vanilla importer occurs in the import_elements() method. It is split into three parts:

  1. Creating an import result object
  2. Creating a group object which acts as the root group
  3. Reading the file line by line

While performing these tasks, the example program demonstrates what type of errors to detect, how errors can be reported, and how to implement an include file mechanism, and similar things, with a recursive call to the mi::neuraylib::IImport_api::import_elements() method.

Registering an importer

Registering importers is similar to registering user-defined classes. However, since importers 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 importers. This registration method expects a pointer to an instance of the custom importer.

To run the example, you need to create two files called test1.vnl and test2.van, each five lines long and starting with a line saying VANILLA. For demonstration purposes, the example will print the generated error messages and the names of the imported elements.

vanilla_importer.h

001 /******************************************************************************
002  * © 1986, 2014 NVIDIA Corporation. All rights reserved.
003  *****************************************************************************/
004 
005 #include <mi/neuraylib.h>
006 
007 #include <string>
008 #include <sstream>
009 
010 // Support function to handle mi::base::Message_severity to std::string conversion
011 static std::string enum_to_str( mi::base::Message_severity severity)
012 {
013     switch( severity) {
014         case mi::base::MESSAGE_SEVERITY_FATAL:
015             return "fatal";
016         case mi::base::MESSAGE_SEVERITY_ERROR:
017             return "error";
018         case mi::base::MESSAGE_SEVERITY_WARNING:
019             return "warning";
020         case mi::base::MESSAGE_SEVERITY_INFO:
021             return "information";
022         case mi::base::MESSAGE_SEVERITY_VERBOSE:
023             return "verbose";
024         case mi::base::MESSAGE_SEVERITY_DEBUG:
025             return "debug";
026         default:
027             return "unknown" ;
028     }
029 }
030 
031 // Define importer state, which is used in the importer function to carry additional data around
032 // for possible recursive invocations of importers. It contains a URI for the imported resource,
033 // a line number, and a pointer to a parent state supporting recursive imports.
034 class Vanilla_import_state
035     : public mi::base::Interface_implement< mi::IImpexp_state>
036 {
037     // Member variables to keep all necessary data
038     std::string               m_uri;
039     mi::Uint32                m_line_number;
040     const mi::IImpexp_state*  m_parent_state;
041 
042 public:
043     // Definition of all interface functions.
044 
045     const char* get_uri() const { return m_uri.empty() ? 0 : m_uri.c_str(); }
046 
047     mi::Uint32 get_line_number() const { return m_line_number; }
048 
049     void set_line_number( mi::Uint32 number) { m_line_number = number; }
050 
051     void incr_line_number() { ++m_line_number; }
052 
053     const mi::IImpexp_state* get_parent_state() const { return m_parent_state; }
054 
055     // Definition of corresponding setters / constructors. They are not part of the interface.
056 
057     // Default constructor, initializes line number to 1.
058     Vanilla_import_state()
059         : m_line_number( 1),
060           m_parent_state( 0)
061     {}
062 
063     void set_uri( const char* uri) { m_uri = uri ? uri : ""; }
064 
065     void set_parent_state( const mi::IImpexp_state* parent_state)
066     {
067         m_parent_state = parent_state;
068     }
069 };
070 
071 // Define importer. It defines all meta information, for example, author, version numbers,
072 // which formats it supports etc. The longer format detection functions and import_elements
073 // function are implemented outside of the class body.
074 class Vanilla_importer
075     : public mi::base::Interface_implement< mi::neuraylib::IImporter>
076 {
077     mi::neuraylib::IPlugin_api* m_iplugin_api;
078 
079 public:
080     // Returns a state suitable for passing it to an import call.
081     // The parameters are used to initialize the corresponding properties of the state.
082     // The line number is set to 1.
083     mi::IImpexp_state* create_impexp_state (
084         const char* uri,
085         const mi::IImpexp_state* parent_state) const
086     {
087         Vanilla_import_state* import_state = new Vanilla_import_state();
088         import_state->set_uri( uri);
089         import_state->set_parent_state( parent_state);
090         return import_state;
091     }
092 
093     // This importer supports the file name extensions ".vnl" and ".van".
094     const char* get_supported_extensions( mi::Uint32 i) const
095     {
096         switch( i) {
097             case 0:  return ".vnl";
098             case 1:  return ".van";
099             default: return 0;
100         }
101     }
102 
103     // Returns the confidence of the importer that its test_file_type() can identify the file and
104     // that the file format is fully supported.
105     mi::Impexp_priority get_priority () const
106     {
107         return mi::IMPEXP_PRIORITY_WELL_DEFINED;
108     }
109 
110     // Returns a concise single-line clear text description of the importer.
111     const char* get_name () const
112     {
113         return "mental images example vanilla (v1) importer";
114     }
115 
116     // Returns a concise single-line clear text description of the author of
117     // this importer.
118     const char* get_author () const
119     {
120         return "mental images GmbH, Berlin, Germany";
121     }
122 
123     // Returns the unique identifier for the importer.
124     mi::base::Uuid get_uuid() const
125     {
126         mi::base::Uuid uuid;
127         uuid.m_id1 = 0x338eca60;
128         uuid.m_id2 = 0x31004802;
129         uuid.m_id3 = 0xaab9046b;
130         uuid.m_id4 = 0x9e0b1d9b;
131         return uuid;
132     }
133 
134     // Returns the major version number of the importer.
135     mi::Uint32 get_major_version() const { return 1; }
136 
137     // Returns the minor version number of the importer.
138     mi::Uint32 get_minor_version() const { return 0; }
139 
140     // Returns true if the importer can handle the file type determined by the file name extension.
141     bool test_file_type ( const char* extension) const;
142 
143     // Returns true if the importer can handle the file type determined by the file name extension
144     // and if the reader has sufficient capabilities for import.
145     bool test_file_type( const char* extension,
146                                  const mi::IReader* reader) const;
147 
148     // Imports all elements from the reader in a format determined by the file extension and
149     // (optionally) the lookahead of the reader.
150     mi::IImport_result* import_elements (
151         mi::neuraylib::ITransaction* transaction,
152         const char*                  extension,
153         mi::IReader*                 reader,
154         const mi::IMap*              options,
155         mi::IImpexp_state*           import_state) const;
156 
157     // Definition of constructors and support functions. They are not part of the interface.
158 
159     // Constructor.
160     Vanilla_importer( mi::neuraylib::IPlugin_api* iplugin_api)
161         : m_iplugin_api( iplugin_api)
162     {
163         m_iplugin_api->retain();
164     }
165 
166     // Destructor.
167     ~Vanilla_importer()
168     {
169         m_iplugin_api->release();
170     }
171 
172     // Format message with context and append it to the messages in the result.
173     static mi::IImport_result_ext* report_message(
174         mi::neuraylib::ITransaction* /*transaction*/,
175         mi::IImport_result_ext*      result,
176         mi::Sint32                   message_number,
177         mi::base::Message_severity   message_severity,
178         std::string                  message,
179         const mi::IImpexp_state*     import_state) // not 0
180     {
181         std::ostringstream s;
182         s << import_state->get_uri()
183           << ":" << import_state->get_line_number() << ": "
184           << "Vanilla importer message " << message_number << ", "
185           << "severity " << enum_to_str( message_severity) << ": "
186           << message;
187         // Report context of all parent import states from recursive
188         // invocations of import_elements in their own lines with indentation.
189         import_state = import_state->get_parent_state();
190         while( import_state) {
191             s << "\n    included from: " << import_state->get_uri()
192               << ":" << import_state->get_line_number();
193             import_state = import_state->get_parent_state();
194         }
195         result->message_push_back( message_number, message_severity, s.str().c_str());
196         return result;
197     }
198 };
199 
200 // Returns true if the importer can handle the file type determined by the file name extension.
201 bool Vanilla_importer::test_file_type ( const char* extension ) const
202 {
203     // This importer supports the file name extensions ".vnl" and ".van".
204     mi::Size len = std::strlen( extension);
205     return (len > 3)
206         &&  (( 0 == strcmp( extension + len - 4, ".vnl"))
207           || ( 0 == strcmp( extension + len - 4, ".van")));
208 }
209 
210 // Returns true if the importer can handle the file type determined by the file name extension
211 // and if the reader has sufficient capabilities for import.
212 bool Vanilla_importer::test_file_type( const char* extension,
213                                        const mi::IReader* reader ) const
214 {
215     // Use magic header check if lookahead is available
216     if ( reader->supports_lookahead()) {
217         // File has to start with "VANILLA" and linebreak, which can
218         // be \n or \r depending on the line ending convention in the file.
219         const char** buffer = 0;
220         mi::Sint64 n = reader->lookahead( 8, buffer);
221         return ( n >= 8) && (0 == std::strncmp( *buffer, "VANILLA", 7))
222                          && ((*buffer[7] == '\n') || (*buffer[7] == '\r'));
223     }
224     // This importer supports the file name extensions ".vnl" and ".van".
225     mi::Size len = std::strlen( extension);
226     return (len > 3)
227         &&  (( 0 == strcmp( extension + len - 4, ".vnl"))
228           || ( 0 == strcmp( extension + len - 4, ".van")));
229 }
230 
231 // Imports all elements from the reader in a format determined by the file extension and
232 // (optionally) the lookahead of the reader.
233 mi::IImport_result* Vanilla_importer::import_elements (
234     mi::neuraylib::ITransaction* transaction,
235     const char*                  extension,
236     mi::IReader*                 reader,
237     const mi::IMap*              importer_options,
238     mi::IImpexp_state*           import_state) const
239 {
240     // Create the importer result instance for the return value.
241     // If that fails something is really wrong and we return 0.
242     mi::IImport_result_ext* result
243         = transaction->create<mi::IImport_result_ext>( "Import_result_ext");
244     if( !result)
245         return 0;
246 
247     // Get the 'prefix' option.
248     MISTD::string prefix;
249     if( importer_options && importer_options->has_key( "prefix")) {
250         mi::base::Handle<const mi::IString> option(
251             importer_options->get_value<mi::IString>( "prefix"));
252         prefix = option->get_c_str();
253     }
254 
255     // Get the 'list_elements' option.
256     bool list_elements = false;
257     if( importer_options && importer_options->has_key( "list_elements")) {
258         mi::base::Handle<const mi::IBoolean> option(
259             importer_options->get_value<mi::IBoolean>( "list_elements"));
260         list_elements = option->get_value<bool>();
261     }
262 
263     // Before we start parsing the file, we create a group that collects all
264     // top-level elements. This will be our rootgroup.
265     std::string root_group_name = prefix + "Vanilla::root_group";
266     mi::base::Handle<mi::IGroup> rootgroup( transaction->create<mi::IGroup>( "Group"));
267     mi::Sint32 error_code = transaction->store( rootgroup.get(), root_group_name.c_str());
268     if( error_code != 0)
269         return report_message( transaction, result, 4010, mi::base::MESSAGE_SEVERITY_ERROR,
270             "failed to create the root group", import_state);
271     rootgroup = transaction->edit<mi::IGroup>( root_group_name.c_str());
272 
273     // Register the rootgroup with the importer result.
274     result->set_rootgroup( root_group_name.c_str());
275 
276     // If the element list flag is set, record the rootgroup element also in the
277     // elements array of the result.
278     if ( list_elements)
279         result->element_push_back( root_group_name.c_str());
280 
281     // Assume it is a line based text format and read it line by line.
282     // Assume lines are no longer than 256 chars, otherwise read it in pieces
283     char buffer[257];
284     while ( reader->readline( buffer, 257) && buffer[0] != '\0') {
285 
286         // Periodically check whether the transaction is still open. If not, stop importing.
287         if( !transaction->is_open())
288             break;
289 
290         // Here you can process the buffer content of size len.
291         // It corresponds to the line import_state->get_line_number() of the input file.
292         mi::Size len = std::strlen( buffer);
293 
294         // We illustrate some actions triggered by fixed line numbers:
295         // Line 3 of a ".vnl" file triggers the recursive inclusion of the file "test2.van" file
296         // with a prefix.
297         mi::Size ext_len = std::strlen( extension);
298         if ( 3 == import_state->get_line_number()
299              && (ext_len > 3)
300              && 0 == strcmp( extension + ext_len - 4, ".vnl")) {
301             // Get a IImport_api handle to call its import_elements() function
302             mi::base::Handle<mi::neuraylib::IImport_api> import_api(
303                 m_iplugin_api->get_api_component<mi::neuraylib::IImport_api>());
304             if ( ! import_api.is_valid_interface())
305                 // Error numbers from 4000 to 5999 are reserved for custom importer messages like
306                 // this one
307                 return report_message( transaction, result,
308                    4001, mi::base::MESSAGE_SEVERITY_ERROR,
309                    "did not get a valid IImport_api object, import failed", import_state);
310             // Call the importer recursively to illustrate the handling of include files and
311             // similar things. We trigger this import only on a ".vnl" file and include the fixed
312             // file "test2.van". We give the included file an extra prefix "Prefix_".
313             mi::base::Handle<mi::neuraylib::IFactory> factory(
314                 m_iplugin_api->get_api_component<mi::neuraylib::IFactory>());
315             mi::base::Handle<mi::IString> child_prefix( factory->create<mi::IString>( "String"));
316             child_prefix->set_c_str( "Prefix_");
317             mi::base::Handle<mi::IMap> child_importer_options( factory->clone<mi::IMap>(
318                 importer_options));
319             child_importer_options->erase( "prefix");
320             child_importer_options->insert( "prefix", child_prefix.get());
321             mi::base::Handle<const mi::IImport_result> include_result(
322                 import_api->import_elements( transaction, "file:test2.van",
323                     child_importer_options.get(), import_state));
324             // Safety check, if this fails, the import is not continued.
325             if ( ! include_result.is_valid_interface())
326                 return report_message( transaction, result,
327                    4002, mi::base::MESSAGE_SEVERITY_ERROR,
328                    "import was not able to create result object, import failed", import_state);
329             // Process the result. Even in the case of an error, we need to process the
330             // elements array.
331             if ( list_elements)
332                 result->append_elements( include_result.get());
333             // Append messages of include to this result
334             result->append_messages( include_result.get());
335             // React on errors during processing of the include
336             if ( include_result->get_error_number() > 0) {
337                 // Report the failure of the include as a separate message too
338                 report_message( transaction, result,
339                     4003, mi::base::MESSAGE_SEVERITY_ERROR,
340                     "including file 'test2.van' failed.", import_state);
341             } else {
342                 // Recursive import was successful. The rootgroup of the
343                 // import is now appended to this rootgroup
344                 if ( 0 == include_result->get_rootgroup())
345                     report_message( transaction, result,
346                         4004, mi::base::MESSAGE_SEVERITY_ERROR,
347                         "include file 'test2.van' did not contain a rootgroup", import_state);
348                 else
349                     rootgroup->attach( include_result->get_rootgroup());
350             }
351         }
352 
353         // Line 4 triggers several messages and adds an empty group to the rootgroup.
354         if ( 4 == import_state->get_line_number()) {
355             // Several messages, file parsing continues
356             report_message( transaction, result, 4005, mi::base::MESSAGE_SEVERITY_FATAL,
357                           "test message in line 4", import_state);
358             report_message( transaction, result, 4006, mi::base::MESSAGE_SEVERITY_ERROR,
359                           "test message in line 4", import_state);
360             report_message( transaction, result, 4007, mi::base::MESSAGE_SEVERITY_WARNING,
361                           "test message in line 4", import_state);
362             report_message( transaction, result, 4008, mi::base::MESSAGE_SEVERITY_INFO,
363                           "test message in line 4", import_state);
364             report_message( transaction, result, 4009, mi::base::MESSAGE_SEVERITY_VERBOSE,
365                           "test message in line 4", import_state);
366             // Create a group "Vanilla::Group1"
367             std::string group_name = prefix + "Vanilla::Group1";
368             mi::base::Handle<mi::IGroup> group( transaction->create<mi::IGroup>( "Group"));
369             mi::Sint32 error_code = transaction->store( group.get(), group_name.c_str());
370             if( error_code != 0)
371                 report_message( transaction, result, 4011, mi::base::MESSAGE_SEVERITY_ERROR,
372                     "unexpected error in line 4", import_state);
373             else {
374                 // Add this group to the rootgroup
375                 rootgroup->attach( group_name.c_str());
376                 // If get_list_elements_flag is set, record the new element in the elements array
377                 // of the result.
378                 if ( list_elements)
379                     result->element_push_back( group_name.c_str());
380             }
381         }
382 
383         // Handle line numbers, buffer might end in '\n' or not if line was too long.
384         if ((len > 0) && ('\n' == buffer[len-1]))
385             import_state->incr_line_number();
386     }
387     if ( reader->eof()) {
388         // Normal end
389         return result;
390     }
391     // Report error condition for a failed reader call
392     return report_message( transaction,
393                          result,
394                          reader->get_error_number(),
395                          mi::base::MESSAGE_SEVERITY_ERROR,
396                          reader->get_error_message() ? reader->get_error_message() : "",
397                          import_state);
398 }

example_importer.cpp

001 /******************************************************************************
002  * © 1986, 2014 NVIDIA Corporation. All rights reserved.
003  *****************************************************************************/
004 
005 // examples/example_importer.cpp
006 //
007 // Demonstrates the implementation of custom importers
008 
009 #include <iostream>
010 
011 // Include code shared by all examples.
012 #include "example_shared.h"
013 
014 // Include header file for the Vanilla importer.
015 #include "vanilla_importer.h"
016 
017 // The importer.
018 mi::base::Handle<mi::neuraylib::IImporter> importer;
019 
020 void configuration( mi::base::Handle<mi::neuraylib::INeuray> neuray)
021 {
022     // Register the Vanilla importer.
023     mi::base::Handle<mi::neuraylib::IExtension_api> extension_api(
024         neuray->get_api_component<mi::neuraylib::IExtension_api>());
025     check_success( extension_api.is_valid_interface());
026     mi::base::Handle<mi::neuraylib::IPlugin_api> plugin_api(
027         neuray->get_api_component<mi::neuraylib::IPlugin_api>());
028     check_success( plugin_api.is_valid_interface());
029     importer = new Vanilla_importer( plugin_api.get());
030     check_success( extension_api->register_importer( importer.get()) == 0);
031 }
032 
033 void test_importer( mi::base::Handle<mi::neuraylib::INeuray> neuray)
034 {
035     // Get the database, the global scope, which is the root for all transactions,
036     // and create a transaction.
037     mi::base::Handle<mi::neuraylib::IDatabase> database(
038         neuray->get_api_component<mi::neuraylib::IDatabase>());
039     check_success( database.is_valid_interface());
040     mi::base::Handle<mi::neuraylib::IScope> scope(
041         database->get_global_scope());
042     mi::base::Handle<mi::neuraylib::ITransaction> transaction(
043         scope->create_transaction());
044     check_success( transaction.is_valid_interface());
045 
046     // Prepare the importer options:
047     // We do not want to have an additional prefix, but a list of all imported elements.
048     mi::base::Handle<mi::IBoolean> list_elements( transaction->create<mi::IBoolean>( "Boolean"));
049     list_elements->set_value( true);
050     mi::base::Handle<mi::IMap> importer_options( transaction->create<mi::IMap>( "Map<Interface>"));
051     importer_options->insert( "list_elements", list_elements.get());
052 
053     // Import the file test1.vnl (implicitly using the Vanilla importer).
054     mi::base::Handle<mi::neuraylib::IImport_api> import_api(
055         neuray->get_api_component<mi::neuraylib::IImport_api>());
056     check_success( import_api.is_valid_interface());
057     mi::base::Handle<const mi::IImport_result> import_result(
058         import_api->import_elements( transaction.get(), "file:test1.vnl", importer_options.get()));
059     check_success( import_result.is_valid_interface());
060 
061     // Print all messages
062     for( mi::Size i = 0; i < import_result->get_messages_length(); ++i)
063         std::cout << import_result->get_message( i) << std::endl;
064 
065     // Print all imported elements
066     for( mi::Size i = 0; i < import_result->get_elements_length(); ++i)
067         std::cout << import_result->get_element( i) << std::endl;
068 
069     transaction->commit();
070 }
071 
072 int main( int /*argc*/, char* /*argv*/[])
073 {
074     // Access the neuray library
075     mi::base::Handle<mi::neuraylib::INeuray> neuray( load_and_get_ineuray());
076     check_success( neuray.is_valid_interface());
077 
078     // Configure the neuray library
079     configuration( neuray);
080 
081     // Start the neuray library
082     check_success( neuray->start() == 0);
083 
084     // Test the Vanilla importer
085     test_importer( neuray);
086 
087     // Shut down the neuray library
088     check_success( neuray->shutdown() == 0);
089 
090     // Unregister the Vanilla importer.
091     mi::base::Handle<mi::neuraylib::IExtension_api> extension_api(
092         neuray->get_api_component<mi::neuraylib::IExtension_api>());
093     check_success( extension_api->unregister_importer( importer.get()) == 0);
094     importer = 0;
095     extension_api = 0;
096     neuray = 0;
097 
098     // Unload the neuray library
099     check_success( unload());
100 
101     keep_console_open();
102     return EXIT_SUCCESS;
103 }