This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/io/common/intern/abstract_hierarchy_iterator.cc
Sonny Campbell d209629806 Fix T85729: Crash when exporting for USD and Alembic
Ensure the "null" node graph, which is the root node of the export
graph, always exists.

The crash occured when "Use Settings For" was set to Render, "Visible
Objects Only" was ticked, and a single parent object is in the scene but
disabled for render.

Because the only object attached to the root of the project was disabled
for export, there was no "null" root node added to the export graph.
This change will always add an empty "null" node with no children to the
graph at the start. Other objects will get added to its children as
required.

Reviewed By: sybren

Maniphest Tasks: T85729

Differential Revision: https://developer.blender.org/D15182
2022-06-14 12:29:56 +02:00

752 lines
25 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2019 Blender Foundation. All rights reserved. */
#include "IO_abstract_hierarchy_iterator.h"
#include "dupli_parent_finder.hh"
#include <climits>
#include <cstdio>
#include <iostream>
#include <sstream>
#include <string>
#include "BKE_anim_data.h"
#include "BKE_duplilist.h"
#include "BKE_key.h"
#include "BKE_object.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 "DNA_rigidbody_types.h"
#include "DEG_depsgraph_query.h"
namespace blender::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();
}
bool HierarchyContext::is_object_visible(const enum eEvaluationMode evaluation_mode) const
{
const bool is_dupli = duplicator != nullptr;
int base_flag;
if (is_dupli) {
/* Construct the object's base flags from its dupli-parent, just like is done in
* deg_objects_dupli_iterator_next(). Without this, the visibility check below will fail. Doing
* this here, instead of a more suitable location in AbstractHierarchyIterator, prevents
* copying the Object for every dupli. */
base_flag = object->base_flag;
object->base_flag = duplicator->base_flag | BASE_FROM_DUPLI;
}
const int visibility = BKE_object_visibility(object, evaluation_mode);
if (is_dupli) {
object->base_flag = base_flag;
}
return (visibility & OB_VISIBLE_SELF) != 0;
}
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_;
}
bool AbstractHierarchyWriter::check_is_animated(const HierarchyContext &context) 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;
}
if (check_has_deforming_physics(context)) {
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;
}
bool AbstractHierarchyWriter::check_has_physics(const HierarchyContext &context)
{
const RigidBodyOb *rbo = context.object->rigidbody_object;
return rbo != nullptr && rbo->type == RBO_TYPE_ACTIVE;
}
bool AbstractHierarchyWriter::check_has_deforming_physics(const HierarchyContext &context)
{
const RigidBodyOb *rbo = context.object->rigidbody_object;
return rbo != nullptr && rbo->type == RBO_TYPE_ACTIVE && (rbo->flag & RBO_FLAG_USE_DEFORM) != 0;
}
AbstractHierarchyIterator::AbstractHierarchyIterator(Main *bmain, Depsgraph *depsgraph)
: bmain_(bmain), depsgraph_(depsgraph), export_subset_({true, true})
{
}
AbstractHierarchyIterator::~AbstractHierarchyIterator()
{
/* release_writers() cannot be called here directly, as it calls into the pure-virtual
* release_writer() function. By the time this destructor is called, the subclass that implements
* that pure-virtual function is already destructed. */
BLI_assert(
writers_.empty() ||
!"release_writers() should be called before the AbstractHierarchyIterator goes out of scope");
}
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_) {
release_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 ObjectIdentifier &parent_info = map_iter.first;
const Object *const export_parent = parent_info.object;
const Object *const duplicator = parent_info.duplicated_by;
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->export_name.c_str(),
child_ctx->weak_export ? " (weak)" : "",
child_ctx->original_export_path.empty() ?
"" :
(std::string("ref ") + child_ctx->original_export_path).c_str());
}
else {
printf(" - %s (dup by %s%s) %s\n",
child_ctx->export_name.c_str(),
child_ctx->duplicator->id.name + 2,
child_ctx->weak_export ? ", weak" : "",
child_ctx->original_export_path.empty() ?
"" :
(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_);
/* Add a "null" root node with no children immediately for the case where the top-most node in
* the scene is not being exported and a root node otherwise wouldn't get added. */
ExportGraph::key_type root_node_id = ObjectIdentifier::for_real_object(nullptr);
export_graph_[root_node_id] = ExportChildren();
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) {
DupliParentFinder dupli_parent_finder;
LISTBASE_FOREACH (DupliObject *, dupli_object, lb) {
PersistentID persistent_id(dupli_object);
if (!should_visit_dupli_object(dupli_object)) {
continue;
}
dupli_parent_finder.insert(dupli_object);
}
LISTBASE_FOREACH (DupliObject *, dupli_object, lb) {
if (!should_visit_dupli_object(dupli_object)) {
continue;
}
visit_dupli_object(dupli_object, object, dupli_parent_finder);
}
}
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'. */
ObjectIdentifier child_oid = ObjectIdentifier::for_hierarchy_context(child);
loose_objects_graph.erase(child_oid);
}
}
/* The root of the hierarchy is always found, so it's never considered 'loose'. */
loose_objects_graph.erase(ObjectIdentifier::for_graph_root());
/* Iterate over the loose objects and connect them to their export parent. */
for (const ExportGraph::value_type &map_iter : loose_objects_graph) {
const ObjectIdentifier &graph_key = map_iter.first;
Object *object = graph_key.object;
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(
ObjectIdentifier::for_real_object(object->parent));
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
* root as nullptr and thus will cause a break above. */
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;
const ObjectIdentifier map_key = ObjectIdentifier::for_hierarchy_context(context);
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 recusing. */
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 = ObjectIdentifier::for_real_object(object);
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 ObjectIdentifier::for_real_object(context->export_parent);
}
void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object,
Object *duplicator,
const DupliParentFinder &dupli_parent_finder)
{
HierarchyContext *context = new HierarchyContext();
context->object = dupli_object->ob;
context->duplicator = duplicator;
context->persistent_id = PersistentID(dupli_object);
context->weak_export = false;
context->export_path = "";
context->original_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 export_name_stream;
export_name_stream << get_object_name(context->object) << "-"
<< context->persistent_id.as_object_name_suffix();
context->export_name = make_valid_name(export_name_stream.str());
ExportGraph::key_type graph_index = determine_graph_index_dupli(
context, dupli_object, dupli_parent_finder);
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 DupliObject *dupli_object,
const DupliParentFinder &dupli_parent_finder)
{
const DupliObject *dupli_parent = dupli_parent_finder.find_suitable_export_parent(dupli_object);
if (dupli_parent != nullptr) {
return ObjectIdentifier::for_duplicated_object(dupli_parent, context->duplicator);
}
return ObjectIdentifier::for_real_object(context->duplicator);
}
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.object;
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)
{
return export_graph_[ObjectIdentifier::for_hierarchy_context(context)];
}
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:
case PART_FLUID_FLIP:
case PART_FLUID_SPRAY:
case PART_FLUID_BUBBLE:
case PART_FLUID_FOAM:
case PART_FLUID_TRACER:
case PART_FLUID_SPRAYFOAM:
case PART_FLUID_SPRAYBUBBLE:
case PART_FLUID_FOAMBUBBLE:
case PART_FLUID_SPRAYFOAMBUBBLE:
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 blender::io