Changeset View
Standalone View
source/blender/usd/intern/abstract_hierarchy_iterator.h
- This file was added.
| /* | |||||
| * This program is free software; you can redistribute it and/or | |||||
| * modify it under the terms of the GNU General Public License | |||||
| * as published by the Free Software Foundation; either version 2 | |||||
| * of the License, or (at your option) any later version. | |||||
| * | |||||
| * This program is distributed in the hope that it will be useful, | |||||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| * GNU General Public License for more details. | |||||
| * | |||||
| * You should have received a copy of the GNU General Public License | |||||
| * along with this program; if not, write to the Free Software Foundation, | |||||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
| * | |||||
| * The Original Code is Copyright (C) 2019 Blender Foundation. | |||||
| * All rights reserved. | |||||
| */ | |||||
| /* | |||||
| * This file contains the AbstractHierarchyIterator. It is intended for exporters for file | |||||
| * formats that concern an entire hierarchy of objects (rather than, for example, an OBJ file that | |||||
| * contains only a single mesh). Examples are Universal Scene Description (USD) and Alembic. | |||||
| * AbstractHierarchyIterator is intended to be subclassed to support concrete file formats. | |||||
| * | |||||
| * The AbstractHierarchyIterator makes a distinction between the actual object hierarchy and the | |||||
| * export hierarchy. The former is the parent/child structure in Blender, which can have multiple | |||||
| * parent-like objects. For example, a duplicated object can have both a duplicator and a parent, | |||||
| * both determining the final transform. The export hierarchy is the hierarchy as written to the | |||||
| * file, and every object has only one export-parent. | |||||
| * | |||||
| * Currently the AbstractHierarchyIterator does not make any decisions about *what* to export. | |||||
| * Selections like "selected only" or "no hair systems" are left to concrete subclasses. | |||||
| */ | |||||
| #ifndef __USD__ABSTRACT_HIERARCHY_ITERATOR_H__ | |||||
| #define __USD__ABSTRACT_HIERARCHY_ITERATOR_H__ | |||||
| #include <map> | |||||
| #include <string> | |||||
| #include <set> | |||||
sergey: @JacquesLucke , in functions branch, do you also use `<map>` and `<set>` directly? | |||||
Not Done Inline ActionsNo, I'm not using the map and set from the standard library. JacquesLucke: No, I'm not using the map and set from the standard library. | |||||
| struct Base; | |||||
| struct Depsgraph; | |||||
| struct DupliObject; | |||||
| struct ID; | |||||
| struct Object; | |||||
| struct ParticleSystem; | |||||
| struct ViewLayer; | |||||
| class AbstractHierarchyWriter; | |||||
| /* HierarchyContext structs are created by the AbstractHierarchyIterator. Each HierarchyContext | |||||
| * struct contains everything necessary to export a single object to a file. */ | |||||
| struct HierarchyContext { | |||||
| /*********** Determined during hierarchy iteration: ***************/ | |||||
Done Inline ActionsSuch rather generic name in a global namespace. I would say USD integration is to be in its own namespace. sergey: Such rather generic name in a global namespace.
I would say USD integration is to be in its… | |||||
| Object *object; | |||||
| Object *export_parent; | |||||
| Object *duplicator; | |||||
| float matrix_world[4][4]; | |||||
| std::string export_name; | |||||
| /* When weak_export=true, the object will be exported only as transform, and only if is an | |||||
| * ancestor of an object with weak_export=false. | |||||
| * | |||||
| * In other words: when weak_export=true but this object has no children, or all decendants also | |||||
Done Inline ActionsAs discussed on Friday, please elaborate on the weak objects concept (since this is our own invention, and you had nice clean and short example). sergey: As discussed on Friday, please elaborate on the weak objects concept (since this is our own… | |||||
| * have weak_export=true, this object (and by recursive reasoning all its decendants) will be | |||||
| * excluded from the export. | |||||
| * | |||||
| * The export hierarchy is kept as close to the the hierarchy in Blender as possible. As such, an | |||||
| * object that serves as a parent for another object, but which should NOT be exported itself, is | |||||
| * exported only as transform (i.e. as empty). This happens with objects that are part of a | |||||
| * holdout collection (which prevents them from being exported) but also parent of an exported | |||||
| * object. */ | |||||
| bool weak_export; | |||||
| /* When true, this object should check its parents for animation data when determining whether | |||||
| * it's animated. This is necessary when a parent object in Blender is not part of the export. */ | |||||
| bool animation_check_include_parent; | |||||
| /*********** Determined during writer creation: ***************/ | |||||
| float parent_matrix_inv_world[4][4]; // Inverse of the parent's world matrix. | |||||
| std::string export_path; // Hierarchical path, such as "/grandparent/parent/objectname". | |||||
Done Inline ActionsWhy such specific about std::set? This applies to any ordered container, std::sort and so on.. Can as well trade this obvious statement to a comment about instancing ;) sergey: Why such specific about `std::set`? This applies to any ordered container, `std::sort` and so… | |||||
Done Inline ActionsBecause inserting into a std::set is what is actually done, so that's the purpose (and the tested use) of the function. sybren: Because inserting into a `std::set` is what is actually done, so that's the purpose (and the… | |||||
| ParticleSystem *particle_system; // Only set for particle/hair writers. | |||||
| /* Hierarchical path of the object this object is duplicating; only set when this object should | |||||
| * be stored as a reference to its original. It can happen that the original is not part of the | |||||
| * exported objects, in which case this string is empty even though 'duplicator' is set. */ | |||||
| std::string original_export_path; | |||||
| bool operator<(const HierarchyContext &other) const; | |||||
Done Inline ActionsWhat kind of instance is this? sergey: What kind of instance is this? | |||||
| /* Return a HierarchyContext representing the root of the export hierarchy. */ | |||||
| static const HierarchyContext *root(); | |||||
| /* For handling instanced collections, instances created by particles, etc. */ | |||||
| bool is_instance() const; | |||||
| void mark_as_instance_of(const std::string &reference_export_path); | |||||
| void mark_as_not_instanced(); | |||||
| }; | |||||
| /* Abstract writer for objects. Create concrete subclasses to write to USD, Alembic, etc. | |||||
| * | |||||
| * Instantiated by the AbstractHierarchyIterator on the first frame an object exists. Generally | |||||
| * that's the first frame to be exported, but can be later, for example when objects are | |||||
| * instantiated by particles. The AbstractHierarchyWriter::write() function is called on every | |||||
Done Inline Actionsbut wasn't used in this iteration? sergey: but wasn't used in this iteration?
Which iteration? | |||||
| * frame the object exists in the dependency graph and should be exported. | |||||
| */ | |||||
| class AbstractHierarchyWriter { | |||||
| public: | |||||
| virtual ~AbstractHierarchyWriter(); | |||||
| virtual void write(HierarchyContext &context) = 0; | |||||
| // TODO(Sybren): add function like absent() that's called when a writer was previously created, | |||||
| // but wasn't used while exporting the current frame (for example, a particle-instanced mesh of | |||||
| // which the particle is no longer alive). | |||||
| }; | |||||
| /* AbstractHierarchyIterator iterates over objects in a dependency graph, and constructs export | |||||
| * writers. These writers are then called to perform the actual writing to a USD or Alembic file. | |||||
| * | |||||
Done Inline ActionsWhat is <Object*, Object*> for? sergey: What is `<Object*, Object*>` for?
Add dedicated typedef for it with a small comment? | |||||
| * Dealing with file- and scene-level data (for example, creating a USD scene, setting the frame | |||||
| * rate, etc.) is not part of the AbstractHierarchyIterator class structure, and should be done | |||||
| * in separate code. | |||||
| */ | |||||
| class AbstractHierarchyIterator { | |||||
| public: | |||||
| /* Mapping from export path to writer. */ | |||||
| typedef std::map<std::string, AbstractHierarchyWriter *> WriterMap; | |||||
| /* Pair of a duplicated object and its duplicator, typically a pair of HierarchyContext::object | |||||
Done Inline ActionsThis misses indication of being private/protected method. Now, don't think we strictly enforce way of doing it. Some code uses m_ prefix, I follow _ suffix. Pick one! :) sergey: This misses indication of being private/protected method.
Now, don't think we strictly enforce… | |||||
| * and HierarchyContext::duplicator. */ | |||||
| typedef std::pair<Object *, Object *> DupliAndDuplicator; | |||||
| /* All the children of some object, as per the export hierarchy. */ | |||||
| typedef std::set<HierarchyContext *> ExportChildren; | |||||
| /* Mapping from an object and its duplicator to the object's export-children. */ | |||||
| typedef std::map<DupliAndDuplicator, ExportChildren> ExportGraph; | |||||
| /* Mapping from (potential) duplicator ID to export path. */ | |||||
| typedef std::map<ID *, std::string> ExportPathMap; | |||||
| protected: | |||||
| ExportGraph export_graph_; | |||||
| ExportPathMap duplisource_export_path_; | |||||
| Depsgraph *depsgraph_; | |||||
| WriterMap writers_; | |||||
| public: | |||||
Done Inline ActionsIs it supposed to be always overriden? virtual std::string make_valid_name(const std::string &name) const = 0; sergey: Is it supposed to be always overriden?
If so, make pure virtual:
virtual std::string… | |||||
Done Inline ActionsIt only needs to be overridden when the exported file format has stricter naming rules than Blender. sybren: It only needs to be overridden when the exported file format has stricter naming rules than… | |||||
| explicit AbstractHierarchyIterator(Depsgraph *depsgraph); | |||||
| virtual ~AbstractHierarchyIterator(); | |||||
Done Inline ActionsProbably? Point is, that part of comment doesn't really add usable information. sergey: Probably?
If i'll be working on a `SergeyHierarchyIterator`, do i need to override it?
Point… | |||||
| /* Iterate over the depsgraph, create writers, and tell the writers to write. | |||||
| * Main entry point for the AbstractHierarchyIterator, must be called for every to-be-exported | |||||
| * frame. */ | |||||
| void iterate_and_write(); | |||||
Done Inline ActionsSame as above. sergey: Same as above. | |||||
| /* Release all writers. Call after all frames have been exported. */ | |||||
| void release_writers(); | |||||
Done Inline ActionsAnother interesting aspect here. To me it sounds a very thing which iterator will want to do: tweak specific of how iteration happens. Personally what i would do if those were a subject for overriding in sub-classes i would do them virtual beforehands. Some other developers only do virtual when needed. If those are not intended to be overriden, add a small comment that it will break things? sergey: Another interesting aspect here.
We don't let subclasses to user/override them?
To me it… | |||||
Done Inline Actions
While making this code for USD, I also kept the Alembic exporter in mind. These would have the same method of iteration, but do something different with the result. As such, I didn't design this bit of the code with overridability in mind. It won't necessarily break things when those functions are overridden, but I don't see the need to do that either. I'd say this is something to revisit when overriding these functions is an actual necessity, and then design to suit that necessity. sybren: > To me it sounds a very thing which iterator will want to do: tweak specific of how iteration… | |||||
| /* Convert the given name to something that is valid for the exported file format. | |||||
| * This base implementation is a no-op; override in a concrete subclass. */ | |||||
| virtual std::string make_valid_name(const std::string &name) const; | |||||
| /* Return the name of this ID datablock that is valid for the exported file format. Overriding is | |||||
| * only necessary if make_valid_name(id->name+2) is not suitable for the exported file format. | |||||
| * NULL-safe: when `id == nullptr` this returns an empty string. */ | |||||
| virtual std::string get_id_name(const ID *id) const; | |||||
| /* Given a HierarchyContext of some Object *, return an export path that is valid for its | |||||
| * object->data. Overriding is necessary when the exported format does NOT expect the object's | |||||
| * data to be a child of the object. */ | |||||
Done Inline ActionsCan we have a dedicated typedef for what is it? With more semantic in it. Also, are you intending to modify the result? If not, use const reference. sergey: Can we have a dedicated typedef for what is it? With more semantic in it.
Also, are you… | |||||
| virtual std::string get_object_data_path(const HierarchyContext *context) const; | |||||
| private: | |||||
| void debug_print_export_graph() const; | |||||
| void export_graph_construct(); | |||||
| void export_graph_prune(); | |||||
| void export_graph_clear(); | |||||
| void visit_object(Object *object, Object *export_parent, bool weak_export); | |||||
| void visit_dupli_object(DupliObject *dupli_object, | |||||
Done Inline ActionsWhat is supposed to happen when get_object_data_name() is called on Empty? sergey: What is supposed to happen when `get_object_data_name()` is called on Empty? | |||||
| Object *duplicator, | |||||
| const std::set<Object *> &dupli_set); | |||||
| ExportChildren &graph_children(const HierarchyContext *parent_context); | |||||
| void determine_export_paths(const HierarchyContext *parent_context); | |||||
| void determine_duplication_references(const HierarchyContext *parent_context, | |||||
| std::string indent); | |||||
| void make_writers(const HierarchyContext *parent_context); | |||||
| void make_writer_object_data(const HierarchyContext *context); | |||||
| void make_writers_particle_systems(const HierarchyContext *context); | |||||
| /* Convenience wrappers around get_id_name(). */ | |||||
| std::string get_object_name(const Object *object) const; | |||||
| std::string get_object_data_name(const Object *object) const; | |||||
Done Inline ActionsWhy link ? Is dupli-object in Blender's code, Instance in the interface. Link is something from a linked list or something. sergey: Why `link` ? Is dupli-object in Blender's code, Instance in the interface.
Link is something… | |||||
| AbstractHierarchyWriter *get_writer(const std::string &export_path); | |||||
Done Inline ActionsWhat's the difference between visit and export? So far my understanding is:
In other words, visit and export are two different things. But then it opens questions like:
sergey: What's the difference between `visit` and `export`?
So far my understanding is:
- We visit the… | |||||
| typedef AbstractHierarchyWriter *(AbstractHierarchyIterator::*create_writer_func)( | |||||
| const HierarchyContext *); | |||||
| /* Ensure that a writer exists; if it doesn't, call create_func(context). The create_func | |||||
| * function should be one of the create_XXXX_writer(context) functions declared below. */ | |||||
| AbstractHierarchyWriter *ensure_writer(HierarchyContext *context, | |||||
Done Inline ActionsIn an abstract class which claims it can do more than just Alembic/USD, implemented in Blender source codebase, why not to follow Blender's terminology and call it tfm? Who owns the writer? Is it up to the caller to delete it? sergey: In an abstract class which claims it can do more than just Alembic/USD, implemented in Blender… | |||||
Done Inline ActionsBecause I didn't know 'tfm' was considered such a good name it should be used more often. And I still don't feel it's a good name. sybren: Because I didn't know 'tfm' was considered such a good name it should be used more often. And I… | |||||
Done Inline ActionsI still don't know xform is superior name over tfm. The latter one is a historical term in Blender and in Cycles. The former one is some newcomer. After deeper look it actually seems that XFORM started to spread into other areas with the Gizmo work. While i don't really agree this is a great move i guess is better to STFU and let the cancer spread.. Ugh. sergey: I still don't know `xform` is superior name over `tfm`.
The latter one is a historical term in… | |||||
| 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; | |||||
| virtual bool should_visit_dupli_object(const DupliObject *dupli_object) const; | |||||
| virtual bool should_export_object(const Object *object) const; | |||||
| /* These functions should create an AbstractHierarchyWriter subclass instance, or return | |||||
| * nullptr if the object or its data should not be exported. Returning a nullptr for | |||||
| * data/hair/particle will NOT prevent the transform to be written. | |||||
| * | |||||
| * 'Xform' is used for 'transform' to be consistent with the terminology of USD and Alembic. | |||||
| * | |||||
| * The returned writer is owned by the AbstractHierarchyWriter, and should be freed in | |||||
| * delete_object_writer(). */ | |||||
| virtual AbstractHierarchyWriter *create_xform_writer(const HierarchyContext *context) = 0; | |||||
| virtual AbstractHierarchyWriter *create_data_writer(const HierarchyContext *context) = 0; | |||||
| virtual AbstractHierarchyWriter *create_hair_writer(const HierarchyContext *context) = 0; | |||||
| virtual AbstractHierarchyWriter *create_particle_writer(const HierarchyContext *context) = 0; | |||||
| /* Called by release_writers() to free what the create_XXX_writer() functions allocated. */ | |||||
| virtual void delete_object_writer(AbstractHierarchyWriter *writer) = 0; | |||||
| }; | |||||
| #endif /* __USD__ABSTRACT_HIERARCHY_ITERATOR_H__ */ | |||||
@Jacques Lucke (JacquesLucke) , in functions branch, do you also use <map> and <set> directly?