/******************************************************************************
* Copyright 1986, 2016 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
#include <mi/neuraylib.h>
#include <string>
#include <list>
#include <set>
#include <map>
#include <utility> // for std::pair
#include <sstream>
// Support function to handle mi::base::Message_severity to std::string conversion
static std::string enum_to_str( mi::base::Message_severity severity)
{
switch( severity) {
case mi::base::MESSAGE_SEVERITY_FATAL: return "fatal";
case mi::base::MESSAGE_SEVERITY_ERROR: return "error";
case mi::base::MESSAGE_SEVERITY_WARNING: return "warning";
case mi::base::MESSAGE_SEVERITY_INFO: return "information";
case mi::base::MESSAGE_SEVERITY_VERBOSE: return "verbose";
case mi::base::MESSAGE_SEVERITY_DEBUG: return "debug";
default: return "unknown" ;
}
}
// Define exporter state, which is used in the exporter function to carry additional data around for
// possible recursive invocations of exporters. It contains a URI for the exported resource, a name
// space, a line number, and a pointer to a parent state supporting recursive exports.
class Vanilla_export_state
: public mi::base::Interface_implement< mi::neuraylib::IImpexp_state>
{
public:
// Constructor.
Vanilla_export_state( const char* uri, const mi::neuraylib::IImpexp_state* parent_state)
: m_line_number( 1),
m_uri( uri ? uri : ""),
m_parent_state( parent_state, mi::base::DUP_INTERFACE) { }
// Definition of all interface functions.
const char* get_uri() const { return m_uri.empty() ? 0 : m_uri.c_str(); }
mi::Uint32 get_line_number() const { return m_line_number; }
void set_line_number( mi::Uint32 number) { m_line_number = number; }
void incr_line_number() { ++m_line_number; }
const mi::neuraylib::IImpexp_state* get_parent_state() const
{
if( m_parent_state)
m_parent_state->retain();
return m_parent_state.get();
}
private:
mi::Uint32 m_line_number;
std::string m_uri;
mi::base::Handle<const mi::neuraylib::IImpexp_state> m_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>
{
public:
// Definition of all interface functions.
mi::neuraylib::IImpexp_state* create_impexp_state(
const char* uri, const mi::neuraylib::IImpexp_state* parent_state) const
{
return new Vanilla_export_state( uri, parent_state);
}
// This exporter supports the file name extensions ".vnl" and ".van".
const char* get_supported_extensions( mi::Uint32 i) const
{
switch( i) {
case 0: return ".vnl";
case 1: return ".van";
default: return 0;
}
}
mi::neuraylib::Impexp_priority get_priority() const
{
return mi::neuraylib::IMPEXP_PRIORITY_WELL_DEFINED;
}
const char* get_name() const { return "NVIDIA example vanilla (v1) exporter"; }
const char* get_author() const { return "NVIDIA ARC GmbH, Berlin, Germany"; }
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;
}
mi::Uint32 get_major_version() const { return 1; }
mi::Uint32 get_minor_version() const { return 0; }
bool test_file_type( const char* extension) const;
bool test_file_type( const char* extension, const mi::neuraylib::IWriter* writer) const;
mi::neuraylib::IExport_result* export_scene(
mi::neuraylib::ITransaction* transaction,
const char* extension,
mi::neuraylib::IWriter* writer,
const char* rootgroup,
const char* caminst,
const char* options,
const mi::IMap* exporter_options,
mi::neuraylib::IImpexp_state* export_state) const;
mi::neuraylib::IExport_result* export_elements(
mi::neuraylib::ITransaction* transaction,
const char* extension,
mi::neuraylib::IWriter* writer,
const mi::IArray* elements,
const mi::IMap* exporter_options,
mi::neuraylib::IImpexp_state* export_state) const;
private:
// Definition of support functions. They are not part of the interface.
// Formats message with context and appends it to the messages in the result.
static mi::neuraylib::IExport_result_ext* report_message(
mi::neuraylib::IExport_result_ext* result,
mi::Uint32 message_number,
mi::base::Message_severity message_severity,
std::string message,
const mi::neuraylib::IImpexp_state* export_state)
{
std::ostringstream s;
const char* uri = export_state->get_uri();
s << (uri ? uri : "(no URI)")
<< ":" << export_state->get_line_number() << ": "
<< "Vanilla exporter message " << message_number << ", "
<< "severity " << enum_to_str( message_severity) << ": "
<< message;
// Report context of all parent export states from recursive invocations of
// export_elements() in their own lines with indentation.
mi::base::Handle<const mi::neuraylib::IImpexp_state> parent_state(
export_state->get_parent_state());
while( parent_state.is_valid_interface()) {
s << "\n included from: " << parent_state->get_uri()
<< ":" << parent_state->get_line_number();
parent_state = parent_state->get_parent_state();
}
result->message_push_back( message_number, message_severity, s.str().c_str());
return result;
}
// Writes name to writer, writes the name without the leading prefix if it is equal to the
// prefix parameter.
static void write_name(
const std::string& name, const std::string& prefix, mi::neuraylib::IWriter* writer)
{
if( prefix.size() > 0 && 0 == name.compare( 0, prefix.size(), prefix))
writer->writeline( name.c_str() + prefix.size());
else
writer->writeline( name.c_str());
}
// Returns the element type of an element.
static std::string get_element_type( const mi::base::IInterface* interface)
{
mi::base::Handle<const mi::neuraylib::IGroup> group(
interface->get_interface<mi::neuraylib::IGroup>());
if( group.is_valid_interface())
return "Group";
mi::base::Handle<const mi::neuraylib::IInstance> instance(
interface->get_interface<mi::neuraylib::IInstance>());
if( instance.is_valid_interface())
return "Instance";
return "Unknown";
}
};
bool Vanilla_exporter::test_file_type( const char* extension) const
{
// This exporter supports the file name extensions ".vnl" and ".van".
mi::Size len = std::strlen( extension);
return (len > 3)
&& (( 0 == strcmp( extension + len - 4, ".vnl"))
|| ( 0 == strcmp( extension + len - 4, ".van")));
}
bool Vanilla_exporter::test_file_type(
const char* extension, const mi::neuraylib::IWriter*) const
{
// The writer capabilities do not matter for this simple format. More involved formats might
// require random access from the writer.
return test_file_type( extension);
}
mi::neuraylib::IExport_result* Vanilla_exporter::export_scene(
mi::neuraylib::ITransaction* transaction,
const char* /*extension*/,
mi::neuraylib::IWriter* writer,
const char* rootgroup,
const char* caminst,
const char* options,
const mi::IMap* exporter_options,
mi::neuraylib::IImpexp_state* export_state) const
{
// Create the exporter result instance for the return value. If that fails something is really
// wrong and we return 0.
mi::neuraylib::IExport_result_ext* result
= transaction->create<mi::neuraylib::IExport_result_ext>( "Export_result_ext");
if( !result)
return 0;
// Get the 'strip_prefix' option.
std::string strip_prefix;
if( exporter_options && exporter_options->has_key( "strip_prefix")) {
mi::base::Handle<const mi::IString> option(
exporter_options->get_value<mi::IString>( "strip_prefix"));
if( !option.is_valid_interface())
return report_message(
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->writeline( "VANILLA\r\n");
// Main loop through all elements
// This is a simplified recursive directed acyclic graph traversal on the scene graph that
// performs a depth first search. The traversal is implemented as an iterative loop and a stack
// on the elements list data structure. The boolean value of entries in the elements list
// encodes whether we are descending or are ascending in the graph. This flag determines whether
// we need to expand into the element and put all its children on the stack, or whether we are
// done with the element and can write it out to file. Other exporters might need to manage more
// data during the traversal, such as a transformation stack.
while( (0 == writer->get_error_number()) && (!elements.empty())) {
if( elements.front().second) {
// Traversal is ascending in the scene graph
// Keep element name and remove it from list
std::string name = elements.front().first;
elements.pop_front();
// Check if element has not been written yet
if( elements_exported.find( name) == elements_exported.end()) {
// Element can be written to file, mark it as written
elements_exported.insert( name);
// Access the element in the DB
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 message.
std::string message( "Element '");
message += name + "' does not exist in database, export failed.";
// Error numbers from 6000 to 7999 are reserved for custom
// exporter messages like this one
return report_message( result, 6001, mi::base::MESSAGE_SEVERITY_ERROR,
message, export_state);
}
writer->writeline( get_element_type( element.get()).c_str());
writer->writeline( " \"");
write_name( name, strip_prefix, writer);
writer->writeline( "\"\r\n");
}
} else {
// Traversal is descending in the scene graph, mark element as expanded.
elements.front().second = true;
// Expand front element, but keep it in the list.
std::string name = elements.front().first;
// Access the element in the DB.
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 message.
std::string message( "Element '");
message += name + "' does not exist in database, export failed.";
return report_message( result, 6002, mi::base::MESSAGE_SEVERITY_ERROR,
message, export_state);
}
// Dispatch on the type name of the element.
std::string element_type = get_element_type( element.get());
if( element_type == "Group") {
mi::base::Handle<const mi::neuraylib::IGroup> group(
element->get_interface<mi::neuraylib::IGroup>());
// Enumerate all elements in the group and push them in reverse order on the
// elements list front.
mi::Uint32 group_size = group->get_length();
for( mi::Uint32 i = 0; i != group_size; ++i) {
const char* element_name = group->get_element( group_size - i -1);
// Optimization: put name only in the elements list if it has not been exported
// yet.
if( elements_exported.find( element_name) == elements_exported.end())
elements.push_front( std::make_pair( std::string( element_name), false));
}
} else if( element_type == "Instance") {
mi::base::Handle<const mi::neuraylib::IInstance> instance(
element->get_interface<mi::neuraylib::IInstance>());
// Get element in the instance and push it on the elements list front.
const char* element_name = instance->get_item();
// Optimization: put name only in the elements list if it has not been exported yet.
if( elements_exported.find( element_name) == elements_exported.end())
elements.push_front( std::make_pair( std::string( element_name), false));
}
}
}
// Report message condition for a possibly failed writer call
if( writer->get_error_number() != 0)
return report_message(
result,
static_cast<mi::Uint32>( writer->get_error_number()),
mi::base::MESSAGE_SEVERITY_ERROR,
writer->get_error_message() ? writer->get_error_message() : "",
export_state);
return result;
}
// Exports all scene elements mentioned in the elements array through the writer.
mi::neuraylib::IExport_result* Vanilla_exporter::export_elements(
mi::neuraylib::ITransaction* transaction,
const char* /*extension*/,
mi::neuraylib::IWriter* writer,
const mi::IArray* elements,
const mi::IMap* exporter_options,
mi::neuraylib::IImpexp_state* export_state) const
{
// Create the exporter result instance for the return value.
// If that fails something is really wrong and we return 0.
mi::neuraylib::IExport_result_ext* result
= transaction->create<mi::neuraylib::IExport_result_ext>( "Export_result_ext");
if( !result)
return 0;
// Get the 'strip_prefix' option.
std::string strip_prefix;
if( exporter_options && exporter_options->has_key( "strip_prefix")) {
mi::base::Handle<const mi::IString> option(
exporter_options->get_value<mi::IString>( "strip_prefix"));
if( !option.is_valid_interface())
return report_message(
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->writeline( "VANILLA\x0D\x0A");
// Iterate through the string array of element names
mi::Size size = elements->get_length();
for( mi::Size i = 0; (0 == writer->get_error_number()) && i < size; ++i) {
// Get string for element i from the array
mi::base::Handle<const mi::IString> name( elements->get_element<mi::IString>( i));
if( !name.is_valid_interface()) {
return report_message( result, 6007, mi::base::MESSAGE_SEVERITY_ERROR,
"element array contains an invalid object", export_state);
}
const char* element_name = name->get_c_str();
// Access the element in the DB
mi::base::Handle<const mi::base::IInterface> element( transaction->access( element_name));
if( !element.is_valid_interface()) {
std::string message( "Element '");
message += element_name;
message += "' does not exist in database, export failed.";
return report_message( result, 6008, mi::base::MESSAGE_SEVERITY_ERROR,
message, export_state);
}
// Write element to file
writer->writeline( get_element_type( element.get()).c_str());
writer->writeline( " \"");
write_name( element_name, strip_prefix, writer);
writer->writeline( "\"\x0D\x0A");
}
return result;
}