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:
Diffstat (limited to 'source/blender/io/common')
-rw-r--r--source/blender/io/common/CMakeLists.txt45
-rw-r--r--source/blender/io/common/IO_abstract_hierarchy_iterator.h321
-rw-r--r--source/blender/io/common/intern/abstract_hierarchy_iterator.cc719
3 files changed, 1085 insertions, 0 deletions
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..5f84fd48b71
--- /dev/null
+++ b/source/blender/io/common/IO_abstract_hierarchy_iterator.h
@@ -0,0 +1,321 @@
+/*
+ * 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 <set>
+#include <string>
+
+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;
+
+ /* Export path of the higher-up exported data. For transforms, this is the export path of the
+ * parent object. For object data, this is the export path of that object's transform.
+ *
+ * From the exported file's point of view, this is the path to the parent in that file. The term
+ * "parent" is not used here to avoid confusion with Blender's meaning of the word (which always
+ * refers to a different object). */
+ std::string higher_up_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;
+};
+
+/* Determines which subset of the writers actually gets to write. */
+struct ExportSubset {
+ bool transforms : 1;
+ bool shapes : 1;
+};
+
+/* EnsuredWriter represents an AbstractHierarchyWriter* combined with information whether it was
+ * newly created or not. It's returned by AbstractHierarchyIterator::ensure_writer(). */
+class EnsuredWriter {
+ private:
+ AbstractHierarchyWriter *writer_;
+
+ /* Is set to truth when ensure_writer() did not find existing writer and created a new one.
+ * Is set to false when writer has been re-used or when allocation of the new one has failed
+ * (`writer` will be `nullptr` in that case and bool(ensured_writer) will be false). */
+ bool newly_created_;
+
+ EnsuredWriter(AbstractHierarchyWriter *writer, bool newly_created);
+
+ public:
+ EnsuredWriter();
+
+ static EnsuredWriter empty();
+ static EnsuredWriter existing(AbstractHierarchyWriter *writer);
+ static EnsuredWriter newly_created(AbstractHierarchyWriter *writer);
+
+ bool is_newly_created() const;
+
+ /* These operators make an EnsuredWriter* act as an AbstractHierarchyWriter* */
+ operator bool() const;
+ AbstractHierarchyWriter *operator->();
+};
+
+/* 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_;
+ ExportSubset export_subset_;
+
+ 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
+ * (sub)frame. */
+ virtual void iterate_and_write();
+
+ /* Release all writers. Call after all frames have been exported. */
+ void release_writers();
+
+ /* Determine which subset of writers is used for exporting.
+ * Set this before calling iterate_and_write().
+ *
+ * Note that writers are created for each iterated object, regardless of this option. When a
+ * writer is created it will also write the current iteration, to ensure the hierarchy is
+ * complete. The `export_subset` option is only in effect when the writer already existed from a
+ * previous iteration. */
+ void set_export_subset(ExportSubset export_subset_);
+
+ /* 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);
+
+ 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);
+
+ /* Return the appropriate HierarchyContext for the data of the object represented by
+ * object_context. */
+ HierarchyContext context_for_object_data(const HierarchyContext *object_context) const;
+
+ /* 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;
+
+ 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. */
+ EnsuredWriter 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<Object *> &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;
+
+ AbstractHierarchyWriter *get_writer(const std::string &export_path) const;
+ ExportChildren &graph_children(const HierarchyContext *parent_context);
+};
+
+} // 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..9a456bfaf69
--- /dev/null
+++ b/source/blender/io/common/intern/abstract_hierarchy_iterator.cc
@@ -0,0 +1,719 @@
+/*
+ * 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 <iostream>
+#include <limits.h>
+#include <sstream>
+#include <stdio.h>
+#include <string>
+
+#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();
+}
+
+EnsuredWriter::EnsuredWriter() : writer_(nullptr), newly_created_(false)
+{
+}
+
+EnsuredWriter::EnsuredWriter(AbstractHierarchyWriter *writer, bool newly_created)
+ : writer_(writer), newly_created_(newly_created)
+{
+}
+
+EnsuredWriter EnsuredWriter::empty()
+{
+ return EnsuredWriter(nullptr, false);
+}
+EnsuredWriter EnsuredWriter::existing(AbstractHierarchyWriter *writer)
+{
+ return EnsuredWriter(writer, false);
+}
+EnsuredWriter EnsuredWriter::newly_created(AbstractHierarchyWriter *writer)
+{
+ return EnsuredWriter(writer, true);
+}
+
+bool EnsuredWriter::is_newly_created() const
+{
+ return newly_created_;
+}
+
+EnsuredWriter::operator bool() const
+{
+ return writer_ != nullptr;
+}
+
+AbstractHierarchyWriter *EnsuredWriter::operator->()
+{
+ return writer_;
+}
+
+AbstractHierarchyWriter::~AbstractHierarchyWriter()
+{
+}
+
+bool AbstractHierarchyWriter::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;
+}
+
+AbstractHierarchyIterator::AbstractHierarchyIterator(Depsgraph *depsgraph)
+ : depsgraph_(depsgraph), writers_(), export_subset_({true, true})
+{
+}
+
+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();
+}
+
+void AbstractHierarchyIterator::set_export_subset(ExportSubset export_subset)
+{
+ export_subset_ = export_subset;
+}
+
+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<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;
+
+ 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 <nullptr, nullptr> 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 = "";
+ context->higher_up_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<Object *> &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<Object *> &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<ID *>(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)
+{
+ 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);
+ if (parent_context != nullptr) {
+ context->higher_up_export_path = parent_context->export_path;
+ }
+
+ // Get or create the transform writer.
+ EnsuredWriter transform_writer = ensure_writer(
+ context, &AbstractHierarchyIterator::create_transform_writer);
+
+ if (!transform_writer) {
+ // 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));
+ if (transform_writer.is_newly_created() || export_subset_.transforms) {
+ /* 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.
+}
+
+HierarchyContext AbstractHierarchyIterator::context_for_object_data(
+ const HierarchyContext *object_context) const
+{
+ HierarchyContext data_context = *object_context;
+ data_context.higher_up_export_path = object_context->export_path;
+ data_context.export_name = get_object_data_name(data_context.object);
+ data_context.export_path = path_concatenate(data_context.higher_up_export_path,
+ data_context.export_name);
+ return data_context;
+}
+
+void AbstractHierarchyIterator::make_writer_object_data(const HierarchyContext *context)
+{
+ if (context->object->data == nullptr) {
+ return;
+ }
+
+ HierarchyContext data_context = context_for_object_data(context);
+ 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());
+ }
+
+ /* Always write upon creation, otherwise depend on which subset is active. */
+ EnsuredWriter data_writer = ensure_writer(&data_context,
+ &AbstractHierarchyIterator::create_data_writer);
+ if (!data_writer) {
+ return;
+ }
+
+ if (data_writer.is_newly_created() || export_subset_.shapes) {
+ 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_name = make_valid_name(psys->name);
+ hair_context.export_path = path_concatenate(transform_context->export_path,
+ hair_context.export_name);
+ hair_context.higher_up_export_path = transform_context->export_path;
+ hair_context.particle_system = psys;
+
+ EnsuredWriter writer;
+ 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) {
+ continue;
+ }
+
+ /* Always write upon creation, otherwise depend on which subset is active. */
+ if (writer.is_newly_created() || export_subset_.shapes) {
+ 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) const
+{
+ WriterMap::const_iterator it = writers_.find(export_path);
+
+ if (it == writers_.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+EnsuredWriter AbstractHierarchyIterator::ensure_writer(
+ HierarchyContext *context, AbstractHierarchyIterator::create_writer_func create_func)
+{
+ AbstractHierarchyWriter *writer = get_writer(context->export_path);
+ if (writer != nullptr) {
+ return EnsuredWriter::existing(writer);
+ }
+
+ writer = (this->*create_func)(context);
+ if (writer == nullptr) {
+ return EnsuredWriter::empty();
+ }
+
+ writers_[context->export_path] = writer;
+ return EnsuredWriter::newly_created(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