Add Chromium-only Blender WebEngine parity work
This commit is contained in:
63
blender-5.2.0/source/blender/io/common/CMakeLists.txt
Normal file
63
blender-5.2.0/source/blender/io/common/CMakeLists.txt
Normal file
@@ -0,0 +1,63 @@
|
||||
# SPDX-FileCopyrightText: 2020 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC
|
||||
.
|
||||
../../makesrna
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
intern/abstract_hierarchy_iterator.cc
|
||||
intern/dupli_parent_finder.cc
|
||||
intern/dupli_persistent_id.cc
|
||||
intern/mesh_utils.cc
|
||||
intern/object_identifier.cc
|
||||
intern/orientation.cc
|
||||
intern/path_util.cc
|
||||
intern/string_utils.cc
|
||||
intern/subdiv_disabler.cc
|
||||
|
||||
IO_abstract_hierarchy_iterator.h
|
||||
IO_dupli_persistent_id.hh
|
||||
IO_mesh_utils.hh
|
||||
IO_orientation.hh
|
||||
IO_path_util.hh
|
||||
IO_path_util_types.hh
|
||||
IO_string_utils.hh
|
||||
IO_subdiv_disabler.hh
|
||||
IO_types.hh
|
||||
IO_validate.hh
|
||||
intern/dupli_parent_finder.hh
|
||||
)
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::depsgraph
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::intern::clog
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
PRIVATE bf::extern::fast_float
|
||||
)
|
||||
|
||||
blender_add_lib(bf_io_common "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
|
||||
if(WITH_GTESTS)
|
||||
set(TEST_SRC
|
||||
intern/abstract_hierarchy_iterator_test.cc
|
||||
intern/object_identifier_test.cc
|
||||
intern/string_utils_tests.cc
|
||||
)
|
||||
set(TEST_INC
|
||||
../../blenloader
|
||||
)
|
||||
set(TEST_LIB
|
||||
bf_blenloader_test_util
|
||||
bf_io_common
|
||||
)
|
||||
blender_add_test_suite_lib(io_common "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
|
||||
endif()
|
||||
@@ -0,0 +1,401 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IO_dupli_persistent_id.hh"
|
||||
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_set.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct DupliObject;
|
||||
struct ID;
|
||||
struct Main;
|
||||
struct Object;
|
||||
struct ParticleSystem;
|
||||
|
||||
namespace io {
|
||||
|
||||
class AbstractHierarchyWriter;
|
||||
class DupliParentFinder;
|
||||
|
||||
/* 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;
|
||||
PersistentID persistent_id;
|
||||
float4x4 matrix_world;
|
||||
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 invisible when
|
||||
* exporting with "Visible Only" enabled, for example. */
|
||||
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;
|
||||
|
||||
/* The flag makes unambiguous the fact that the current context targets object or data. This is
|
||||
* notably used in USDHierarchyIterator::create_usd_export_context: options like
|
||||
* merge_parent_xform option is meaningless for object, it only makes sense for data. */
|
||||
bool is_object_data_context;
|
||||
|
||||
/* This flag tells, within a object data context, if an object is the parent of other objects.
|
||||
* This is useful when exporting UsdGeomGprim: those cannot be nested into each other. For
|
||||
* example, an UsdGeomMesh cannot have other UsdGeomMesh as descendants and other hierarchy
|
||||
* strategies need to be adopted.
|
||||
*/
|
||||
bool is_parent;
|
||||
|
||||
/* When true this is duplisource object. This flag is used to identify instance prototypes. */
|
||||
bool is_duplisource;
|
||||
|
||||
/* This flag tells whether an object is a valid point instance of other objects.
|
||||
* If true, it means the object has a valid reference path and its value can be included
|
||||
* in the instances data of UsdGeomPointInstancer. */
|
||||
bool is_point_instance;
|
||||
|
||||
/* This flag tells if an object is a valid prototype of a point instancer. */
|
||||
bool is_point_proto;
|
||||
|
||||
/* True if this context is a descendant of any context with is_point_instance set to true.
|
||||
* This helps skip redundant instancing data during export. */
|
||||
bool has_point_instance_ancestor;
|
||||
|
||||
/*********** Determined during writer creation: ***************/
|
||||
float4x4 parent_matrix_inv_world; /* Inverse of the parent's world matrix. */
|
||||
std::string export_path; /* Hierarchical path, such as "/grandparent/parent/object_name". */
|
||||
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;
|
||||
|
||||
/* 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();
|
||||
bool is_prototype() const;
|
||||
|
||||
/* For handling point instancing (Instance on Points geometry node). */
|
||||
bool is_point_instancer() const;
|
||||
|
||||
bool is_object_visible(enum eEvaluationMode evaluation_mode) const;
|
||||
};
|
||||
|
||||
/* 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() = default;
|
||||
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:
|
||||
/* Return true if the data written by this writer changes over time.
|
||||
* Note that this function assumes this is an object data writer. Transform writers should not
|
||||
* call this but implement their own logic. */
|
||||
virtual bool check_is_animated(const HierarchyContext &context) const;
|
||||
|
||||
/* Helper functions for animation checks. */
|
||||
static bool check_has_physics(const HierarchyContext &context);
|
||||
static bool check_has_deforming_physics(const HierarchyContext &context);
|
||||
};
|
||||
|
||||
/* 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->();
|
||||
};
|
||||
|
||||
/* Unique identifier for a (potentially duplicated) object.
|
||||
*
|
||||
* Instances of this class serve as key in the export graph of the
|
||||
* AbstractHierarchyIterator. */
|
||||
class ObjectIdentifier {
|
||||
public:
|
||||
Object *object;
|
||||
Object *duplicated_by; /* nullptr for real objects. */
|
||||
PersistentID persistent_id;
|
||||
|
||||
protected:
|
||||
ObjectIdentifier(Object *object, Object *duplicated_by, const PersistentID &persistent_id);
|
||||
|
||||
public:
|
||||
static ObjectIdentifier for_graph_root();
|
||||
static ObjectIdentifier for_real_object(Object *object);
|
||||
static ObjectIdentifier for_hierarchy_context(const HierarchyContext *context);
|
||||
static ObjectIdentifier for_duplicated_object(const DupliObject *dupli_object,
|
||||
Object *duplicated_by);
|
||||
|
||||
bool is_root() const;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(object, duplicated_by, persistent_id);
|
||||
}
|
||||
};
|
||||
|
||||
bool operator==(const ObjectIdentifier &obj_ident_a, const ObjectIdentifier &obj_ident_b);
|
||||
|
||||
/* 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. */
|
||||
using WriterMap = Map<std::string, AbstractHierarchyWriter *>;
|
||||
/* All the children of some object, as per the export hierarchy. */
|
||||
using ExportChildren = Set<HierarchyContext *>;
|
||||
/* Mapping from an object and its duplicator to the object's export-children. */
|
||||
using ExportGraph = Map<ObjectIdentifier, ExportChildren>;
|
||||
/* 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. */
|
||||
using ExportPathMap = Map<ID *, std::string>;
|
||||
/* Mapping from ID name to a set of names logically residing "under" it. Used for unique
|
||||
* name generation. */
|
||||
using ExportUsedNameMap = Map<std::string, Set<std::string>>;
|
||||
/* IDs of all duplisource objects, used to identify instance prototypes. */
|
||||
using DupliSources = Set<ID *>;
|
||||
|
||||
protected:
|
||||
ExportGraph export_graph_;
|
||||
ExportPathMap duplisource_export_path_;
|
||||
Main *bmain_;
|
||||
Depsgraph *depsgraph_;
|
||||
WriterMap writers_;
|
||||
ExportSubset export_subset_;
|
||||
DupliSources duplisources_;
|
||||
ExportUsedNameMap used_names_;
|
||||
|
||||
public:
|
||||
explicit AbstractHierarchyIterator(Main *bmain, 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;
|
||||
|
||||
virtual std::string make_unique_name(const std::string &original_name,
|
||||
Set<std::string> &used_names);
|
||||
|
||||
/* 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(const DupliObject *dupli_object,
|
||||
Object *duplicator,
|
||||
const DupliParentFinder &dupli_parent_finder);
|
||||
|
||||
void context_update_for_graph_index(HierarchyContext *context,
|
||||
const ObjectIdentifier &graph_index) const;
|
||||
|
||||
void determine_export_paths(const HierarchyContext *parent_context);
|
||||
bool determine_duplication_references(const HierarchyContext *parent_context,
|
||||
const 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 *transform_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_name(const Object *object, const Object *parent);
|
||||
std::string get_object_data_name(const Object *object) const;
|
||||
|
||||
using create_writer_func =
|
||||
AbstractHierarchyWriter *(AbstractHierarchyIterator::*)(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(const 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 ObjectIdentifier determine_graph_index_object(const HierarchyContext *context);
|
||||
virtual ObjectIdentifier determine_graph_index_dupli(
|
||||
const HierarchyContext *context,
|
||||
const DupliObject *dupli_object,
|
||||
const DupliParentFinder &dupli_parent_finder);
|
||||
|
||||
/* 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 release_writer(AbstractHierarchyWriter *writer) = 0;
|
||||
|
||||
/* Return true if data writers should be created for this context. */
|
||||
virtual bool include_data_writers(const HierarchyContext *) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Return true if children of the context should be converted to writers. */
|
||||
virtual bool include_child_writers(const HierarchyContext *) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
AbstractHierarchyWriter *get_writer(const std::string &export_path) const;
|
||||
ExportChildren *graph_children(const HierarchyContext *context);
|
||||
};
|
||||
|
||||
} // namespace io
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,48 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "BKE_duplilist.hh"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
/* Wrapper for DupliObject::persistent_id that can act as a map key. */
|
||||
class PersistentID {
|
||||
protected:
|
||||
constexpr static int array_length_ = MAX_DUPLI_RECUR;
|
||||
using PIDArray = std::array<int, array_length_>;
|
||||
PIDArray persistent_id_;
|
||||
|
||||
explicit PersistentID(const PIDArray &persistent_id_values);
|
||||
|
||||
public:
|
||||
PersistentID();
|
||||
explicit PersistentID(const DupliObject *dupli_ob);
|
||||
|
||||
/* Return true if the persistent IDs are the same, ignoring the first digit. */
|
||||
bool is_from_same_instancer_as(const PersistentID &other) const;
|
||||
|
||||
/* Construct the persistent ID of this instance's instancer. */
|
||||
PersistentID instancer_pid() const;
|
||||
|
||||
/* Construct a string representation by reversing the persistent ID.
|
||||
* In case of a duplicator that is duplicated itself as well, this
|
||||
* results in strings like:
|
||||
* "3" for the duplicated duplicator, and
|
||||
* "3-0", "3-1", etc. for its duplis. */
|
||||
std::string as_object_name_suffix() const;
|
||||
|
||||
uint64_t hash() const;
|
||||
|
||||
friend bool operator==(const PersistentID &persistent_id_a, const PersistentID &persistent_id_b);
|
||||
|
||||
private:
|
||||
void copy_values_from(const PIDArray &persistent_id_values);
|
||||
};
|
||||
|
||||
} // namespace blender::io
|
||||
46
blender-5.2.0/source/blender/io/common/IO_mesh_utils.hh
Normal file
46
blender-5.2.0/source/blender/io/common/IO_mesh_utils.hh
Normal file
@@ -0,0 +1,46 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct Mesh;
|
||||
struct Object;
|
||||
|
||||
namespace io {
|
||||
|
||||
/**
|
||||
* Conditionally coerce objects to mesh for export.
|
||||
* For use by formats that only support meshes.
|
||||
*/
|
||||
struct MeshCoerceForExport {
|
||||
/**
|
||||
* Mesh to read geometry from, referencing evaluated, pre-modified or `owned` data.
|
||||
*
|
||||
* The same as `owned` when a conversion was performed.
|
||||
*/
|
||||
const Mesh *mesh = nullptr;
|
||||
/**
|
||||
* Mesh converted from a non-mesh, or null.
|
||||
*/
|
||||
Mesh *owned = nullptr;
|
||||
|
||||
~MeshCoerceForExport();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the mesh to export for `obj_eval`.
|
||||
*
|
||||
* The mesh may be converted on demand and stored in #MeshCoerceForExport::owned.
|
||||
*
|
||||
* \return The mesh to export.
|
||||
*/
|
||||
const Mesh *mesh_coerce_for_export_setup(MeshCoerceForExport &coerce,
|
||||
Depsgraph *depsgraph,
|
||||
Object *obj_eval,
|
||||
bool apply_modifiers);
|
||||
|
||||
} // namespace io
|
||||
} // namespace blender
|
||||
28
blender-5.2.0/source/blender/io/common/IO_orientation.hh
Normal file
28
blender-5.2.0/source/blender/io/common/IO_orientation.hh
Normal file
@@ -0,0 +1,28 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct EnumPropertyItem;
|
||||
struct Main;
|
||||
struct PointerRNA;
|
||||
struct Scene;
|
||||
|
||||
enum eIOAxis {
|
||||
IO_AXIS_X = 0,
|
||||
IO_AXIS_Y = 1,
|
||||
IO_AXIS_Z = 2,
|
||||
IO_AXIS_NEGATIVE_X = 3,
|
||||
IO_AXIS_NEGATIVE_Y = 4,
|
||||
IO_AXIS_NEGATIVE_Z = 5,
|
||||
};
|
||||
|
||||
extern const EnumPropertyItem io_transform_axis[];
|
||||
|
||||
void io_ui_forward_axis_update(Main *main, Scene *scene, PointerRNA *ptr);
|
||||
void io_ui_up_axis_update(Main *main, Scene *scene, PointerRNA *ptr);
|
||||
|
||||
} // namespace blender
|
||||
31
blender-5.2.0/source/blender/io/common/IO_path_util.hh
Normal file
31
blender-5.2.0/source/blender/io/common/IO_path_util.hh
Normal file
@@ -0,0 +1,31 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "IO_path_util_types.hh"
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
/**
|
||||
* Return a filepath relative to a destination directory, for use with
|
||||
* exporters.
|
||||
*
|
||||
* When PATH_REFERENCE_COPY mode is used, the file path pair (source
|
||||
* path, destination path) is added to the `copy_set`.
|
||||
*
|
||||
* Equivalent of bpy_extras.io_utils.path_reference.
|
||||
*/
|
||||
std::string path_reference(StringRefNull filepath,
|
||||
StringRefNull base_src,
|
||||
StringRefNull base_dst,
|
||||
ePathReferenceMode mode,
|
||||
Set<std::pair<std::string, std::string>> *copy_set = nullptr);
|
||||
|
||||
/** Execute copying files of path_reference. */
|
||||
void path_reference_copy(const Set<std::pair<std::string, std::string>> ©_set);
|
||||
|
||||
} // namespace blender::io
|
||||
24
blender-5.2.0/source/blender/io/common/IO_path_util_types.hh
Normal file
24
blender-5.2.0/source/blender/io/common/IO_path_util_types.hh
Normal file
@@ -0,0 +1,24 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
/** Method used to reference paths. Equivalent of bpy_extras.io_utils.path_reference_mode. */
|
||||
enum ePathReferenceMode {
|
||||
/** Use relative paths with subdirectories only. */
|
||||
PATH_REFERENCE_AUTO = 0,
|
||||
/** Always write absolute paths. */
|
||||
PATH_REFERENCE_ABSOLUTE = 1,
|
||||
/** Write relative paths where possible. */
|
||||
PATH_REFERENCE_RELATIVE = 2,
|
||||
/** Match absolute/relative setting with input path. */
|
||||
PATH_REFERENCE_MATCH = 3,
|
||||
/** Filename only. */
|
||||
PATH_REFERENCE_STRIP = 4,
|
||||
/** Copy the file to the destination path. */
|
||||
PATH_REFERENCE_COPY = 5,
|
||||
};
|
||||
|
||||
} // namespace blender
|
||||
114
blender-5.2.0/source/blender/io/common/IO_string_utils.hh
Normal file
114
blender-5.2.0/source/blender/io/common/IO_string_utils.hh
Normal file
@@ -0,0 +1,114 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
/*
|
||||
* Various text parsing utilities used by importers.
|
||||
*
|
||||
* Many of these functions take two pointers (p, end) indicating
|
||||
* which part of a string to operate on, and return a possibly
|
||||
* changed new start of the string. They could be taking a StringRef
|
||||
* as input and returning a new StringRef, but this is a hot path
|
||||
* in CSV and OBJ parsing, and the StringRef approach does lose performance
|
||||
* (mostly due to return of StringRef being two register-size values
|
||||
* instead of just one pointer).
|
||||
*/
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
/**
|
||||
* Fetches next line from an input string buffer.
|
||||
*
|
||||
* The returned line will not have '\n' characters at the end;
|
||||
* the `buffer` is modified to contain remaining text without
|
||||
* the input line.
|
||||
*/
|
||||
StringRef read_next_line(StringRef &buffer);
|
||||
|
||||
/**
|
||||
* Drop leading white-space from a string part.
|
||||
*/
|
||||
const char *drop_whitespace(const char *p, const char *end);
|
||||
|
||||
/**
|
||||
* Drop leading non-white-space from a string part.
|
||||
*/
|
||||
const char *drop_non_whitespace(const char *p, const char *end);
|
||||
|
||||
/**
|
||||
* Parse an integer from an input string.
|
||||
* The parsed result is stored in `dst`. The function skips
|
||||
* leading white-space unless `skip_space=false`. If the
|
||||
* number can't be parsed (invalid syntax, out of range),
|
||||
* `success` value is false.
|
||||
*
|
||||
* Returns the start of remainder of the input string after parsing.
|
||||
*/
|
||||
const char *try_parse_int(
|
||||
const char *p, const char *end, int fallback, bool &success, int &dst, bool skip_space = true);
|
||||
|
||||
/**
|
||||
* Parse a float from an input string.
|
||||
* The parsed result is stored in `dst`. The function skips
|
||||
* leading white-space unless `skip_space=false`. If the
|
||||
* number can't be parsed (invalid syntax, out of range),
|
||||
* `success` value is false.
|
||||
*
|
||||
* Returns the start of remainder of the input string after parsing.
|
||||
*/
|
||||
const char *try_parse_float(const char *p,
|
||||
const char *end,
|
||||
int fallback,
|
||||
bool &success,
|
||||
float &dst,
|
||||
bool skip_space = true);
|
||||
|
||||
/**
|
||||
* Parse an integer from an input string.
|
||||
* The parsed result is stored in `dst`. The function skips
|
||||
* leading white-space unless `skip_space=false`. If the
|
||||
* number can't be parsed (invalid syntax, out of range),
|
||||
* `fallback` value is stored instead.
|
||||
*
|
||||
* Returns the start of remainder of the input string after parsing.
|
||||
*/
|
||||
const char *parse_int(
|
||||
const char *p, const char *end, int fallback, int &dst, bool skip_space = true);
|
||||
|
||||
/**
|
||||
* Parse a float from an input string.
|
||||
* The parsed result is stored in `dst`. The function skips
|
||||
* leading white-space unless `skip_space=false`. If the
|
||||
* number can't be parsed (invalid syntax, out of range),
|
||||
* `fallback` value is stored instead. If `require_trailing_space`
|
||||
* is true, the character after the number has to be whitespace.
|
||||
*
|
||||
* Returns the start of remainder of the input string after parsing.
|
||||
*/
|
||||
const char *parse_float(const char *p,
|
||||
const char *end,
|
||||
float fallback,
|
||||
float &dst,
|
||||
bool skip_space = true,
|
||||
bool require_trailing_space = false);
|
||||
|
||||
/**
|
||||
* Parse a number of white-space separated floats from an input string.
|
||||
* The parsed `count` numbers are stored in `dst`. If a
|
||||
* number can't be parsed (invalid syntax, out of range),
|
||||
* `fallback` value is stored instead.
|
||||
*
|
||||
* Returns the start of remainder of the input string after parsing.
|
||||
*/
|
||||
const char *parse_floats(const char *p,
|
||||
const char *end,
|
||||
float fallback,
|
||||
float *dst,
|
||||
int count,
|
||||
bool require_trailing_space = false);
|
||||
|
||||
} // namespace blender::io
|
||||
67
blender-5.2.0/source/blender/io/common/IO_subdiv_disabler.hh
Normal file
67
blender-5.2.0/source/blender/io/common/IO_subdiv_disabler.hh
Normal file
@@ -0,0 +1,67 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "DNA_modifier_types.h"
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct ModifierData;
|
||||
struct Object;
|
||||
struct Scene;
|
||||
|
||||
namespace io {
|
||||
|
||||
/**
|
||||
* This code is shared between the Alembic and USD exporters.
|
||||
* Temporarily disable the subdiv modifier on mesh objects,
|
||||
* if the subdiv modifier is last on the modifier stack.
|
||||
*
|
||||
* The destructor restores all disabled modifiers.
|
||||
*
|
||||
* Currently, this class is used to disable Catmull-Clark subdivision modifiers.
|
||||
* It is done in a separate step before the exporter starts iterating over all
|
||||
* the frames, so that it only has to happen once per export.
|
||||
*/
|
||||
class SubdivModifierDisabler final {
|
||||
private:
|
||||
Depsgraph *depsgraph_;
|
||||
|
||||
/* TODO: Track the object and its disabled modifier in a single struct and use just 1 Vector. */
|
||||
Vector<ModifierData *> disabled_modifiers_;
|
||||
Vector<Object *> modified_objects_;
|
||||
|
||||
public:
|
||||
explicit SubdivModifierDisabler(Depsgraph *depsgraph);
|
||||
~SubdivModifierDisabler();
|
||||
|
||||
/**
|
||||
* Disable subdiv modifiers on all mesh objects.
|
||||
*/
|
||||
void disable_modifiers();
|
||||
|
||||
/**
|
||||
* Return the Catmull-Clark subdiv modifier on the mesh, if it's the last modifier
|
||||
* in the list or if it's the last modifier preceding any particle system modifiers.
|
||||
* This function ignores Simple subdiv modifiers.
|
||||
*/
|
||||
static ModifierData *get_subdiv_modifier(Scene *scene, const Object *ob, ModifierMode mode);
|
||||
|
||||
/* Disallow copying. */
|
||||
SubdivModifierDisabler(const SubdivModifierDisabler &) = delete;
|
||||
SubdivModifierDisabler &operator=(const SubdivModifierDisabler &) = delete;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Disable the given modifier and add it to the disabled
|
||||
* modifiers list.
|
||||
*/
|
||||
void disable_modifier(ModifierData *mod);
|
||||
};
|
||||
|
||||
} // namespace io
|
||||
} // namespace blender
|
||||
23
blender-5.2.0/source/blender/io/common/IO_types.hh
Normal file
23
blender-5.2.0/source/blender/io/common/IO_types.hh
Normal file
@@ -0,0 +1,23 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
/* The CacheArchiveHandle struct is only used for anonymous pointers,
|
||||
* to interface between C and C++ code. This is currently used
|
||||
* to hide pointers to alembic ArchiveReader and USDStageReader. */
|
||||
struct CacheArchiveHandle {
|
||||
int unused;
|
||||
};
|
||||
|
||||
/* The CacheReader struct is only used for anonymous pointers,
|
||||
* to interface between C and C++ code. This is currently used
|
||||
* to hide pointers to AbcObjectReader and USDPrimReader
|
||||
* (or subclasses thereof). */
|
||||
struct CacheReader {
|
||||
int unused;
|
||||
};
|
||||
|
||||
} // namespace blender
|
||||
30
blender-5.2.0/source/blender/io/common/IO_validate.hh
Normal file
30
blender-5.2.0/source/blender/io/common/IO_validate.hh
Normal file
@@ -0,0 +1,30 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup io
|
||||
*
|
||||
* Validation utilities for importers.
|
||||
*/
|
||||
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
|
||||
namespace blender::io::validate {
|
||||
|
||||
/** Check if size fits in an `int` as used by Blender geometry types. */
|
||||
inline bool size_fits_in_int(const int64_t size)
|
||||
{
|
||||
return size >= 0 && size <= INT_MAX;
|
||||
}
|
||||
|
||||
/** Check if index fits in the range. */
|
||||
inline bool index_in_range(const int64_t index, const int64_t size)
|
||||
{
|
||||
return (index >= 0 && index < size);
|
||||
}
|
||||
|
||||
} // namespace blender::io::validate
|
||||
@@ -0,0 +1,849 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "IO_abstract_hierarchy_iterator.h"
|
||||
#include "dupli_parent_finder.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "BKE_anim_data.hh"
|
||||
#include "BKE_duplilist.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_geometry_set_instances.hh"
|
||||
#include "BKE_key.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_particle.h"
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string_utils.hh"
|
||||
|
||||
#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.hh"
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
const HierarchyContext *HierarchyContext::root()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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_prototype() const
|
||||
{
|
||||
/* The context is for a prototype if it's for a duplisource or
|
||||
* for a duplicated object that was designated to be a prototype
|
||||
* because the original was not included in the export. */
|
||||
return is_duplisource || (duplicator != nullptr && !is_instance());
|
||||
}
|
||||
|
||||
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(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;
|
||||
}
|
||||
|
||||
bool HierarchyContext::is_point_instancer() const
|
||||
{
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Collection instancers are handled elsewhere as part of Scene instancing. */
|
||||
if (object->type == OB_EMPTY && object->instance_collection != nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bke::GeometrySet geometry_set = bke::object_get_evaluated_geometry_set(*object);
|
||||
return geometry_set.has_instances();
|
||||
}
|
||||
|
||||
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_msg(
|
||||
writers_.is_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 (AbstractHierarchyWriter *writer : writers_.values()) {
|
||||
release_writer(writer);
|
||||
}
|
||||
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::make_unique_name(const std::string &original_name,
|
||||
Set<std::string> &used_names)
|
||||
{
|
||||
if (original_name.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string name = BLI_uniquename_cb(
|
||||
[&](const StringRef check_name) { return used_names.contains_as(check_name); },
|
||||
'_',
|
||||
make_valid_name(original_name));
|
||||
|
||||
used_names.add_new(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
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 auto item : graph.items()) {
|
||||
const ObjectIdentifier &parent_info = item.key;
|
||||
const Object *const export_parent = parent_info.object;
|
||||
const Object *const duplicator = parent_info.duplicated_by;
|
||||
|
||||
if (duplicator != nullptr) {
|
||||
fmt::println(" DU {} (as dupped by {}):",
|
||||
export_parent == nullptr ? "-null-" : (export_parent->id.name + 2),
|
||||
duplicator->id.name + 2);
|
||||
}
|
||||
else {
|
||||
fmt::println(" OB {}:",
|
||||
export_parent == nullptr ? "-null-" : (export_parent->id.name + 2));
|
||||
}
|
||||
|
||||
total_graph_size += item.value.size();
|
||||
for (HierarchyContext *child_ctx : item.value) {
|
||||
if (child_ctx->duplicator == nullptr) {
|
||||
fmt::println(" - {}{}{}",
|
||||
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 {
|
||||
fmt::println(" - {} (dup by {}{}) {}",
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt::println(" (Total graph size: {} objects)", total_graph_size);
|
||||
}
|
||||
|
||||
void AbstractHierarchyIterator::export_graph_construct()
|
||||
{
|
||||
/* 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. */
|
||||
ObjectIdentifier root_node_id = ObjectIdentifier::for_real_object(nullptr);
|
||||
export_graph_.add_new(root_node_id, {});
|
||||
|
||||
DEGObjectIterSettings deg_iter_settings{};
|
||||
deg_iter_settings.depsgraph = depsgraph_;
|
||||
deg_iter_settings.flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
|
||||
DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET;
|
||||
DupliList duplilist;
|
||||
DEG_OBJECT_ITER_BEGIN (°_iter_settings, object) {
|
||||
/* 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. */
|
||||
object_duplilist(depsgraph_, object, nullptr, duplilist);
|
||||
if (!duplilist.is_empty()) {
|
||||
DupliParentFinder dupli_parent_finder;
|
||||
|
||||
for (const DupliObject &dupli_object : duplilist) {
|
||||
if (!should_visit_dupli_object(&dupli_object)) {
|
||||
continue;
|
||||
}
|
||||
dupli_parent_finder.insert(&dupli_object);
|
||||
}
|
||||
|
||||
for (const DupliObject &dupli_object : duplilist) {
|
||||
if (!should_visit_dupli_object(&dupli_object)) {
|
||||
continue;
|
||||
}
|
||||
visit_dupli_object(&dupli_object, object, dupli_parent_finder);
|
||||
}
|
||||
}
|
||||
|
||||
duplilist.clear();
|
||||
}
|
||||
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 ExportChildren &children : export_graph_.values()) {
|
||||
for (const HierarchyContext *child : children) {
|
||||
/* 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.remove(child_oid);
|
||||
}
|
||||
}
|
||||
/* The root of the hierarchy is always found, so it's never considered 'loose'. */
|
||||
loose_objects_graph.remove_contained(ObjectIdentifier::for_graph_root());
|
||||
|
||||
/* Iterate over the loose objects and connect them to their export parent. */
|
||||
for (const ObjectIdentifier &graph_key : loose_objects_graph.keys()) {
|
||||
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. */
|
||||
|
||||
const bool found = export_graph_.contains(ObjectIdentifier::for_real_object(object->parent));
|
||||
visit_object(object, object->parent, true);
|
||||
if (found) {
|
||||
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);
|
||||
|
||||
const AbstractHierarchyIterator::ExportChildren *children = input_graph.lookup_ptr(map_key);
|
||||
if (children) {
|
||||
for (HierarchyContext *child_context : *children) {
|
||||
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.lookup(map_key).remove(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.remove(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 (const ExportChildren &children : export_graph_.values()) {
|
||||
for (HierarchyContext *context : children) {
|
||||
delete context;
|
||||
}
|
||||
}
|
||||
export_graph_.clear();
|
||||
used_names_.clear_and_keep_capacity();
|
||||
}
|
||||
|
||||
void AbstractHierarchyIterator::visit_object(Object *object,
|
||||
Object *export_parent,
|
||||
bool weak_export)
|
||||
{
|
||||
HierarchyContext *context = new HierarchyContext();
|
||||
context->object = object;
|
||||
context->is_object_data_context = false;
|
||||
context->export_name = get_object_name(object, export_parent);
|
||||
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 = "";
|
||||
context->is_duplisource = false;
|
||||
context->matrix_world = object->object_to_world();
|
||||
|
||||
ObjectIdentifier 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_.lookup_or_add(graph_index, {}).add_new(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. */
|
||||
ObjectIdentifier object_key = ObjectIdentifier::for_real_object(object);
|
||||
export_graph_.add(object_key, {});
|
||||
}
|
||||
|
||||
ObjectIdentifier AbstractHierarchyIterator::determine_graph_index_object(
|
||||
const HierarchyContext *context)
|
||||
{
|
||||
return ObjectIdentifier::for_real_object(context->export_parent);
|
||||
}
|
||||
|
||||
void AbstractHierarchyIterator::visit_dupli_object(const DupliObject *dupli_object,
|
||||
Object *duplicator,
|
||||
const DupliParentFinder &dupli_parent_finder)
|
||||
{
|
||||
HierarchyContext *context = new HierarchyContext();
|
||||
context->object = dupli_object->ob;
|
||||
context->is_object_data_context = false;
|
||||
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;
|
||||
context->is_duplisource = false;
|
||||
context->matrix_world = float4x4(dupli_object->mat);
|
||||
|
||||
/* Construct export name for the dupli-instance. */
|
||||
std::string export_name = get_object_name(context->object) + "-" +
|
||||
context->persistent_id.as_object_name_suffix();
|
||||
|
||||
Set<std::string> &used_names = used_names_.lookup_or_add(duplicator->id.name, {});
|
||||
context->export_name = make_unique_name(make_valid_name(export_name), used_names);
|
||||
|
||||
ObjectIdentifier graph_index = determine_graph_index_dupli(
|
||||
context, dupli_object, dupli_parent_finder);
|
||||
context_update_for_graph_index(context, graph_index);
|
||||
|
||||
export_graph_.lookup_or_add(graph_index, {}).add_new(context);
|
||||
|
||||
if (dupli_object->ob) {
|
||||
this->duplisources_.add(&dupli_object->ob->id);
|
||||
}
|
||||
}
|
||||
|
||||
ObjectIdentifier 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 ObjectIdentifier &graph_index) const
|
||||
{
|
||||
/* Update the HierarchyContext so that it is consistent with the graph index. */
|
||||
context->export_parent = graph_index.object;
|
||||
|
||||
/* If the parent type is such that it cannot be exported (at least not currently to USD or
|
||||
* Alembic), always check the parent for animation. */
|
||||
const eObject_Partype partype = context->object->partype & PARTYPE;
|
||||
context->animation_check_include_parent |= ELEM(partype, PARBONE, PARVERT1, PARVERT3, PARSKEL);
|
||||
|
||||
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)
|
||||
{
|
||||
/* Note: `graph_children` is called during recursive iteration and MUST NOT change the export
|
||||
* graph, which would invalidate the iteration. As a result, we cannot add an entry in the
|
||||
* graph if the incoming `context` is not found. */
|
||||
return export_graph_.lookup_ptr(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 : "";
|
||||
|
||||
const ExportChildren *children = graph_children(parent_context);
|
||||
if (!children) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (HierarchyContext *context : *children) {
|
||||
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_.add(source_ob, context->export_path);
|
||||
|
||||
if (context->object->data != nullptr) {
|
||||
ID *source_data = context->object->data;
|
||||
duplisource_export_path_.add(source_data, get_object_data_path(context));
|
||||
}
|
||||
}
|
||||
|
||||
determine_export_paths(context);
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractHierarchyIterator::determine_duplication_references(
|
||||
const HierarchyContext *parent_context, const std::string &indent)
|
||||
{
|
||||
const ExportChildren *children = graph_children(parent_context);
|
||||
if (!children) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Will be set to true if any child contexts are instances that were designated
|
||||
* as proxies for the original prototype. */
|
||||
bool contains_proxy_prototype = false;
|
||||
|
||||
for (HierarchyContext *context : *children) {
|
||||
if (context->duplicator != nullptr) {
|
||||
ID *source_id = &context->object->id;
|
||||
const std::string *source_path = duplisource_export_path_.lookup_ptr(source_id);
|
||||
if (!source_path) {
|
||||
/* The original was not found, so mark this instance as "the original". */
|
||||
context->mark_as_not_instanced();
|
||||
duplisource_export_path_.add_new(source_id, context->export_path);
|
||||
contains_proxy_prototype = true;
|
||||
}
|
||||
else {
|
||||
context->mark_as_instance_of(*source_path);
|
||||
}
|
||||
|
||||
if (context->object->data) {
|
||||
ID *source_data_id = context->object->data;
|
||||
if (!duplisource_export_path_.contains(source_data_id)) {
|
||||
/* 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_.add_overwrite(source_id, context->export_path);
|
||||
duplisource_export_path_.add_new(source_data_id, data_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Determine is this context is for an instance prototype. */
|
||||
ID *id = &context->object->id;
|
||||
if (duplisources_.contains(id)) {
|
||||
context->is_duplisource = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (determine_duplication_references(context, indent + " ")) {
|
||||
/* A descendant was designated a prototype proxy. If the current context
|
||||
* is an instance, we must change it to a prototype proxy as well. */
|
||||
if (context->is_instance()) {
|
||||
context->mark_as_not_instanced();
|
||||
ID *source_id = &context->object->id;
|
||||
duplisource_export_path_.add_overwrite(source_id, context->export_path);
|
||||
}
|
||||
contains_proxy_prototype = true;
|
||||
}
|
||||
}
|
||||
return contains_proxy_prototype;
|
||||
}
|
||||
|
||||
void AbstractHierarchyIterator::make_writers(const HierarchyContext *parent_context)
|
||||
{
|
||||
float4x4 parent_matrix_inv_world;
|
||||
|
||||
if (parent_context) {
|
||||
parent_matrix_inv_world = math::invert(parent_context->matrix_world);
|
||||
}
|
||||
else {
|
||||
parent_matrix_inv_world = float4x4::identity();
|
||||
}
|
||||
|
||||
const ExportChildren *children = graph_children(parent_context);
|
||||
if (!children) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_point_instance_ancestor = false;
|
||||
if (parent_context &&
|
||||
(parent_context->is_point_instance || parent_context->has_point_instance_ancestor))
|
||||
{
|
||||
has_point_instance_ancestor = true;
|
||||
}
|
||||
|
||||
for (HierarchyContext *context : *children) {
|
||||
context->has_point_instance_ancestor = has_point_instance_ancestor;
|
||||
|
||||
/* Update the context so that it is correct for this parent-child relation. */
|
||||
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. */
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool need_writers = context->is_point_proto || (!context->is_point_instance &&
|
||||
!context->has_point_instance_ancestor);
|
||||
|
||||
BLI_assert(DEG_is_evaluated_id(&context->object->id));
|
||||
if ((transform_writer.is_newly_created() || export_subset_.transforms) && need_writers) {
|
||||
/* 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 && include_data_writers(context) && need_writers) {
|
||||
make_writers_particle_systems(context);
|
||||
make_writer_object_data(context);
|
||||
}
|
||||
|
||||
if (include_child_writers(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.is_object_data_context = true;
|
||||
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);
|
||||
|
||||
const ObjectIdentifier object_key = ObjectIdentifier::for_hierarchy_context(&data_context);
|
||||
const ExportChildren *children = export_graph_.lookup_ptr(object_key);
|
||||
data_context.is_parent = children ? (children->size() > 0) : false;
|
||||
|
||||
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 = context->object->data;
|
||||
data_context.original_export_path = duplisource_export_path_.lookup(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;
|
||||
default:
|
||||
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_name(const Object *object, const Object *parent)
|
||||
{
|
||||
Set<std::string> &used_names = used_names_.lookup_or_add(parent ? parent->id.name : "", {});
|
||||
return make_unique_name(object->id.name + 2, used_names);
|
||||
}
|
||||
|
||||
std::string AbstractHierarchyIterator::get_object_data_name(const Object *object) const
|
||||
{
|
||||
const ID *object_data = object->data;
|
||||
return get_id_name(object_data);
|
||||
}
|
||||
|
||||
AbstractHierarchyWriter *AbstractHierarchyIterator::get_writer(
|
||||
const std::string &export_path) const
|
||||
{
|
||||
return writers_.lookup_default(export_path, nullptr);
|
||||
}
|
||||
|
||||
EnsuredWriter AbstractHierarchyIterator::ensure_writer(
|
||||
const 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_.add_new(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
|
||||
{
|
||||
/* Do not visit dupli objects if their `no_draw` flag is set (things like custom bone shapes) or
|
||||
* if they are meta-balls / text objects / NURBS surfaces. */
|
||||
if (dupli_object->no_draw || ELEM(dupli_object->ob->type, OB_MBALL, OB_FONT, OB_SURF)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace blender::io
|
||||
@@ -0,0 +1,348 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "IO_abstract_hierarchy_iterator.h"
|
||||
|
||||
#include "tests/blendfile_loading_base_test.h"
|
||||
|
||||
#include "BKE_scene.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLO_readfile.hh"
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Mapping from ID.name to set of export hierarchy path. Duplicated objects can be exported
|
||||
* multiple times with different export paths, hence the set. */
|
||||
using used_writers = Map<std::string, Set<std::string>>;
|
||||
|
||||
class TestHierarchyWriter : public AbstractHierarchyWriter {
|
||||
public:
|
||||
std::string writer_type;
|
||||
used_writers &writers_map;
|
||||
|
||||
TestHierarchyWriter(const std::string &writer_type, used_writers &writers_map)
|
||||
: writer_type(writer_type), writers_map(writers_map)
|
||||
{
|
||||
}
|
||||
|
||||
void write(HierarchyContext &context) override
|
||||
{
|
||||
const char *id_name = context.object->id.name;
|
||||
Set<std::string> &writers = writers_map.lookup_or_add(id_name, {});
|
||||
|
||||
if (writers.contains(context.export_path)) {
|
||||
ADD_FAILURE() << "Unexpectedly found another " << writer_type << " writer for " << id_name
|
||||
<< " to export to " << context.export_path;
|
||||
}
|
||||
writers.add_new(context.export_path);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class TestingHierarchyIterator : public AbstractHierarchyIterator {
|
||||
public: /* Public so that the test cases can directly inspect the created writers. */
|
||||
used_writers transform_writers;
|
||||
used_writers data_writers;
|
||||
used_writers hair_writers;
|
||||
used_writers particle_writers;
|
||||
|
||||
explicit TestingHierarchyIterator(Main *bmain, Depsgraph *depsgraph)
|
||||
: AbstractHierarchyIterator(bmain, depsgraph)
|
||||
{
|
||||
}
|
||||
~TestingHierarchyIterator() override
|
||||
{
|
||||
release_writers();
|
||||
}
|
||||
|
||||
protected:
|
||||
AbstractHierarchyWriter *create_transform_writer(const HierarchyContext * /*context*/) override
|
||||
{
|
||||
return new TestHierarchyWriter("transform", transform_writers);
|
||||
}
|
||||
AbstractHierarchyWriter *create_data_writer(const HierarchyContext * /*context*/) override
|
||||
{
|
||||
return new TestHierarchyWriter("data", data_writers);
|
||||
}
|
||||
AbstractHierarchyWriter *create_hair_writer(const HierarchyContext * /*context*/) override
|
||||
{
|
||||
return new TestHierarchyWriter("hair", hair_writers);
|
||||
}
|
||||
AbstractHierarchyWriter *create_particle_writer(const HierarchyContext * /*context*/) override
|
||||
{
|
||||
return new TestHierarchyWriter("particle", particle_writers);
|
||||
}
|
||||
|
||||
void release_writer(AbstractHierarchyWriter *writer) override
|
||||
{
|
||||
delete writer;
|
||||
}
|
||||
};
|
||||
|
||||
class AbstractHierarchyIteratorTest : public BlendfileLoadingBaseTest {
|
||||
protected:
|
||||
TestingHierarchyIterator *iterator;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
BlendfileLoadingBaseTest::SetUp();
|
||||
iterator = nullptr;
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
iterator_free();
|
||||
BlendfileLoadingBaseTest::TearDown();
|
||||
}
|
||||
|
||||
/* Create a test iterator. */
|
||||
void iterator_create()
|
||||
{
|
||||
iterator = new TestingHierarchyIterator(bfile->main, depsgraph);
|
||||
}
|
||||
/* Free the test iterator if it is not nullptr. */
|
||||
void iterator_free()
|
||||
{
|
||||
if (iterator == nullptr) {
|
||||
return;
|
||||
}
|
||||
delete iterator;
|
||||
iterator = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(AbstractHierarchyIteratorTest, ExportHierarchyTest)
|
||||
{
|
||||
/* Load the test blend file. */
|
||||
if (!blendfile_load("usd" SEP_STR "usd_hierarchy_export_test.blend")) {
|
||||
return;
|
||||
}
|
||||
depsgraph_create(DAG_EVAL_RENDER);
|
||||
iterator_create();
|
||||
|
||||
iterator->iterate_and_write();
|
||||
|
||||
/* Mapping from object name to set of export paths. */
|
||||
used_writers expected_transforms = {
|
||||
{"OBCamera", {"/Camera"}},
|
||||
{"OBDupli1", {"/Dupli1"}},
|
||||
{"OBDupli2", {"/ParentOfDupli2/Dupli2"}},
|
||||
{"OBGEO_Ear_L",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Ear_L-1",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1"}},
|
||||
{"OBGEO_Ear_R",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Ear_R-2",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2"}},
|
||||
{"OBGEO_Head",
|
||||
{"/Dupli1/GEO_Head-0",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0"}},
|
||||
{"OBGEO_Nose",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Nose-3",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3"}},
|
||||
{"OBGround plane", {"/Ground plane"}},
|
||||
{"OBOutsideDupliGrandParent", {"/Ground plane/OutsideDupliGrandParent"}},
|
||||
{"OBOutsideDupliParent", {"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent"}},
|
||||
{"OBParentOfDupli2", {"/ParentOfDupli2"}}};
|
||||
EXPECT_EQ(expected_transforms, iterator->transform_writers);
|
||||
|
||||
used_writers expected_data = {
|
||||
{"OBCamera", {"/Camera/Camera"}},
|
||||
{"OBGEO_Ear_L",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Ear_L-1/Ear",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L/Ear",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1/Ear"}},
|
||||
{"OBGEO_Ear_R",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Ear_R-2/Ear",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R/Ear",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2/Ear"}},
|
||||
{"OBGEO_Head",
|
||||
{"/Dupli1/GEO_Head-0/Face",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/Face",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/Face"}},
|
||||
{"OBGEO_Nose",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Nose-3/Nose",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose/Nose",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3/Nose"}},
|
||||
{"OBGround plane", {"/Ground plane/Plane"}},
|
||||
{"OBParentOfDupli2", {"/ParentOfDupli2/Icosphere"}},
|
||||
};
|
||||
|
||||
EXPECT_EQ(expected_data, iterator->data_writers);
|
||||
|
||||
/* The scene has no hair or particle systems. */
|
||||
EXPECT_EQ(0, iterator->hair_writers.size());
|
||||
EXPECT_EQ(0, iterator->particle_writers.size());
|
||||
|
||||
/* On the second iteration, everything should be written as well.
|
||||
* This tests the default value of iterator->export_subset_. */
|
||||
iterator->transform_writers.clear();
|
||||
iterator->data_writers.clear();
|
||||
iterator->iterate_and_write();
|
||||
EXPECT_EQ(expected_transforms, iterator->transform_writers);
|
||||
EXPECT_EQ(expected_data, iterator->data_writers);
|
||||
}
|
||||
|
||||
TEST_F(AbstractHierarchyIteratorTest, ExportSubsetTest)
|
||||
{
|
||||
/* The scene has no hair or particle systems, and this is already covered by ExportHierarchyTest,
|
||||
* so not included here. Update this test when hair & particle systems are included. */
|
||||
|
||||
/* Load the test blend file. */
|
||||
if (!blendfile_load("usd" SEP_STR "usd_hierarchy_export_test.blend")) {
|
||||
return;
|
||||
}
|
||||
depsgraph_create(DAG_EVAL_RENDER);
|
||||
iterator_create();
|
||||
|
||||
/* Mapping from object name to set of export paths. */
|
||||
used_writers expected_transforms = {
|
||||
{"OBCamera", {"/Camera"}},
|
||||
{"OBDupli1", {"/Dupli1"}},
|
||||
{"OBDupli2", {"/ParentOfDupli2/Dupli2"}},
|
||||
{"OBGEO_Ear_L",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Ear_L-1",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1"}},
|
||||
{"OBGEO_Ear_R",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Ear_R-2",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2"}},
|
||||
{"OBGEO_Head",
|
||||
{"/Dupli1/GEO_Head-0",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0"}},
|
||||
{"OBGEO_Nose",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Nose-3",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3"}},
|
||||
{"OBGround plane", {"/Ground plane"}},
|
||||
{"OBOutsideDupliGrandParent", {"/Ground plane/OutsideDupliGrandParent"}},
|
||||
{"OBOutsideDupliParent", {"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent"}},
|
||||
{"OBParentOfDupli2", {"/ParentOfDupli2"}}};
|
||||
|
||||
used_writers expected_data = {
|
||||
{"OBCamera", {"/Camera/Camera"}},
|
||||
{"OBGEO_Ear_L",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Ear_L-1/Ear",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L/Ear",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1/Ear"}},
|
||||
{"OBGEO_Ear_R",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Ear_R-2/Ear",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R/Ear",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2/Ear"}},
|
||||
{"OBGEO_Head",
|
||||
{"/Dupli1/GEO_Head-0/Face",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/Face",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/Face"}},
|
||||
{"OBGEO_Nose",
|
||||
{"/Dupli1/GEO_Head-0/GEO_Nose-3/Nose",
|
||||
"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose/Nose",
|
||||
"/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3/Nose"}},
|
||||
{"OBGround plane", {"/Ground plane/Plane"}},
|
||||
{"OBParentOfDupli2", {"/ParentOfDupli2/Icosphere"}},
|
||||
};
|
||||
|
||||
/* Even when only asking an export of transforms, on the first frame everything should be
|
||||
* exported. */
|
||||
{
|
||||
ExportSubset export_subset = {false};
|
||||
export_subset.transforms = true;
|
||||
export_subset.shapes = false;
|
||||
iterator->set_export_subset(export_subset);
|
||||
}
|
||||
iterator->iterate_and_write();
|
||||
EXPECT_EQ(expected_transforms, iterator->transform_writers);
|
||||
EXPECT_EQ(expected_data, iterator->data_writers);
|
||||
|
||||
/* Clear data to prepare for the next iteration. */
|
||||
iterator->transform_writers.clear();
|
||||
iterator->data_writers.clear();
|
||||
|
||||
/* Second iteration, should only write transforms now. */
|
||||
iterator->iterate_and_write();
|
||||
EXPECT_EQ(expected_transforms, iterator->transform_writers);
|
||||
EXPECT_EQ(0, iterator->data_writers.size());
|
||||
|
||||
/* Clear data to prepare for the next iteration. */
|
||||
iterator->transform_writers.clear();
|
||||
iterator->data_writers.clear();
|
||||
|
||||
/* Third iteration, should only write data now. */
|
||||
{
|
||||
ExportSubset export_subset = {false};
|
||||
export_subset.transforms = false;
|
||||
export_subset.shapes = true;
|
||||
iterator->set_export_subset(export_subset);
|
||||
}
|
||||
iterator->iterate_and_write();
|
||||
EXPECT_EQ(0, iterator->transform_writers.size());
|
||||
EXPECT_EQ(expected_data, iterator->data_writers);
|
||||
|
||||
/* Clear data to prepare for the next iteration. */
|
||||
iterator->transform_writers.clear();
|
||||
iterator->data_writers.clear();
|
||||
|
||||
/* Fourth iteration, should export everything now. */
|
||||
{
|
||||
ExportSubset export_subset = {false};
|
||||
export_subset.transforms = true;
|
||||
export_subset.shapes = true;
|
||||
iterator->set_export_subset(export_subset);
|
||||
}
|
||||
iterator->iterate_and_write();
|
||||
EXPECT_EQ(expected_transforms, iterator->transform_writers);
|
||||
EXPECT_EQ(expected_data, iterator->data_writers);
|
||||
}
|
||||
|
||||
/* Test class that constructs a depsgraph in such a way that it includes invisible objects. */
|
||||
class AbstractHierarchyIteratorInvisibleTest : public AbstractHierarchyIteratorTest {
|
||||
protected:
|
||||
void depsgraph_create(eEvaluationMode depsgraph_evaluation_mode) override
|
||||
{
|
||||
depsgraph = DEG_graph_new(
|
||||
bfile->main, bfile->curscene, bfile->cur_view_layer, depsgraph_evaluation_mode);
|
||||
DEG_graph_build_for_all_objects(depsgraph);
|
||||
BKE_scene_graph_update_tagged(depsgraph, bfile->main);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(AbstractHierarchyIteratorInvisibleTest, ExportInvisibleTest)
|
||||
{
|
||||
if (!blendfile_load("alembic" SEP_STR "visibility.blend")) {
|
||||
return;
|
||||
}
|
||||
depsgraph_create(DAG_EVAL_RENDER);
|
||||
iterator_create();
|
||||
|
||||
iterator->iterate_and_write();
|
||||
|
||||
/* Mapping from object name to set of export paths. */
|
||||
used_writers expected_transforms = {{"OBInvisibleAnimatedCube", {"/InvisibleAnimatedCube"}},
|
||||
{"OBInvisibleCube", {"/InvisibleCube"}},
|
||||
{"OBVisibleCube", {"/VisibleCube"}}};
|
||||
EXPECT_EQ(expected_transforms, iterator->transform_writers);
|
||||
|
||||
used_writers expected_data = {{"OBInvisibleAnimatedCube", {"/InvisibleAnimatedCube/Cube"}},
|
||||
{"OBInvisibleCube", {"/InvisibleCube/Cube"}},
|
||||
{"OBVisibleCube", {"/VisibleCube/Cube"}}};
|
||||
|
||||
EXPECT_EQ(expected_data, iterator->data_writers);
|
||||
|
||||
/* The scene has no hair or particle systems. */
|
||||
EXPECT_EQ(0, iterator->hair_writers.size());
|
||||
EXPECT_EQ(0, iterator->particle_writers.size());
|
||||
}
|
||||
|
||||
} // namespace blender::io
|
||||
@@ -0,0 +1,81 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "dupli_parent_finder.hh"
|
||||
|
||||
#include "BLI_assert.h"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
void DupliParentFinder::insert(const DupliObject *dupli_ob)
|
||||
{
|
||||
dupli_set_.add(dupli_ob->ob);
|
||||
|
||||
PersistentID dupli_pid(dupli_ob);
|
||||
pid_to_dupli_.add(dupli_pid, dupli_ob);
|
||||
instancer_pid_to_duplis_.lookup_or_add(dupli_pid.instancer_pid(), {}).add(dupli_ob);
|
||||
}
|
||||
|
||||
bool DupliParentFinder::is_duplicated(const Object *object) const
|
||||
{
|
||||
return dupli_set_.contains(object);
|
||||
}
|
||||
|
||||
const DupliObject *DupliParentFinder::find_suitable_export_parent(
|
||||
const DupliObject *dupli_ob) const
|
||||
{
|
||||
if (dupli_ob->ob->parent != nullptr) {
|
||||
const DupliObject *parent = find_duplicated_parent(dupli_ob);
|
||||
if (parent != nullptr) {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
||||
return find_instancer(dupli_ob);
|
||||
}
|
||||
|
||||
const DupliObject *DupliParentFinder::find_duplicated_parent(const DupliObject *dupli_ob) const
|
||||
{
|
||||
const PersistentID dupli_pid(dupli_ob);
|
||||
PersistentID parent_pid = dupli_pid.instancer_pid();
|
||||
|
||||
const Object *parent_ob = dupli_ob->ob->parent;
|
||||
BLI_assert(parent_ob != nullptr);
|
||||
|
||||
const Set<const DupliObject *> *found = instancer_pid_to_duplis_.lookup_ptr(parent_pid);
|
||||
if (!found) {
|
||||
/* Unexpected, as there should be at least one entry here, for the dupli_ob itself. */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const DupliObject *potential_parent_dupli : *found) {
|
||||
if (potential_parent_dupli->ob != parent_ob) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PersistentID potential_parent_pid(potential_parent_dupli);
|
||||
if (potential_parent_pid.is_from_same_instancer_as(dupli_pid)) {
|
||||
return potential_parent_dupli;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const DupliObject *DupliParentFinder::find_instancer(const DupliObject *dupli_ob) const
|
||||
{
|
||||
PersistentID dupli_pid(dupli_ob);
|
||||
PersistentID parent_pid = dupli_pid.instancer_pid();
|
||||
|
||||
const DupliObject *const *found = pid_to_dupli_.lookup_ptr(parent_pid);
|
||||
if (!found) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const DupliObject *instancer_dupli = *found;
|
||||
return instancer_dupli;
|
||||
}
|
||||
|
||||
} // namespace blender::io
|
||||
@@ -0,0 +1,41 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "IO_dupli_persistent_id.hh"
|
||||
|
||||
#include "BKE_duplilist.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_set.hh"
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
/* Find relations between duplicated objects. This class should be instanced for a single real
|
||||
* object, and fed its dupli-objects. */
|
||||
class DupliParentFinder final {
|
||||
private:
|
||||
/* To check whether an Object * is instanced by this duplicator. */
|
||||
Set<const Object *> dupli_set_;
|
||||
|
||||
/* To find the DupliObject given its Persistent ID. */
|
||||
using PIDToDupliMap = Map<const PersistentID, const DupliObject *>;
|
||||
PIDToDupliMap pid_to_dupli_;
|
||||
|
||||
/* Mapping from instancer PID to duplis instanced by it. */
|
||||
using InstancerPIDToDuplisMap = Map<const PersistentID, Set<const DupliObject *>>;
|
||||
InstancerPIDToDuplisMap instancer_pid_to_duplis_;
|
||||
|
||||
public:
|
||||
void insert(const DupliObject *dupli_ob);
|
||||
|
||||
bool is_duplicated(const Object *object) const;
|
||||
const DupliObject *find_suitable_export_parent(const DupliObject *dupli_ob) const;
|
||||
|
||||
private:
|
||||
const DupliObject *find_duplicated_parent(const DupliObject *dupli_ob) const;
|
||||
const DupliObject *find_instancer(const DupliObject *dupli_ob) const;
|
||||
};
|
||||
|
||||
} // namespace blender::io
|
||||
@@ -0,0 +1,126 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "dupli_parent_finder.hh"
|
||||
|
||||
#include <climits>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
PersistentID::PersistentID()
|
||||
{
|
||||
persistent_id_[0] = INT_MAX;
|
||||
}
|
||||
|
||||
PersistentID::PersistentID(const DupliObject *dupli_ob)
|
||||
{
|
||||
for (int index = 0; index < array_length_; ++index) {
|
||||
persistent_id_[index] = dupli_ob->persistent_id[index];
|
||||
}
|
||||
}
|
||||
|
||||
PersistentID::PersistentID(const PIDArray &persistent_id_values)
|
||||
{
|
||||
persistent_id_ = persistent_id_values;
|
||||
}
|
||||
|
||||
bool PersistentID::is_from_same_instancer_as(const PersistentID &other) const
|
||||
{
|
||||
if (persistent_id_[0] == INT_MAX || other.persistent_id_[0] == INT_MAX) {
|
||||
/* Either one or the other is not instanced at all, so definitely not from the same instancer.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Start at index 1 to skip the first digit. */
|
||||
for (int index = 1; index < array_length_; ++index) {
|
||||
const int pid_digit_a = persistent_id_[index];
|
||||
const int pid_digit_b = other.persistent_id_[index];
|
||||
|
||||
if (pid_digit_a != pid_digit_b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pid_digit_a == INT_MAX) {
|
||||
/* Both persistent IDs were identical so far, and this marks the end of the useful data. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
PersistentID PersistentID::instancer_pid() const
|
||||
{
|
||||
if (persistent_id_[0] == INT_MAX) {
|
||||
return PersistentID();
|
||||
}
|
||||
|
||||
/* Left-shift the entire PID by 1. */
|
||||
PIDArray new_pid_values;
|
||||
int index;
|
||||
for (index = 0; index < array_length_ - 1; ++index) {
|
||||
new_pid_values[index] = persistent_id_[index + 1];
|
||||
}
|
||||
new_pid_values[index] = INT_MAX;
|
||||
|
||||
return PersistentID(new_pid_values);
|
||||
}
|
||||
|
||||
std::string PersistentID::as_object_name_suffix() const
|
||||
{
|
||||
fmt::basic_memory_buffer<char, 64> buf;
|
||||
|
||||
/* Find one past the last index. */
|
||||
int index;
|
||||
for (index = 0; index < array_length_ && persistent_id_[index] < INT_MAX; ++index) {
|
||||
;
|
||||
}
|
||||
|
||||
/* Iterate backward to construct the string. */
|
||||
--index;
|
||||
for (; index >= 0; --index) {
|
||||
fmt::format_to(fmt::appender(buf), "{}", persistent_id_[index]);
|
||||
if (index > 0) {
|
||||
fmt::format_to(fmt::appender(buf), "-");
|
||||
}
|
||||
}
|
||||
|
||||
return fmt::to_string(buf);
|
||||
}
|
||||
|
||||
uint64_t PersistentID::hash() const
|
||||
{
|
||||
uint64_t hash = 5381;
|
||||
for (const int value : persistent_id_) {
|
||||
if (value == INT_MAX) {
|
||||
break;
|
||||
}
|
||||
hash = hash * 33 ^ uint64_t(value);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool operator==(const PersistentID &persistent_id_a, const PersistentID &persistent_id_b)
|
||||
{
|
||||
const PersistentID::PIDArray &pid_a = persistent_id_a.persistent_id_;
|
||||
const PersistentID::PIDArray &pid_b = persistent_id_b.persistent_id_;
|
||||
|
||||
for (int index = 0; index < PersistentID::array_length_; ++index) {
|
||||
const int pid_digit_a = pid_a[index];
|
||||
const int pid_digit_b = pid_b[index];
|
||||
|
||||
if (pid_digit_a != pid_digit_b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pid_a[index] == INT_MAX) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace blender::io
|
||||
55
blender-5.2.0/source/blender/io/common/intern/mesh_utils.cc
Normal file
55
blender-5.2.0/source/blender/io/common/intern/mesh_utils.cc
Normal file
@@ -0,0 +1,55 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_ID.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_object_types.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "IO_mesh_utils.hh"
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
const Mesh *mesh_coerce_for_export_setup(MeshCoerceForExport &coerce,
|
||||
Depsgraph *depsgraph,
|
||||
Object *obj_eval,
|
||||
const bool apply_modifiers)
|
||||
{
|
||||
/* Curves and NURBS surfaces have no mesh in their pre-modified state,
|
||||
* convert one on demand. */
|
||||
bool is_original_mesh_type = true;
|
||||
if (const ID *data_orig = obj_eval->runtime->data_orig) {
|
||||
if (GS(data_orig->name) != ID_ME) {
|
||||
is_original_mesh_type = false;
|
||||
if (!apply_modifiers) {
|
||||
coerce.owned = BKE_mesh_new_from_object(
|
||||
depsgraph, DEG_get_original(obj_eval), true, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (apply_modifiers) {
|
||||
coerce.mesh = BKE_object_get_evaluated_mesh(obj_eval);
|
||||
}
|
||||
else {
|
||||
coerce.mesh = is_original_mesh_type ? BKE_object_get_pre_modified_mesh(obj_eval) :
|
||||
coerce.owned;
|
||||
}
|
||||
|
||||
return coerce.mesh;
|
||||
}
|
||||
|
||||
MeshCoerceForExport::~MeshCoerceForExport()
|
||||
{
|
||||
if (owned) {
|
||||
BKE_id_free(nullptr, owned);
|
||||
owned = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::io
|
||||
@@ -0,0 +1,74 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "IO_abstract_hierarchy_iterator.h"
|
||||
|
||||
#include "BLI_assert.h"
|
||||
|
||||
#include "BKE_duplilist.hh"
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
ObjectIdentifier::ObjectIdentifier(Object *object,
|
||||
Object *duplicated_by,
|
||||
const PersistentID &persistent_id)
|
||||
: object(object), duplicated_by(duplicated_by), persistent_id(persistent_id)
|
||||
{
|
||||
/* Class invariants:
|
||||
* If duplicated_by is null, persistent_id must be default.
|
||||
* If duplicated_by is not null, persistent_id must not be default. */
|
||||
BLI_assert(duplicated_by == nullptr ? persistent_id == PersistentID() :
|
||||
!(persistent_id == PersistentID()));
|
||||
}
|
||||
|
||||
ObjectIdentifier ObjectIdentifier::for_real_object(Object *object)
|
||||
{
|
||||
return ObjectIdentifier(object, nullptr, PersistentID());
|
||||
}
|
||||
|
||||
ObjectIdentifier ObjectIdentifier::for_hierarchy_context(const HierarchyContext *context)
|
||||
{
|
||||
if (context == nullptr) {
|
||||
return for_graph_root();
|
||||
}
|
||||
if (context->duplicator != nullptr) {
|
||||
return ObjectIdentifier(context->object, context->duplicator, context->persistent_id);
|
||||
}
|
||||
return for_real_object(context->object);
|
||||
}
|
||||
|
||||
ObjectIdentifier ObjectIdentifier::for_duplicated_object(const DupliObject *dupli_object,
|
||||
Object *duplicated_by)
|
||||
{
|
||||
return ObjectIdentifier(dupli_object->ob, duplicated_by, PersistentID(dupli_object));
|
||||
}
|
||||
|
||||
ObjectIdentifier ObjectIdentifier::for_graph_root()
|
||||
{
|
||||
return ObjectIdentifier(nullptr, nullptr, PersistentID());
|
||||
}
|
||||
|
||||
bool ObjectIdentifier::is_root() const
|
||||
{
|
||||
return object == nullptr;
|
||||
}
|
||||
|
||||
bool operator==(const ObjectIdentifier &obj_ident_a, const ObjectIdentifier &obj_ident_b)
|
||||
{
|
||||
if (obj_ident_a.object != obj_ident_b.object) {
|
||||
return false;
|
||||
}
|
||||
if (obj_ident_a.duplicated_by != obj_ident_b.duplicated_by) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Return early if we know the expensive persistent_id check won't be necessary. */
|
||||
if (obj_ident_a.duplicated_by == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Same object, both are duplicated, use the persistent IDs to determine equality. */
|
||||
return obj_ident_a.persistent_id == obj_ident_b.persistent_id;
|
||||
}
|
||||
|
||||
} // namespace blender::io
|
||||
@@ -0,0 +1,209 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "IO_abstract_hierarchy_iterator.h"
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include <climits>
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Return object pointer for use in tests. This makes it possible to reliably test for
|
||||
* order/equality functions while using hard-coded values for simplicity. */
|
||||
Object *fake_pointer(int value)
|
||||
{
|
||||
return static_cast<Object *>(POINTER_FROM_INT(value));
|
||||
}
|
||||
|
||||
/* PersistentID subclass for use in tests, making it easier to construct test values. */
|
||||
class TestPersistentID : public PersistentID {
|
||||
public:
|
||||
TestPersistentID(int value0,
|
||||
int value1,
|
||||
int value2,
|
||||
int value3,
|
||||
int value4,
|
||||
int value5,
|
||||
int value6,
|
||||
int value7)
|
||||
{
|
||||
persistent_id_[0] = value0;
|
||||
persistent_id_[1] = value1;
|
||||
persistent_id_[2] = value2;
|
||||
persistent_id_[3] = value3;
|
||||
persistent_id_[4] = value4;
|
||||
persistent_id_[5] = value5;
|
||||
persistent_id_[6] = value6;
|
||||
persistent_id_[7] = value7;
|
||||
}
|
||||
TestPersistentID(int value0, int value1, int value2)
|
||||
: TestPersistentID(value0, value1, value2, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX)
|
||||
{
|
||||
}
|
||||
TestPersistentID(int value0, int value1) : TestPersistentID(value0, value1, INT_MAX) {}
|
||||
explicit TestPersistentID(int value0) : TestPersistentID(value0, INT_MAX) {}
|
||||
};
|
||||
|
||||
/* ObjectIdentifier subclass for use in tests, making it easier to construct test values. */
|
||||
class TestObjectIdentifier : public ObjectIdentifier {
|
||||
public:
|
||||
TestObjectIdentifier(Object *object, Object *duplicated_by, const PersistentID &persistent_id)
|
||||
: ObjectIdentifier(object, duplicated_by, persistent_id)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class ObjectIdentifierOrderTest : public testing::Test {};
|
||||
|
||||
TEST_F(ObjectIdentifierOrderTest, graph_root)
|
||||
{
|
||||
ObjectIdentifier id_root_1 = ObjectIdentifier::for_graph_root();
|
||||
ObjectIdentifier id_root_2 = ObjectIdentifier::for_graph_root();
|
||||
EXPECT_TRUE(id_root_1 == id_root_2);
|
||||
EXPECT_TRUE(id_root_1.hash() == id_root_2.hash());
|
||||
|
||||
ObjectIdentifier id_a = ObjectIdentifier::for_real_object(fake_pointer(1));
|
||||
EXPECT_FALSE(id_root_1 == id_a);
|
||||
|
||||
ObjectIdentifier id_accidental_root = ObjectIdentifier::for_real_object(nullptr);
|
||||
EXPECT_TRUE(id_root_1 == id_accidental_root);
|
||||
EXPECT_TRUE(id_root_1.hash() == id_accidental_root.hash());
|
||||
}
|
||||
|
||||
TEST_F(ObjectIdentifierOrderTest, real_objects)
|
||||
{
|
||||
ObjectIdentifier id_a = ObjectIdentifier::for_real_object(fake_pointer(1));
|
||||
ObjectIdentifier id_b = ObjectIdentifier::for_real_object(fake_pointer(2));
|
||||
EXPECT_FALSE(id_a == id_b);
|
||||
|
||||
ObjectIdentifier id_c = ObjectIdentifier::for_real_object(fake_pointer(1));
|
||||
EXPECT_TRUE(id_a == id_c);
|
||||
EXPECT_TRUE(id_a.hash() == id_c.hash());
|
||||
}
|
||||
|
||||
TEST_F(ObjectIdentifierOrderTest, duplicated_objects)
|
||||
{
|
||||
ObjectIdentifier id_real_a = ObjectIdentifier::for_real_object(fake_pointer(1));
|
||||
TestObjectIdentifier id_dupli_a(fake_pointer(1), fake_pointer(2), TestPersistentID(0));
|
||||
TestObjectIdentifier id_dupli_b(fake_pointer(1), fake_pointer(3), TestPersistentID(0));
|
||||
TestObjectIdentifier id_same_dupli_a(fake_pointer(1), fake_pointer(2), TestPersistentID(0));
|
||||
TestObjectIdentifier id_different_dupli_b(fake_pointer(1), fake_pointer(3), TestPersistentID(1));
|
||||
|
||||
EXPECT_FALSE(id_real_a == id_dupli_a);
|
||||
EXPECT_FALSE(id_dupli_a == id_dupli_b);
|
||||
|
||||
EXPECT_FALSE(id_dupli_b == id_different_dupli_b);
|
||||
EXPECT_FALSE(id_dupli_a == id_different_dupli_b);
|
||||
|
||||
EXPECT_TRUE(id_dupli_a == id_same_dupli_a);
|
||||
EXPECT_TRUE(id_dupli_a.hash() == id_same_dupli_a.hash());
|
||||
}
|
||||
|
||||
TEST_F(ObjectIdentifierOrderTest, behavior_as_map_keys)
|
||||
{
|
||||
ObjectIdentifier id_root = ObjectIdentifier::for_graph_root();
|
||||
ObjectIdentifier id_another_root = ObjectIdentifier::for_graph_root();
|
||||
ObjectIdentifier id_real_a = ObjectIdentifier::for_real_object(fake_pointer(1));
|
||||
TestObjectIdentifier id_dupli_a(fake_pointer(1), fake_pointer(2), TestPersistentID(0));
|
||||
TestObjectIdentifier id_dupli_b(fake_pointer(1), fake_pointer(3), TestPersistentID(0));
|
||||
AbstractHierarchyIterator::ExportGraph graph;
|
||||
|
||||
/* This inserts the keys with default values. */
|
||||
graph.add_new(id_root, {});
|
||||
graph.add_new(id_real_a, {});
|
||||
graph.add_new(id_dupli_a, {});
|
||||
graph.add_new(id_dupli_b, {});
|
||||
graph.add(id_another_root, {});
|
||||
|
||||
EXPECT_EQ(4, graph.size());
|
||||
|
||||
graph.remove_contained(id_another_root);
|
||||
EXPECT_EQ(3, graph.size());
|
||||
|
||||
TestObjectIdentifier id_another_dupli_b(fake_pointer(1), fake_pointer(3), TestPersistentID(0));
|
||||
graph.remove_contained(id_another_dupli_b);
|
||||
EXPECT_EQ(2, graph.size());
|
||||
}
|
||||
|
||||
TEST_F(ObjectIdentifierOrderTest, map_copy_and_update)
|
||||
{
|
||||
ObjectIdentifier id_root = ObjectIdentifier::for_graph_root();
|
||||
ObjectIdentifier id_real_a = ObjectIdentifier::for_real_object(fake_pointer(1));
|
||||
TestObjectIdentifier id_dupli_a(fake_pointer(1), fake_pointer(2), TestPersistentID(0));
|
||||
TestObjectIdentifier id_dupli_b(fake_pointer(1), fake_pointer(3), TestPersistentID(0));
|
||||
TestObjectIdentifier id_dupli_c(fake_pointer(1), fake_pointer(3), TestPersistentID(1));
|
||||
AbstractHierarchyIterator::ExportGraph graph;
|
||||
|
||||
/* This inserts the keys with default values. */
|
||||
graph.add_new(id_root, {});
|
||||
graph.add_new(id_real_a, {});
|
||||
graph.add_new(id_dupli_a, {});
|
||||
graph.add_new(id_dupli_b, {});
|
||||
graph.add_new(id_dupli_c, {});
|
||||
EXPECT_EQ(5, graph.size());
|
||||
|
||||
AbstractHierarchyIterator::ExportGraph graph_copy = graph;
|
||||
EXPECT_EQ(5, graph_copy.size());
|
||||
|
||||
/* Updating a value in a copy should not update the original. */
|
||||
HierarchyContext ctx1;
|
||||
HierarchyContext ctx2;
|
||||
ctx1.object = fake_pointer(1);
|
||||
ctx2.object = fake_pointer(2);
|
||||
|
||||
graph_copy.lookup(id_root).add_new(&ctx1);
|
||||
EXPECT_EQ(0, graph.lookup(id_root).size());
|
||||
|
||||
/* Deleting a key in the copy should not update the original. */
|
||||
graph_copy.remove_contained(id_dupli_c);
|
||||
EXPECT_EQ(4, graph_copy.size());
|
||||
EXPECT_EQ(5, graph.size());
|
||||
}
|
||||
|
||||
class PersistentIDTest : public testing::Test {};
|
||||
|
||||
TEST_F(PersistentIDTest, is_from_same_instancer)
|
||||
{
|
||||
PersistentID child_id_a = TestPersistentID(42, 327);
|
||||
PersistentID child_id_b = TestPersistentID(17, 327);
|
||||
PersistentID child_id_c = TestPersistentID(17);
|
||||
|
||||
EXPECT_TRUE(child_id_a.is_from_same_instancer_as(child_id_b));
|
||||
EXPECT_FALSE(child_id_a.is_from_same_instancer_as(child_id_c));
|
||||
}
|
||||
|
||||
TEST_F(PersistentIDTest, instancer_id)
|
||||
{
|
||||
PersistentID child_id = TestPersistentID(42, 327);
|
||||
|
||||
PersistentID expect_instancer_id = TestPersistentID(327);
|
||||
EXPECT_EQ(expect_instancer_id, child_id.instancer_pid());
|
||||
EXPECT_EQ(expect_instancer_id.hash(), child_id.instancer_pid().hash());
|
||||
|
||||
PersistentID empty_id;
|
||||
EXPECT_EQ(empty_id, child_id.instancer_pid().instancer_pid());
|
||||
EXPECT_EQ(empty_id.hash(), child_id.instancer_pid().instancer_pid().hash());
|
||||
}
|
||||
|
||||
TEST_F(PersistentIDTest, as_object_name_suffix)
|
||||
{
|
||||
EXPECT_EQ("", PersistentID().as_object_name_suffix());
|
||||
EXPECT_EQ("47", TestPersistentID(47).as_object_name_suffix());
|
||||
EXPECT_EQ("327-47", TestPersistentID(47, 327).as_object_name_suffix());
|
||||
EXPECT_EQ("42-327-47", TestPersistentID(47, 327, 42).as_object_name_suffix());
|
||||
|
||||
EXPECT_EQ("7-6-5-4-3-2-1-0", TestPersistentID(0, 1, 2, 3, 4, 5, 6, 7).as_object_name_suffix());
|
||||
|
||||
EXPECT_EQ("0-0-0", TestPersistentID(0, 0, 0).as_object_name_suffix());
|
||||
EXPECT_EQ("0-0", TestPersistentID(0, 0).as_object_name_suffix());
|
||||
EXPECT_EQ("-3--2--1", TestPersistentID(-1, -2, -3).as_object_name_suffix());
|
||||
}
|
||||
|
||||
} // namespace blender::io
|
||||
41
blender-5.2.0/source/blender/io/common/intern/orientation.cc
Normal file
41
blender-5.2.0/source/blender/io/common/intern/orientation.cc
Normal file
@@ -0,0 +1,41 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_types.hh"
|
||||
|
||||
#include "IO_orientation.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
const EnumPropertyItem io_transform_axis[] = {
|
||||
{IO_AXIS_X, "X", 0, "X", "Positive X axis"},
|
||||
{IO_AXIS_Y, "Y", 0, "Y", "Positive Y axis"},
|
||||
{IO_AXIS_Z, "Z", 0, "Z", "Positive Z axis"},
|
||||
{IO_AXIS_NEGATIVE_X, "NEGATIVE_X", 0, "-X", "Negative X axis"},
|
||||
{IO_AXIS_NEGATIVE_Y, "NEGATIVE_Y", 0, "-Y", "Negative Y axis"},
|
||||
{IO_AXIS_NEGATIVE_Z, "NEGATIVE_Z", 0, "-Z", "Negative Z axis"},
|
||||
{0, nullptr, 0, nullptr, nullptr}};
|
||||
|
||||
void io_ui_forward_axis_update(Main * /*main*/, Scene * /*scene*/, PointerRNA *ptr)
|
||||
{
|
||||
/* Both forward and up axes cannot be along the same direction. */
|
||||
|
||||
int forward = RNA_enum_get(ptr, "forward_axis");
|
||||
int up = RNA_enum_get(ptr, "up_axis");
|
||||
if ((forward % 3) == (up % 3)) {
|
||||
RNA_enum_set(ptr, "up_axis", (up + 1) % 6);
|
||||
}
|
||||
}
|
||||
|
||||
void io_ui_up_axis_update(Main * /*main*/, Scene * /*scene*/, PointerRNA *ptr)
|
||||
{
|
||||
int forward = RNA_enum_get(ptr, "forward_axis");
|
||||
int up = RNA_enum_get(ptr, "up_axis");
|
||||
if ((forward % 3) == (up % 3)) {
|
||||
RNA_enum_set(ptr, "forward_axis", (forward + 1) % 6);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
91
blender-5.2.0/source/blender/io/common/intern/path_util.cc
Normal file
91
blender-5.2.0/source/blender/io/common/intern/path_util.cc
Normal file
@@ -0,0 +1,91 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "IO_path_util.hh"
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.common"};
|
||||
|
||||
namespace io {
|
||||
|
||||
std::string path_reference(StringRefNull filepath,
|
||||
StringRefNull base_src,
|
||||
StringRefNull base_dst,
|
||||
ePathReferenceMode mode,
|
||||
Set<std::pair<std::string, std::string>> *copy_set)
|
||||
{
|
||||
const bool is_relative = BLI_path_is_rel(filepath.c_str());
|
||||
char filepath_abs[PATH_MAX];
|
||||
STRNCPY(filepath_abs, filepath.c_str());
|
||||
BLI_path_abs(filepath_abs, base_src.c_str());
|
||||
BLI_path_normalize(filepath_abs);
|
||||
|
||||
/* Figure out final mode to be used. */
|
||||
if (mode == PATH_REFERENCE_MATCH) {
|
||||
mode = is_relative ? PATH_REFERENCE_RELATIVE : PATH_REFERENCE_ABSOLUTE;
|
||||
}
|
||||
else if (mode == PATH_REFERENCE_AUTO) {
|
||||
mode = BLI_path_contains(base_dst.c_str(), filepath_abs) ? PATH_REFERENCE_RELATIVE :
|
||||
PATH_REFERENCE_ABSOLUTE;
|
||||
}
|
||||
else if (mode == PATH_REFERENCE_COPY) {
|
||||
char filepath_cpy[PATH_MAX];
|
||||
BLI_path_join(filepath_cpy, PATH_MAX, base_dst.c_str(), BLI_path_basename(filepath_abs));
|
||||
copy_set->add(std::make_pair(filepath_abs, filepath_cpy));
|
||||
STRNCPY(filepath_abs, filepath_cpy);
|
||||
mode = PATH_REFERENCE_RELATIVE;
|
||||
}
|
||||
|
||||
/* Now we know the final path mode. */
|
||||
if (mode == PATH_REFERENCE_ABSOLUTE) {
|
||||
return filepath_abs;
|
||||
}
|
||||
if (mode == PATH_REFERENCE_RELATIVE) {
|
||||
char rel_path[PATH_MAX];
|
||||
STRNCPY(rel_path, filepath_abs);
|
||||
BLI_path_rel(rel_path, base_dst.c_str());
|
||||
/* Can't always find relative path (e.g. between different drives). */
|
||||
if (!BLI_path_is_rel(rel_path)) {
|
||||
return filepath_abs;
|
||||
}
|
||||
return rel_path + 2; /* Skip blender's internal "//" prefix. */
|
||||
}
|
||||
if (mode == PATH_REFERENCE_STRIP) {
|
||||
return BLI_path_basename(filepath_abs);
|
||||
}
|
||||
BLI_assert_msg(false, "Invalid path reference mode");
|
||||
return filepath_abs;
|
||||
}
|
||||
|
||||
void path_reference_copy(const Set<std::pair<std::string, std::string>> ©_set)
|
||||
{
|
||||
for (const auto © : copy_set) {
|
||||
const char *src = copy.first.c_str();
|
||||
const char *dst = copy.second.c_str();
|
||||
if (!BLI_exists(src)) {
|
||||
CLOG_WARN(&LOG, "Missing source file '%s', not copying", src);
|
||||
continue;
|
||||
}
|
||||
if (0 == BLI_path_cmp_normalized(src, dst)) {
|
||||
continue; /* Source and destination are the same. */
|
||||
}
|
||||
if (!BLI_file_ensure_parent_dir_exists(dst)) {
|
||||
CLOG_WARN(&LOG, "Can't make directory for '%s', not copying", dst);
|
||||
continue;
|
||||
}
|
||||
if (BLI_copy(src, dst) != 0) {
|
||||
CLOG_WARN(&LOG, "Can't copy '%s' to '%s'", src, dst);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace io
|
||||
} // namespace blender
|
||||
169
blender-5.2.0/source/blender/io/common/intern/string_utils.cc
Normal file
169
blender-5.2.0/source/blender/io/common/intern/string_utils.cc
Normal file
@@ -0,0 +1,169 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "IO_string_utils.hh"
|
||||
|
||||
/* NOTE: we could use C++17 <charconv> from_chars to parse
|
||||
* floats, but even if some compilers claim full support,
|
||||
* their standard libraries are not quite there yet.
|
||||
* LLVM/libc++ only has a float parser since LLVM 14,
|
||||
* and GCC/libstdc++ since 11.1. So until at least these are
|
||||
* the minimum spec, use an external library. */
|
||||
#include "fast_float.h"
|
||||
#include <charconv>
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
StringRef read_next_line(StringRef &buffer)
|
||||
{
|
||||
const char *start = buffer.begin();
|
||||
const char *end = buffer.end();
|
||||
size_t len = 0;
|
||||
const char *ptr = start;
|
||||
while (ptr < end) {
|
||||
char c = *ptr++;
|
||||
if (c == '\n') {
|
||||
break;
|
||||
}
|
||||
++len;
|
||||
}
|
||||
|
||||
buffer = StringRef(ptr, end);
|
||||
return StringRef(start, len);
|
||||
}
|
||||
|
||||
static bool is_whitespace(char c)
|
||||
{
|
||||
return c <= ' ';
|
||||
}
|
||||
|
||||
const char *drop_whitespace(const char *p, const char *end)
|
||||
{
|
||||
while (p < end && is_whitespace(*p)) {
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
const char *drop_non_whitespace(const char *p, const char *end)
|
||||
{
|
||||
while (p < end && !is_whitespace(*p)) {
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static const char *drop_sign(const char *p, const char *end, int &sign)
|
||||
{
|
||||
sign = 1;
|
||||
if (p < end) {
|
||||
if (*p == '+') {
|
||||
++p;
|
||||
}
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
const char *try_parse_float(
|
||||
const char *p, const char *end, int fallback, bool &success, float &dst, bool skip_space)
|
||||
{
|
||||
if (skip_space) {
|
||||
p = drop_whitespace(p, end);
|
||||
}
|
||||
int sign = 0;
|
||||
p = drop_sign(p, end, sign);
|
||||
fast_float::from_chars_result res = fast_float::from_chars(p, end, dst);
|
||||
if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range) || res.ptr < end) {
|
||||
dst = fallback;
|
||||
success = false;
|
||||
}
|
||||
else {
|
||||
dst *= sign;
|
||||
success = true;
|
||||
}
|
||||
return res.ptr;
|
||||
}
|
||||
|
||||
const char *try_parse_int(
|
||||
const char *p, const char *end, int fallback, bool &success, int &dst, bool skip_space)
|
||||
{
|
||||
if (skip_space) {
|
||||
p = drop_whitespace(p, end);
|
||||
}
|
||||
int sign = 0;
|
||||
p = drop_sign(p, end, sign);
|
||||
std::from_chars_result res = std::from_chars(p, end, dst);
|
||||
if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range) || res.ptr < end) {
|
||||
dst = fallback;
|
||||
success = false;
|
||||
}
|
||||
else {
|
||||
dst *= sign;
|
||||
success = true;
|
||||
}
|
||||
return res.ptr;
|
||||
}
|
||||
|
||||
static const char *drop_plus(const char *p, const char *end)
|
||||
{
|
||||
if (p < end && *p == '+') {
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
const char *parse_float(const char *p,
|
||||
const char *end,
|
||||
float fallback,
|
||||
float &dst,
|
||||
bool skip_space,
|
||||
bool require_trailing_space)
|
||||
{
|
||||
if (skip_space) {
|
||||
p = drop_whitespace(p, end);
|
||||
}
|
||||
p = drop_plus(p, end);
|
||||
fast_float::from_chars_result res = fast_float::from_chars(p, end, dst);
|
||||
if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range)) {
|
||||
dst = fallback;
|
||||
}
|
||||
else if (require_trailing_space && res.ptr < end && !is_whitespace(*res.ptr)) {
|
||||
/* If there are trailing non-space characters, do not eat up the number. */
|
||||
dst = fallback;
|
||||
return p;
|
||||
}
|
||||
return res.ptr;
|
||||
}
|
||||
|
||||
const char *parse_floats(const char *p,
|
||||
const char *end,
|
||||
float fallback,
|
||||
float *dst,
|
||||
int count,
|
||||
bool require_trailing_space)
|
||||
{
|
||||
for (int i = 0; i < count; ++i) {
|
||||
p = parse_float(p, end, fallback, dst[i], true, require_trailing_space);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
const char *parse_int(const char *p, const char *end, int fallback, int &dst, bool skip_space)
|
||||
{
|
||||
if (skip_space) {
|
||||
p = drop_whitespace(p, end);
|
||||
}
|
||||
p = drop_plus(p, end);
|
||||
std::from_chars_result res = std::from_chars(p, end, dst);
|
||||
if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range)) {
|
||||
dst = fallback;
|
||||
}
|
||||
return res.ptr;
|
||||
}
|
||||
|
||||
} // namespace blender::io
|
||||
@@ -0,0 +1,139 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "IO_string_utils.hh"
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
#define EXPECT_STRREF_EQ(str1, str2) EXPECT_STREQ(str1, std::string(str2).c_str())
|
||||
|
||||
TEST(io_common_string_utils, read_next_line)
|
||||
{
|
||||
std::string str = "abc\n \n\nline with \t spaces\nCRLF ending:\r\na";
|
||||
StringRef s = str;
|
||||
EXPECT_STRREF_EQ("abc", read_next_line(s));
|
||||
EXPECT_STRREF_EQ(" ", read_next_line(s));
|
||||
EXPECT_STRREF_EQ("", read_next_line(s));
|
||||
EXPECT_STRREF_EQ("line with \t spaces", read_next_line(s));
|
||||
EXPECT_STRREF_EQ("CRLF ending:\r", read_next_line(s));
|
||||
EXPECT_STRREF_EQ("a", read_next_line(s));
|
||||
EXPECT_TRUE(s.is_empty());
|
||||
}
|
||||
|
||||
static StringRef drop_whitespace(StringRef s)
|
||||
{
|
||||
return StringRef(drop_whitespace(s.begin(), s.end()), s.end());
|
||||
}
|
||||
static StringRef parse_int(StringRef s, int fallback, int &dst, bool skip_space = true)
|
||||
{
|
||||
return StringRef(parse_int(s.begin(), s.end(), fallback, dst, skip_space), s.end());
|
||||
}
|
||||
static StringRef parse_float(StringRef s,
|
||||
float fallback,
|
||||
float &dst,
|
||||
bool skip_space = true,
|
||||
bool require_trailing_space = false)
|
||||
{
|
||||
return StringRef(
|
||||
parse_float(s.begin(), s.end(), fallback, dst, skip_space, require_trailing_space), s.end());
|
||||
}
|
||||
|
||||
TEST(io_common_string_utils, drop_whitespace)
|
||||
{
|
||||
/* Empty */
|
||||
EXPECT_STRREF_EQ("", drop_whitespace(""));
|
||||
/* Only whitespace */
|
||||
EXPECT_STRREF_EQ("", drop_whitespace(" "));
|
||||
EXPECT_STRREF_EQ("", drop_whitespace(" "));
|
||||
EXPECT_STRREF_EQ("", drop_whitespace(" \t\n\r "));
|
||||
/* Drops leading whitespace */
|
||||
EXPECT_STRREF_EQ("a", drop_whitespace(" a"));
|
||||
EXPECT_STRREF_EQ("a b", drop_whitespace(" a b"));
|
||||
EXPECT_STRREF_EQ("a b ", drop_whitespace(" a b "));
|
||||
/* No leading whitespace */
|
||||
EXPECT_STRREF_EQ("c", drop_whitespace("c"));
|
||||
/* Case with backslash, should be treated as whitespace */
|
||||
EXPECT_STRREF_EQ("d", drop_whitespace(" \t d"));
|
||||
}
|
||||
|
||||
TEST(io_common_string_utils, parse_int_valid)
|
||||
{
|
||||
std::string str = "1 -10 \t 1234 1234567890 +7 123a";
|
||||
StringRef s = str;
|
||||
int val;
|
||||
s = parse_int(s, 0, val);
|
||||
EXPECT_EQ(1, val);
|
||||
s = parse_int(s, 0, val);
|
||||
EXPECT_EQ(-10, val);
|
||||
s = parse_int(s, 0, val);
|
||||
EXPECT_EQ(1234, val);
|
||||
s = parse_int(s, 0, val);
|
||||
EXPECT_EQ(1234567890, val);
|
||||
s = parse_int(s, 0, val);
|
||||
EXPECT_EQ(7, val);
|
||||
s = parse_int(s, 0, val);
|
||||
EXPECT_EQ(123, val);
|
||||
EXPECT_STRREF_EQ("a", s);
|
||||
}
|
||||
|
||||
TEST(io_common_string_utils, parse_int_invalid)
|
||||
{
|
||||
int val;
|
||||
/* Invalid syntax */
|
||||
EXPECT_STRREF_EQ("--123", parse_int("--123", -1, val));
|
||||
EXPECT_EQ(val, -1);
|
||||
EXPECT_STRREF_EQ("foobar", parse_int("foobar", -2, val));
|
||||
EXPECT_EQ(val, -2);
|
||||
/* Out of integer range */
|
||||
EXPECT_STRREF_EQ(" a", parse_int("1234567890123 a", -3, val));
|
||||
EXPECT_EQ(val, -3);
|
||||
/* Has leading white-space when we don't expect it */
|
||||
EXPECT_STRREF_EQ(" 1", parse_int(" 1", -4, val, false));
|
||||
EXPECT_EQ(val, -4);
|
||||
}
|
||||
|
||||
TEST(io_common_string_utils, parse_float_valid)
|
||||
{
|
||||
std::string str = "1 -10 123.5 -17.125 0.1 1e6 50.0e-1";
|
||||
StringRef s = str;
|
||||
float val;
|
||||
s = parse_float(s, 0, val);
|
||||
EXPECT_EQ(1.0f, val);
|
||||
s = parse_float(s, 0, val);
|
||||
EXPECT_EQ(-10.0f, val);
|
||||
s = parse_float(s, 0, val);
|
||||
EXPECT_EQ(123.5f, val);
|
||||
s = parse_float(s, 0, val);
|
||||
EXPECT_EQ(-17.125f, val);
|
||||
s = parse_float(s, 0, val);
|
||||
EXPECT_EQ(0.1f, val);
|
||||
s = parse_float(s, 0, val);
|
||||
EXPECT_EQ(1.0e6f, val);
|
||||
s = parse_float(s, 0, val);
|
||||
EXPECT_EQ(5.0f, val);
|
||||
EXPECT_TRUE(s.is_empty());
|
||||
}
|
||||
|
||||
TEST(io_common_string_utils, parse_float_invalid)
|
||||
{
|
||||
float val;
|
||||
/* Invalid syntax */
|
||||
EXPECT_STRREF_EQ("_0", parse_float("_0", -1.0f, val));
|
||||
EXPECT_EQ(val, -1.0f);
|
||||
EXPECT_STRREF_EQ("..5", parse_float("..5", -2.0f, val));
|
||||
EXPECT_EQ(val, -2.0f);
|
||||
/* Out of float range. */
|
||||
EXPECT_STRREF_EQ(" a", parse_float("9.0e500 a", -3.0f, val));
|
||||
EXPECT_EQ(val, -3.0f);
|
||||
/* Has leading white-space when we don't expect it */
|
||||
EXPECT_STRREF_EQ(" 1", parse_float(" 1", -4.0f, val, false));
|
||||
EXPECT_EQ(val, -4.0f);
|
||||
/* Has trailing non-number characters when we don't want them */
|
||||
EXPECT_STRREF_EQ("123.5.png", parse_float(" 123.5.png", -5.0f, val, true, true));
|
||||
EXPECT_EQ(val, -5.0f);
|
||||
}
|
||||
|
||||
} // namespace blender::io
|
||||
117
blender-5.2.0/source/blender/io/common/intern/subdiv_disabler.cc
Normal file
117
blender-5.2.0/source/blender/io/common/intern/subdiv_disabler.cc
Normal file
@@ -0,0 +1,117 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "IO_subdiv_disabler.hh"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "DNA_layer_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "BKE_layer.hh"
|
||||
#include "BKE_modifier.hh"
|
||||
|
||||
namespace blender::io {
|
||||
|
||||
ModifierData *SubdivModifierDisabler::get_subdiv_modifier(Scene *scene,
|
||||
const Object *ob,
|
||||
ModifierMode mode)
|
||||
{
|
||||
/* Returns the last subdiv modifier associated with an object,
|
||||
* if that modifier should be disabled.
|
||||
* We do not disable the subdiv modifier if other modifiers are
|
||||
* applied after it, with the sole exception of particle modifiers,
|
||||
* which are allowed.
|
||||
* Returns nullptr if there is not any subdiv modifier to disable.
|
||||
*/
|
||||
|
||||
ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last);
|
||||
|
||||
for (; md; md = md->prev) {
|
||||
/* Ignore disabled modifiers. */
|
||||
if (!BKE_modifier_is_enabled(scene, md, mode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (md->type == eModifierType_Subsurf) {
|
||||
SubsurfModifierData *smd = reinterpret_cast<SubsurfModifierData *>(md);
|
||||
|
||||
if (smd->subdivType == ME_CC_SUBSURF) {
|
||||
/* This is a Catmull-Clark modifier. */
|
||||
return md;
|
||||
}
|
||||
|
||||
/* Not Catmull-Clark, so ignore it. */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* If any modifier other than a particle system exists after the
|
||||
* subdiv modifier, then abort. */
|
||||
if (md->type != eModifierType_ParticleSystem) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SubdivModifierDisabler::SubdivModifierDisabler(Depsgraph *depsgraph) : depsgraph_(depsgraph) {}
|
||||
|
||||
SubdivModifierDisabler::~SubdivModifierDisabler()
|
||||
{
|
||||
/* Enable previously disabled modifiers. */
|
||||
for (ModifierData *modifier : disabled_modifiers_) {
|
||||
modifier->mode &= ~eModifierMode_DisableTemporary;
|
||||
}
|
||||
|
||||
/* Update object to render with restored modifiers in the viewport. */
|
||||
for (Object *object : modified_objects_) {
|
||||
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
||||
}
|
||||
}
|
||||
|
||||
void SubdivModifierDisabler::disable_modifiers()
|
||||
{
|
||||
eEvaluationMode eval_mode = DEG_get_mode(depsgraph_);
|
||||
const ModifierMode mode = eval_mode == DAG_EVAL_VIEWPORT ? eModifierMode_Realtime :
|
||||
eModifierMode_Render;
|
||||
|
||||
Scene *scene = DEG_get_input_scene(depsgraph_);
|
||||
ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph_);
|
||||
|
||||
BKE_view_layer_synced_ensure(*DEG_get_bmain(depsgraph_), scene, view_layer);
|
||||
for (Base &base : *BKE_view_layer_object_bases_get(view_layer)) {
|
||||
Object *object = base.object;
|
||||
|
||||
if (object->type != OB_MESH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if a subdiv modifier exists, and should be disabled. */
|
||||
ModifierData *mod = get_subdiv_modifier(scene, object, mode);
|
||||
if (!mod) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* This might disable more modifiers than necessary, as it doesn't take restrictions like
|
||||
* "export selected objects only" into account. However, with the subdivisions disabled,
|
||||
* moving to a different frame is also going to be faster, so in the end this is probably
|
||||
* a good thing to do. */
|
||||
disable_modifier(mod);
|
||||
modified_objects_.append(object);
|
||||
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
||||
}
|
||||
}
|
||||
|
||||
void SubdivModifierDisabler::disable_modifier(ModifierData *mod)
|
||||
{
|
||||
mod->mode |= eModifierMode_DisableTemporary;
|
||||
disabled_modifiers_.append(mod);
|
||||
}
|
||||
|
||||
} // namespace blender::io
|
||||
Reference in New Issue
Block a user