From 084c5d6c7e2cf89bb9a7a9a9d00e9ee4475e222f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 8 May 2020 16:17:08 +0200 Subject: IO: Move Abstract Hierarchy Iterator into `io/common` The goal of the `AbstractHierarchyIterator` class (and supporting classes) was to use it in different exporters. It shouldn't be part of the USD module + namespace any more, now that it will also be used in the upcoming Alembic exporter rewrite. The source files are moved into `io/common`, which is compiled & linked into a new library `bf_io_common`. The unittests are still inside the `tests/gtests/usd` directory. They should be moved to a separate test module too, but that will be delayed until after T73268 has been resolved. Reviewed By: mont29 Differential Revision: https://developer.blender.org/D7669 --- source/blender/io/CMakeLists.txt | 2 + source/blender/io/alembic/CMakeLists.txt | 2 + source/blender/io/common/CMakeLists.txt | 45 ++ .../io/common/IO_abstract_hierarchy_iterator.h | 265 +++++++++ .../common/intern/abstract_hierarchy_iterator.cc | 654 +++++++++++++++++++++ source/blender/io/usd/CMakeLists.txt | 4 +- .../io/usd/intern/abstract_hierarchy_iterator.cc | 652 -------------------- .../io/usd/intern/abstract_hierarchy_iterator.h | 263 --------- .../blender/io/usd/intern/usd_hierarchy_iterator.h | 6 +- source/blender/io/usd/intern/usd_writer_abstract.h | 5 +- 10 files changed, 979 insertions(+), 919 deletions(-) create mode 100644 source/blender/io/common/CMakeLists.txt create mode 100644 source/blender/io/common/IO_abstract_hierarchy_iterator.h create mode 100644 source/blender/io/common/intern/abstract_hierarchy_iterator.cc delete mode 100644 source/blender/io/usd/intern/abstract_hierarchy_iterator.cc delete mode 100644 source/blender/io/usd/intern/abstract_hierarchy_iterator.h (limited to 'source/blender/io') diff --git a/source/blender/io/CMakeLists.txt b/source/blender/io/CMakeLists.txt index bc2f8d628e2..360cacc4360 100644 --- a/source/blender/io/CMakeLists.txt +++ b/source/blender/io/CMakeLists.txt @@ -18,6 +18,8 @@ # All rights reserved. # ***** END GPL LICENSE BLOCK ***** +add_subdirectory(common) + if(WITH_ALEMBIC) add_subdirectory(alembic) endif() diff --git a/source/blender/io/alembic/CMakeLists.txt b/source/blender/io/alembic/CMakeLists.txt index 16f2d944876..d864aa51e64 100644 --- a/source/blender/io/alembic/CMakeLists.txt +++ b/source/blender/io/alembic/CMakeLists.txt @@ -20,6 +20,7 @@ set(INC . + ../common ../../blenkernel ../../blenlib ../../blenloader @@ -92,6 +93,7 @@ set(SRC set(LIB bf_blenkernel bf_blenlib + bf_io_common ${ALEMBIC_LIBRARIES} ${OPENEXR_LIBRARIES} diff --git a/source/blender/io/common/CMakeLists.txt b/source/blender/io/common/CMakeLists.txt new file mode 100644 index 00000000000..4ed6f12762e --- /dev/null +++ b/source/blender/io/common/CMakeLists.txt @@ -0,0 +1,45 @@ +# ***** 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) 2020, Blender Foundation +# All rights reserved. +# ***** END GPL LICENSE BLOCK ***** + +set(INC + . + ../../blenkernel + ../../blenlib + ../../depsgraph + ../../makesdna +) + +set(INC_SYS +) + +set(SRC + intern/abstract_hierarchy_iterator.cc + + IO_abstract_hierarchy_iterator.h +) + +set(LIB + bf_blenkernel + bf_blenlib +) + +blender_add_lib(bf_io_common "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") + +target_link_libraries(bf_io_common INTERFACE) diff --git a/source/blender/io/common/IO_abstract_hierarchy_iterator.h b/source/blender/io/common/IO_abstract_hierarchy_iterator.h new file mode 100644 index 00000000000..480b72ea0cf --- /dev/null +++ b/source/blender/io/common/IO_abstract_hierarchy_iterator.h @@ -0,0 +1,265 @@ +/* + * 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 +#include +#include + +struct Base; +struct Depsgraph; +struct DupliObject; +struct ID; +struct Object; +struct ParticleSystem; +struct ViewLayer; + +namespace blender { +namespace io { + +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; /* Evaluated 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 descendants also + * have weak_export=true, this object (and by recursive reasoning all its descendants) will be + * excluded from the export. + * + * The export hierarchy is kept as close to 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). + protected: + virtual bool check_is_animated(const HierarchyContext &context) const; +}; + +/* 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 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 DupliAndDuplicator; + /* All the children of some object, as per the export hierarchy. */ + typedef std::set ExportChildren; + /* Mapping from an object and its duplicator to the object's export-children. */ + typedef std::map 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 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 &dupli_set); + + ExportChildren &graph_children(const HierarchyContext *parent_context); + void context_update_for_graph_index(HierarchyContext *context, + const ExportGraph::key_type &graph_index) const; + + void determine_export_paths(const HierarchyContext *parent_context); + void determine_duplication_references(const HierarchyContext *parent_context, + std::string indent); + + /* These three functions create writers and call their write() method. */ + 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) const; + + 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 descendant 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; + + virtual ExportGraph::key_type determine_graph_index_object(const HierarchyContext *context); + virtual ExportGraph::key_type determine_graph_index_dupli(const HierarchyContext *context, + const std::set &dupli_set); + + /* 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(). + * + * The created AbstractHierarchyWriter instances should NOT keep a copy of the context pointer. + * The context can be stack-allocated and go out of scope. */ + 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 io +} // namespace blender + +#endif /* __ABSTRACT_HIERARCHY_ITERATOR_H__ */ diff --git a/source/blender/io/common/intern/abstract_hierarchy_iterator.cc b/source/blender/io/common/intern/abstract_hierarchy_iterator.cc new file mode 100644 index 00000000000..5a318485203 --- /dev/null +++ b/source/blender/io/common/intern/abstract_hierarchy_iterator.cc @@ -0,0 +1,654 @@ +/* + * 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 "IO_abstract_hierarchy_iterator.h" + +#include +#include +#include +#include +#include + +#include "BKE_anim_data.h" +#include "BKE_duplilist.h" +#include "BKE_key.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_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_particle_types.h" + +#include "DEG_depsgraph_query.h" + +namespace blender { +namespace io { + +const HierarchyContext *HierarchyContext::root() +{ + return nullptr; +} + +bool HierarchyContext::operator<(const HierarchyContext &other) const +{ + if (object != other.object) { + return object < other.object; + } + if (duplicator != nullptr && 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() +{ +} + +bool AbstractHierarchyWriter::check_is_animated(const HierarchyContext &context) const +{ + const Object *object = context.object; + + if (BKE_animdata_id_is_animated(static_cast(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(object->modifiers.first); + while (md) { + if (md->type != eModifierType_Subsurf) { + return true; + } + md = md->next; + } + + return false; +} + +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: %zu 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 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; + + 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(object->parent, nullptr)); + visit_object(object, object->parent, true); + if (found_parent_iter != export_graph_.end()) { + break; + } + // 'object->parent' will never be nullptr here, as the export graph contains the + // tuple as root and thus will cause a break. + BLI_assert(object->parent != nullptr); + + object = object->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); + + ExportGraph::key_type graph_index = determine_graph_index_object(context); + context_update_for_graph_index(context, graph_index); + + // Store this HierarchyContext as child of the export parent. + export_graph_[graph_index].insert(context); + + // Create an empty entry for this object to indicate it is part of the export. This will be used + // by connect_loose_objects(). Having such an "indicator" will make it possible to do an O(log n) + // check on whether an object is part of the export, rather than having to check all objects in + // the map. Note that it's not possible to simply search for (object->parent, nullptr), as the + // object's parent in Blender may not be the same as its export-parent. + ExportGraph::key_type object_key = std::make_pair(object, nullptr); + if (export_graph_.find(object_key) == export_graph_.end()) { + export_graph_[object_key] = ExportChildren(); + } +} + +AbstractHierarchyIterator::ExportGraph::key_type AbstractHierarchyIterator:: + determine_graph_index_object(const HierarchyContext *context) +{ + return std::make_pair(context->export_parent, nullptr); +} + +void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object, + Object *duplicator, + const std::set &dupli_set) +{ + HierarchyContext *context = new HierarchyContext(); + context->object = dupli_object->ob; + context->duplicator = duplicator; + context->weak_export = false; + context->export_path = ""; + context->original_export_path = ""; + context->export_path = ""; + context->animation_check_include_parent = false; + + 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()); + + ExportGraph::key_type graph_index = determine_graph_index_dupli(context, dupli_set); + context_update_for_graph_index(context, graph_index); + export_graph_[graph_index].insert(context); +} + +AbstractHierarchyIterator::ExportGraph::key_type AbstractHierarchyIterator:: + determine_graph_index_dupli(const HierarchyContext *context, + const std::set &dupli_set) +{ + /* 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 = context->object->parent; + if (parent != nullptr && dupli_set.find(parent) != dupli_set.end()) { + // The parent object is part of the duplicated collection. + return std::make_pair(parent, context->duplicator); + } + return std::make_pair(context->duplicator, nullptr); +} + +void AbstractHierarchyIterator::context_update_for_graph_index( + HierarchyContext *context, const ExportGraph::key_type &graph_index) const +{ + // Update the HierarchyContext so that it is consistent with the graph index. + context->export_parent = graph_index.first; + if (context->export_parent != context->object->parent) { + /* The parent object in Blender is NOT used as the export parent. This means + * that the world transform of this object can be influenced by objects that + * are not part of its export graph. */ + context->animation_check_include_parent = true; + } +} + +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 *source_data = static_cast(context->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)) { + // Update the context so that it is correct for this parent-child relation. + 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(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(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, + make_valid_name(psys->name)); + 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(object->data); + return get_id_name(object_data); +} + +AbstractHierarchyWriter *AbstractHierarchyIterator::get_writer( + const std::string &export_path) const +{ + WriterMap::const_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 io +} // namespace blender diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt index 732a638a255..fc4f45cdef8 100644 --- a/source/blender/io/usd/CMakeLists.txt +++ b/source/blender/io/usd/CMakeLists.txt @@ -32,6 +32,7 @@ add_definitions(-DPXR_STATIC) set(INC . + ../common ../../blenkernel ../../blenlib ../../blenloader @@ -52,7 +53,6 @@ set(INC_SYS ) set(SRC - intern/abstract_hierarchy_iterator.cc intern/usd_capi.cc intern/usd_hierarchy_iterator.cc intern/usd_writer_abstract.cc @@ -64,7 +64,6 @@ set(SRC 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 @@ -79,6 +78,7 @@ set(SRC set(LIB bf_blenkernel bf_blenlib + bf_io_common ) list(APPEND LIB diff --git a/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc b/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc deleted file mode 100644 index ab83ea2c3c4..00000000000 --- a/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc +++ /dev/null @@ -1,652 +0,0 @@ -/* - * 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 -#include -#include -#include -#include - -#include "BKE_anim_data.h" -#include "BKE_duplilist.h" -#include "BKE_key.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_modifier_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 != nullptr && 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() -{ -} - -bool AbstractHierarchyWriter::check_is_animated(const HierarchyContext &context) const -{ - const Object *object = context.object; - - if (BKE_animdata_id_is_animated(static_cast(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(object->modifiers.first); - while (md) { - if (md->type != eModifierType_Subsurf) { - return true; - } - md = md->next; - } - - return false; -} - -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: %zu 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 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; - - 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(object->parent, nullptr)); - visit_object(object, object->parent, true); - if (found_parent_iter != export_graph_.end()) { - break; - } - // 'object->parent' will never be nullptr here, as the export graph contains the - // tuple as root and thus will cause a break. - BLI_assert(object->parent != nullptr); - - object = object->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); - - ExportGraph::key_type graph_index = determine_graph_index_object(context); - context_update_for_graph_index(context, graph_index); - - // Store this HierarchyContext as child of the export parent. - export_graph_[graph_index].insert(context); - - // Create an empty entry for this object to indicate it is part of the export. This will be used - // by connect_loose_objects(). Having such an "indicator" will make it possible to do an O(log n) - // check on whether an object is part of the export, rather than having to check all objects in - // the map. Note that it's not possible to simply search for (object->parent, nullptr), as the - // object's parent in Blender may not be the same as its export-parent. - ExportGraph::key_type object_key = std::make_pair(object, nullptr); - if (export_graph_.find(object_key) == export_graph_.end()) { - export_graph_[object_key] = ExportChildren(); - } -} - -AbstractHierarchyIterator::ExportGraph::key_type AbstractHierarchyIterator:: - determine_graph_index_object(const HierarchyContext *context) -{ - return std::make_pair(context->export_parent, nullptr); -} - -void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object, - Object *duplicator, - const std::set &dupli_set) -{ - HierarchyContext *context = new HierarchyContext(); - context->object = dupli_object->ob; - context->duplicator = duplicator; - context->weak_export = false; - context->export_path = ""; - context->original_export_path = ""; - context->export_path = ""; - context->animation_check_include_parent = false; - - 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()); - - ExportGraph::key_type graph_index = determine_graph_index_dupli(context, dupli_set); - context_update_for_graph_index(context, graph_index); - export_graph_[graph_index].insert(context); -} - -AbstractHierarchyIterator::ExportGraph::key_type AbstractHierarchyIterator:: - determine_graph_index_dupli(const HierarchyContext *context, - const std::set &dupli_set) -{ - /* 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 = context->object->parent; - if (parent != nullptr && dupli_set.find(parent) != dupli_set.end()) { - // The parent object is part of the duplicated collection. - return std::make_pair(parent, context->duplicator); - } - return std::make_pair(context->duplicator, nullptr); -} - -void AbstractHierarchyIterator::context_update_for_graph_index( - HierarchyContext *context, const ExportGraph::key_type &graph_index) const -{ - // Update the HierarchyContext so that it is consistent with the graph index. - context->export_parent = graph_index.first; - if (context->export_parent != context->object->parent) { - /* The parent object in Blender is NOT used as the export parent. This means - * that the world transform of this object can be influenced by objects that - * are not part of its export graph. */ - context->animation_check_include_parent = true; - } -} - -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 *source_data = static_cast(context->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)) { - // Update the context so that it is correct for this parent-child relation. - 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(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(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, - make_valid_name(psys->name)); - 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(object->data); - return get_id_name(object_data); -} - -AbstractHierarchyWriter *AbstractHierarchyIterator::get_writer( - const std::string &export_path) const -{ - WriterMap::const_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 deleted file mode 100644 index e31d5c91252..00000000000 --- a/source/blender/io/usd/intern/abstract_hierarchy_iterator.h +++ /dev/null @@ -1,263 +0,0 @@ -/* - * 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 -#include -#include - -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; /* Evaluated 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 descendants also - * have weak_export=true, this object (and by recursive reasoning all its descendants) will be - * excluded from the export. - * - * The export hierarchy is kept as close to 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). - protected: - virtual bool check_is_animated(const HierarchyContext &context) const; -}; - -/* 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 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 DupliAndDuplicator; - /* All the children of some object, as per the export hierarchy. */ - typedef std::set ExportChildren; - /* Mapping from an object and its duplicator to the object's export-children. */ - typedef std::map 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 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 &dupli_set); - - ExportChildren &graph_children(const HierarchyContext *parent_context); - void context_update_for_graph_index(HierarchyContext *context, - const ExportGraph::key_type &graph_index) const; - - void determine_export_paths(const HierarchyContext *parent_context); - void determine_duplication_references(const HierarchyContext *parent_context, - std::string indent); - - /* These three functions create writers and call their write() method. */ - 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) const; - - 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 descendant 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; - - virtual ExportGraph::key_type determine_graph_index_object(const HierarchyContext *context); - virtual ExportGraph::key_type determine_graph_index_dupli(const HierarchyContext *context, - const std::set &dupli_set); - - /* 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(). - * - * The created AbstractHierarchyWriter instances should NOT keep a copy of the context pointer. - * The context can be stack-allocated and go out of scope. */ - 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_hierarchy_iterator.h b/source/blender/io/usd/intern/usd_hierarchy_iterator.h index a608012a390..c61a0d2bf07 100644 --- a/source/blender/io/usd/intern/usd_hierarchy_iterator.h +++ b/source/blender/io/usd/intern/usd_hierarchy_iterator.h @@ -19,7 +19,7 @@ #ifndef __USD_HIERARCHY_ITERATOR_H__ #define __USD_HIERARCHY_ITERATOR_H__ -#include "abstract_hierarchy_iterator.h" +#include "IO_abstract_hierarchy_iterator.h" #include "usd.h" #include "usd_exporter_context.h" @@ -34,6 +34,10 @@ struct Object; namespace USD { +using blender::io::AbstractHierarchyIterator; +using blender::io::AbstractHierarchyWriter; +using blender::io::HierarchyContext; + class USDHierarchyIterator : public AbstractHierarchyIterator { private: const pxr::UsdStageRefPtr stage_; diff --git a/source/blender/io/usd/intern/usd_writer_abstract.h b/source/blender/io/usd/intern/usd_writer_abstract.h index 01b53f4c916..03c4c263e32 100644 --- a/source/blender/io/usd/intern/usd_writer_abstract.h +++ b/source/blender/io/usd/intern/usd_writer_abstract.h @@ -19,7 +19,7 @@ #ifndef __USD_WRITER_ABSTRACT_H__ #define __USD_WRITER_ABSTRACT_H__ -#include "abstract_hierarchy_iterator.h" +#include "IO_abstract_hierarchy_iterator.h" #include "usd_exporter_context.h" #include @@ -38,6 +38,9 @@ struct Object; namespace USD { +using blender::io::AbstractHierarchyWriter; +using blender::io::HierarchyContext; + class USDAbstractWriter : public AbstractHierarchyWriter { protected: const USDExporterContext usd_export_context_; -- cgit v1.2.3