Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSybren A. Stüvel <sybren@blender.org>2020-03-06 18:19:35 +0300
committerSybren A. Stüvel <sybren@blender.org>2020-03-06 18:19:45 +0300
commiteb522af4fec58876ac1b0a73ad9bcdae2d82d33f (patch)
tree485c6a1fb23b5be256757375e2157378d3a5c61b /source/blender/io/usd
parentff60dd8b18ed00902e5bdfd36882072db7af8735 (diff)
Cleanup: move Alembic, AVI, Collada, and USD to `source/blender/io`
This moves the `alembic`, `avi`, `collada`, and `usd` modules into a common `io` directory. This also cleans up some `#include "../../{somedir}/{somefile}.h"` by adding `../../io/{somedir}` to `CMakeLists.txt` and then just using `#include "{somefile}.h"`. No functional changes.
Diffstat (limited to 'source/blender/io/usd')
-rw-r--r--source/blender/io/usd/CMakeLists.txt111
-rw-r--r--source/blender/io/usd/intern/abstract_hierarchy_iterator.cc595
-rw-r--r--source/blender/io/usd/intern/abstract_hierarchy_iterator.h251
-rw-r--r--source/blender/io/usd/intern/usd_capi.cc233
-rw-r--r--source/blender/io/usd/intern/usd_exporter_context.h44
-rw-r--r--source/blender/io/usd/intern/usd_hierarchy_iterator.cc150
-rw-r--r--source/blender/io/usd/intern/usd_hierarchy_iterator.h71
-rw-r--r--source/blender/io/usd/intern/usd_writer_abstract.cc147
-rw-r--r--source/blender/io/usd/intern/usd_writer_abstract.h77
-rw-r--r--source/blender/io/usd/intern/usd_writer_camera.cc111
-rw-r--r--source/blender/io/usd/intern/usd_writer_camera.h38
-rw-r--r--source/blender/io/usd/intern/usd_writer_hair.cc90
-rw-r--r--source/blender/io/usd/intern/usd_writer_hair.h38
-rw-r--r--source/blender/io/usd/intern/usd_writer_light.cc112
-rw-r--r--source/blender/io/usd/intern/usd_writer_light.h37
-rw-r--r--source/blender/io/usd/intern/usd_writer_mesh.cc489
-rw-r--r--source/blender/io/usd/intern/usd_writer_mesh.h66
-rw-r--r--source/blender/io/usd/intern/usd_writer_metaball.cc81
-rw-r--r--source/blender/io/usd/intern/usd_writer_metaball.h42
-rw-r--r--source/blender/io/usd/intern/usd_writer_transform.cc64
-rw-r--r--source/blender/io/usd/intern/usd_writer_transform.h42
-rw-r--r--source/blender/io/usd/usd.h63
22 files changed, 2952 insertions, 0 deletions
diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt
new file mode 100644
index 00000000000..732a638a255
--- /dev/null
+++ b/source/blender/io/usd/CMakeLists.txt
@@ -0,0 +1,111 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# The Original Code is Copyright (C) 2019, Blender Foundation
+# All rights reserved.
+# ***** END GPL LICENSE BLOCK *****
+
+# This suppresses the warning "This file includes at least one deprecated or antiquated
+# header which may be removed without further notice at a future date", which is caused
+# by the USD library including <ext/hash_set> on Linux. This has been reported at:
+# https://github.com/PixarAnimationStudios/USD/issues/1057.
+if(UNIX AND NOT APPLE)
+ add_definitions(-D_GLIBCXX_PERMIT_BACKWARD_HASH)
+endif()
+if(WIN32)
+ add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
+endif()
+add_definitions(-DPXR_STATIC)
+
+set(INC
+ .
+ ../../blenkernel
+ ../../blenlib
+ ../../blenloader
+ ../../bmesh
+ ../../depsgraph
+ ../../editors/include
+ ../../makesdna
+ ../../makesrna
+ ../../windowmanager
+ ../../../../intern/guardedalloc
+ ../../../../intern/utfconv
+)
+
+set(INC_SYS
+ ${USD_INCLUDE_DIRS}
+ ${BOOST_INCLUDE_DIR}
+ ${TBB_INCLUDE_DIR}
+)
+
+set(SRC
+ intern/abstract_hierarchy_iterator.cc
+ intern/usd_capi.cc
+ intern/usd_hierarchy_iterator.cc
+ intern/usd_writer_abstract.cc
+ intern/usd_writer_camera.cc
+ intern/usd_writer_hair.cc
+ intern/usd_writer_light.cc
+ intern/usd_writer_mesh.cc
+ intern/usd_writer_metaball.cc
+ intern/usd_writer_transform.cc
+
+ usd.h
+ intern/abstract_hierarchy_iterator.h
+ intern/usd_exporter_context.h
+ intern/usd_hierarchy_iterator.h
+ intern/usd_writer_abstract.h
+ intern/usd_writer_camera.h
+ intern/usd_writer_hair.h
+ intern/usd_writer_light.h
+ intern/usd_writer_mesh.h
+ intern/usd_writer_metaball.h
+ intern/usd_writer_transform.h
+)
+
+set(LIB
+ bf_blenkernel
+ bf_blenlib
+)
+
+list(APPEND LIB
+ ${BOOST_LIBRARIES}
+)
+
+list(APPEND LIB
+)
+
+blender_add_lib(bf_usd "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
+
+if(WIN32)
+ set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_DEBUG " /WHOLEARCHIVE:${USD_DEBUG_LIB}")
+ set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_RELEASE " /WHOLEARCHIVE:${USD_RELEASE_LIB}")
+ set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_RELWITHDEBINFO " /WHOLEARCHIVE:${USD_RELEASE_LIB}")
+ set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_MINSIZEREL " /WHOLEARCHIVE:${USD_RELEASE_LIB}")
+endif()
+
+# Source: https://github.com/PixarAnimationStudios/USD/blob/master/BUILDING.md#linking-whole-archives
+if(WIN32)
+ target_link_libraries(bf_usd INTERFACE ${USD_LIBRARIES})
+elseif(CMAKE_COMPILER_IS_GNUCXX)
+ target_link_libraries(bf_usd INTERFACE "-Wl,--whole-archive ${USD_LIBRARIES} -Wl,--no-whole-archive ${TBB_LIBRARIES}")
+elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
+ target_link_libraries(bf_usd INTERFACE -Wl,-force_load ${USD_LIBRARIES})
+else()
+ message(FATAL_ERROR "Unknown how to link USD with your compiler ${CMAKE_CXX_COMPILER_ID}")
+endif()
+
+target_link_libraries(bf_usd INTERFACE ${TBB_LIBRARIES})
diff --git a/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc b/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc
new file mode 100644
index 00000000000..a8ed2c5f2a5
--- /dev/null
+++ b/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc
@@ -0,0 +1,595 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "abstract_hierarchy_iterator.h"
+
+#include <iostream>
+#include <limits.h>
+#include <sstream>
+#include <string>
+
+extern "C" {
+#include "BKE_anim.h"
+#include "BKE_particle.h"
+
+#include "BLI_assert.h"
+#include "BLI_listbase.h"
+#include "BLI_math_matrix.h"
+
+#include "DNA_ID.h"
+#include "DNA_layer_types.h"
+#include "DNA_object_types.h"
+#include "DNA_particle_types.h"
+
+#include "DEG_depsgraph_query.h"
+}
+
+namespace USD {
+
+const HierarchyContext *HierarchyContext::root()
+{
+ return nullptr;
+}
+
+bool HierarchyContext::operator<(const HierarchyContext &other) const
+{
+ if (object != other.object) {
+ return object < other.object;
+ }
+ if (duplicator != NULL && duplicator == other.duplicator) {
+ // Only resort to string comparisons when both objects are created by the same duplicator.
+ return export_name < other.export_name;
+ }
+
+ return export_parent < other.export_parent;
+}
+
+bool HierarchyContext::is_instance() const
+{
+ return !original_export_path.empty();
+}
+void HierarchyContext::mark_as_instance_of(const std::string &reference_export_path)
+{
+ original_export_path = reference_export_path;
+}
+void HierarchyContext::mark_as_not_instanced()
+{
+ original_export_path.clear();
+}
+
+AbstractHierarchyWriter::~AbstractHierarchyWriter()
+{
+}
+
+AbstractHierarchyIterator::AbstractHierarchyIterator(Depsgraph *depsgraph)
+ : depsgraph_(depsgraph), writers_()
+{
+}
+
+AbstractHierarchyIterator::~AbstractHierarchyIterator()
+{
+}
+
+void AbstractHierarchyIterator::iterate_and_write()
+{
+ export_graph_construct();
+ connect_loose_objects();
+ export_graph_prune();
+ determine_export_paths(HierarchyContext::root());
+ determine_duplication_references(HierarchyContext::root(), "");
+ make_writers(HierarchyContext::root());
+ export_graph_clear();
+}
+
+void AbstractHierarchyIterator::release_writers()
+{
+ for (WriterMap::value_type it : writers_) {
+ delete_object_writer(it.second);
+ }
+ writers_.clear();
+}
+
+std::string AbstractHierarchyIterator::make_valid_name(const std::string &name) const
+{
+ return name;
+}
+
+std::string AbstractHierarchyIterator::get_id_name(const ID *id) const
+{
+ if (id == nullptr) {
+ return "";
+ }
+
+ return make_valid_name(std::string(id->name + 2));
+}
+
+std::string AbstractHierarchyIterator::get_object_data_path(const HierarchyContext *context) const
+{
+ BLI_assert(!context->export_path.empty());
+ BLI_assert(context->object->data);
+
+ return path_concatenate(context->export_path, get_object_data_name(context->object));
+}
+
+void AbstractHierarchyIterator::debug_print_export_graph(const ExportGraph &graph) const
+{
+ size_t total_graph_size = 0;
+ for (const ExportGraph::value_type &map_iter : graph) {
+ const DupliAndDuplicator &parent_info = map_iter.first;
+ Object *const export_parent = parent_info.first;
+ Object *const duplicator = parent_info.second;
+
+ if (duplicator != nullptr) {
+ printf(" DU %s (as dupped by %s):\n",
+ export_parent == nullptr ? "-null-" : (export_parent->id.name + 2),
+ duplicator->id.name + 2);
+ }
+ else {
+ printf(" OB %s:\n", export_parent == nullptr ? "-null-" : (export_parent->id.name + 2));
+ }
+
+ total_graph_size += map_iter.second.size();
+ for (HierarchyContext *child_ctx : map_iter.second) {
+ if (child_ctx->duplicator == nullptr) {
+ printf(" - %s%s%s\n",
+ child_ctx->object->id.name + 2,
+ child_ctx->weak_export ? " (weak)" : "",
+ child_ctx->original_export_path.size() ?
+ (std::string("ref ") + child_ctx->original_export_path).c_str() :
+ "");
+ }
+ else {
+ printf(" - %s (dup by %s%s) %s\n",
+ child_ctx->object->id.name + 2,
+ child_ctx->duplicator->id.name + 2,
+ child_ctx->weak_export ? ", weak" : "",
+ child_ctx->original_export_path.size() ?
+ (std::string("ref ") + child_ctx->original_export_path).c_str() :
+ "");
+ }
+ }
+ }
+ printf(" (Total graph size: %lu objects\n", total_graph_size);
+}
+
+void AbstractHierarchyIterator::export_graph_construct()
+{
+ Scene *scene = DEG_get_evaluated_scene(depsgraph_);
+
+ DEG_OBJECT_ITER_BEGIN (depsgraph_,
+ object,
+ DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
+ DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET) {
+ // Non-instanced objects always have their object-parent as export-parent.
+ const bool weak_export = mark_as_weak_export(object);
+ visit_object(object, object->parent, weak_export);
+
+ if (weak_export) {
+ // If a duplicator shouldn't be exported, its duplilist also shouldn't be.
+ continue;
+ }
+
+ // Export the duplicated objects instanced by this object.
+ ListBase *lb = object_duplilist(depsgraph_, scene, object);
+ if (lb) {
+ // Construct the set of duplicated objects, so that later we can determine whether a parent
+ // is also duplicated itself.
+ std::set<Object *> dupli_set;
+ LISTBASE_FOREACH (DupliObject *, dupli_object, lb) {
+ if (!should_visit_dupli_object(dupli_object)) {
+ continue;
+ }
+ dupli_set.insert(dupli_object->ob);
+ }
+
+ LISTBASE_FOREACH (DupliObject *, dupli_object, lb) {
+ if (!should_visit_dupli_object(dupli_object)) {
+ continue;
+ }
+
+ visit_dupli_object(dupli_object, object, dupli_set);
+ }
+ }
+
+ free_object_duplilist(lb);
+ }
+ DEG_OBJECT_ITER_END;
+}
+
+void AbstractHierarchyIterator::connect_loose_objects()
+{
+ // Find those objects whose parent is not part of the export graph; these
+ // objects would be skipped when traversing the graph as a hierarchy.
+ // These objects will have to be re-attached to some parent object in order to
+ // fit into the hierarchy.
+ ExportGraph loose_objects_graph = export_graph_;
+ for (const ExportGraph::value_type &map_iter : export_graph_) {
+ for (const HierarchyContext *child : map_iter.second) {
+ // An object that is marked as a child of another object is not considered 'loose'.
+ loose_objects_graph.erase(std::make_pair(child->object, child->duplicator));
+ }
+ }
+ // The root of the hierarchy is always found, so it's never considered 'loose'.
+ loose_objects_graph.erase(std::make_pair(nullptr, nullptr));
+
+ // Iterate over the loose objects and connect them to their export parent.
+ for (const ExportGraph::value_type &map_iter : loose_objects_graph) {
+ const DupliAndDuplicator &export_info = map_iter.first;
+ Object *object = export_info.first;
+ Object *export_parent = object->parent;
+
+ while (true) {
+ // Loose objects will all be real objects, as duplicated objects always have
+ // their duplicator or other exported duplicated object as ancestor.
+ ExportGraph::iterator found_parent_iter = export_graph_.find(
+ std::make_pair(export_parent, nullptr));
+
+ visit_object(object, export_parent, true);
+ if (found_parent_iter != export_graph_.end()) {
+ break;
+ }
+ // 'export_parent' will never be nullptr here, as the export graph contains the
+ // tuple <nullptr, nullptr> as root and thus will cause a break.
+ BLI_assert(export_parent != nullptr);
+
+ object = export_parent;
+ export_parent = export_parent->parent;
+ }
+ }
+}
+
+static bool remove_weak_subtrees(const HierarchyContext *context,
+ AbstractHierarchyIterator::ExportGraph &clean_graph,
+ const AbstractHierarchyIterator::ExportGraph &input_graph)
+{
+ bool all_is_weak = context != nullptr && context->weak_export;
+ Object *object = context != nullptr ? context->object : nullptr;
+ Object *duplicator = context != nullptr ? context->duplicator : nullptr;
+
+ const AbstractHierarchyIterator::DupliAndDuplicator map_key = std::make_pair(object, duplicator);
+ AbstractHierarchyIterator::ExportGraph::const_iterator child_iterator;
+
+ child_iterator = input_graph.find(map_key);
+ if (child_iterator != input_graph.end()) {
+ for (HierarchyContext *child_context : child_iterator->second) {
+ bool child_tree_is_weak = remove_weak_subtrees(child_context, clean_graph, input_graph);
+ all_is_weak &= child_tree_is_weak;
+
+ if (child_tree_is_weak) {
+ // This subtree is all weak, so we can remove it from the current object's children.
+ clean_graph[map_key].erase(child_context);
+ delete child_context;
+ }
+ }
+ }
+
+ if (all_is_weak) {
+ // This node and all its children are weak, so it can be removed from the export graph.
+ clean_graph.erase(map_key);
+ }
+
+ return all_is_weak;
+}
+
+void AbstractHierarchyIterator::export_graph_prune()
+{
+ // Take a copy of the map so that we can modify while recursing.
+ ExportGraph unpruned_export_graph = export_graph_;
+ remove_weak_subtrees(HierarchyContext::root(), export_graph_, unpruned_export_graph);
+}
+
+void AbstractHierarchyIterator::export_graph_clear()
+{
+ for (ExportGraph::iterator::value_type &it : export_graph_) {
+ for (HierarchyContext *context : it.second) {
+ delete context;
+ }
+ }
+ export_graph_.clear();
+}
+
+void AbstractHierarchyIterator::visit_object(Object *object,
+ Object *export_parent,
+ bool weak_export)
+{
+ HierarchyContext *context = new HierarchyContext();
+ context->object = object;
+ context->export_name = get_object_name(object);
+ context->export_parent = export_parent;
+ context->duplicator = nullptr;
+ context->weak_export = weak_export;
+ context->animation_check_include_parent = false;
+ context->export_path = "";
+ context->original_export_path = "";
+ copy_m4_m4(context->matrix_world, object->obmat);
+
+ export_graph_[std::make_pair(export_parent, nullptr)].insert(context);
+}
+
+void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object,
+ Object *duplicator,
+ const std::set<Object *> &dupli_set)
+{
+ ExportGraph::key_type graph_index;
+ bool animation_check_include_parent = false;
+
+ HierarchyContext *context = new HierarchyContext();
+ context->object = dupli_object->ob;
+ context->duplicator = duplicator;
+ context->weak_export = false;
+ context->export_path = "";
+ context->original_export_path = "";
+
+ /* If the dupli-object's parent is also instanced by this object, use that as the
+ * export parent. Otherwise use the dupli-parent as export parent. */
+ Object *parent = dupli_object->ob->parent;
+ if (parent != nullptr && dupli_set.find(parent) != dupli_set.end()) {
+ // The parent object is part of the duplicated collection.
+ context->export_parent = parent;
+ graph_index = std::make_pair(parent, duplicator);
+ }
+ else {
+ /* The parent object is NOT part of the duplicated collection. This means that the world
+ * transform of this dupliobject can be influenced by objects that are not part of its
+ * export graph. */
+ animation_check_include_parent = true;
+ context->export_parent = duplicator;
+ graph_index = std::make_pair(duplicator, nullptr);
+ }
+
+ context->animation_check_include_parent = animation_check_include_parent;
+ copy_m4_m4(context->matrix_world, dupli_object->mat);
+
+ // Construct export name for the dupli-instance.
+ std::stringstream suffix_stream;
+ suffix_stream << std::hex;
+ for (int i = 0; i < MAX_DUPLI_RECUR && dupli_object->persistent_id[i] != INT_MAX; i++) {
+ suffix_stream << "-" << dupli_object->persistent_id[i];
+ }
+ context->export_name = make_valid_name(get_object_name(context->object) + suffix_stream.str());
+
+ export_graph_[graph_index].insert(context);
+}
+
+AbstractHierarchyIterator::ExportChildren &AbstractHierarchyIterator::graph_children(
+ const HierarchyContext *context)
+{
+ if (context == nullptr) {
+ return export_graph_[std::make_pair(nullptr, nullptr)];
+ }
+
+ return export_graph_[std::make_pair(context->object, context->duplicator)];
+}
+
+void AbstractHierarchyIterator::determine_export_paths(const HierarchyContext *parent_context)
+{
+ const std::string &parent_export_path = parent_context ? parent_context->export_path : "";
+
+ for (HierarchyContext *context : graph_children(parent_context)) {
+ context->export_path = path_concatenate(parent_export_path, context->export_name);
+
+ if (context->duplicator == nullptr) {
+ /* This is an original (i.e. non-instanced) object, so we should keep track of where it was
+ * exported to, just in case it gets instanced somewhere. */
+ ID *source_ob = &context->object->id;
+ duplisource_export_path_[source_ob] = context->export_path;
+
+ if (context->object->data != nullptr) {
+ ID *object_data = static_cast<ID *>(context->object->data);
+ ID *source_data = object_data;
+ duplisource_export_path_[source_data] = get_object_data_path(context);
+ }
+ }
+
+ determine_export_paths(context);
+ }
+}
+
+void AbstractHierarchyIterator::determine_duplication_references(
+ const HierarchyContext *parent_context, std::string indent)
+{
+ ExportChildren children = graph_children(parent_context);
+
+ for (HierarchyContext *context : children) {
+ if (context->duplicator != nullptr) {
+ ID *source_id = &context->object->id;
+ const ExportPathMap::const_iterator &it = duplisource_export_path_.find(source_id);
+
+ if (it == duplisource_export_path_.end()) {
+ // The original was not found, so mark this instance as "the original".
+ context->mark_as_not_instanced();
+ duplisource_export_path_[source_id] = context->export_path;
+ }
+ else {
+ context->mark_as_instance_of(it->second);
+ }
+
+ if (context->object->data) {
+ ID *source_data_id = (ID *)context->object->data;
+ const ExportPathMap::const_iterator &it = duplisource_export_path_.find(source_data_id);
+
+ if (it == duplisource_export_path_.end()) {
+ // The original was not found, so mark this instance as "original".
+ std::string data_path = get_object_data_path(context);
+ context->mark_as_not_instanced();
+ duplisource_export_path_[source_id] = context->export_path;
+ duplisource_export_path_[source_data_id] = data_path;
+ }
+ }
+ }
+
+ determine_duplication_references(context, indent + " ");
+ }
+}
+
+void AbstractHierarchyIterator::make_writers(const HierarchyContext *parent_context)
+{
+ AbstractHierarchyWriter *transform_writer = nullptr;
+ float parent_matrix_inv_world[4][4];
+
+ if (parent_context) {
+ invert_m4_m4(parent_matrix_inv_world, parent_context->matrix_world);
+ }
+ else {
+ unit_m4(parent_matrix_inv_world);
+ }
+
+ for (HierarchyContext *context : graph_children(parent_context)) {
+ copy_m4_m4(context->parent_matrix_inv_world, parent_matrix_inv_world);
+
+ // Get or create the transform writer.
+ transform_writer = ensure_writer(context, &AbstractHierarchyIterator::create_transform_writer);
+ if (transform_writer == nullptr) {
+ // Unable to export, so there is nothing to attach any children to; just abort this entire
+ // branch of the export hierarchy.
+ return;
+ }
+
+ BLI_assert(DEG_is_evaluated_object(context->object));
+ /* XXX This can lead to too many XForms being written. For example, a camera writer can refuse
+ * to write an orthographic camera. By the time that this is known, the XForm has already been
+ * written. */
+ transform_writer->write(*context);
+
+ if (!context->weak_export) {
+ make_writers_particle_systems(context);
+ make_writer_object_data(context);
+ }
+
+ // Recurse into this object's children.
+ make_writers(context);
+ }
+
+ // TODO(Sybren): iterate over all unused writers and call unused_during_iteration() or something.
+}
+
+void AbstractHierarchyIterator::make_writer_object_data(const HierarchyContext *context)
+{
+ if (context->object->data == nullptr) {
+ return;
+ }
+
+ HierarchyContext data_context = *context;
+ data_context.export_path = get_object_data_path(context);
+
+ /* data_context.original_export_path is just a copy from the context. It points to the object,
+ * but needs to point to the object data. */
+ if (data_context.is_instance()) {
+ ID *object_data = static_cast<ID *>(context->object->data);
+ data_context.original_export_path = duplisource_export_path_[object_data];
+
+ /* If the object is marked as an instance, so should the object data. */
+ BLI_assert(data_context.is_instance());
+ }
+
+ AbstractHierarchyWriter *data_writer;
+ data_writer = ensure_writer(&data_context, &AbstractHierarchyIterator::create_data_writer);
+ if (data_writer == nullptr) {
+ return;
+ }
+
+ data_writer->write(data_context);
+}
+
+void AbstractHierarchyIterator::make_writers_particle_systems(
+ const HierarchyContext *transform_context)
+{
+ Object *object = transform_context->object;
+ ParticleSystem *psys = static_cast<ParticleSystem *>(object->particlesystem.first);
+ for (; psys; psys = psys->next) {
+ if (!psys_check_enabled(object, psys, true)) {
+ continue;
+ }
+
+ HierarchyContext hair_context = *transform_context;
+ hair_context.export_path = path_concatenate(transform_context->export_path,
+ get_id_name(&psys->part->id));
+ hair_context.particle_system = psys;
+
+ AbstractHierarchyWriter *writer = nullptr;
+ switch (psys->part->type) {
+ case PART_HAIR:
+ writer = ensure_writer(&hair_context, &AbstractHierarchyIterator::create_hair_writer);
+ break;
+ case PART_EMITTER:
+ writer = ensure_writer(&hair_context, &AbstractHierarchyIterator::create_particle_writer);
+ break;
+ }
+
+ if (writer != nullptr) {
+ writer->write(hair_context);
+ }
+ }
+}
+
+std::string AbstractHierarchyIterator::get_object_name(const Object *object) const
+{
+ return get_id_name(&object->id);
+}
+
+std::string AbstractHierarchyIterator::get_object_data_name(const Object *object) const
+{
+ ID *object_data = static_cast<ID *>(object->data);
+ return get_id_name(object_data);
+}
+
+AbstractHierarchyWriter *AbstractHierarchyIterator::get_writer(const std::string &export_path)
+{
+ WriterMap::iterator it = writers_.find(export_path);
+
+ if (it == writers_.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+AbstractHierarchyWriter *AbstractHierarchyIterator::ensure_writer(
+ HierarchyContext *context, AbstractHierarchyIterator::create_writer_func create_func)
+{
+ AbstractHierarchyWriter *writer = get_writer(context->export_path);
+ if (writer != nullptr) {
+ return writer;
+ }
+
+ writer = (this->*create_func)(context);
+ if (writer == nullptr) {
+ return nullptr;
+ }
+
+ writers_[context->export_path] = writer;
+
+ return writer;
+}
+
+std::string AbstractHierarchyIterator::path_concatenate(const std::string &parent_path,
+ const std::string &child_path) const
+{
+ return parent_path + "/" + child_path;
+}
+
+bool AbstractHierarchyIterator::mark_as_weak_export(const Object * /*object*/) const
+{
+ return false;
+}
+bool AbstractHierarchyIterator::should_visit_dupli_object(const DupliObject *dupli_object) const
+{
+ // Removing dupli_object->no_draw hides things like custom bone shapes.
+ return !dupli_object->no_draw;
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/abstract_hierarchy_iterator.h b/source/blender/io/usd/intern/abstract_hierarchy_iterator.h
new file mode 100644
index 00000000000..8bca2ddd447
--- /dev/null
+++ b/source/blender/io/usd/intern/abstract_hierarchy_iterator.h
@@ -0,0 +1,251 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+
+/*
+ * This file contains the AbstractHierarchyIterator. It is intended for exporters for file
+ * formats that concern an entire hierarchy of objects (rather than, for example, an OBJ file that
+ * contains only a single mesh). Examples are Universal Scene Description (USD) and Alembic.
+ * AbstractHierarchyIterator is intended to be subclassed to support concrete file formats.
+ *
+ * The AbstractHierarchyIterator makes a distinction between the actual object hierarchy and the
+ * export hierarchy. The former is the parent/child structure in Blender, which can have multiple
+ * parent-like objects. For example, a duplicated object can have both a duplicator and a parent,
+ * both determining the final transform. The export hierarchy is the hierarchy as written to the
+ * file, and every object has only one export-parent.
+ *
+ * Currently the AbstractHierarchyIterator does not make any decisions about *what* to export.
+ * Selections like "selected only" or "no hair systems" are left to concrete subclasses.
+ */
+
+#ifndef __ABSTRACT_HIERARCHY_ITERATOR_H__
+#define __ABSTRACT_HIERARCHY_ITERATOR_H__
+
+#include <map>
+#include <string>
+#include <set>
+
+struct Base;
+struct Depsgraph;
+struct DupliObject;
+struct ID;
+struct Object;
+struct ParticleSystem;
+struct ViewLayer;
+
+namespace USD {
+
+class AbstractHierarchyWriter;
+
+/* HierarchyContext structs are created by the AbstractHierarchyIterator. Each HierarchyContext
+ * struct contains everything necessary to export a single object to a file. */
+struct HierarchyContext {
+ /*********** Determined during hierarchy iteration: ***************/
+ Object *object;
+ Object *export_parent;
+ Object *duplicator;
+ float matrix_world[4][4];
+ std::string export_name;
+
+ /* When weak_export=true, the object will be exported only as transform, and only if is an
+ * ancestor of an object with weak_export=false.
+ *
+ * In other words: when weak_export=true but this object has no children, or all decendants also
+ * have weak_export=true, this object (and by recursive reasoning all its decendants) will be
+ * excluded from the export.
+ *
+ * The export hierarchy is kept as close to the the hierarchy in Blender as possible. As such, an
+ * object that serves as a parent for another object, but which should NOT be exported itself, is
+ * exported only as transform (i.e. as empty). This happens with objects that are part of a
+ * holdout collection (which prevents them from being exported) but also parent of an exported
+ * object. */
+ bool weak_export;
+
+ /* When true, this object should check its parents for animation data when determining whether
+ * it's animated. This is necessary when a parent object in Blender is not part of the export. */
+ bool animation_check_include_parent;
+
+ /*********** Determined during writer creation: ***************/
+ float parent_matrix_inv_world[4][4]; // Inverse of the parent's world matrix.
+ std::string export_path; // Hierarchical path, such as "/grandparent/parent/objectname".
+ ParticleSystem *particle_system; // Only set for particle/hair writers.
+
+ /* Hierarchical path of the object this object is duplicating; only set when this object should
+ * be stored as a reference to its original. It can happen that the original is not part of the
+ * exported objects, in which case this string is empty even though 'duplicator' is set. */
+ std::string original_export_path;
+
+ bool operator<(const HierarchyContext &other) const;
+
+ /* Return a HierarchyContext representing the root of the export hierarchy. */
+ static const HierarchyContext *root();
+
+ /* For handling instanced collections, instances created by particles, etc. */
+ bool is_instance() const;
+ void mark_as_instance_of(const std::string &reference_export_path);
+ void mark_as_not_instanced();
+};
+
+/* Abstract writer for objects. Create concrete subclasses to write to USD, Alembic, etc.
+ *
+ * Instantiated by the AbstractHierarchyIterator on the first frame an object exists. Generally
+ * that's the first frame to be exported, but can be later, for example when objects are
+ * instantiated by particles. The AbstractHierarchyWriter::write() function is called on every
+ * frame the object exists in the dependency graph and should be exported.
+ */
+class AbstractHierarchyWriter {
+ public:
+ virtual ~AbstractHierarchyWriter();
+ virtual void write(HierarchyContext &context) = 0;
+ // TODO(Sybren): add function like absent() that's called when a writer was previously created,
+ // but wasn't used while exporting the current frame (for example, a particle-instanced mesh of
+ // which the particle is no longer alive).
+};
+
+/* AbstractHierarchyIterator iterates over objects in a dependency graph, and constructs export
+ * writers. These writers are then called to perform the actual writing to a USD or Alembic file.
+ *
+ * Dealing with file- and scene-level data (for example, creating a USD scene, setting the frame
+ * rate, etc.) is not part of the AbstractHierarchyIterator class structure, and should be done
+ * in separate code.
+ */
+class AbstractHierarchyIterator {
+ public:
+ /* Mapping from export path to writer. */
+ typedef std::map<std::string, AbstractHierarchyWriter *> WriterMap;
+ /* Pair of a (potentially duplicated) object and its duplicator (or nullptr).
+ * This is typically used to store a pair of HierarchyContext::object and
+ * HierarchyContext::duplicator. */
+ typedef std::pair<Object *, Object *> DupliAndDuplicator;
+ /* All the children of some object, as per the export hierarchy. */
+ typedef std::set<HierarchyContext *> ExportChildren;
+ /* Mapping from an object and its duplicator to the object's export-children. */
+ typedef std::map<DupliAndDuplicator, ExportChildren> ExportGraph;
+ /* Mapping from ID to its export path. This is used for instancing; given an
+ * instanced datablock, the export path of the original can be looked up. */
+ typedef std::map<ID *, std::string> ExportPathMap;
+
+ protected:
+ ExportGraph export_graph_;
+ ExportPathMap duplisource_export_path_;
+ Depsgraph *depsgraph_;
+ WriterMap writers_;
+
+ public:
+ explicit AbstractHierarchyIterator(Depsgraph *depsgraph);
+ virtual ~AbstractHierarchyIterator();
+
+ /* Iterate over the depsgraph, create writers, and tell the writers to write.
+ * Main entry point for the AbstractHierarchyIterator, must be called for every to-be-exported
+ * frame. */
+ void iterate_and_write();
+
+ /* Release all writers. Call after all frames have been exported. */
+ void release_writers();
+
+ /* Convert the given name to something that is valid for the exported file format.
+ * This base implementation is a no-op; override in a concrete subclass. */
+ virtual std::string make_valid_name(const std::string &name) const;
+
+ /* Return the name of this ID datablock that is valid for the exported file format. Overriding is
+ * only necessary if make_valid_name(id->name+2) is not suitable for the exported file format.
+ * NULL-safe: when `id == nullptr` this returns an empty string. */
+ virtual std::string get_id_name(const ID *id) const;
+
+ /* Given a HierarchyContext of some Object *, return an export path that is valid for its
+ * object->data. Overriding is necessary when the exported format does NOT expect the object's
+ * data to be a child of the object. */
+ virtual std::string get_object_data_path(const HierarchyContext *context) const;
+
+ private:
+ void debug_print_export_graph(const ExportGraph &graph) const;
+
+ void export_graph_construct();
+ void connect_loose_objects();
+ void export_graph_prune();
+ void export_graph_clear();
+
+ void visit_object(Object *object, Object *export_parent, bool weak_export);
+ void visit_dupli_object(DupliObject *dupli_object,
+ Object *duplicator,
+ const std::set<Object *> &dupli_set);
+
+ ExportChildren &graph_children(const HierarchyContext *parent_context);
+
+ void determine_export_paths(const HierarchyContext *parent_context);
+ void determine_duplication_references(const HierarchyContext *parent_context,
+ std::string indent);
+
+ void make_writers(const HierarchyContext *parent_context);
+ void make_writer_object_data(const HierarchyContext *context);
+ void make_writers_particle_systems(const HierarchyContext *context);
+
+ /* Convenience wrappers around get_id_name(). */
+ std::string get_object_name(const Object *object) const;
+ std::string get_object_data_name(const Object *object) const;
+
+ AbstractHierarchyWriter *get_writer(const std::string &export_path);
+
+ typedef AbstractHierarchyWriter *(AbstractHierarchyIterator::*create_writer_func)(
+ const HierarchyContext *);
+ /* Ensure that a writer exists; if it doesn't, call create_func(context). The create_func
+ * function should be one of the create_XXXX_writer(context) functions declared below. */
+ AbstractHierarchyWriter *ensure_writer(HierarchyContext *context,
+ create_writer_func create_func);
+
+ protected:
+ /* Construct a valid path for the export file format. This class concatenates by using '/' as a
+ * path separator, which is valid for both Alembic and USD. */
+ virtual std::string path_concatenate(const std::string &parent_path,
+ const std::string &child_path) const;
+
+ /* Return whether this object should be marked as 'weak export' or not.
+ *
+ * When this returns false, writers for the transform and data are created,
+ * and dupli-objects dupli-object generated from this object will be passed to
+ * should_visit_dupli_object().
+ *
+ * When this returns true, only a transform writer is created and marked as
+ * 'weak export'. In this case, the transform writer will be removed before
+ * exporting starts, unless a decendant of this object is to be exported.
+ * Dupli-object generated from this object will also be skipped.
+ *
+ * See HierarchyContext::weak_export.
+ */
+ virtual bool mark_as_weak_export(const Object *object) const;
+
+ virtual bool should_visit_dupli_object(const DupliObject *dupli_object) const;
+
+ /* These functions should create an AbstractHierarchyWriter subclass instance, or return
+ * nullptr if the object or its data should not be exported. Returning a nullptr for
+ * data/hair/particle will NOT prevent the transform to be written.
+ *
+ * The returned writer is owned by the AbstractHierarchyWriter, and should be freed in
+ * delete_object_writer(). */
+ virtual AbstractHierarchyWriter *create_transform_writer(const HierarchyContext *context) = 0;
+ virtual AbstractHierarchyWriter *create_data_writer(const HierarchyContext *context) = 0;
+ virtual AbstractHierarchyWriter *create_hair_writer(const HierarchyContext *context) = 0;
+ virtual AbstractHierarchyWriter *create_particle_writer(const HierarchyContext *context) = 0;
+
+ /* Called by release_writers() to free what the create_XXX_writer() functions allocated. */
+ virtual void delete_object_writer(AbstractHierarchyWriter *writer) = 0;
+};
+
+} // namespace USD
+
+#endif /* __ABSTRACT_HIERARCHY_ITERATOR_H__ */
diff --git a/source/blender/io/usd/intern/usd_capi.cc b/source/blender/io/usd/intern/usd_capi.cc
new file mode 100644
index 00000000000..83e11cd7bf3
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_capi.cc
@@ -0,0 +1,233 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+
+#include "usd.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/pxr.h>
+#include <pxr/usd/usd/stage.h>
+#include <pxr/usd/usdGeom/tokens.h>
+
+#include "MEM_guardedalloc.h"
+
+extern "C" {
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+#include "DEG_depsgraph_query.h"
+
+#include "DNA_scene_types.h"
+
+#include "BKE_blender_version.h"
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_scene.h"
+
+#include "BLI_fileops.h"
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+}
+
+namespace USD {
+
+struct ExportJobData {
+ ViewLayer *view_layer;
+ Main *bmain;
+ Depsgraph *depsgraph;
+ wmWindowManager *wm;
+
+ char filename[FILE_MAX];
+ USDExportParams params;
+
+ short *stop;
+ short *do_update;
+ float *progress;
+
+ bool was_canceled;
+ bool export_ok;
+};
+
+static void export_startjob(void *customdata, short *stop, short *do_update, float *progress)
+{
+ ExportJobData *data = static_cast<ExportJobData *>(customdata);
+
+ data->stop = stop;
+ data->do_update = do_update;
+ data->progress = progress;
+ data->was_canceled = false;
+
+ G.is_rendering = true;
+ WM_set_locked_interface(data->wm, true);
+ G.is_break = false;
+
+ // Construct the depsgraph for exporting.
+ Scene *scene = DEG_get_input_scene(data->depsgraph);
+ ViewLayer *view_layer = DEG_get_input_view_layer(data->depsgraph);
+ DEG_graph_build_from_view_layer(data->depsgraph, data->bmain, scene, view_layer);
+ BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
+
+ *progress = 0.0f;
+ *do_update = true;
+
+ // For restoring the current frame after exporting animation is done.
+ const int orig_frame = CFRA;
+
+ pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filename);
+ if (!usd_stage) {
+ /* This happens when the USD JSON files cannot be found. When that happens,
+ * the USD library doesn't know it has the functionality to write USDA and
+ * USDC files, and creating a new UsdStage fails. */
+ WM_reportf(
+ RPT_ERROR, "USD Export: unable to find suitable USD plugin to write %s", data->filename);
+ data->export_ok = false;
+ return;
+ }
+
+ usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z));
+ usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit,
+ pxr::VtValue(scene->unit.scale_length));
+ usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender ") + versionstr);
+
+ // Set up the stage for animated data.
+ if (data->params.export_animation) {
+ usd_stage->SetTimeCodesPerSecond(FPS);
+ usd_stage->SetStartTimeCode(scene->r.sfra);
+ usd_stage->SetEndTimeCode(scene->r.efra);
+ }
+
+ USDHierarchyIterator iter(data->depsgraph, usd_stage, data->params);
+
+ if (data->params.export_animation) {
+ // Writing the animated frames is not 100% of the work, but it's our best guess.
+ float progress_per_frame = 1.0f / std::max(1, (scene->r.efra - scene->r.sfra + 1));
+
+ for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) {
+ if (G.is_break || (stop != nullptr && *stop)) {
+ break;
+ }
+
+ // Update the scene for the next frame to render.
+ scene->r.cfra = static_cast<int>(frame);
+ scene->r.subframe = frame - scene->r.cfra;
+ BKE_scene_graph_update_for_newframe(data->depsgraph, data->bmain);
+
+ iter.set_export_frame(frame);
+ iter.iterate_and_write();
+
+ *progress += progress_per_frame;
+ *do_update = true;
+ }
+ }
+ else {
+ // If we're not animating, a single iteration over all objects is enough.
+ iter.iterate_and_write();
+ }
+
+ iter.release_writers();
+ usd_stage->GetRootLayer()->Save();
+
+ // Finish up by going back to the keyframe that was current before we started.
+ if (CFRA != orig_frame) {
+ CFRA = orig_frame;
+ BKE_scene_graph_update_for_newframe(data->depsgraph, data->bmain);
+ }
+
+ data->export_ok = !data->was_canceled;
+
+ *progress = 1.0f;
+ *do_update = true;
+}
+
+static void export_endjob(void *customdata)
+{
+ ExportJobData *data = static_cast<ExportJobData *>(customdata);
+
+ DEG_graph_free(data->depsgraph);
+
+ if (data->was_canceled && BLI_exists(data->filename)) {
+ BLI_delete(data->filename, false, false);
+ }
+
+ G.is_rendering = false;
+ WM_set_locked_interface(data->wm, false);
+}
+
+} // namespace USD
+
+bool USD_export(bContext *C,
+ const char *filepath,
+ const USDExportParams *params,
+ bool as_background_job)
+{
+ ViewLayer *view_layer = CTX_data_view_layer(C);
+ Scene *scene = CTX_data_scene(C);
+
+ USD::ExportJobData *job = static_cast<USD::ExportJobData *>(
+ MEM_mallocN(sizeof(USD::ExportJobData), "ExportJobData"));
+
+ job->bmain = CTX_data_main(C);
+ job->wm = CTX_wm_manager(C);
+ job->export_ok = false;
+ BLI_strncpy(job->filename, filepath, sizeof(job->filename));
+
+ job->depsgraph = DEG_graph_new(job->bmain, scene, view_layer, params->evaluation_mode);
+ job->params = *params;
+
+ bool export_ok = false;
+ if (as_background_job) {
+ wmJob *wm_job = WM_jobs_get(
+ job->wm, CTX_wm_window(C), scene, "USD Export", WM_JOB_PROGRESS, WM_JOB_TYPE_ALEMBIC);
+
+ /* setup job */
+ WM_jobs_customdata_set(wm_job, job, MEM_freeN);
+ WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME);
+ WM_jobs_callbacks(wm_job, USD::export_startjob, NULL, NULL, USD::export_endjob);
+
+ WM_jobs_start(CTX_wm_manager(C), wm_job);
+ }
+ else {
+ /* Fake a job context, so that we don't need NULL pointer checks while exporting. */
+ short stop = 0, do_update = 0;
+ float progress = 0.f;
+
+ USD::export_startjob(job, &stop, &do_update, &progress);
+ USD::export_endjob(job);
+ export_ok = job->export_ok;
+
+ MEM_freeN(job);
+ }
+
+ return export_ok;
+}
+
+int USD_get_version(void)
+{
+ /* USD 19.11 defines:
+ *
+ * #define PXR_MAJOR_VERSION 0
+ * #define PXR_MINOR_VERSION 19
+ * #define PXR_PATCH_VERSION 11
+ * #define PXR_VERSION 1911
+ *
+ * So the major version is implicit/invisible in the public version number.
+ */
+ return PXR_VERSION;
+}
diff --git a/source/blender/io/usd/intern/usd_exporter_context.h b/source/blender/io/usd/intern/usd_exporter_context.h
new file mode 100644
index 00000000000..4ae415b3d34
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_exporter_context.h
@@ -0,0 +1,44 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_EXPORTER_CONTEXT_H__
+#define __USD_EXPORTER_CONTEXT_H__
+
+#include "usd.h"
+
+#include <pxr/usd/sdf/path.h>
+#include <pxr/usd/usd/common.h>
+
+struct Depsgraph;
+struct Object;
+
+namespace USD {
+
+class USDHierarchyIterator;
+
+struct USDExporterContext {
+ Depsgraph *depsgraph;
+ const pxr::UsdStageRefPtr stage;
+ const pxr::SdfPath usd_path;
+ const USDHierarchyIterator *hierarchy_iterator;
+ const USDExportParams &export_params;
+};
+
+} // namespace USD
+
+#endif /* __USD_EXPORTER_CONTEXT_H__ */
diff --git a/source/blender/io/usd/intern/usd_hierarchy_iterator.cc b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc
new file mode 100644
index 00000000000..fd888f39adc
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc
@@ -0,0 +1,150 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd.h"
+
+#include "usd_hierarchy_iterator.h"
+#include "usd_writer_abstract.h"
+#include "usd_writer_camera.h"
+#include "usd_writer_hair.h"
+#include "usd_writer_light.h"
+#include "usd_writer_mesh.h"
+#include "usd_writer_metaball.h"
+#include "usd_writer_transform.h"
+
+#include <string>
+
+#include <pxr/base/tf/stringUtils.h>
+
+extern "C" {
+#include "BKE_anim.h"
+
+#include "BLI_assert.h"
+
+#include "DEG_depsgraph_query.h"
+
+#include "DNA_ID.h"
+#include "DNA_layer_types.h"
+#include "DNA_object_types.h"
+}
+
+namespace USD {
+
+USDHierarchyIterator::USDHierarchyIterator(Depsgraph *depsgraph,
+ pxr::UsdStageRefPtr stage,
+ const USDExportParams &params)
+ : AbstractHierarchyIterator(depsgraph), stage_(stage), params_(params)
+{
+}
+
+bool USDHierarchyIterator::mark_as_weak_export(const Object *object) const
+{
+ if (params_.selected_objects_only && (object->base_flag & BASE_SELECTED) == 0) {
+ return true;
+ }
+ return false;
+}
+
+void USDHierarchyIterator::delete_object_writer(AbstractHierarchyWriter *writer)
+{
+ delete static_cast<USDAbstractWriter *>(writer);
+}
+
+std::string USDHierarchyIterator::make_valid_name(const std::string &name) const
+{
+ return pxr::TfMakeValidIdentifier(name);
+}
+
+void USDHierarchyIterator::set_export_frame(float frame_nr)
+{
+ // The USD stage is already set up to have FPS timecodes per frame.
+ export_time_ = pxr::UsdTimeCode(frame_nr);
+}
+
+const pxr::UsdTimeCode &USDHierarchyIterator::get_export_time_code() const
+{
+ return export_time_;
+}
+
+USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context)
+{
+ return USDExporterContext{depsgraph_, stage_, pxr::SdfPath(context->export_path), this, params_};
+}
+
+AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer(
+ const HierarchyContext *context)
+{
+ return new USDTransformWriter(create_usd_export_context(context));
+}
+
+AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const HierarchyContext *context)
+{
+ USDExporterContext usd_export_context = create_usd_export_context(context);
+ USDAbstractWriter *data_writer = nullptr;
+
+ switch (context->object->type) {
+ case OB_MESH:
+ data_writer = new USDMeshWriter(usd_export_context);
+ break;
+ case OB_CAMERA:
+ data_writer = new USDCameraWriter(usd_export_context);
+ break;
+ case OB_LAMP:
+ data_writer = new USDLightWriter(usd_export_context);
+ break;
+ case OB_MBALL:
+ data_writer = new USDMetaballWriter(usd_export_context);
+ break;
+
+ case OB_EMPTY:
+ case OB_CURVE:
+ case OB_SURF:
+ case OB_FONT:
+ case OB_SPEAKER:
+ case OB_LIGHTPROBE:
+ case OB_LATTICE:
+ case OB_ARMATURE:
+ case OB_GPENCIL:
+ return nullptr;
+ case OB_TYPE_MAX:
+ BLI_assert(!"OB_TYPE_MAX should not be used");
+ return nullptr;
+ }
+
+ if (!data_writer->is_supported(context)) {
+ delete data_writer;
+ return nullptr;
+ }
+
+ return data_writer;
+}
+
+AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const HierarchyContext *context)
+{
+ if (!params_.export_hair) {
+ return nullptr;
+ }
+ return new USDHairWriter(create_usd_export_context(context));
+}
+
+AbstractHierarchyWriter *USDHierarchyIterator::create_particle_writer(const HierarchyContext *)
+{
+ return nullptr;
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_hierarchy_iterator.h b/source/blender/io/usd/intern/usd_hierarchy_iterator.h
new file mode 100644
index 00000000000..90c82c6e551
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_hierarchy_iterator.h
@@ -0,0 +1,71 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_HIERARCHY_ITERATOR_H__
+#define __USD_HIERARCHY_ITERATOR_H__
+
+#include "abstract_hierarchy_iterator.h"
+#include "usd_exporter_context.h"
+#include "usd.h"
+
+#include <string>
+
+#include <pxr/usd/usd/common.h>
+#include <pxr/usd/usd/timeCode.h>
+
+struct Depsgraph;
+struct ID;
+struct Object;
+
+namespace USD {
+
+class USDHierarchyIterator : public AbstractHierarchyIterator {
+ private:
+ const pxr::UsdStageRefPtr stage_;
+ pxr::UsdTimeCode export_time_;
+ const USDExportParams &params_;
+
+ public:
+ USDHierarchyIterator(Depsgraph *depsgraph,
+ pxr::UsdStageRefPtr stage,
+ const USDExportParams &params);
+
+ void set_export_frame(float frame_nr);
+ const pxr::UsdTimeCode &get_export_time_code() const;
+
+ virtual std::string make_valid_name(const std::string &name) const override;
+
+ protected:
+ virtual bool mark_as_weak_export(const Object *object) const override;
+
+ virtual AbstractHierarchyWriter *create_transform_writer(
+ const HierarchyContext *context) override;
+ virtual AbstractHierarchyWriter *create_data_writer(const HierarchyContext *context) override;
+ virtual AbstractHierarchyWriter *create_hair_writer(const HierarchyContext *context) override;
+ virtual AbstractHierarchyWriter *create_particle_writer(
+ const HierarchyContext *context) override;
+
+ virtual void delete_object_writer(AbstractHierarchyWriter *writer) override;
+
+ private:
+ USDExporterContext create_usd_export_context(const HierarchyContext *context);
+};
+
+} // namespace USD
+
+#endif /* __USD_HIERARCHY_ITERATOR_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_abstract.cc b/source/blender/io/usd/intern/usd_writer_abstract.cc
new file mode 100644
index 00000000000..4d0b4364fb5
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_abstract.cc
@@ -0,0 +1,147 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_abstract.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/base/tf/stringUtils.h>
+
+extern "C" {
+#include "BKE_animsys.h"
+#include "BKE_key.h"
+
+#include "DNA_modifier_types.h"
+}
+
+/* TfToken objects are not cheap to construct, so we do it once. */
+namespace usdtokens {
+// Materials
+static const pxr::TfToken diffuse_color("diffuseColor", pxr::TfToken::Immortal);
+static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal);
+static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal);
+static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
+static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
+static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
+} // namespace usdtokens
+
+namespace USD {
+
+USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
+ : usd_export_context_(usd_export_context),
+ usd_value_writer_(),
+ frame_has_been_written_(false),
+ is_animated_(false)
+{
+}
+
+USDAbstractWriter::~USDAbstractWriter()
+{
+}
+
+bool USDAbstractWriter::is_supported(const HierarchyContext * /*context*/) const
+{
+ return true;
+}
+
+pxr::UsdTimeCode USDAbstractWriter::get_export_time_code() const
+{
+ if (is_animated_) {
+ return usd_export_context_.hierarchy_iterator->get_export_time_code();
+ }
+ // By using the default timecode USD won't even write a single `timeSample` for non-animated
+ // data. Instead, it writes it as non-timesampled.
+ static pxr::UsdTimeCode default_timecode = pxr::UsdTimeCode::Default();
+ return default_timecode;
+}
+
+void USDAbstractWriter::write(HierarchyContext &context)
+{
+ if (!frame_has_been_written_) {
+ is_animated_ = usd_export_context_.export_params.export_animation &&
+ check_is_animated(context);
+ }
+ else if (!is_animated_) {
+ /* A frame has already been written, and without animation one frame is enough. */
+ return;
+ }
+
+ do_write(context);
+
+ frame_has_been_written_ = true;
+}
+
+bool USDAbstractWriter::check_is_animated(const HierarchyContext &context) const
+{
+ const Object *object = context.object;
+
+ if (BKE_animdata_id_is_animated(static_cast<ID *>(object->data))) {
+ return true;
+ }
+ if (BKE_key_from_object(object) != nullptr) {
+ return true;
+ }
+
+ /* Test modifiers. */
+ /* TODO(Sybren): replace this with a check on the depsgraph to properly check for dependency on
+ * time. */
+ ModifierData *md = static_cast<ModifierData *>(object->modifiers.first);
+ while (md) {
+ if (md->type != eModifierType_Subsurf) {
+ return true;
+ }
+ md = md->next;
+ }
+
+ return false;
+}
+
+const pxr::SdfPath &USDAbstractWriter::usd_path() const
+{
+ return usd_export_context_.usd_path;
+}
+
+pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(Material *material)
+{
+ static pxr::SdfPath material_library_path("/_materials");
+ pxr::UsdStageRefPtr stage = usd_export_context_.stage;
+
+ // Construct the material.
+ pxr::TfToken material_name(usd_export_context_.hierarchy_iterator->get_id_name(&material->id));
+ pxr::SdfPath usd_path = material_library_path.AppendChild(material_name);
+ pxr::UsdShadeMaterial usd_material = pxr::UsdShadeMaterial::Get(stage, usd_path);
+ if (usd_material) {
+ return usd_material;
+ }
+ usd_material = pxr::UsdShadeMaterial::Define(stage, usd_path);
+
+ // Construct the shader.
+ pxr::SdfPath shader_path = usd_path.AppendChild(usdtokens::preview_shader);
+ pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(stage, shader_path);
+ shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
+ shader.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f)
+ .Set(pxr::GfVec3f(material->r, material->g, material->b));
+ shader.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float).Set(material->roughness);
+ shader.CreateInput(usdtokens::metallic, pxr::SdfValueTypeNames->Float).Set(material->metallic);
+
+ // Connect the shader and the material together.
+ usd_material.CreateSurfaceOutput().ConnectToSource(shader, usdtokens::surface);
+
+ return usd_material;
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_abstract.h b/source/blender/io/usd/intern/usd_writer_abstract.h
new file mode 100644
index 00000000000..835d3a42c80
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_abstract.h
@@ -0,0 +1,77 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_ABSTRACT_H__
+#define __USD_WRITER_ABSTRACT_H__
+
+#include "usd_exporter_context.h"
+#include "abstract_hierarchy_iterator.h"
+
+#include <pxr/usd/sdf/path.h>
+#include <pxr/usd/usd/stage.h>
+#include <pxr/usd/usdShade/material.h>
+#include <pxr/usd/usdUtils/sparseValueWriter.h>
+
+#include <vector>
+
+extern "C" {
+#include "DEG_depsgraph_query.h"
+#include "DNA_material_types.h"
+}
+
+struct Material;
+struct Object;
+
+namespace USD {
+
+class USDAbstractWriter : public AbstractHierarchyWriter {
+ protected:
+ const USDExporterContext usd_export_context_;
+ pxr::UsdUtilsSparseValueWriter usd_value_writer_;
+
+ bool frame_has_been_written_;
+ bool is_animated_;
+
+ public:
+ USDAbstractWriter(const USDExporterContext &usd_export_context);
+ virtual ~USDAbstractWriter();
+
+ virtual void write(HierarchyContext &context) override;
+
+ /* Returns true if the data to be written is actually supported. This would, for example, allow a
+ * hypothetical camera writer accept a perspective camera but reject an orthogonal one.
+ *
+ * Returning false from a transform writer will prevent the object and all its decendants from
+ * being exported. Returning false from a data writer (object data, hair, or particles) will
+ * only prevent that data from being written (and thus cause the object to be exported as an
+ * Empty). */
+ virtual bool is_supported(const HierarchyContext *context) const;
+
+ const pxr::SdfPath &usd_path() const;
+
+ protected:
+ virtual void do_write(HierarchyContext &context) = 0;
+ virtual bool check_is_animated(const HierarchyContext &context) const;
+ pxr::UsdTimeCode get_export_time_code() const;
+
+ pxr::UsdShadeMaterial ensure_usd_material(Material *material);
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_ABSTRACT_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_camera.cc b/source/blender/io/usd/intern/usd_writer_camera.cc
new file mode 100644
index 00000000000..9b85d69559c
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_camera.cc
@@ -0,0 +1,111 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_camera.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/usd/usdGeom/camera.h>
+#include <pxr/usd/usdGeom/tokens.h>
+
+extern "C" {
+#include "BKE_camera.h"
+#include "BLI_assert.h"
+
+#include "DNA_camera_types.h"
+#include "DNA_scene_types.h"
+}
+
+namespace USD {
+
+USDCameraWriter::USDCameraWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
+{
+}
+
+bool USDCameraWriter::is_supported(const HierarchyContext *context) const
+{
+ Camera *camera = static_cast<Camera *>(context->object->data);
+ return camera->type == CAM_PERSP;
+}
+
+static void camera_sensor_size_for_render(const Camera *camera,
+ const struct RenderData *rd,
+ float *r_sensor_x,
+ float *r_sensor_y)
+{
+ /* Compute the final image size in pixels. */
+ float sizex = rd->xsch * rd->xasp;
+ float sizey = rd->ysch * rd->yasp;
+
+ int sensor_fit = BKE_camera_sensor_fit(camera->sensor_fit, sizex, sizey);
+
+ switch (sensor_fit) {
+ case CAMERA_SENSOR_FIT_HOR:
+ *r_sensor_x = camera->sensor_x;
+ *r_sensor_y = camera->sensor_x * sizey / sizex;
+ break;
+ case CAMERA_SENSOR_FIT_VERT:
+ *r_sensor_x = camera->sensor_y * sizex / sizey;
+ *r_sensor_y = camera->sensor_y;
+ break;
+ case CAMERA_SENSOR_FIT_AUTO:
+ BLI_assert(!"Camera fit should be either horizontal or vertical");
+ break;
+ }
+}
+
+void USDCameraWriter::do_write(HierarchyContext &context)
+{
+ pxr::UsdTimeCode timecode = get_export_time_code();
+ pxr::UsdGeomCamera usd_camera = pxr::UsdGeomCamera::Define(usd_export_context_.stage,
+ usd_export_context_.usd_path);
+
+ Camera *camera = static_cast<Camera *>(context.object->data);
+ Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
+
+ usd_camera.CreateProjectionAttr().Set(pxr::UsdGeomTokens->perspective);
+
+ /* USD stores the focal length in "millimeters or tenths of world units", because at some point
+ * they decided world units might be centimeters. Quite confusing, as the USD Viewer shows the
+ * correct FoV when we write millimeters and not "tenths of world units".
+ */
+ usd_camera.CreateFocalLengthAttr().Set(camera->lens, timecode);
+
+ float aperture_x, aperture_y;
+ camera_sensor_size_for_render(camera, &scene->r, &aperture_x, &aperture_y);
+
+ float film_aspect = aperture_x / aperture_y;
+ usd_camera.CreateHorizontalApertureAttr().Set(aperture_x, timecode);
+ usd_camera.CreateVerticalApertureAttr().Set(aperture_y, timecode);
+ usd_camera.CreateHorizontalApertureOffsetAttr().Set(aperture_x * camera->shiftx, timecode);
+ usd_camera.CreateVerticalApertureOffsetAttr().Set(aperture_y * camera->shifty * film_aspect,
+ timecode);
+
+ usd_camera.CreateClippingRangeAttr().Set(
+ pxr::VtValue(pxr::GfVec2f(camera->clip_start, camera->clip_end)), timecode);
+
+ // Write DoF-related attributes.
+ if (camera->dof.flag & CAM_DOF_ENABLED) {
+ usd_camera.CreateFStopAttr().Set(camera->dof.aperture_fstop, timecode);
+
+ float focus_distance = scene->unit.scale_length *
+ BKE_camera_object_dof_distance(context.object);
+ usd_camera.CreateFocusDistanceAttr().Set(focus_distance, timecode);
+ }
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_camera.h b/source/blender/io/usd/intern/usd_writer_camera.h
new file mode 100644
index 00000000000..971264ef11e
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_camera.h
@@ -0,0 +1,38 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_CAMERA_H__
+#define __USD_WRITER_CAMERA_H__
+
+#include "usd_writer_abstract.h"
+
+namespace USD {
+
+/* Writer for writing camera data to UsdGeomCamera. */
+class USDCameraWriter : public USDAbstractWriter {
+ public:
+ USDCameraWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual bool is_supported(const HierarchyContext *context) const override;
+ virtual void do_write(HierarchyContext &context) override;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_CAMERA_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_hair.cc b/source/blender/io/usd/intern/usd_writer_hair.cc
new file mode 100644
index 00000000000..9251425c0b8
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_hair.cc
@@ -0,0 +1,90 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_hair.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/usd/usdGeom/basisCurves.h>
+#include <pxr/usd/usdGeom/tokens.h>
+
+extern "C" {
+#include "BKE_particle.h"
+
+#include "DNA_particle_types.h"
+}
+
+namespace USD {
+
+USDHairWriter::USDHairWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
+{
+}
+
+void USDHairWriter::do_write(HierarchyContext &context)
+{
+ ParticleSystem *psys = context.particle_system;
+ ParticleCacheKey **cache = psys->pathcache;
+ if (cache == nullptr) {
+ return;
+ }
+
+ pxr::UsdTimeCode timecode = get_export_time_code();
+ pxr::UsdGeomBasisCurves curves = pxr::UsdGeomBasisCurves::Define(usd_export_context_.stage,
+ usd_export_context_.usd_path);
+
+ // TODO(Sybren): deal with (psys->part->flag & PART_HAIR_BSPLINE)
+ curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bspline));
+ curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
+
+ pxr::VtArray<pxr::GfVec3f> points;
+ pxr::VtIntArray curve_point_counts;
+ curve_point_counts.reserve(psys->totpart);
+
+ ParticleCacheKey *strand;
+ for (int strand_index = 0; strand_index < psys->totpart; ++strand_index) {
+ strand = cache[strand_index];
+
+ int point_count = strand->segments + 1;
+ curve_point_counts.push_back(point_count);
+
+ for (int point_index = 0; point_index < point_count; ++point_index, ++strand) {
+ points.push_back(pxr::GfVec3f(strand->co));
+ }
+ }
+
+ pxr::UsdAttribute attr_points = curves.CreatePointsAttr(pxr::VtValue(), true);
+ pxr::UsdAttribute attr_vertex_counts = curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true);
+ if (!attr_points.HasValue()) {
+ attr_points.Set(points, pxr::UsdTimeCode::Default());
+ attr_vertex_counts.Set(curve_point_counts, pxr::UsdTimeCode::Default());
+ }
+ usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(points), timecode);
+ usd_value_writer_.SetAttribute(attr_vertex_counts, pxr::VtValue(curve_point_counts), timecode);
+
+ if (psys->totpart > 0) {
+ pxr::VtArray<pxr::GfVec3f> colors;
+ colors.push_back(pxr::GfVec3f(cache[0]->col));
+ curves.CreateDisplayColorAttr(pxr::VtValue(colors));
+ }
+}
+
+bool USDHairWriter::check_is_animated(const HierarchyContext &) const
+{
+ return true;
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_hair.h b/source/blender/io/usd/intern/usd_writer_hair.h
new file mode 100644
index 00000000000..1e882fa1654
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_hair.h
@@ -0,0 +1,38 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_HAIR_H__
+#define __USD_WRITER_HAIR_H__
+
+#include "usd_writer_abstract.h"
+
+namespace USD {
+
+/* Writer for writing hair particle data as USD curves. */
+class USDHairWriter : public USDAbstractWriter {
+ public:
+ USDHairWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual void do_write(HierarchyContext &context) override;
+ virtual bool check_is_animated(const HierarchyContext &context) const override;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_HAIR_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_light.cc b/source/blender/io/usd/intern/usd_writer_light.cc
new file mode 100644
index 00000000000..e13e2c58a79
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_light.cc
@@ -0,0 +1,112 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_light.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/usd/usdLux/diskLight.h>
+#include <pxr/usd/usdLux/distantLight.h>
+#include <pxr/usd/usdLux/rectLight.h>
+#include <pxr/usd/usdLux/sphereLight.h>
+
+extern "C" {
+#include "BLI_assert.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_light_types.h"
+#include "DNA_object_types.h"
+}
+
+namespace USD {
+
+USDLightWriter::USDLightWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
+{
+}
+
+bool USDLightWriter::is_supported(const HierarchyContext *context) const
+{
+ Light *light = static_cast<Light *>(context->object->data);
+ return ELEM(light->type, LA_AREA, LA_LOCAL, LA_SUN);
+}
+
+void USDLightWriter::do_write(HierarchyContext &context)
+{
+ pxr::UsdStageRefPtr stage = usd_export_context_.stage;
+ const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
+ pxr::UsdTimeCode timecode = get_export_time_code();
+
+ Light *light = static_cast<Light *>(context.object->data);
+ pxr::UsdLuxLight usd_light;
+
+ switch (light->type) {
+ case LA_AREA:
+ switch (light->area_shape) {
+ case LA_AREA_DISK:
+ case LA_AREA_ELLIPSE: { /* An ellipse light will deteriorate into a disk light. */
+ pxr::UsdLuxDiskLight disk_light = pxr::UsdLuxDiskLight::Define(stage, usd_path);
+ disk_light.CreateRadiusAttr().Set(light->area_size, timecode);
+ usd_light = disk_light;
+ break;
+ }
+ case LA_AREA_RECT: {
+ pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path);
+ rect_light.CreateWidthAttr().Set(light->area_size, timecode);
+ rect_light.CreateHeightAttr().Set(light->area_sizey, timecode);
+ usd_light = rect_light;
+ break;
+ }
+ case LA_AREA_SQUARE: {
+ pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path);
+ rect_light.CreateWidthAttr().Set(light->area_size, timecode);
+ rect_light.CreateHeightAttr().Set(light->area_size, timecode);
+ usd_light = rect_light;
+ break;
+ }
+ }
+ break;
+ case LA_LOCAL: {
+ pxr::UsdLuxSphereLight sphere_light = pxr::UsdLuxSphereLight::Define(stage, usd_path);
+ sphere_light.CreateRadiusAttr().Set(light->area_size, timecode);
+ usd_light = sphere_light;
+ break;
+ }
+ case LA_SUN:
+ usd_light = pxr::UsdLuxDistantLight::Define(stage, usd_path);
+ break;
+ default:
+ BLI_assert(!"is_supported() returned true for unsupported light type");
+ }
+
+ /* Scale factor to get to somewhat-similar illumination. Since the USDViewer had similar
+ * over-exposure as Blender Internal with the same values, this code applies the reverse of the
+ * versioning code in light_emission_unify(). */
+ float usd_intensity;
+ if (light->type == LA_SUN) {
+ /* Untested, as the Hydra GL viewport of USDViewer doesn't support distant lights. */
+ usd_intensity = light->energy;
+ }
+ else {
+ usd_intensity = light->energy / 100.f;
+ }
+ usd_light.CreateIntensityAttr().Set(usd_intensity, timecode);
+
+ usd_light.CreateColorAttr().Set(pxr::GfVec3f(light->r, light->g, light->b), timecode);
+ usd_light.CreateSpecularAttr().Set(light->spec_fac, timecode);
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_light.h b/source/blender/io/usd/intern/usd_writer_light.h
new file mode 100644
index 00000000000..349c034b6bc
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_light.h
@@ -0,0 +1,37 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_LIGHT_H__
+#define __USD_WRITER_LIGHT_H__
+
+#include "usd_writer_abstract.h"
+
+namespace USD {
+
+class USDLightWriter : public USDAbstractWriter {
+ public:
+ USDLightWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual bool is_supported(const HierarchyContext *context) const override;
+ virtual void do_write(HierarchyContext &context) override;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_LIGHT_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc
new file mode 100644
index 00000000000..74005afaf31
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_mesh.cc
@@ -0,0 +1,489 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_mesh.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/usd/usdGeom/mesh.h>
+#include <pxr/usd/usdShade/material.h>
+#include <pxr/usd/usdShade/materialBindingAPI.h>
+
+extern "C" {
+#include "BLI_assert.h"
+#include "BLI_math_vector.h"
+
+#include "BKE_anim.h"
+#include "BKE_customdata.h"
+#include "BKE_lib_id.h"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+#include "BKE_modifier.h"
+#include "BKE_object.h"
+
+#include "DEG_depsgraph.h"
+
+#include "DNA_layer_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_object_fluidsim_types.h"
+#include "DNA_particle_types.h"
+}
+
+namespace USD {
+
+USDGenericMeshWriter::USDGenericMeshWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
+{
+}
+
+bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const
+{
+ Object *object = context->object;
+ bool is_dupli = context->duplicator != nullptr;
+ int base_flag;
+
+ if (is_dupli) {
+ /* Construct the object's base flags from its dupliparent, just like is done in
+ * deg_objects_dupli_iterator_next(). Without this, the visibility check below will fail. Doing
+ * this here, instead of a more suitable location in AbstractHierarchyIterator, prevents
+ * copying the Object for every dupli. */
+ base_flag = object->base_flag;
+ object->base_flag = context->duplicator->base_flag | BASE_FROM_DUPLI;
+ }
+
+ int visibility = BKE_object_visibility(object,
+ usd_export_context_.export_params.evaluation_mode);
+
+ if (is_dupli) {
+ object->base_flag = base_flag;
+ }
+
+ return (visibility & OB_VISIBLE_SELF) != 0;
+}
+
+void USDGenericMeshWriter::do_write(HierarchyContext &context)
+{
+ Object *object_eval = context.object;
+ bool needsfree = false;
+ Mesh *mesh = get_export_mesh(object_eval, needsfree);
+
+ if (mesh == NULL) {
+ return;
+ }
+
+ try {
+ write_mesh(context, mesh);
+
+ if (needsfree) {
+ free_export_mesh(mesh);
+ }
+ }
+ catch (...) {
+ if (needsfree) {
+ free_export_mesh(mesh);
+ }
+ throw;
+ }
+}
+
+void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
+{
+ BKE_id_free(NULL, mesh);
+}
+
+struct USDMeshData {
+ pxr::VtArray<pxr::GfVec3f> points;
+ pxr::VtIntArray face_vertex_counts;
+ pxr::VtIntArray face_indices;
+ std::map<short, pxr::VtIntArray> face_groups;
+
+ /* The length of this array specifies the number of creases on the surface. Each element gives
+ * the number of (must be adjacent) vertices in each crease, whose indices are linearly laid out
+ * in the 'creaseIndices' attribute. Since each crease must be at least one edge long, each
+ * element of this array should be greater than one. */
+ pxr::VtIntArray crease_lengths;
+ /* The indices of all vertices forming creased edges. The size of this array must be equal to the
+ * sum of all elements of the 'creaseLengths' attribute. */
+ pxr::VtIntArray crease_vertex_indices;
+ /* The per-crease or per-edge sharpness for all creases (Usd.Mesh.SHARPNESS_INFINITE for a
+ * perfectly sharp crease). Since 'creaseLengths' encodes the number of vertices in each crease,
+ * the number of elements in this array will be either len(creaseLengths) or the sum over all X
+ * of (creaseLengths[X] - 1). Note that while the RI spec allows each crease to have either a
+ * single sharpness or a value per-edge, USD will encode either a single sharpness per crease on
+ * a mesh, or sharpnesses for all edges making up the creases on a mesh. */
+ pxr::VtFloatArray crease_sharpnesses;
+};
+
+void USDGenericMeshWriter::write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
+{
+ pxr::UsdTimeCode timecode = get_export_time_code();
+
+ const CustomData *ldata = &mesh->ldata;
+ for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
+ const CustomDataLayer *layer = &ldata->layers[layer_idx];
+ if (layer->type != CD_MLOOPUV) {
+ continue;
+ }
+
+ /* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials.
+ * The primvar name is the same as the UV Map name. This is to allow the standard name "st"
+ * for texture coordinates by naming the UV Map as such, without having to guess which UV Map
+ * is the "standard" one. */
+ pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(layer->name));
+ pxr::UsdGeomPrimvar uv_coords_primvar = usd_mesh.CreatePrimvar(
+ primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
+
+ MLoopUV *mloopuv = static_cast<MLoopUV *>(layer->data);
+ pxr::VtArray<pxr::GfVec2f> uv_coords;
+ for (int loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) {
+ uv_coords.push_back(pxr::GfVec2f(mloopuv[loop_idx].uv));
+ }
+
+ if (!uv_coords_primvar.HasValue()) {
+ uv_coords_primvar.Set(uv_coords, pxr::UsdTimeCode::Default());
+ }
+ const pxr::UsdAttribute &uv_coords_attr = uv_coords_primvar.GetAttr();
+ usd_value_writer_.SetAttribute(uv_coords_attr, pxr::VtValue(uv_coords), timecode);
+ }
+}
+
+void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
+{
+ pxr::UsdTimeCode timecode = get_export_time_code();
+ pxr::UsdTimeCode defaultTime = pxr::UsdTimeCode::Default();
+ pxr::UsdStageRefPtr stage = usd_export_context_.stage;
+ const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
+
+ pxr::UsdGeomMesh usd_mesh = pxr::UsdGeomMesh::Define(stage, usd_path);
+ USDMeshData usd_mesh_data;
+ get_geometry_data(mesh, usd_mesh_data);
+
+ if (usd_export_context_.export_params.use_instancing && context.is_instance()) {
+ // This object data is instanced, just reference the original instead of writing a copy.
+ if (context.export_path == context.original_export_path) {
+ printf("USD ref error: export path is reference path: %s\n", context.export_path.c_str());
+ BLI_assert(!"USD reference error");
+ return;
+ }
+ pxr::SdfPath ref_path(context.original_export_path);
+ if (!usd_mesh.GetPrim().GetReferences().AddInternalReference(ref_path)) {
+ /* See this URL for a description fo why referencing may fail"
+ * https://graphics.pixar.com/usd/docs/api/class_usd_references.html#Usd_Failing_References
+ */
+ printf("USD Export warning: unable to add reference from %s to %s, not instancing object\n",
+ context.export_path.c_str(),
+ context.original_export_path.c_str());
+ return;
+ }
+ /* The material path will be of the form </_materials/{material name}>, which is outside the
+ subtree pointed to by ref_path. As a result, the referenced data is not allowed to point out
+ of its own subtree. It does work when we override the material with exactly the same path,
+ though.*/
+ if (usd_export_context_.export_params.export_materials) {
+ assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
+ }
+ return;
+ }
+
+ pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true);
+ pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(),
+ true);
+ pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(pxr::VtValue(),
+ true);
+
+ if (!attr_points.HasValue()) {
+ // Provide the initial value as default. This makes USD write the value as constant if they
+ // don't change over time.
+ attr_points.Set(usd_mesh_data.points, defaultTime);
+ attr_face_vertex_counts.Set(usd_mesh_data.face_vertex_counts, defaultTime);
+ attr_face_vertex_indices.Set(usd_mesh_data.face_indices, defaultTime);
+ }
+
+ usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(usd_mesh_data.points), timecode);
+ usd_value_writer_.SetAttribute(
+ attr_face_vertex_counts, pxr::VtValue(usd_mesh_data.face_vertex_counts), timecode);
+ usd_value_writer_.SetAttribute(
+ attr_face_vertex_indices, pxr::VtValue(usd_mesh_data.face_indices), timecode);
+
+ if (!usd_mesh_data.crease_lengths.empty()) {
+ pxr::UsdAttribute attr_crease_lengths = usd_mesh.CreateCreaseLengthsAttr(pxr::VtValue(), true);
+ pxr::UsdAttribute attr_crease_indices = usd_mesh.CreateCreaseIndicesAttr(pxr::VtValue(), true);
+ pxr::UsdAttribute attr_crease_sharpness = usd_mesh.CreateCreaseSharpnessesAttr(pxr::VtValue(),
+ true);
+
+ if (!attr_crease_lengths.HasValue()) {
+ attr_crease_lengths.Set(usd_mesh_data.crease_lengths, defaultTime);
+ attr_crease_indices.Set(usd_mesh_data.crease_vertex_indices, defaultTime);
+ attr_crease_sharpness.Set(usd_mesh_data.crease_sharpnesses, defaultTime);
+ }
+
+ usd_value_writer_.SetAttribute(
+ attr_crease_lengths, pxr::VtValue(usd_mesh_data.crease_lengths), timecode);
+ usd_value_writer_.SetAttribute(
+ attr_crease_indices, pxr::VtValue(usd_mesh_data.crease_vertex_indices), timecode);
+ usd_value_writer_.SetAttribute(
+ attr_crease_sharpness, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
+ }
+
+ if (usd_export_context_.export_params.export_uvmaps) {
+ write_uv_maps(mesh, usd_mesh);
+ }
+ if (usd_export_context_.export_params.export_normals) {
+ write_normals(mesh, usd_mesh);
+ }
+ write_surface_velocity(context.object, mesh, usd_mesh);
+
+ // TODO(Sybren): figure out what happens when the face groups change.
+ if (frame_has_been_written_) {
+ return;
+ }
+
+ usd_mesh.CreateSubdivisionSchemeAttr().Set(pxr::UsdGeomTokens->none);
+
+ if (usd_export_context_.export_params.export_materials) {
+ assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
+ }
+}
+
+static void get_vertices(const Mesh *mesh, USDMeshData &usd_mesh_data)
+{
+ usd_mesh_data.points.reserve(mesh->totvert);
+
+ const MVert *verts = mesh->mvert;
+ for (int i = 0; i < mesh->totvert; ++i) {
+ usd_mesh_data.points.push_back(pxr::GfVec3f(verts[i].co));
+ }
+}
+
+static void get_loops_polys(const Mesh *mesh, USDMeshData &usd_mesh_data)
+{
+ /* Only construct face groups (a.k.a. geometry subsets) when we need them for material
+ * assignments. */
+ bool construct_face_groups = mesh->totcol > 1;
+
+ usd_mesh_data.face_vertex_counts.reserve(mesh->totpoly);
+ usd_mesh_data.face_indices.reserve(mesh->totloop);
+
+ MLoop *mloop = mesh->mloop;
+ MPoly *mpoly = mesh->mpoly;
+ for (int i = 0; i < mesh->totpoly; ++i, ++mpoly) {
+ MLoop *loop = mloop + mpoly->loopstart;
+ usd_mesh_data.face_vertex_counts.push_back(mpoly->totloop);
+ for (int j = 0; j < mpoly->totloop; ++j, ++loop) {
+ usd_mesh_data.face_indices.push_back(loop->v);
+ }
+
+ if (construct_face_groups) {
+ usd_mesh_data.face_groups[mpoly->mat_nr].push_back(i);
+ }
+ }
+}
+
+static void get_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
+{
+ const float factor = 1.0f / 255.0f;
+
+ MEdge *edge = mesh->medge;
+ float sharpness;
+ for (int edge_idx = 0, totedge = mesh->totedge; edge_idx < totedge; ++edge_idx, ++edge) {
+ if (edge->crease == 0) {
+ continue;
+ }
+
+ if (edge->crease == 255) {
+ sharpness = pxr::UsdGeomMesh::SHARPNESS_INFINITE;
+ }
+ else {
+ sharpness = static_cast<float>(edge->crease) * factor;
+ }
+
+ usd_mesh_data.crease_vertex_indices.push_back(edge->v1);
+ usd_mesh_data.crease_vertex_indices.push_back(edge->v2);
+ usd_mesh_data.crease_lengths.push_back(2);
+ usd_mesh_data.crease_sharpnesses.push_back(sharpness);
+ }
+}
+
+void USDGenericMeshWriter::get_geometry_data(const Mesh *mesh, USDMeshData &usd_mesh_data)
+{
+ get_vertices(mesh, usd_mesh_data);
+ get_loops_polys(mesh, usd_mesh_data);
+ get_creases(mesh, usd_mesh_data);
+}
+
+void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
+ pxr::UsdGeomMesh usd_mesh,
+ const MaterialFaceGroups &usd_face_groups)
+{
+ if (context.object->totcol == 0) {
+ return;
+ }
+
+ /* Binding a material to a geometry subset isn't supported by the Hydra GL viewport yet,
+ * which is why we always bind the first material to the entire mesh. See
+ * https://github.com/PixarAnimationStudios/USD/issues/542 for more info. */
+ bool mesh_material_bound = false;
+ for (short mat_num = 0; mat_num < context.object->totcol; mat_num++) {
+ Material *material = BKE_object_material_get(context.object, mat_num + 1);
+ if (material == nullptr) {
+ continue;
+ }
+
+ pxr::UsdShadeMaterial usd_material = ensure_usd_material(material);
+ usd_material.Bind(usd_mesh.GetPrim());
+
+ /* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
+ * use the flag from the first non-empty material slot. */
+ usd_mesh.CreateDoubleSidedAttr(
+ pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
+
+ mesh_material_bound = true;
+ break;
+ }
+
+ if (!mesh_material_bound) {
+ /* Blender defaults to double-sided, but USD to single-sided. */
+ usd_mesh.CreateDoubleSidedAttr(pxr::VtValue(true));
+ }
+
+ if (!mesh_material_bound || usd_face_groups.size() < 2) {
+ /* Either all material slots were empty or there is only one material in use. As geometry
+ * subsets are only written when actually used to assign a material, and the mesh already has
+ * the material assigned, there is no need to continue. */
+ return;
+ }
+
+ // Define a geometry subset per material.
+ for (const MaterialFaceGroups::value_type &face_group : usd_face_groups) {
+ short material_number = face_group.first;
+ const pxr::VtIntArray &face_indices = face_group.second;
+
+ Material *material = BKE_object_material_get(context.object, material_number + 1);
+ if (material == nullptr) {
+ continue;
+ }
+
+ pxr::UsdShadeMaterial usd_material = ensure_usd_material(material);
+ pxr::TfToken material_name = usd_material.GetPath().GetNameToken();
+
+ pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(usd_mesh);
+ pxr::UsdGeomSubset usd_face_subset = api.CreateMaterialBindSubset(material_name, face_indices);
+ usd_material.Bind(usd_face_subset.GetPrim());
+ }
+}
+
+void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
+{
+ pxr::UsdTimeCode timecode = get_export_time_code();
+ const float(*lnors)[3] = static_cast<float(*)[3]>(CustomData_get_layer(&mesh->ldata, CD_NORMAL));
+
+ pxr::VtVec3fArray loop_normals;
+ loop_normals.reserve(mesh->totloop);
+
+ if (lnors != nullptr) {
+ /* Export custom loop normals. */
+ for (int loop_idx = 0, totloop = mesh->totloop; loop_idx < totloop; ++loop_idx) {
+ loop_normals.push_back(pxr::GfVec3f(lnors[loop_idx]));
+ }
+ }
+ else {
+ /* Compute the loop normals based on the 'smooth' flag. */
+ float normal[3];
+ MPoly *mpoly = mesh->mpoly;
+ const MVert *mvert = mesh->mvert;
+ for (int poly_idx = 0, totpoly = mesh->totpoly; poly_idx < totpoly; ++poly_idx, ++mpoly) {
+ MLoop *mloop = mesh->mloop + mpoly->loopstart;
+
+ if ((mpoly->flag & ME_SMOOTH) == 0) {
+ /* Flat shaded, use common normal for all verts. */
+ BKE_mesh_calc_poly_normal(mpoly, mloop, mvert, normal);
+ pxr::GfVec3f pxr_normal(normal);
+ for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx) {
+ loop_normals.push_back(pxr_normal);
+ }
+ }
+ else {
+ /* Smooth shaded, use individual vert normals. */
+ for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx, ++mloop) {
+ normal_short_to_float_v3(normal, mvert[mloop->v].no);
+ loop_normals.push_back(pxr::GfVec3f(normal));
+ }
+ }
+ }
+ }
+
+ pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true);
+ if (!attr_normals.HasValue()) {
+ attr_normals.Set(loop_normals, pxr::UsdTimeCode::Default());
+ }
+ usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), timecode);
+ usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
+}
+
+void USDGenericMeshWriter::write_surface_velocity(Object *object,
+ const Mesh *mesh,
+ pxr::UsdGeomMesh usd_mesh)
+{
+ /* Only velocities from the fluid simulation are exported. This is the most important case,
+ * though, as the baked mesh changes topology all the time, and thus computing the velocities
+ * at import time in a post-processing step is hard. */
+ ModifierData *md = modifiers_findByType(object, eModifierType_Fluidsim);
+ if (md == nullptr) {
+ return;
+ }
+
+ /* Check that the fluid sim modifier is enabled and has useful data. */
+ const bool use_render = (DEG_get_mode(usd_export_context_.depsgraph) == DAG_EVAL_RENDER);
+ const ModifierMode required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime;
+ const Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
+ if (!modifier_isEnabled(scene, md, required_mode)) {
+ return;
+ }
+ FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md);
+ if (!fsmd->fss || fsmd->fss->type != OB_FLUIDSIM_DOMAIN) {
+ return;
+ }
+ FluidsimSettings *fss = fsmd->fss;
+ if (!fss->meshVelocities) {
+ return;
+ }
+
+ /* Export per-vertex velocity vectors. */
+ pxr::VtVec3fArray usd_velocities;
+ usd_velocities.reserve(mesh->totvert);
+
+ FluidVertexVelocity *mesh_velocities = fss->meshVelocities;
+ for (int vertex_idx = 0, totvert = mesh->totvert; vertex_idx < totvert;
+ ++vertex_idx, ++mesh_velocities) {
+ usd_velocities.push_back(pxr::GfVec3f(mesh_velocities->vel));
+ }
+
+ pxr::UsdTimeCode timecode = get_export_time_code();
+ usd_mesh.CreateVelocitiesAttr().Set(usd_velocities, timecode);
+}
+
+USDMeshWriter::USDMeshWriter(const USDExporterContext &ctx) : USDGenericMeshWriter(ctx)
+{
+}
+
+Mesh *USDMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/)
+{
+ return BKE_object_get_evaluated_mesh(object_eval);
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_mesh.h b/source/blender/io/usd/intern/usd_writer_mesh.h
new file mode 100644
index 00000000000..4175e2b7e27
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_mesh.h
@@ -0,0 +1,66 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_MESH_H__
+#define __USD_WRITER_MESH_H__
+
+#include "usd_writer_abstract.h"
+
+#include <pxr/usd/usdGeom/mesh.h>
+
+namespace USD {
+
+struct USDMeshData;
+
+/* Writer for USD geometry. Does not assume the object is a mesh object. */
+class USDGenericMeshWriter : public USDAbstractWriter {
+ public:
+ USDGenericMeshWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual bool is_supported(const HierarchyContext *context) const override;
+ virtual void do_write(HierarchyContext &context) override;
+
+ virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) = 0;
+ virtual void free_export_mesh(Mesh *mesh);
+
+ private:
+ /* Mapping from material slot number to array of face indices with that material. */
+ typedef std::map<short, pxr::VtIntArray> MaterialFaceGroups;
+
+ void write_mesh(HierarchyContext &context, Mesh *mesh);
+ void get_geometry_data(const Mesh *mesh, struct USDMeshData &usd_mesh_data);
+ void assign_materials(const HierarchyContext &context,
+ pxr::UsdGeomMesh usd_mesh,
+ const MaterialFaceGroups &usd_face_groups);
+ void write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
+ void write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
+ void write_surface_velocity(Object *object, const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
+};
+
+class USDMeshWriter : public USDGenericMeshWriter {
+ public:
+ USDMeshWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_MESH_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_metaball.cc b/source/blender/io/usd/intern/usd_writer_metaball.cc
new file mode 100644
index 00000000000..25b216d20f3
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_metaball.cc
@@ -0,0 +1,81 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_metaball.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/usd/usdGeom/mesh.h>
+#include <pxr/usd/usdShade/material.h>
+#include <pxr/usd/usdShade/materialBindingAPI.h>
+
+extern "C" {
+#include "BLI_assert.h"
+
+#include "BKE_displist.h"
+#include "BKE_lib_id.h"
+#include "BKE_mball.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meta_types.h"
+}
+
+namespace USD {
+
+USDMetaballWriter::USDMetaballWriter(const USDExporterContext &ctx) : USDGenericMeshWriter(ctx)
+{
+}
+
+bool USDMetaballWriter::is_supported(const HierarchyContext *context) const
+{
+ Scene *scene = DEG_get_input_scene(usd_export_context_.depsgraph);
+ return is_basis_ball(scene, context->object) && USDGenericMeshWriter::is_supported(context);
+}
+
+bool USDMetaballWriter::check_is_animated(const HierarchyContext & /*context*/) const
+{
+ /* We assume that metaballs are always animated, as the current object may
+ * not be animated but another ball in the same group may be. */
+ return true;
+}
+
+Mesh *USDMetaballWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
+{
+ Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object_eval);
+ if (mesh_eval != nullptr) {
+ /* Mesh_eval only exists when generative modifiers are in use. */
+ r_needsfree = false;
+ return mesh_eval;
+ }
+ r_needsfree = true;
+ return BKE_mesh_new_from_object(usd_export_context_.depsgraph, object_eval, false);
+}
+
+void USDMetaballWriter::free_export_mesh(Mesh *mesh)
+{
+ BKE_id_free(nullptr, mesh);
+}
+
+bool USDMetaballWriter::is_basis_ball(Scene *scene, Object *ob) const
+{
+ Object *basis_ob = BKE_mball_basis_find(scene, ob);
+ return ob == basis_ob;
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_metaball.h b/source/blender/io/usd/intern/usd_writer_metaball.h
new file mode 100644
index 00000000000..1a86daae2ae
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_metaball.h
@@ -0,0 +1,42 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_METABALL_H__
+#define __USD_WRITER_METABALL_H__
+
+#include "usd_writer_mesh.h"
+
+namespace USD {
+
+class USDMetaballWriter : public USDGenericMeshWriter {
+ public:
+ USDMetaballWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
+ virtual void free_export_mesh(Mesh *mesh) override;
+ virtual bool is_supported(const HierarchyContext *context) const override;
+ virtual bool check_is_animated(const HierarchyContext &context) const override;
+
+ private:
+ bool is_basis_ball(Scene *scene, Object *ob) const;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_METABALL_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_transform.cc b/source/blender/io/usd/intern/usd_writer_transform.cc
new file mode 100644
index 00000000000..321b516221a
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_transform.cc
@@ -0,0 +1,64 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_transform.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/base/gf/matrix4f.h>
+#include <pxr/usd/usdGeom/xform.h>
+
+extern "C" {
+#include "BKE_object.h"
+
+#include "BLI_math_matrix.h"
+
+#include "DNA_layer_types.h"
+}
+
+namespace USD {
+
+USDTransformWriter::USDTransformWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
+{
+}
+
+void USDTransformWriter::do_write(HierarchyContext &context)
+{
+ float parent_relative_matrix[4][4]; // The object matrix relative to the parent.
+ mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, context.matrix_world);
+
+ // Write the transform relative to the parent.
+ pxr::UsdGeomXform xform = pxr::UsdGeomXform::Define(usd_export_context_.stage,
+ usd_export_context_.usd_path);
+ if (!xformOp_) {
+ xformOp_ = xform.AddTransformOp();
+ }
+ xformOp_.Set(pxr::GfMatrix4d(parent_relative_matrix), get_export_time_code());
+}
+
+bool USDTransformWriter::check_is_animated(const HierarchyContext &context) const
+{
+ if (context.duplicator != NULL) {
+ /* This object is being duplicated, so could be emitted by a particle system and thus
+ * influenced by forces. TODO(Sybren): Make this more strict. Probably better to get from the
+ * depsgraph whether this object instance has a time source. */
+ return true;
+ }
+ return BKE_object_moves_in_time(context.object, context.animation_check_include_parent);
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_transform.h b/source/blender/io/usd/intern/usd_writer_transform.h
new file mode 100644
index 00000000000..52c4a657f33
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_transform.h
@@ -0,0 +1,42 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_TRANSFORM_H__
+#define __USD_WRITER_TRANSFORM_H__
+
+#include "usd_writer_abstract.h"
+
+#include <pxr/usd/usdGeom/xform.h>
+
+namespace USD {
+
+class USDTransformWriter : public USDAbstractWriter {
+ private:
+ pxr::UsdGeomXformOp xformOp_;
+
+ public:
+ USDTransformWriter(const USDExporterContext &ctx);
+
+ protected:
+ void do_write(HierarchyContext &context) override;
+ bool check_is_animated(const HierarchyContext &context) const override;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_TRANSFORM_H__ */
diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h
new file mode 100644
index 00000000000..8a5575d53cf
--- /dev/null
+++ b/source/blender/io/usd/usd.h
@@ -0,0 +1,63 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+
+#ifndef __USD_H__
+#define __USD_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "DEG_depsgraph.h"
+
+struct Scene;
+struct bContext;
+
+struct USDExportParams {
+ bool export_animation;
+ bool export_hair;
+ bool export_uvmaps;
+ bool export_normals;
+ bool export_materials;
+ bool selected_objects_only;
+ bool use_instancing;
+ enum eEvaluationMode evaluation_mode;
+};
+
+/* The USD_export takes a as_background_job parameter, and returns a boolean.
+ *
+ * When as_background_job=true, returns false immediately after scheduling
+ * a background job.
+ *
+ * When as_background_job=false, performs the export synchronously, and returns
+ * true when the export was ok, and false if there were any errors.
+ */
+
+bool USD_export(struct bContext *C,
+ const char *filepath,
+ const struct USDExportParams *params,
+ bool as_background_job);
+
+int USD_get_version(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __USD_H__ */