diff options
54 files changed, 3014 insertions, 942 deletions
diff --git a/source/blender/blenkernel/BKE_attribute.h b/source/blender/blenkernel/BKE_attribute.h index ff207997e79..73ade2ff7ac 100644 --- a/source/blender/blenkernel/BKE_attribute.h +++ b/source/blender/blenkernel/BKE_attribute.h @@ -65,6 +65,14 @@ struct CustomDataLayer *BKE_id_attribute_find(const struct ID *id, int type, AttributeDomain domain); +/* Return an existing CustomDataLayer, or create a new one if none is found for the given + * parameters and return it. */ +struct CustomDataLayer *BKE_id_attribute_ensure(struct ID *id, + const char *name, + const int type, + const AttributeDomain domain, + struct ReportList *reports); + AttributeDomain BKE_id_attribute_domain(struct ID *id, struct CustomDataLayer *layer); int BKE_id_attribute_data_length(struct ID *id, struct CustomDataLayer *layer); bool BKE_id_attribute_required(struct ID *id, struct CustomDataLayer *layer); diff --git a/source/blender/blenkernel/BKE_cachefile.h b/source/blender/blenkernel/BKE_cachefile.h index c6821d88d2a..70d6985153c 100644 --- a/source/blender/blenkernel/BKE_cachefile.h +++ b/source/blender/blenkernel/BKE_cachefile.h @@ -27,6 +27,7 @@ extern "C" { #endif +struct CacheAttributeMapping; struct CacheFile; struct CacheFileLayer; struct CacheReader; @@ -79,6 +80,9 @@ struct CacheFileLayer *BKE_cachefile_get_active_layer(struct CacheFile *cache_fi void BKE_cachefile_remove_layer(struct CacheFile *cache_file, struct CacheFileLayer *layer); +struct CacheAttributeMapping *BKE_cachefile_get_active_attribute_mapping( + struct CacheFile *cache_file); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index a05ed67063a..8fd22c70128 100644 --- a/source/blender/blenkernel/BKE_modifier.h +++ b/source/blender/blenkernel/BKE_modifier.h @@ -611,6 +611,10 @@ void BKE_modifier_blend_read_data(struct BlendDataReader *reader, struct Object *ob); void BKE_modifier_blend_read_lib(struct BlendLibReader *reader, struct Object *ob); +/* Return whether the modifier can operate directly on a Curve, i.e. without needing a conversion + * to a temporary Mesh. */ +bool BKE_modifier_supports_curve_data(const ModifierData *md); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_pointcloud.h b/source/blender/blenkernel/BKE_pointcloud.h index d330ea41e6a..e796967eb61 100644 --- a/source/blender/blenkernel/BKE_pointcloud.h +++ b/source/blender/blenkernel/BKE_pointcloud.h @@ -39,6 +39,9 @@ extern const char *POINTCLOUD_ATTR_RADIUS; void *BKE_pointcloud_add(struct Main *bmain, const char *name); void *BKE_pointcloud_add_default(struct Main *bmain, const char *name); struct PointCloud *BKE_pointcloud_new_nomain(int totpoint); +void BKE_pointcloud_nomain_to_pointcloud(struct PointCloud *pointcloud_src, + struct PointCloud *pointcloud_dst, + bool take_ownership); struct BoundBox *BKE_pointcloud_boundbox_get(struct Object *ob); bool BKE_pointcloud_minmax(const struct PointCloud *pointcloud, float r_min[3], float r_max[3]); diff --git a/source/blender/blenkernel/intern/attribute.c b/source/blender/blenkernel/intern/attribute.c index fdd84f3dfd4..4ee5abc1816 100644 --- a/source/blender/blenkernel/intern/attribute.c +++ b/source/blender/blenkernel/intern/attribute.c @@ -245,6 +245,18 @@ CustomDataLayer *BKE_id_attribute_find(const ID *id, return NULL; } +CustomDataLayer *BKE_id_attribute_ensure( + ID *id, const char *name, const int type, const AttributeDomain domain, ReportList *reports) +{ + CustomDataLayer *layer = BKE_id_attribute_find(id, name, type, domain); + + if (layer) { + return layer; + } + + return BKE_id_attribute_new(id, name, type, domain, reports); +} + int BKE_id_attributes_length(ID *id, const CustomDataMask mask) { DomainInfo info[ATTR_DOMAIN_NUM]; diff --git a/source/blender/blenkernel/intern/cachefile.c b/source/blender/blenkernel/intern/cachefile.c index 75df2e98fcd..12133b5a5dc 100644 --- a/source/blender/blenkernel/intern/cachefile.c +++ b/source/blender/blenkernel/intern/cachefile.c @@ -89,6 +89,7 @@ static void cache_file_copy_data(Main *UNUSED(bmain), cache_file_dst->handle_readers = NULL; BLI_duplicatelist(&cache_file_dst->object_paths, &cache_file_src->object_paths); BLI_duplicatelist(&cache_file_dst->layers, &cache_file_src->layers); + BLI_duplicatelist(&cache_file_dst->attribute_mappings, &cache_file_src->attribute_mappings); } static void cache_file_free_data(ID *id) @@ -97,6 +98,7 @@ static void cache_file_free_data(ID *id) cachefile_handle_free(cache_file); BLI_freelistN(&cache_file->object_paths); BLI_freelistN(&cache_file->layers); + BLI_freelistN(&cache_file->attribute_mappings); } static void cache_file_foreach_path(ID *id, BPathForeachPathData *bpath_data) @@ -126,6 +128,11 @@ static void cache_file_blend_write(BlendWriter *writer, ID *id, const void *id_a LISTBASE_FOREACH (CacheFileLayer *, layer, &cache_file->layers) { BLO_write_struct(writer, CacheFileLayer, layer); } + + /* write attribute mappings */ + LISTBASE_FOREACH (CacheAttributeMapping *, mapping, &cache_file->attribute_mappings) { + BLO_write_struct(writer, CacheAttributeMapping, mapping); + } } static void cache_file_blend_read_data(BlendDataReader *reader, ID *id) @@ -142,6 +149,9 @@ static void cache_file_blend_read_data(BlendDataReader *reader, ID *id) /* relink layers */ BLO_read_list(reader, &cache_file->layers); + + /* relink attribute mappings */ + BLO_read_list(reader, &cache_file->attribute_mappings); } IDTypeInfo IDType_ID_CF = { @@ -449,6 +459,11 @@ bool BKE_cache_file_uses_render_procedural(const CacheFile *cache_file, return cache_file->use_render_procedural && !is_final_render; } +bool BKE_cache_file_supports_layers(const CacheFile *cache_file) +{ + return cache_file->type == CACHEFILE_TYPE_ALEMBIC; +} + CacheFileLayer *BKE_cachefile_add_layer(CacheFile *cache_file, const char filename[1024]) { for (CacheFileLayer *layer = cache_file->layers.first; layer; layer = layer->next) { @@ -480,3 +495,9 @@ void BKE_cachefile_remove_layer(CacheFile *cache_file, CacheFileLayer *layer) BLI_remlink(&cache_file->layers, layer); MEM_freeN(layer); } + +CacheAttributeMapping *BKE_cachefile_get_active_attribute_mapping(CacheFile *cache_file) +{ + /* BLI_findlink handles the out of bounds checks. */ + return BLI_findlink(&cache_file->attribute_mappings, cache_file->active_attribute_mapping - 1); +} diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc index 78177095a77..53a976adc89 100644 --- a/source/blender/blenkernel/intern/displist.cc +++ b/source/blender/blenkernel/intern/displist.cc @@ -828,12 +828,12 @@ static bool do_curve_implicit_mesh_conversion(const Curve *curve, return true; } - /* If a non-geometry-nodes modifier is enabled before a nodes modifier, - * force conversion to mesh, since only the nodes modifier supports curve data. */ + /* If a modifier which does not support curve data is enabled before one that does, + * force conversion to mesh. */ ModifierData *md = first_modifier; for (; md; md = md->next) { if (BKE_modifier_is_enabled(scene, md, required_mode)) { - if (md->type == eModifierType_Nodes) { + if (BKE_modifier_supports_curve_data(md)) { break; } return true; @@ -890,7 +890,7 @@ static GeometrySet curve_calc_modifiers_post(Depsgraph *depsgraph, continue; } - if (md->type == eModifierType_Nodes) { + if (BKE_modifier_supports_curve_data(md)) { mti->modifyGeometrySet(md, &mectx_apply, &geometry_set); continue; } diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 4cedaff7c00..598d4e495d6 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -36,6 +36,7 @@ #include "MEM_guardedalloc.h" #include "DNA_armature_types.h" +#include "DNA_cachefile_types.h" #include "DNA_cloth_types.h" #include "DNA_dynamicpaint_types.h" #include "DNA_fluid_types.h" @@ -1517,3 +1518,27 @@ void BKE_modifier_blend_read_lib(BlendLibReader *reader, Object *ob) } } } + +bool BKE_modifier_supports_curve_data(const ModifierData *md) +{ + /* Sanity check. */ + const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + if (mti->modifyGeometrySet == NULL) { + return false; + } + + if (md->type == eModifierType_Nodes) { + return true; + } + + /* For the MeshSequenceCache, only the Alembic backend supports directly reading/modifying + * curves. + */ + if (md->type == eModifierType_MeshSequenceCache) { + MeshSeqCacheModifierData *msmd = (MeshSeqCacheModifierData *)md; + CacheFile *cache_file = msmd->cache_file; + return cache_file && cache_file->type == CACHEFILE_TYPE_ALEMBIC; + } + + return false; +} diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc index b5f016e4d76..2090434a5de 100644 --- a/source/blender/blenkernel/intern/pointcloud.cc +++ b/source/blender/blenkernel/intern/pointcloud.cc @@ -268,6 +268,47 @@ PointCloud *BKE_pointcloud_new_nomain(const int totpoint) return pointcloud; } +void BKE_pointcloud_nomain_to_pointcloud(PointCloud *pointcloud_src, + PointCloud *pointcloud_dst, + bool take_ownership) +{ + BLI_assert(pointcloud_src->id.tag & LIB_TAG_NO_MAIN); + + /* pointcloud_src might depend on pointcloud_dst, so we need to do everything with a local copy + */ + PointCloud tmp = *pointcloud_dst; + eCDAllocType alloctype = CD_DUPLICATE; + + if (take_ownership) { + bool has_any_referenced_layers = CustomData_has_referenced(&pointcloud_src->pdata); + + if (!has_any_referenced_layers) { + alloctype = CD_ASSIGN; + } + } + + CustomData_reset(&tmp.pdata); + + const CustomDataMask pmask = CD_MASK_ALL; + + const int totpoint = tmp.totpoint = pointcloud_src->totpoint; + CustomData_copy(&pointcloud_src->pdata, &tmp.pdata, pmask, alloctype, totpoint); + + BKE_pointcloud_update_customdata_pointers(&tmp); + + CustomData_free(&pointcloud_dst->pdata, pointcloud_dst->totpoint); + + /* skip the listbase */ + MEMCPY_STRUCT_AFTER(pointcloud_dst, &tmp, id.prev); + + if (take_ownership) { + if (alloctype == CD_ASSIGN) { + CustomData_free_typemask(&pointcloud_src->pdata, pointcloud_src->totpoint, ~pmask); + } + BKE_id_free(nullptr, pointcloud_src); + } +} + struct MinMaxResult { float3 min; float3 max; diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index ae4c2ff16fd..2118a2e6b85 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -2435,6 +2435,13 @@ void uiTemplateCacheFileLayers(uiLayout *layout, const struct bContext *C, struct PointerRNA *fileptr); +/** + * Draw the attribute remapping related properties of the CacheFile. + */ +void uiTemplateCacheFileAttributeRemapping(uiLayout *layout, + const struct bContext *C, + struct PointerRNA *fileptr); + /* Default UIList class name, keep in sync with its declaration in bl_ui/__init__.py */ #define UI_UL_DEFAULT_CLASS_NAME "UI_UL_list" enum uiTemplateListFlags { diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 2d1138b46a7..665a6fe8e44 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -1493,6 +1493,9 @@ void UI_OT_eyedropper_gpencil_color(struct wmOperatorType *ot); struct uiListType *UI_UL_asset_view(void); +/* interface_templates.c */ +struct uiListType *UI_UL_cache_file_attribute_mappings(void); + /** * For use with #ui_rna_collection_search_update_fn. */ diff --git a/source/blender/editors/interface/interface_template_list.cc b/source/blender/editors/interface/interface_template_list.cc index 817599605a9..754a97338ff 100644 --- a/source/blender/editors/interface/interface_template_list.cc +++ b/source/blender/editors/interface/interface_template_list.cc @@ -1323,6 +1323,7 @@ void ED_uilisttypes_ui() { WM_uilisttype_add(UI_UL_asset_view()); WM_uilisttype_add(UI_UL_cache_file_layers()); + WM_uilisttype_add(UI_UL_cache_file_attribute_mappings()); } /** \} */ diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 03d0cba5ec8..db33141de38 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -6503,6 +6503,63 @@ bool uiTemplateCacheFilePointer(PointerRNA *ptr, const char *propname, PointerRN return true; } +static void cache_file_attribute_mapping_item(uiList *UNUSED(ui_list), + bContext *UNUSED(C), + uiLayout *layout, + PointerRNA *UNUSED(dataptr), + PointerRNA *itemptr, + int UNUSED(icon), + PointerRNA *UNUSED(active_dataptr), + const char *UNUSED(active_propname), + int UNUSED(index), + int UNUSED(flt_flag)) +{ + uiLayout *row = uiLayoutRow(layout, true); + uiItemR(row, itemptr, "name", UI_ITEM_R_NO_BG, "", ICON_NONE); + uiItemR(row, itemptr, "mapping", UI_ITEM_R_NO_BG, "", ICON_NONE); + uiItemR(row, itemptr, "domain", UI_ITEM_R_NO_BG, "", ICON_NONE); +} + +uiListType *UI_UL_cache_file_attribute_mappings() +{ + uiListType *list_type = (uiListType *)MEM_callocN(sizeof(*list_type), __func__); + + BLI_strncpy(list_type->idname, "UI_UL_cache_file_attribute_mappings", sizeof(list_type->idname)); + list_type->draw_item = cache_file_attribute_mapping_item; + + return list_type; +} + +void uiTemplateCacheFileAttributeRemapping(uiLayout *layout, + const bContext *C, + PointerRNA *fileptr) +{ + /* Ensure that the context has a CacheFile as this may not be set inside of modifiers panels. */ + uiLayoutSetContextPointer(layout, "edit_cachefile", fileptr); + + uiLayout *row = uiLayoutRow(layout, false); + uiLayout *col = uiLayoutColumn(row, true); + + uiTemplateList(col, + (bContext *)C, + "UI_UL_cache_file_attribute_mappings", + "cache_file_attribute_mappings", + fileptr, + "attribute_mappings", + fileptr, + "active_attribute_mapping_index", + "", + 1, + 5, + UILST_LAYOUT_DEFAULT, + 1, + UI_TEMPLATE_LIST_FLAG_NONE); + + col = uiLayoutColumn(row, true); + uiItemO(col, "", ICON_ADD, "cachefile.attribute_mapping_add"); + uiItemO(col, "", ICON_REMOVE, "cachefile.attribute_mapping_remove"); +} + void uiTemplateCacheFile(uiLayout *layout, const bContext *C, PointerRNA *ptr, diff --git a/source/blender/editors/io/io_cache.c b/source/blender/editors/io/io_cache.c index 4f71a804986..0bd842d3fbe 100644 --- a/source/blender/editors/io/io_cache.c +++ b/source/blender/editors/io/io_cache.c @@ -328,3 +328,87 @@ void CACHEFILE_OT_layer_move(wmOperatorType *ot) "Direction", "Direction to move the active vertex group towards"); } + +/* ***************************** Add Attribute Mapping Operator **************************** */ + +static bool cachefile_attribute_mapping_poll(bContext *C) +{ + return CTX_data_edit_cachefile(C) != NULL; +} + +static int cachefile_attribute_mapping_add_exec(bContext *C, wmOperator *UNUSED(op)) +{ + CacheFile *cache_file = CTX_data_edit_cachefile(C); + + if (!cache_file) { + return OPERATOR_CANCELLED; + } + + const int current_mapping_count = BLI_listbase_count(&cache_file->attribute_mappings); + + CacheAttributeMapping *mapping = MEM_callocN(sizeof(CacheAttributeMapping), + "CacheAttributeMapping"); + + BLI_addtail(&cache_file->attribute_mappings, mapping); + + cache_file->active_attribute_mapping = current_mapping_count + 1; + + /* Since the mapping is not initialized, adding a mapping does not trigger a CacheFile update. */ + + return OPERATOR_FINISHED; +} + +void CACHEFILE_OT_attribute_mapping_add(wmOperatorType *ot) +{ + ot->name = "Add Attribute Mapping"; + ot->description = "Add an attribute mapping for this CacheFile"; + ot->idname = __func__; + + /* api callbacks */ + ot->exec = cachefile_attribute_mapping_add_exec; + ot->poll = cachefile_attribute_mapping_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ***************************** Remove Attribute Mapping Operator **************************** */ + +static int cachefile_attribute_mapping_remove_exec(bContext *C, wmOperator *UNUSED(op)) +{ + CacheFile *cache_file = CTX_data_edit_cachefile(C); + + if (!cache_file) { + return OPERATOR_CANCELLED; + } + + CacheAttributeMapping *mapping = BKE_cachefile_get_active_attribute_mapping(cache_file); + + /* Reset this now as it will have to be done whether we have a mapping or not. */ + cache_file->active_attribute_mapping = 0; + + if (!mapping) { + return OPERATOR_CANCELLED; + } + + BLI_remlink(&cache_file->attribute_mappings, mapping); + + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + BKE_cachefile_reload(depsgraph, cache_file); + + return OPERATOR_FINISHED; +} + +void CACHEFILE_OT_attribute_mapping_remove(wmOperatorType *ot) +{ + ot->name = "Remove Attribute Mapping"; + ot->description = "Remove an attribute mapping from this CacheFile"; + ot->idname = __func__; + + /* api callbacks */ + ot->exec = cachefile_attribute_mapping_remove_exec; + ot->poll = cachefile_attribute_mapping_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} diff --git a/source/blender/editors/io/io_cache.h b/source/blender/editors/io/io_cache.h index 297e065434f..2868e57687b 100644 --- a/source/blender/editors/io/io_cache.h +++ b/source/blender/editors/io/io_cache.h @@ -31,3 +31,6 @@ void CACHEFILE_OT_reload(struct wmOperatorType *ot); void CACHEFILE_OT_layer_add(struct wmOperatorType *ot); void CACHEFILE_OT_layer_remove(struct wmOperatorType *ot); void CACHEFILE_OT_layer_move(struct wmOperatorType *ot); + +void CACHEFILE_OT_attribute_mapping_add(struct wmOperatorType *ot); +void CACHEFILE_OT_attribute_mapping_remove(struct wmOperatorType *ot); diff --git a/source/blender/editors/io/io_ops.c b/source/blender/editors/io/io_ops.c index d9bbd7d8692..6c8ed5c7756 100644 --- a/source/blender/editors/io/io_ops.c +++ b/source/blender/editors/io/io_ops.c @@ -74,5 +74,8 @@ void ED_operatortypes_io(void) WM_operatortype_append(CACHEFILE_OT_layer_remove); WM_operatortype_append(CACHEFILE_OT_layer_move); + WM_operatortype_append(CACHEFILE_OT_attribute_mapping_add); + WM_operatortype_append(CACHEFILE_OT_attribute_mapping_remove); + WM_operatortype_append(WM_OT_obj_export); } diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h index 18b0c91b67c..e603418eb40 100644 --- a/source/blender/io/alembic/ABC_alembic.h +++ b/source/blender/io/alembic/ABC_alembic.h @@ -28,6 +28,7 @@ extern "C" { struct CacheArchiveHandle; struct CacheFileLayer; struct CacheReader; +struct GeometrySet; struct ListBase; struct Main; struct Mesh; @@ -113,15 +114,23 @@ void ABC_get_transform(struct CacheReader *reader, float time, float scale); -/* Either modifies existing_mesh in-place or constructs a new mesh. */ -struct Mesh *ABC_read_mesh(struct CacheReader *reader, - struct Object *ob, - struct Mesh *existing_mesh, - float time, - const char **err_str, - int read_flags, - const char *velocity_name, - float velocity_scale); +typedef struct ABCReadParams { + float time; + int read_flags; + const char *velocity_name; + float velocity_scale; + + ListBase *mappings; +} ABCReadParams; + +#ifdef __cplusplus +/* Either modifies the existing geometry component, or create a new one. */ +void ABC_read_geometry(CacheReader *reader, + Object *ob, + GeometrySet &geometry_set, + const ABCReadParams *params, + const char **err_str); +#endif bool ABC_mesh_topology_changed(struct CacheReader *reader, struct Object *ob, diff --git a/source/blender/io/alembic/CMakeLists.txt b/source/blender/io/alembic/CMakeLists.txt index d55f2382a9b..919376b971b 100644 --- a/source/blender/io/alembic/CMakeLists.txt +++ b/source/blender/io/alembic/CMakeLists.txt @@ -24,9 +24,11 @@ set(INC ../../blenkernel ../../blenlib ../../blenloader + ../../blentranslation ../../bmesh ../../depsgraph ../../editors/include + ../../functions ../../makesdna ../../makesrna ../../windowmanager @@ -47,6 +49,8 @@ set(SRC intern/abc_reader_archive.cc intern/abc_reader_camera.cc intern/abc_reader_curves.cc + intern/abc_reader_instance.cc + intern/abc_reader_manager.cc intern/abc_reader_mesh.cc intern/abc_reader_nurbs.cc intern/abc_reader_object.cc @@ -77,6 +81,8 @@ set(SRC intern/abc_reader_archive.h intern/abc_reader_camera.h intern/abc_reader_curves.h + intern/abc_reader_instance.h + intern/abc_reader_manager.h intern/abc_reader_mesh.h intern/abc_reader_nurbs.h intern/abc_reader_object.h diff --git a/source/blender/io/alembic/intern/abc_customdata.cc b/source/blender/io/alembic/intern/abc_customdata.cc index c3ff64fd0ad..9f9bbc246f5 100644 --- a/source/blender/io/alembic/intern/abc_customdata.cc +++ b/source/blender/io/alembic/intern/abc_customdata.cc @@ -26,15 +26,26 @@ #include <Alembic/AbcGeom/All.h> #include <algorithm> +#include <optional> #include <unordered_map> +#include "DNA_cachefile_types.h" #include "DNA_customdata_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "BLI_assert.h" +#include "BLI_color.hh" #include "BLI_math_base.h" +#include "BLI_math_bits.h" +#include "BLI_math_vec_types.hh" #include "BLI_utildefines.h" +#include "BLI_vector.hh" +#include "BLT_translation.h" + +#include "BKE_attribute.h" #include "BKE_customdata.h" #include "BKE_mesh.h" @@ -44,6 +55,8 @@ * such data in a way that lets other DCC know what they are for. See comments * in the write code for the conventions. */ +using namespace Alembic::AbcGeom; + using Alembic::AbcGeom::kFacevaryingScope; using Alembic::AbcGeom::kVaryingScope; using Alembic::AbcGeom::kVertexScope; @@ -328,42 +341,409 @@ using Alembic::AbcGeom::IC4fGeomParam; using Alembic::AbcGeom::IV2fGeomParam; using Alembic::AbcGeom::IV3fGeomParam; -static void read_uvs(const CDStreamConfig &config, - void *data, - const AbcUvScope uv_scope, - const Alembic::AbcGeom::V2fArraySamplePtr &uvs, - const UInt32ArraySamplePtr &indices) +/* -------------------------------------------------------------------- */ + +/** \name BlenderScope + * + * This enumeration is used to translate the Alembic scope to a scope more suitable for Blender's + * possible CustomData and attribute domains. + * It is defined as a bit field in case routines need to resolve the scope for a set of potential + * scopes. This can be done by or-ing the candidate scopes together, and validate by checking that + * only a single bit is set. + * \{ */ + +enum class BlenderScope : uint32_t { + UNKNOWN = 0, + POINT = (1 << 0), + POLYGON = (1 << 1), + LOOPS = (1 << 2), +}; + +static BlenderScope &operator|=(BlenderScope &lhs, BlenderScope rhs) { - MPoly *mpolys = config.mpoly; - MLoop *mloops = config.mloop; - MLoopUV *mloopuvs = static_cast<MLoopUV *>(data); + lhs = static_cast<BlenderScope>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs)); + return lhs; +} - unsigned int uv_index, loop_index, rev_loop_index; +/* Check the bits for the scope and return it if only one of them is active. */ +static BlenderScope valid_scope_or_unknown(BlenderScope scope) +{ + if (count_bits_i(static_cast<uint32_t>(scope)) == 1) { + return scope; + } - BLI_assert(uv_scope != ABC_UV_SCOPE_NONE); - const bool do_uvs_per_loop = (uv_scope == ABC_UV_SCOPE_LOOP); + return BlenderScope::UNKNOWN; +} - for (int i = 0; i < config.totpoly; i++) { - MPoly &poly = mpolys[i]; - unsigned int rev_loop_offset = poly.loopstart + poly.totloop - 1; +/* Determine the matching scope given some number of values. The dimensions parameter is used to + * scale the scope_sizes and is meant to be used (not equal to 1) when processing a scalar array + * for a possible remapping to some n-dimensionnal data type. */ +static BlenderScope matching_scope(const ScopeSizeInfo scope_sizes, + int dimensions, + size_t num_values, + char requested_domain) +{ + if (requested_domain != CACHEFILE_ATTR_MAP_DOMAIN_AUTO) { + if (requested_domain == CACHEFILE_ATTR_MAP_DOMAIN_POINT) { + if (static_cast<size_t>(scope_sizes.point_scope_size * dimensions) == num_values) { + return BlenderScope::POINT; + } - for (int f = 0; f < poly.totloop; f++) { - rev_loop_index = rev_loop_offset - f; - loop_index = do_uvs_per_loop ? poly.loopstart + f : mloops[rev_loop_index].v; - uv_index = (*indices)[loop_index]; - const Imath::V2f &uv = (*uvs)[uv_index]; + return BlenderScope::UNKNOWN; + } + + if (requested_domain == CACHEFILE_ATTR_MAP_DOMAIN_FACE_CORNER) { + if (static_cast<size_t>(scope_sizes.loop_scope_size * dimensions) == num_values) { + return BlenderScope::LOOPS; + } + + return BlenderScope::UNKNOWN; + } + + if (requested_domain == CACHEFILE_ATTR_MAP_DOMAIN_FACE) { + if (static_cast<size_t>(scope_sizes.polygon_scope_size * dimensions) == num_values) { + return BlenderScope::POLYGON; + } + + return BlenderScope::UNKNOWN; + } + + return BlenderScope::UNKNOWN; + } + + BlenderScope scope = BlenderScope::UNKNOWN; + + if (static_cast<size_t>(scope_sizes.loop_scope_size * dimensions) == num_values) { + scope |= BlenderScope::LOOPS; + } + + if (static_cast<size_t>(scope_sizes.polygon_scope_size * dimensions) == num_values) { + scope |= BlenderScope::POLYGON; + } + + if (static_cast<size_t>(scope_sizes.point_scope_size * dimensions) == num_values) { + scope |= BlenderScope::POINT; + } + + return valid_scope_or_unknown(scope); +} + +static CustomData *custom_data_for_scope(ScopeCustomDataPointers custom_data_pointers, + BlenderScope bl_scope) +{ + if (bl_scope == BlenderScope::POINT) { + return custom_data_pointers.point_custom_data; + } + + if (bl_scope == BlenderScope::POLYGON) { + return custom_data_pointers.polygon_custom_data; + } + + if (bl_scope == BlenderScope::LOOPS) { + return custom_data_pointers.loop_custom_data; + } + + return nullptr; +} + +#if 0 +/* Useful for debugging. Turned off to quiet warnings. */ +static std::ostream &operator<<(std::ostream &os, BlenderScope bl_scope) +{ + switch (bl_scope) { + case BlenderScope::POINT: { + os << "POINT"; + break; + } + case BlenderScope::LOOPS: { + os << "LOOPS"; + break; + } + case BlenderScope::POLYGON: { + os << "POLYGON"; + break; + } + default: { + os << "UNKNOWN"; + break; + } + } + return os; +} +#endif + +static int size_for_scope(const ScopeSizeInfo scope_sizes, BlenderScope scope) +{ + if (scope == BlenderScope::POINT) { + return scope_sizes.point_scope_size; + } + + if (scope == BlenderScope::LOOPS) { + return scope_sizes.loop_scope_size; + } + + if (scope == BlenderScope::POLYGON) { + return scope_sizes.polygon_scope_size; + } + + return 0; +} + +/* UVs can be defined per-loop (one value per vertex per face), or per-vertex (one value per + * vertex). The first case is the most common, as this is the standard way of storing this data + * given that some vertices might be on UV seams and have multiple possible UV coordinates; the + * second case can happen when the mesh is split according to the UV islands, in which case storing + * a single UV value per vertex allows to deduplicate data and thus to reduce the file size since + * vertices are guaranteed to only have a single UV coordinate. */ +static bool is_valid_uv_scope(BlenderScope scope) +{ + return scope == BlenderScope::LOOPS || scope == BlenderScope::POINT; +} + +static bool is_valid_vertex_color_scope(BlenderScope scope) +{ + return scope == BlenderScope::LOOPS || scope == BlenderScope::POINT; +} + +static bool is_valid_vertex_group_scope(BlenderScope scope) +{ + return scope == BlenderScope::POINT; +} + +static bool is_valid_scope_for_layer(BlenderScope scope, CustomDataType custom_data_type) +{ + if (scope == BlenderScope::UNKNOWN) { + return false; + } + + if (custom_data_type == CD_MLOOPUV) { + return is_valid_uv_scope(scope); + } + + if (custom_data_type == CD_MCOL) { + return is_valid_vertex_color_scope(scope); + } + + if (custom_data_type == CD_ORCO) { + return scope == BlenderScope::POINT; + } + + return true; +} - MLoopUV &loopuv = mloopuvs[rev_loop_index]; - loopuv.uv[0] = uv[0]; - loopuv.uv[1] = uv[1]; +/* Converts an Alembic scope to a Blender one. We need to be careful as some Alembic scopes depend + * on the domain on which they appear. For example, kVaryingScope could mean that the data is + * varying across the polygons or the vertices. To resolve this, we also use the expected number of + * elements for each scopes, and the matching scope is returned based on this. + * If we cannot resolve the scope, BlenderScope::UNKNOWN is returned. + */ +static BlenderScope to_blender_scope(Alembic::AbcGeom::GeometryScope abc_scope, + size_t element_size, + ScopeSizeInfo scope_sizes) +{ + switch (abc_scope) { + case kConstantScope: { + return matching_scope(scope_sizes, 1, element_size, CACHEFILE_ATTR_MAP_DOMAIN_AUTO); + } + case kUniformScope: { + /* This would mean one value for the whole object, but we don't support that yet. */ + return BlenderScope::UNKNOWN; + } + case kVertexScope: { + /* This is pretty straightforward, just ensure that the sizes match. */ + if (static_cast<size_t>(scope_sizes.point_scope_size) == element_size) { + return BlenderScope::POINT; + } + return BlenderScope::UNKNOWN; + } + case kFacevaryingScope: { + /* Fist check that the loops size match, since this is the right domain. */ + if (static_cast<size_t>(scope_sizes.loop_scope_size) == element_size) { + return BlenderScope::LOOPS; + } + /* If not, we may have some data that is face varying, but was written for each vertex + * instead (e.g. vertex colors). */ + if (static_cast<size_t>(scope_sizes.point_scope_size) == element_size) { + return BlenderScope::POINT; + } + return BlenderScope::UNKNOWN; + } + case kVaryingScope: { + /* We have a varying field over some domain, which we need to determine. */ + return matching_scope(scope_sizes, 1, element_size, CACHEFILE_ATTR_MAP_DOMAIN_AUTO); } + case kUnknownScope: { + return BlenderScope::UNKNOWN; + } + } + + return BlenderScope::UNKNOWN; +} + +static AttributeDomain attribute_domain_from_blender_scope(BlenderScope scope) +{ + if (scope == BlenderScope::POINT) { + return ATTR_DOMAIN_POINT; + } + + if (scope == BlenderScope::LOOPS) { + return ATTR_DOMAIN_CORNER; + } + + if (scope == BlenderScope::POLYGON) { + return ATTR_DOMAIN_FACE; } + + BLI_assert_msg(0, "Obtained an invalid scope when converting to an AttributeDomain"); + return ATTR_DOMAIN_NUM; } +/** \} */ + +/* -------------------------------------------------------------------- */ + +/** \name value_type_converter + * + * Utilities to convert values from the types used in Alembic to the ones used in Blender. Besides + * type conversions, vectors are also converted from Y-up to Z-up. + * \{ */ + +template<typename T> struct value_type_converter { + static bool map_to_bool(const T *value) + { + return static_cast<bool>(value[0]); + } + + static int32_t map_to_int32(const T *value) + { + return static_cast<int32_t>(value[0]); + } + + static float map_to_float(const T *value) + { + return static_cast<float>(value[0]); + } + + static float2 map_to_float2(const T *value) + { + return {static_cast<float>(value[0]), static_cast<float>(value[1])}; + } + + static float3 map_to_float3(const T *value) + { + float3 result{ + static_cast<float>(value[0]), static_cast<float>(value[1]), static_cast<float>(value[2])}; + copy_zup_from_yup(result, result); + return result; + } + + static ColorGeometry4f map_to_color_from_rgb(const T *ptr) + { + return ColorGeometry4f( + static_cast<float>(ptr[0]), static_cast<float>(ptr[1]), static_cast<float>(ptr[2]), 1.0f); + } + + static ColorGeometry4f map_to_color_from_rgba(const T *ptr) + { + return ColorGeometry4f(static_cast<float>(ptr[0]), + static_cast<float>(ptr[1]), + static_cast<float>(ptr[2]), + static_cast<float>(ptr[3])); + } + + static MCol map_to_mcol_from_rgb(const T *ptr) + { + MCol mcol; + mcol.a = unit_float_to_uchar_clamp(static_cast<float>(ptr[0])); + mcol.r = unit_float_to_uchar_clamp(static_cast<float>(ptr[1])); + mcol.g = unit_float_to_uchar_clamp(static_cast<float>(ptr[2])); + mcol.b = 255; + return mcol; + } + + static MCol map_to_mcol_from_rgba(const T *ptr) + { + MCol mcol; + mcol.a = unit_float_to_uchar_clamp(static_cast<float>(ptr[0])); + mcol.r = unit_float_to_uchar_clamp(static_cast<float>(ptr[1])); + mcol.g = unit_float_to_uchar_clamp(static_cast<float>(ptr[2])); + mcol.b = unit_float_to_uchar_clamp(static_cast<float>(ptr[3])); + return mcol; + } +}; + +template<> struct value_type_converter<Imath::V3f> { + static float3 convert_value(const Imath::V3f &v) + { + return v.getValue(); + } +}; + +template<> struct value_type_converter<Imath::V3d> { + static float3 convert_value(const Imath::V3d &v) + { + const double *ptr = v.getValue(); + return float3( + static_cast<float>(ptr[0]), static_cast<float>(ptr[1]), static_cast<float>(ptr[2])); + } +}; + +template<> struct value_type_converter<Imath::C3f> { + static MCol convert_value(const Imath::C3f &v) + { + MCol mcol; + mcol.a = unit_float_to_uchar_clamp(static_cast<float>(v[0])); + mcol.r = unit_float_to_uchar_clamp(static_cast<float>(v[1])); + mcol.g = unit_float_to_uchar_clamp(static_cast<float>(v[2])); + mcol.b = 255; + return mcol; + } +}; + +template<> struct value_type_converter<Imath::C4f> { + static MCol convert_value(const Imath::C4f &v) + { + MCol mcol; + mcol.a = unit_float_to_uchar_clamp(static_cast<float>(v[0])); + mcol.r = unit_float_to_uchar_clamp(static_cast<float>(v[1])); + mcol.g = unit_float_to_uchar_clamp(static_cast<float>(v[2])); + mcol.b = unit_float_to_uchar_clamp(static_cast<float>(v[3])); + return mcol; + } +}; + +template<typename T> struct to_scalar_type { + using type = T; +}; + +template<> struct to_scalar_type<Imath::V2f> { + using type = float; +}; + +template<> struct to_scalar_type<Imath::V3f> { + using type = float; +}; + +template<> struct to_scalar_type<Imath::V3d> { + using type = double; +}; + +template<> struct to_scalar_type<Imath::C3f> { + using type = float; +}; + +template<> struct to_scalar_type<Imath::C4f> { + using type = float; +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ + static size_t mcols_out_of_bounds_check(const size_t color_index, const size_t array_size, const std::string &iobject_full_name, - const PropertyHeader &prop_header, + const std::string &prop_name, bool &r_is_out_of_bounds, bool &r_bounds_warning_given) { @@ -374,257 +754,942 @@ static size_t mcols_out_of_bounds_check(const size_t color_index, if (!r_bounds_warning_given) { std::cerr << "Alembic: color index out of bounds " "reading face colors for object " - << iobject_full_name << ", property " << prop_header.getName() << std::endl; + << iobject_full_name << ", property " << prop_name << std::endl; r_bounds_warning_given = true; } r_is_out_of_bounds = true; return 0; } -static void read_custom_data_mcols(const std::string &iobject_full_name, - const ICompoundProperty &arbGeomParams, - const PropertyHeader &prop_header, - const CDStreamConfig &config, - const Alembic::Abc::ISampleSelector &iss) +/* -------------------------------------------------------------------- */ + +/** \name AbcAttributeMapping + * + * Mirror of the mapping enumeration in DNA_cachefile_types.h. It is more specific and used as + * some sort of token to indicate that the mapping is valid for the attribute. + * \{ */ + +struct AbcAttributeMapping { + CustomDataType type; + BlenderScope scope; + /* This is to differentiate between RGB and RGBA colors. */ + bool is_rgba = false; +}; + +/* Try to resolve the mapping for an attribute from the number of individual elements in the array + * sample and the mapping as desired by the user. */ +static std::optional<AbcAttributeMapping> final_mapping_from_cache_mapping( + const CacheAttributeMapping &mapping, + const ScopeSizeInfo scope_sizes, + AbcAttributeMapping original_mapping, + const uint num_elements) { - C3fArraySamplePtr c3f_ptr = C3fArraySamplePtr(); - C4fArraySamplePtr c4f_ptr = C4fArraySamplePtr(); - Alembic::Abc::UInt32ArraySamplePtr indices; - bool use_c3f_ptr; - bool is_facevarying; + BLI_assert_msg(num_elements != 0, + "The number of elements in the array is null, although this should have been " + "checked before."); + + switch (mapping.mapping) { + case CACHEFILE_ATTRIBUTE_MAP_NONE: { + if (mapping.domain == CACHEFILE_ATTR_MAP_DOMAIN_POINT) { + original_mapping.scope = BlenderScope::POINT; + } + if (mapping.domain == CACHEFILE_ATTR_MAP_DOMAIN_FACE_CORNER) { + original_mapping.scope = BlenderScope::LOOPS; + } + return original_mapping; + } + case CACHEFILE_ATTRIBUTE_MAP_TO_UVS: { + /* UVs have 2 values per element. */ + const BlenderScope scope_hint = matching_scope(scope_sizes, 2, num_elements, mapping.domain); + if (!is_valid_uv_scope(scope_hint)) { + return {}; + } + return AbcAttributeMapping{CD_MLOOPUV, scope_hint}; + } + case CACHEFILE_ATTRIBUTE_MAP_TO_VERTEX_COLORS: { + /* 3 values for RGB, 4 for RGBA */ + BlenderScope rgb_scope_hint = matching_scope(scope_sizes, 3, num_elements, mapping.domain); + BlenderScope rgba_scope_hint = matching_scope(scope_sizes, 4, num_elements, mapping.domain); + + if (is_valid_vertex_color_scope(rgb_scope_hint) && + rgba_scope_hint == BlenderScope::UNKNOWN) { + return AbcAttributeMapping{CD_MCOL, rgb_scope_hint, false}; + } - /* Find the correct interpretation of the data */ - if (IC3fGeomParam::matches(prop_header)) { - IC3fGeomParam color_param(arbGeomParams, prop_header.getName()); - IC3fGeomParam::Sample sample; - BLI_assert(STREQ("rgb", color_param.getInterpretation())); + if (rgb_scope_hint == BlenderScope::UNKNOWN && + is_valid_vertex_color_scope(rgba_scope_hint)) { + return AbcAttributeMapping{CD_MCOL, rgba_scope_hint, true}; + } - color_param.getIndexed(sample, iss); - is_facevarying = sample.getScope() == kFacevaryingScope && - config.totloop == sample.getIndices()->size(); + /* Either both are unknown, or we matched two different scopes in which case we cannot + * resolve the ambiguity. */ + return {}; + } + case CACHEFILE_ATTRIBUTE_MAP_TO_WEIGHT_GROUPS: { + BlenderScope r_scope_hint = matching_scope(scope_sizes, 1, num_elements, mapping.domain); + if (!is_valid_vertex_group_scope(r_scope_hint)) { + return {}; + } + return AbcAttributeMapping{CD_BWEIGHT, r_scope_hint}; + } + case CACHEFILE_ATTRIBUTE_MAP_TO_FLOAT2: { + BlenderScope r_scope_hint = matching_scope(scope_sizes, 2, num_elements, mapping.domain); + if (r_scope_hint == BlenderScope::UNKNOWN) { + return {}; + } + return AbcAttributeMapping{CD_PROP_FLOAT2, r_scope_hint}; + } + case CACHEFILE_ATTRIBUTE_MAP_TO_FLOAT3: { + BlenderScope r_scope_hint = matching_scope(scope_sizes, 3, num_elements, mapping.domain); + if (r_scope_hint == BlenderScope::UNKNOWN) { + return {}; + } + return AbcAttributeMapping{CD_PROP_FLOAT3, r_scope_hint}; + } + case CACHEFILE_ATTRIBUTE_MAP_TO_COLOR: { + /* 3 values for RGB, 4 for RGBA */ + BlenderScope rgb_scope_hint = matching_scope(scope_sizes, 3, num_elements, mapping.domain); + BlenderScope rgba_scope_hint = matching_scope(scope_sizes, 4, num_elements, mapping.domain); - c3f_ptr = sample.getVals(); - indices = sample.getIndices(); - use_c3f_ptr = true; - } - else if (IC4fGeomParam::matches(prop_header)) { - IC4fGeomParam color_param(arbGeomParams, prop_header.getName()); - IC4fGeomParam::Sample sample; - BLI_assert(STREQ("rgba", color_param.getInterpretation())); + if (rgb_scope_hint != BlenderScope::UNKNOWN && rgba_scope_hint == BlenderScope::UNKNOWN) { + return AbcAttributeMapping{CD_PROP_COLOR, rgb_scope_hint, false}; + } - color_param.getIndexed(sample, iss); - is_facevarying = sample.getScope() == kFacevaryingScope && - config.totloop == sample.getIndices()->size(); + if (rgb_scope_hint == BlenderScope::UNKNOWN && rgba_scope_hint != BlenderScope::UNKNOWN) { + return AbcAttributeMapping{CD_PROP_COLOR, rgba_scope_hint, true}; + } - c4f_ptr = sample.getVals(); - indices = sample.getIndices(); - use_c3f_ptr = false; + /* Either both are unknown, or we matched two different scopes in which case we cannot + * resolve the ambiguity. */ + return {}; + } } - else { - /* this won't happen due to the checks in read_custom_data() */ - return; + + return {}; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ + +static bool can_add_custom_data_layer(const CustomData *data, + CustomDataType type, + int max_allowed_layers) +{ + if (data == nullptr) { + return false; } - BLI_assert(c3f_ptr || c4f_ptr); - /* Read the vertex colors */ - void *cd_data = config.add_customdata_cb( - config.mesh, prop_header.getName().c_str(), CD_MLOOPCOL); - MCol *cfaces = static_cast<MCol *>(cd_data); - MPoly *mpolys = config.mpoly; - MLoop *mloops = config.mloop; + return CustomData_number_of_layers(data, type) < max_allowed_layers; +} - size_t face_index = 0; - size_t color_index; - bool bounds_warning_given = false; +/* Check if we do not already have reached the maximum allowed number of vertex colors layers. */ +static bool can_add_vertex_color_layer(const CDStreamConfig &config) +{ + return can_add_custom_data_layer( + config.custom_data_pointers.loop_custom_data, CD_MLOOPCOL, MAX_MCOL); +} - /* The colors can go through two layers of indexing. Often the 'indices' - * array doesn't do anything (i.e. indices[n] = n), but when it does, it's - * important. Blender 2.79 writes indices incorrectly (see T53745), which - * is why we have to check for indices->size() > 0 */ - bool use_dual_indexing = is_facevarying && indices->size() > 0; +/* Check if we do not already have reached the maximum allowed number of UV layers. */ +static bool can_add_uv_layer(const CDStreamConfig &config) +{ + return can_add_custom_data_layer( + config.custom_data_pointers.loop_custom_data, CD_MLOOPUV, MAX_MTFACE); +} - for (int i = 0; i < config.totpoly; i++) { - MPoly *poly = &mpolys[i]; - MCol *cface = &cfaces[poly->loopstart + poly->totloop]; - MLoop *mloop = &mloops[poly->loopstart + poly->totloop]; +/* For converting attributes with a loop scope, we need to convert the polygon winding order as + * well, as viewed from Blender, Alembic orders vertices around a polygon in reverse. + * The callback will called for each loop, and the index passed to it will be the index of the data + * in the source_scope. If source_scope is POINT, the passed index will be that of the point of the + * corresponding loop vertex. If it is LOOPS, it will be that of the loop. + * For now, we do support POLYGON scope as a source_scope. + */ +template<typename BlenderType, typename Callback> +static void iterate_attribute_loop_scope(const CDStreamConfig &config, + BlenderType *attribute_data, + BlenderScope source_scope, + Callback callback) +{ + if (source_scope != BlenderScope::LOOPS && source_scope != BlenderScope::POINT) { + return; + } - for (int j = 0; j < poly->totloop; j++, face_index++) { - cface--; - mloop--; + Mesh *mesh = config.mesh; + MPoly *mpolys = mesh->mpoly; + MLoop *mloops = mesh->mloop; + unsigned int loop_index, rev_loop_index; + const bool index_per_loop = source_scope == BlenderScope::LOOPS; - color_index = is_facevarying ? face_index : mloop->v; - if (use_dual_indexing) { - color_index = (*indices)[color_index]; - } - if (use_c3f_ptr) { - bool is_mcols_out_of_bounds = false; - color_index = mcols_out_of_bounds_check(color_index, - c3f_ptr->size(), - iobject_full_name, - prop_header, - is_mcols_out_of_bounds, - bounds_warning_given); - if (is_mcols_out_of_bounds) { - continue; - } - const Imath::C3f &color = (*c3f_ptr)[color_index]; - cface->a = unit_float_to_uchar_clamp(color[0]); - cface->r = unit_float_to_uchar_clamp(color[1]); - cface->g = unit_float_to_uchar_clamp(color[2]); - cface->b = 255; - } - else { - bool is_mcols_out_of_bounds = false; - color_index = mcols_out_of_bounds_check(color_index, - c4f_ptr->size(), - iobject_full_name, - prop_header, - is_mcols_out_of_bounds, - bounds_warning_given); - if (is_mcols_out_of_bounds) { - continue; - } - const Imath::C4f &color = (*c4f_ptr)[color_index]; - cface->a = unit_float_to_uchar_clamp(color[0]); - cface->r = unit_float_to_uchar_clamp(color[1]); - cface->g = unit_float_to_uchar_clamp(color[2]); - cface->b = unit_float_to_uchar_clamp(color[3]); - } + for (int i = 0; i < mesh->totpoly; i++) { + MPoly &poly = mpolys[i]; + unsigned int rev_loop_offset = poly.loopstart + poly.totloop - 1; + + for (int f = 0; f < poly.totloop; f++) { + rev_loop_index = rev_loop_offset - f; + loop_index = index_per_loop ? poly.loopstart + f : mloops[rev_loop_index].v; + attribute_data[rev_loop_index] = callback(loop_index); } } } -static void read_custom_data_uvs(const ICompoundProperty &prop, - const PropertyHeader &prop_header, - const CDStreamConfig &config, - const Alembic::Abc::ISampleSelector &iss) +/* Main entry point for creating a custom data layer from an Alembic attribute. + * + * The callback is responsible for converting filling the custom data layer, one value at a time. + * Its signature should be : BlenderType(size_t). The parameter is the current index of the + * iteration, and is dependant on the target_scope. + * + * The source_scope is the resolved scope for the Alembic data, while the target_scope is the scope + * of the final Blender data. We split these as they can be different either due to remapping, or + * because we have an attribute that is always on one scope in Blender, but can be expressed in + * multiple scopes in Alembic (e.g. UV maps are loop scope in Blender, but loop or vertex in + * Alembic). + */ +template<typename BlenderType, typename Callback> +static void create_layer_for_scope(const CDStreamConfig &config, + BlenderScope source_scope, + BlenderScope target_scope, + CustomDataType cd_type, + const std::string &name, + Callback callback) { - IV2fGeomParam uv_param(prop, prop_header.getName()); + BLI_assert(source_scope != BlenderScope::UNKNOWN); + BLI_assert(target_scope != BlenderScope::UNKNOWN); - if (!uv_param.isIndexed()) { + const ScopeCustomDataPointers &custom_data_pointers = config.custom_data_pointers; + CustomData *custom_data = custom_data_for_scope(custom_data_pointers, target_scope); + if (!custom_data) { return; } - IV2fGeomParam::Sample sample; - uv_param.getIndexed(sample, iss); + const int layer_size = size_for_scope(config.scope_sizes, target_scope); - UInt32ArraySamplePtr uvs_indices = sample.getIndices(); + const AttributeDomain domain = attribute_domain_from_blender_scope(target_scope); + CustomDataLayer *layer = BKE_id_attribute_ensure( + config.id, name.c_str(), cd_type, domain, nullptr); - const AbcUvScope uv_scope = get_uv_scope(uv_param.getScope(), config, uvs_indices); + BlenderType *layer_data = static_cast<BlenderType *>(layer->data); - if (uv_scope == ABC_UV_SCOPE_NONE) { + if (target_scope == BlenderScope::LOOPS) { + iterate_attribute_loop_scope(config, layer_data, source_scope, callback); return; } - void *cd_data = config.add_customdata_cb(config.mesh, prop_header.getName().c_str(), CD_MLOOPUV); + /* POINT and POLYGON scopes can be simply iterated. */ + for (size_t i = 0; i < static_cast<size_t>(layer_size); i++) { + *layer_data++ = callback(i); + } +} + +/* Wrapper around create_layer_for_scope with similar source and target scopes. */ +template<typename BlenderType, typename Callback> +static void create_layer_for_scope(const CDStreamConfig &config, + BlenderScope bl_scope, + CustomDataType cd_type, + const std::string &name, + Callback callback) +{ + return create_layer_for_scope<BlenderType>(config, bl_scope, bl_scope, cd_type, name, callback); +} - read_uvs(config, cd_data, uv_scope, sample.getVals(), uvs_indices); +/* Wrapper around create_layer_for_scope with the loop scope being the target scope. */ +template<typename BlenderType, typename Callback> +static void create_loop_layer_for_scope(const CDStreamConfig &config, + BlenderScope source_scope, + CustomDataType cd_type, + const std::string &name, + Callback callback) +{ + return create_layer_for_scope<BlenderType>( + config, source_scope, BlenderScope::LOOPS, cd_type, name, callback); } -void read_generated_coordinates(const ICompoundProperty &prop, - const CDStreamConfig &config, - const Alembic::Abc::ISampleSelector &iss) +enum class AbcAttributeReadError { + /* Default value for success. */ + READ_SUCCESS, + /* We do not support this attribute type. */ + UNSUPPORTED_TYPE, + /* The attribute is invalid (e.g. corrupt file or data). */ + INVALID_ATTRIBUTE, + /* We cannot determine the scope for the attribute, either from mismatching element size, or + * ambiguous scope used in the Alembic archive. */ + SCOPE_RESOLUTION_FAILED, + /* Scope resolution succeeded, but the scope is not valid for the data. */ + INVALID_SCOPE, + /* The mapping selected by the user is not possible. */ + MAPPING_IMPOSSIBLE, + /* The limit of a attribute for the CustomData layer is reached (this should only concern UVs and + * vertex colors). */ + TOO_MANY_ATTRIBUTES, +}; + +static CustomDataType custom_data_type_for_pod(PlainOldDataType pod_type, uint extent) { - if (!prop.valid() || prop.getPropertyHeader(propNameOriginalCoordinates) == nullptr) { - /* The ORCO property isn't there, so don't bother trying to process it. */ - return; + switch (pod_type) { + case kBooleanPOD: { + return CD_PROP_BOOL; + } + case kUint8POD: + case kInt8POD: + case kUint16POD: + case kInt16POD: + case kUint32POD: + case kInt32POD: { + return CD_PROP_INT32; + } + case kFloat32POD: + case kFloat64POD: { + if (extent == 2) { + return CD_PROP_FLOAT2; + } + + if (extent == 3) { + return CD_PROP_FLOAT3; + } + + return CD_PROP_FLOAT; + } + /* These are unsupported for now. */ + case kFloat16POD: + case kUint64POD: + case kInt64POD: + case kStringPOD: + case kWstringPOD: + default: { + /* Use this as invalid value. */ + return CD_AUTO_FROM_NAME; + } } +} - IV3fGeomParam param(prop, propNameOriginalCoordinates); - if (!param.valid() || param.isIndexed()) { - /* Invalid or indexed coordinates aren't supported. */ - return; +template<typename TRAIT> +static CustomDataType get_default_custom_data_type(const CDStreamConfig &config, + const ITypedGeomParam<TRAIT> ¶m, + bool &r_is_rgba) +{ + if (std::is_same_v<ITypedGeomParam<TRAIT>, IC3fGeomParam>) { + if (!can_add_vertex_color_layer(config)) { + /* CD_MCOL is full, fallback to generic colors. */ + return CD_PROP_COLOR; + } + + return CD_MCOL; } - if (param.getScope() != kVertexScope) { - /* These are original vertex coordinates, so must be vertex-scoped. */ - return; + + if (std::is_same_v<ITypedGeomParam<TRAIT>, IC4fGeomParam>) { + r_is_rgba = true; + if (!can_add_vertex_color_layer(config)) { + /* CD_MCOL is full, fallback to generic colors. */ + return CD_PROP_COLOR; + } + + return CD_MCOL; } - IV3fGeomParam::Sample sample = param.getExpandedValue(iss); - Alembic::AbcGeom::V3fArraySamplePtr abc_orco = sample.getVals(); - const size_t totvert = abc_orco.get()->size(); - Mesh *mesh = config.mesh; + if (std::is_same_v<ITypedGeomParam<TRAIT>, IV2fGeomParam>) { + if (Alembic::AbcGeom::isUV(param.getHeader())) { + if (can_add_uv_layer(config)) { + return CD_MLOOPUV; + } + } - if (totvert != mesh->totvert) { - /* Either the data is somehow corrupted, or we have a dynamic simulation where only the ORCOs - * for the first frame were exported. */ - return; + /* If it is not a UV map or if CD_MLOOPUV is full, fallback to generic 2D vectors. */ + return CD_PROP_FLOAT2; } - void *cd_data; - if (CustomData_has_layer(&mesh->vdata, CD_ORCO)) { - cd_data = CustomData_get_layer(&mesh->vdata, CD_ORCO); + /* Ensure UVs are also detected if written as doubles. */ + if (std::is_same_v<ITypedGeomParam<TRAIT>, IV2dGeomParam>) { + if (Alembic::AbcGeom::isUV(param.getHeader())) { + if (can_add_uv_layer(config)) { + return CD_MLOOPUV; + } + } + + /* If it is not a UV map or if CD_MLOOPUV is full, fallback to generic 2D vectors. */ + return CD_PROP_FLOAT2; } - else { - cd_data = CustomData_add_layer(&mesh->vdata, CD_ORCO, CD_CALLOC, nullptr, totvert); + + if (param.getName() == propNameOriginalCoordinates) { + return CD_ORCO; } - float(*orcodata)[3] = static_cast<float(*)[3]>(cd_data); - for (int vertex_idx = 0; vertex_idx < totvert; ++vertex_idx) { - const Imath::V3f &abc_coords = (*abc_orco)[vertex_idx]; - copy_zup_from_yup(orcodata[vertex_idx], abc_coords.getValue()); + const DataType &data_type = param.getDataType(); + return custom_data_type_for_pod(data_type.getPod(), data_type.getExtent()); +} + +template<typename TRAIT> +static std::optional<AbcAttributeMapping> determine_attribute_mapping( + const CDStreamConfig &config, + const ITypedGeomParam<TRAIT> ¶m, + size_t num_values, + const CacheAttributeMapping *desired_mapping) +{ + const BlenderScope bl_scope = to_blender_scope(param.getScope(), num_values, config.scope_sizes); + AbcAttributeMapping default_mapping; + default_mapping.scope = bl_scope; + default_mapping.type = get_default_custom_data_type(config, param, default_mapping.is_rgba); + + if (desired_mapping) { + auto opt_final_mapping = final_mapping_from_cache_mapping(*desired_mapping, + config.scope_sizes, + default_mapping, + num_values * + param.getDataType().getExtent()); + + if (opt_final_mapping.has_value()) { + /* Verify that the scope is valid, it may be that we cannot apply the desired mapping, or + * that the data matches the default mapping, but that is also invalid. */ + AbcAttributeMapping final_mapping = *opt_final_mapping; + if (final_mapping.scope != BlenderScope::UNKNOWN) { + return opt_final_mapping; + } + } } - /* ORCOs are always stored in the normalized 0..1 range in Blender, but Alembic stores them - * unnormalized, so we need to normalize them. */ - BKE_mesh_orco_verts_transform(mesh, orcodata, mesh->totvert, false); + if (!is_valid_scope_for_layer(default_mapping.scope, default_mapping.type)) { + return {}; + } + + return default_mapping; } -void read_custom_data(const std::string &iobject_full_name, - const ICompoundProperty &prop, - const CDStreamConfig &config, - const Alembic::Abc::ISampleSelector &iss) +template<typename TRAIT> +static AbcAttributeReadError process_typed_attribute(const CDStreamConfig &config, + const CacheAttributeMapping *mapping, + const ITypedGeomParam<TRAIT> ¶m, + ISampleSelector iss) { - if (!prop.valid()) { - return; + typename ITypedGeomParam<TRAIT>::Sample sample; + param.getIndexed(sample, iss); + + if (!sample.valid()) { + return AbcAttributeReadError::INVALID_ATTRIBUTE; } - int num_uvs = 0; - int num_colors = 0; + const AttributeSelector &attr_sel = *config.attr_selector; - const size_t num_props = prop.getNumProperties(); + const TypedArraySample<TRAIT> &values = *sample.getVals(); + const UInt32ArraySamplePtr indices = sample.getIndices(); + const size_t num_values = indices->size() > 0 ? indices->size() : values.size(); - for (size_t i = 0; i < num_props; i++) { - const Alembic::Abc::PropertyHeader &prop_header = prop.getPropertyHeader(i); + std::optional<AbcAttributeMapping> opt_abc_mapping = determine_attribute_mapping( + config, param, num_values, mapping); - /* Read UVs according to convention. */ - if (IV2fGeomParam::matches(prop_header) && Alembic::AbcGeom::isUV(prop_header)) { - if (++num_uvs > MAX_MTFACE) { - continue; + if (!opt_abc_mapping.has_value()) { + return AbcAttributeReadError::MAPPING_IMPOSSIBLE; + } + + AbcAttributeMapping abc_mapping = *opt_abc_mapping; + BlenderScope bl_scope = abc_mapping.scope; + + if (!attr_sel.select_attribute(param.getName())) { + return AbcAttributeReadError::READ_SUCCESS; + } + + using abc_type = typename TRAIT::value_type; + using abc_scalar_type = typename to_scalar_type<abc_type>::type; + + const abc_scalar_type *input_data = reinterpret_cast<const abc_scalar_type *>(values.get()); + + switch (abc_mapping.type) { + default: { + return AbcAttributeReadError::MAPPING_IMPOSSIBLE; + } + case CD_PROP_BOOL: { + create_layer_for_scope<bool>(config, bl_scope, CD_PROP_BOOL, param.getName(), [&](size_t i) { + return value_type_converter<abc_scalar_type>::map_to_bool(&input_data[i]); + }); + return AbcAttributeReadError::READ_SUCCESS; + } + case CD_PROP_INT32: { + create_layer_for_scope<int32_t>( + config, bl_scope, CD_PROP_INT32, param.getName(), [&](size_t i) { + return value_type_converter<abc_scalar_type>::map_to_int32(&input_data[i]); + }); + return AbcAttributeReadError::READ_SUCCESS; + } + case CD_PROP_FLOAT: { + create_layer_for_scope<float>( + config, bl_scope, CD_PROP_FLOAT, param.getName(), [&](size_t i) { + return value_type_converter<abc_scalar_type>::map_to_float(&input_data[i]); + }); + return AbcAttributeReadError::READ_SUCCESS; + } + case CD_PROP_FLOAT2: { + create_layer_for_scope<float2>( + config, bl_scope, CD_PROP_FLOAT2, param.getName(), [&](size_t i) { + return value_type_converter<abc_scalar_type>::map_to_float2(&input_data[i * 2]); + }); + return AbcAttributeReadError::READ_SUCCESS; + } + case CD_ORCO: + case CD_PROP_FLOAT3: { + std::string param_name = param.getName(); + if (param.getName() == attr_sel.velocity_name()) { + param_name = "velocity"; + + /* Check that we indeed have an attribute on the points. */ + if (bl_scope != BlenderScope::POINT) { + return AbcAttributeReadError::INVALID_SCOPE; + } } - read_custom_data_uvs(prop, prop_header, config, iss); - continue; + create_layer_for_scope<float3>( + config, bl_scope, abc_mapping.type, param_name, [&](size_t i) { + return value_type_converter<abc_scalar_type>::map_to_float3(&input_data[i * 3]); + }); + return AbcAttributeReadError::READ_SUCCESS; + } + case CD_PROP_COLOR: { + if (abc_mapping.is_rgba) { + create_layer_for_scope<ColorGeometry4f>( + config, bl_scope, CD_PROP_COLOR, param.getName(), [&](size_t i) { + return value_type_converter<abc_scalar_type>::map_to_color_from_rgba( + &input_data[i * 4]); + }); + } + else { + create_layer_for_scope<ColorGeometry4f>( + config, bl_scope, CD_PROP_COLOR, param.getName(), [&](size_t i) { + return value_type_converter<abc_scalar_type>::map_to_color_from_rgb( + &input_data[i * 3]); + }); + } + return AbcAttributeReadError::READ_SUCCESS; + } + case CD_MLOOPUV: { + if (!can_add_uv_layer(config)) { + return AbcAttributeReadError::TOO_MANY_ATTRIBUTES; + } + + create_loop_layer_for_scope<float2>( + config, bl_scope, CD_MLOOPUV, param.getName(), [&](size_t i) { + i = indices->size() ? (*indices)[i] : i; + return value_type_converter<abc_scalar_type>::map_to_float2(&input_data[i * 2]); + }); + + return AbcAttributeReadError::READ_SUCCESS; } + case CD_MCOL: { + if (!can_add_vertex_color_layer(config)) { + return AbcAttributeReadError::TOO_MANY_ATTRIBUTES; + } - /* Read vertex colors according to convention. */ - if (IC3fGeomParam::matches(prop_header) || IC4fGeomParam::matches(prop_header)) { - if (++num_colors > MAX_MCOL) { - continue; + const bool is_facevarying = bl_scope == BlenderScope::LOOPS; + + /* Read the vertex colors */ + bool bounds_warning_given = false; + + /* The colors can go through two layers of indexing. Often the 'indices' + * array doesn't do anything (i.e. indices[n] = n), but when it does, it's + * important. Blender 2.79 writes indices incorrectly (see T53745), which + * is why we have to check for indices->size() > 0 */ + bool use_dual_indexing = is_facevarying && indices->size() > 0; + + create_loop_layer_for_scope<MCol>( + config, bl_scope, CD_MLOOPCOL, param.getName(), [&](size_t loop_index) { + size_t color_index = loop_index; + if (use_dual_indexing) { + color_index = (*indices)[color_index]; + } + + bool is_mcols_out_of_bounds = false; + color_index = mcols_out_of_bounds_check(color_index, + values.size(), + config.iobject_full_name, + param.getName(), + is_mcols_out_of_bounds, + bounds_warning_given); + + if (is_mcols_out_of_bounds) { + return MCol{}; + } + + return value_type_converter<abc_scalar_type>::map_to_mcol_from_rgb( + &input_data[loop_index * (3 + abc_mapping.is_rgba)]); + }); + + return AbcAttributeReadError::READ_SUCCESS; + } + case CD_BWEIGHT: { + if (config.mesh) { + Mesh *mesh = config.mesh; + MVert *mvert = config.mvert; + for (int i = 0; i < mesh->totvert; i++) { + mvert[i].bweight = unit_float_to_uchar_clamp( + value_type_converter<abc_scalar_type>::map_to_float(&input_data[i])); + } } + return AbcAttributeReadError::READ_SUCCESS; + } + } + return AbcAttributeReadError::READ_SUCCESS; +} + +static AbcAttributeReadError read_mesh_uvs(const CDStreamConfig &config, + const IV2fGeomParam &uv_param, + ISampleSelector sample_sel) +{ + BLI_assert(config.mesh); + + if (!uv_param.valid() || !uv_param.isIndexed()) { + return AbcAttributeReadError::INVALID_ATTRIBUTE; + } + + IV2fGeomParam::Sample sample; + uv_param.getIndexed(sample, sample_sel); + + const Alembic::AbcGeom::V2fArraySamplePtr &uvs = sample.getVals(); + const Alembic::AbcGeom::UInt32ArraySamplePtr &indices = sample.getIndices(); + + const BlenderScope bl_scope = to_blender_scope( + uv_param.getScope(), indices->size(), config.scope_sizes); + + if (!is_valid_uv_scope(bl_scope)) { + return AbcAttributeReadError::INVALID_SCOPE; + } + + /* According to the convention, primary UVs should have had their name set using + * Alembic::Abc::SetSourceName. If there is no such name, use the name defined for the uv_param. + */ + std::string name = Alembic::Abc::GetSourceName(uv_param.getMetaData()); + if (name.empty()) { + name = uv_param.getName(); + } + + create_loop_layer_for_scope<MLoopUV>(config, bl_scope, CD_MLOOPUV, name, [&](size_t loop_index) { + size_t uv_index = (*indices)[loop_index]; + const Imath::V2f &uv = (*uvs)[uv_index]; + + MLoopUV result; + result.uv[0] = uv[0]; + result.uv[1] = uv[1]; + return result; + }); + + return AbcAttributeReadError::READ_SUCCESS; +} + +/* This structure holds data for an attribute found on the Alembic object. */ +struct ParsedAttributeDesc { + ICompoundProperty parent; + const PropertyHeader &prop_header; + const CacheAttributeMapping *mapping; +}; + +/* Extract supported attributes from the ICompoundProperty, and associate them with any mapping + * with a matching name. */ +static Vector<ParsedAttributeDesc> parse_attributes(const AttributeSelector *attr_sel, + const ICompoundProperty &arb_geom_params) +{ + Vector<ParsedAttributeDesc> result; + if (!arb_geom_params.valid()) { + return result; + } + + for (size_t i = 0; i < arb_geom_params.getNumProperties(); ++i) { + const PropertyHeader &prop = arb_geom_params.getPropertyHeader(i); - read_custom_data_mcols(iobject_full_name, prop, prop_header, config, iss); + /* TODO(kevindietrich): support scalar properties. */ + if (prop.isScalar()) { continue; } + + const CacheAttributeMapping *mapping = attr_sel ? attr_sel->get_mapping(prop.getName()) : + nullptr; + result.append({arb_geom_params, prop, mapping}); + } + + return result; +} + +/* This function should be used for any attribute processing to ensure that all supported attribute + * types are handled. + * + * Unsupported attribute types are: + * - int64 and uint64, since we may have data loss as Blender only supports int32 + * - half floating points (scalar, vec2, vec3, color) + * - matrices (3x3, 4x4) + * - string + * - quaternions + * - 2d normals + * - 2d & 3d bounding boxes + */ +template<typename OpType> +static auto abc_attribute_type_operation(const ICompoundProperty &parent_prop, + const PropertyHeader &prop, + OpType &&op) +{ + if (IFloatGeomParam::matches(prop)) { + IFloatGeomParam param = IFloatGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IDoubleGeomParam::matches(prop)) { + IDoubleGeomParam param = IDoubleGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IBoolGeomParam::matches(prop)) { + IBoolGeomParam param = IBoolGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (ICharGeomParam::matches(prop)) { + ICharGeomParam param = ICharGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IInt16GeomParam::matches(prop)) { + IInt16GeomParam param = IInt16GeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IInt32GeomParam::matches(prop)) { + IInt32GeomParam param = IInt32GeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IUcharGeomParam::matches(prop)) { + IUcharGeomParam param = IUcharGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IUInt16GeomParam::matches(prop)) { + IUInt16GeomParam param = IUInt16GeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IUInt32GeomParam::matches(prop)) { + IUInt32GeomParam param = IUInt32GeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IV2fGeomParam::matches(prop)) { + IV2fGeomParam param = IV2fGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IN3fGeomParam::matches(prop)) { + IN3fGeomParam param = IN3fGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IN3dGeomParam::matches(prop)) { + IN3dGeomParam param = IN3dGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IP3fGeomParam::matches(prop)) { + IP3fGeomParam param = IP3fGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IP3dGeomParam::matches(prop)) { + IP3dGeomParam param = IP3dGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IV3fGeomParam::matches(prop)) { + IV3fGeomParam param = IV3fGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IV3dGeomParam::matches(prop)) { + IV3dGeomParam param = IV3dGeomParam(parent_prop, prop.getName()); + return op(param); + } + if (IC3fGeomParam::matches(prop)) { + IC3fGeomParam param = IC3fGeomParam(parent_prop, prop.getName()); + return op(param); } + if (IC4fGeomParam::matches(prop)) { + IC4fGeomParam param = IC4fGeomParam(parent_prop, prop.getName()); + return op(param); + } + + return op.default_handler_for_unsupported_attribute_type(prop.getName()); } -AbcUvScope get_uv_scope(const Alembic::AbcGeom::GeometryScope scope, - const CDStreamConfig &config, - const Alembic::AbcGeom::UInt32ArraySamplePtr &indices) +struct AttributeReadOperator { + const CDStreamConfig &config; + const ISampleSelector &sample_sel; + const AttributeSelector &attr_sel; + float velocity_scale; + + ParsedAttributeDesc *desc; + + bool has_error = false; + + void handle_error(AbcAttributeReadError error, const std::string &attribute_name) + { + has_error |= error != AbcAttributeReadError::READ_SUCCESS; + + switch (error) { + case AbcAttributeReadError::READ_SUCCESS: { + return; + } + case AbcAttributeReadError::TOO_MANY_ATTRIBUTES: { + std::cerr << "Cannot read attribute \"" << attribute_name + << "\", too many attributes of the same type have been read!\n"; + return; + } + case AbcAttributeReadError::INVALID_ATTRIBUTE: { + std::cerr << "Cannot read attribute \"" << attribute_name << "\" as it is invalid!\n"; + return; + } + case AbcAttributeReadError::MAPPING_IMPOSSIBLE: { + std::cerr << "Cannot read attribute \"" << attribute_name + << "\" as the mapping is impossible!\n"; + return; + } + case AbcAttributeReadError::SCOPE_RESOLUTION_FAILED: { + std::cerr << "Cannot read attribute \"" << attribute_name + << "\" as the scope is undeterminable!\n"; + return; + } + case AbcAttributeReadError::INVALID_SCOPE: { + std::cerr << "Cannot read attribute \"" << attribute_name + << "\" as the scope is invalid for the specific data type!\n"; + return; + } + case AbcAttributeReadError::UNSUPPORTED_TYPE: { + std::cerr << "Cannot read attribute \"" << attribute_name << "\"" + << " as the data type is not supported!\n"; + return; + } + } + } + + void default_handler_for_unsupported_attribute_type(const std::string ¶m_name) + { + handle_error(AbcAttributeReadError::UNSUPPORTED_TYPE, param_name); + } + + template<typename GeomParamType> void operator()(const GeomParamType ¶m) + { + AbcAttributeReadError error = process_typed_attribute( + config, desc->mapping, param, sample_sel); + handle_error(error, desc->prop_header.getName()); + } +}; + +void read_arbitrary_attributes(const CDStreamConfig &config, + const ICompoundProperty &schema, + const IV2fGeomParam &primary_uvs, + const ISampleSelector &sample_sel, + float velocity_scale) { - if (scope == kFacevaryingScope && indices->size() == config.totloop) { - return ABC_UV_SCOPE_LOOP; + + /* The attribute selector may be null when run through the import operator. */ + if (!config.attr_selector) { + return; + } + + /* Manually extract the arbitrary geometry parameters. We do it this way to avoid complicating + * the code when dealing with schemas and default velocities which are not accessible via an + * IGeomParam as we would like for the sake of genericity, but as an IArrayProperty. */ + const PropertyHeader *arb_geom_prop_header = schema.getPropertyHeader(".arbGeomParams"); + ICompoundProperty arb_geom_params; + if (arb_geom_prop_header) { + arb_geom_params = ICompoundProperty(schema, ".arbGeomParams"); } - /* kVaryingScope is sometimes used for vertex scopes as the values vary across the vertices. To - * be sure, one has to check the size of the data against the number of vertices, as it could - * also be a varying attribute across the faces (i.e. one value per face). */ - if ((ELEM(scope, kVaryingScope, kVertexScope)) && indices->size() == config.totvert) { - return ABC_UV_SCOPE_VERTEX; + const AttributeSelector &attr_sel = *config.attr_selector; + + Vector<ParsedAttributeDesc> attributes = parse_attributes(config.attr_selector, arb_geom_params); + + /* At this point the velocities attribute should have the default, standard, attribute velocity + * name in Alembic. So we also expect any remapping to also use this name. If there is no + * attribute with the standard name, then either there are no velocities, or it has a different + * name which should have been set in the UI (both for the selection and the remapping). */ + const PropertyHeader *velocity_prop_header = schema.getPropertyHeader(".velocities"); + if (velocity_prop_header) { + attributes.append({schema, *velocity_prop_header, attr_sel.get_mapping(".velocities")}); + } + + if (primary_uvs.valid() && attr_sel.uvs_requested()) { + read_mesh_uvs(config, primary_uvs, sample_sel); + } + + AttributeReadOperator op{config, sample_sel, attr_sel, velocity_scale, nullptr}; + + for (ParsedAttributeDesc desc : attributes) { + op.desc = &desc; + abc_attribute_type_operation(desc.parent, desc.prop_header, op); + } + + if (op.has_error && config.modifier_error_message) { + *config.modifier_error_message = N_( + "Errors while trying to read attributes, see console for details..."); + } +} + +struct AnimatedAttributeOperator { + bool default_handler_for_unsupported_attribute_type(const std::string & /*param_name*/) + { + return false; + } + + template<typename ParamType> bool operator()(const ParamType ¶m) + { + return param.valid() && !param.isConstant(); + } +}; + +bool has_animated_attributes(const ICompoundProperty &arb_geom_params) +{ + /* Since this function is mainly called during data creation when importing an archive, we do not + * have an attribute selector to pass here. Also most of its settings (what to read, velocity + * name, etc.) would not be initialized anyway. */ + Vector<ParsedAttributeDesc> attributes = parse_attributes(nullptr, arb_geom_params); + + AnimatedAttributeOperator animated_param_op; + for (ParsedAttributeDesc desc : attributes) { + const PropertyHeader &prop = desc.prop_header; + if (abc_attribute_type_operation(desc.parent, prop, animated_param_op)) { + return true; + } + } + + return false; +} + +void AttributeSelector::set_read_flags(int flags) +{ + read_flags = flags; +} + +const CacheAttributeMapping *AttributeSelector::get_mapping(const std::string &attr_name) const +{ + for (const CacheAttributeMapping *mapping : mappings) { + if (attr_name == mapping->name) { + return mapping; + } + } + + return nullptr; +} + +const std::string &AttributeSelector::velocity_name() const +{ + return velocity_attribute; +} + +void AttributeSelector::set_velocity_attribute(const char *name) +{ + velocity_attribute = name; +} + +bool AttributeSelector::uvs_requested() const +{ + return (read_flags & MOD_MESHSEQ_READ_UV) != 0; +} + +bool AttributeSelector::vertex_colors_requested() const +{ + return (read_flags & MOD_MESHSEQ_READ_COLOR) != 0; +} + +bool AttributeSelector::original_coordinates_requested() const +{ + /* There is no flag for the ORCO layer, we load it if the vertices are requested. */ + return (read_flags & MOD_MESHSEQ_READ_VERT) != 0; +} + +bool AttributeSelector::select_attribute(const std::string &attr_name) const +{ + /* Empty names are invalid. Those beginning with a '.' are special and correspond to data + * accessible through specific APIs (e.g. the vertices are named ".P") */ + if (attr_name.empty() || (attr_name[0] == '.' && attr_name != velocity_attribute)) { + return false; } - return ABC_UV_SCOPE_NONE; + return true; } } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h index 97a9235753f..54c15cd02e5 100644 --- a/source/blender/io/alembic/intern/abc_customdata.h +++ b/source/blender/io/alembic/intern/abc_customdata.h @@ -27,7 +27,11 @@ #include <map> +#include "BLI_listbase_wrapper.hh" + +struct CacheAttributeMapping; struct CustomData; +struct ID; struct MLoop; struct MLoopUV; struct MPoly; @@ -38,11 +42,26 @@ using Alembic::Abc::ICompoundProperty; using Alembic::Abc::OCompoundProperty; namespace blender::io::alembic { +class AttributeSelector; +enum class BlenderScope : uint32_t; + struct UVSample { std::vector<Imath::V2f> uvs; std::vector<uint32_t> indices; }; +struct ScopeSizeInfo { + int point_scope_size; + int polygon_scope_size; + int loop_scope_size; +}; + +struct ScopeCustomDataPointers { + CustomData *point_custom_data; + CustomData *polygon_custom_data; + CustomData *loop_custom_data; +}; + struct CDStreamConfig { MLoop *mloop; int totloop; @@ -59,10 +78,12 @@ struct CDStreamConfig { bool pack_uvs; - /* TODO(kevin): might need a better way to handle adding and/or updating - * custom data such that it updates the custom data holder and its pointers properly. */ + /* NOTE: the mesh is mostly used for iterating over loops for loop attributes (UVs, MCol, etc.). + * It would be nice to remove it, in favor of a more generic way to iterate valid attribute + * indices. + */ Mesh *mesh; - void *(*add_customdata_cb)(Mesh *mesh, const char *name, int data_type); + ID *id; float weight; float time; @@ -73,6 +94,14 @@ struct CDStreamConfig { const char **modifier_error_message; + ScopeCustomDataPointers custom_data_pointers; + ScopeSizeInfo scope_sizes; + + /* For error reporting when reading vertex colors. */ + std::string iobject_full_name; + + const AttributeSelector *attr_selector; + /* Alembic needs Blender to keep references to C++ objects (the destructors finalize the writing * to ABC). The following fields are all used to keep these references. */ @@ -94,12 +123,12 @@ struct CDStreamConfig { totvert(0), pack_uvs(false), mesh(NULL), - add_customdata_cb(NULL), weight(0.0f), time(0.0f), index(0), ceil_index(0), - modifier_error_message(NULL) + modifier_error_message(NULL), + attr_selector(nullptr) { } }; @@ -112,36 +141,47 @@ const char *get_uv_sample(UVSample &sample, const CDStreamConfig &config, Custom void write_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig &config); -void read_generated_coordinates(const ICompoundProperty &prop, - const CDStreamConfig &config, - const Alembic::Abc::ISampleSelector &iss); - void write_custom_data(const OCompoundProperty &prop, CDStreamConfig &config, CustomData *data, int data_type); -void read_custom_data(const std::string &iobject_full_name, - const ICompoundProperty &prop, - const CDStreamConfig &config, - const Alembic::Abc::ISampleSelector &iss); - -typedef enum { - ABC_UV_SCOPE_NONE, - ABC_UV_SCOPE_LOOP, - ABC_UV_SCOPE_VERTEX, -} AbcUvScope; - -/** - * UVs can be defined per-loop (one value per vertex per face), or per-vertex (one value per - * vertex). The first case is the most common, as this is the standard way of storing this data - * given that some vertices might be on UV seams and have multiple possible UV coordinates; the - * second case can happen when the mesh is split according to the UV islands, in which case storing - * a single UV value per vertex allows to de-duplicate data and thus to reduce the file size since - * vertices are guaranteed to only have a single UV coordinate. - */ -AbcUvScope get_uv_scope(const Alembic::AbcGeom::GeometryScope scope, - const CDStreamConfig &config, - const Alembic::AbcGeom::UInt32ArraySamplePtr &indices); +class AttributeSelector { + /* Name of the velocity attribute, it is ignored since we deal with separately. */ + std::string velocity_attribute = ""; + + int read_flags = 0; + + ListBaseWrapper<const CacheAttributeMapping> mappings; + + public: + AttributeSelector(ListBase *mappings_) : mappings(mappings_) + { + } + + void set_velocity_attribute(const char *name); + + void set_read_flags(int flags); + + const CacheAttributeMapping *get_mapping(const std::string &attr_name) const; + + const std::string &velocity_name() const; + + bool uvs_requested() const; + + bool vertex_colors_requested() const; + + bool original_coordinates_requested() const; + + bool select_attribute(const std::string &attr_name) const; +}; + +void read_arbitrary_attributes(const CDStreamConfig &config, + const ICompoundProperty &schema, + const Alembic::AbcGeom::IV2fGeomParam &primary_uvs, + const Alembic::Abc::ISampleSelector &sample_sel, + float velocity_scale); + +bool has_animated_attributes(const ICompoundProperty &arb_geom_params); } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_camera.cc b/source/blender/io/alembic/intern/abc_reader_camera.cc index d7a096a4ed8..ae0bf901758 100644 --- a/source/blender/io/alembic/intern/abc_reader_camera.cc +++ b/source/blender/io/alembic/intern/abc_reader_camera.cc @@ -73,7 +73,9 @@ bool AbcCameraReader::accepts_object_type( return true; } -void AbcCameraReader::readObjectData(Main *bmain, const ISampleSelector &sample_sel) +void AbcCameraReader::readObjectData(Main *bmain, + const AbcReaderManager & /*manager*/, + const ISampleSelector &sample_sel) { Camera *bcam = static_cast<Camera *>(BKE_camera_add(bmain, m_data_name.c_str())); diff --git a/source/blender/io/alembic/intern/abc_reader_camera.h b/source/blender/io/alembic/intern/abc_reader_camera.h index ca8dee80c9d..929832c89d7 100644 --- a/source/blender/io/alembic/intern/abc_reader_camera.h +++ b/source/blender/io/alembic/intern/abc_reader_camera.h @@ -34,7 +34,9 @@ class AbcCameraReader final : public AbcObjectReader { const Object *const ob, const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; + void readObjectData(Main *bmain, + const AbcReaderManager &manager, + const Alembic::Abc::ISampleSelector &sample_sel) override; }; } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_curves.cc b/source/blender/io/alembic/intern/abc_reader_curves.cc index bd1e57da648..702176672c1 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.cc +++ b/source/blender/io/alembic/intern/abc_reader_curves.cc @@ -36,8 +36,10 @@ #include "BLI_listbase.h" #include "BKE_curve.h" +#include "BKE_geometry_set.hh" #include "BKE_mesh.h" #include "BKE_object.h" +#include "BKE_spline.hh" using Alembic::Abc::FloatArraySamplePtr; using Alembic::Abc::Int32ArraySamplePtr; @@ -90,23 +92,90 @@ bool AbcCurveReader::accepts_object_type( return true; } -void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +static short get_curve_resolution(const ICurvesSchema &schema, + const Alembic::Abc::ISampleSelector &sample_sel) { - Curve *cu = BKE_curve_add(bmain, m_data_name.c_str(), OB_CURVE); - - cu->flag |= CU_3D; - cu->actvert = CU_ACT_NONE; - cu->resolu = 1; - - ICompoundProperty user_props = m_curves_schema.getUserProperties(); + ICompoundProperty user_props = schema.getUserProperties(); if (user_props) { const PropertyHeader *header = user_props.getPropertyHeader(ABC_CURVE_RESOLUTION_U_PROPNAME); if (header != nullptr && header->isScalar() && IInt16Property::matches(*header)) { IInt16Property resolu(user_props, header->getName()); - cu->resolu = resolu.getValue(sample_sel); + return resolu.getValue(sample_sel); } } + return 1; +} + +static short get_curve_order(Alembic::AbcGeom::CurveType abc_curve_type, + const UcharArraySamplePtr orders, + size_t curve_index) +{ + switch (abc_curve_type) { + case Alembic::AbcGeom::kCubic: + return 4; + case Alembic::AbcGeom::kVariableOrder: + if (orders && orders->size() > curve_index) { + return static_cast<short>((*orders)[curve_index]); + } + ATTR_FALLTHROUGH; + case Alembic::AbcGeom::kLinear: + default: + return 2; + } +} + +static int get_curve_overlap(Alembic::AbcGeom::CurvePeriodicity periodicity, + const P3fArraySamplePtr positions, + int idx, + int num_verts, + short order) +{ + if (periodicity == Alembic::AbcGeom::kPeriodic) { + /* Check the number of points which overlap, we don't have + * overlapping points in Blender, but other software do use them to + * indicate that a curve is actually cyclic. Usually the number of + * overlapping points is equal to the order/degree of the curve. + */ + + const int start = idx; + const int end = idx + num_verts; + int overlap = 0; + + for (int j = start, k = end - order; j < order; j++, k++) { + const Imath::V3f &p1 = (*positions)[j]; + const Imath::V3f &p2 = (*positions)[k]; + + if (p1 != p2) { + break; + } + + overlap++; + } + + /* TODO: Special case, need to figure out how it coincides with knots. */ + if (overlap == 0 && num_verts > 2 && (*positions)[start] == (*positions)[end - 1]) { + overlap = 1; + } + + /* There is no real cycles. */ + return overlap; + } + + /* kNonPeriodic is always assumed to have no overlap. */ + return 0; +} + +void AbcCurveReader::readObjectData(Main *bmain, + const AbcReaderManager & /*manager*/, + const Alembic::Abc::ISampleSelector &sample_sel) +{ + Curve *cu = BKE_curve_add(bmain, m_data_name.c_str(), OB_CURVE); + + cu->flag |= CU_3D; + cu->actvert = CU_ACT_NONE; + cu->resolu = get_curve_resolution(m_curves_schema, sample_sel); + m_object = BKE_object_add_only_object(bmain, OB_CURVE, m_object_name.c_str()); m_object->data = cu; @@ -161,60 +230,15 @@ void AbcCurveReader::read_curve_sample(Curve *cu, nu->pntsu = num_verts; nu->pntsv = 1; nu->flag |= CU_SMOOTH; + nu->orderu = get_curve_order(smp.getType(), orders, i); - switch (smp.getType()) { - case Alembic::AbcGeom::kCubic: - nu->orderu = 4; - break; - case Alembic::AbcGeom::kVariableOrder: - if (orders && orders->size() > i) { - nu->orderu = static_cast<short>((*orders)[i]); - break; - } - ATTR_FALLTHROUGH; - case Alembic::AbcGeom::kLinear: - default: - nu->orderu = 2; - } + const int overlap = get_curve_overlap(periodicity, positions, idx, num_verts, nu->orderu); - if (periodicity == Alembic::AbcGeom::kNonPeriodic) { + if (overlap == 0) { nu->flagu |= CU_NURB_ENDPOINT; } - else if (periodicity == Alembic::AbcGeom::kPeriodic) { + else { nu->flagu |= CU_NURB_CYCLIC; - - /* Check the number of points which overlap, we don't have - * overlapping points in Blender, but other software do use them to - * indicate that a curve is actually cyclic. Usually the number of - * overlapping points is equal to the order/degree of the curve. - */ - - const int start = idx; - const int end = idx + num_verts; - int overlap = 0; - - for (int j = start, k = end - nu->orderu; j < nu->orderu; j++, k++) { - const Imath::V3f &p1 = (*positions)[j]; - const Imath::V3f &p2 = (*positions)[k]; - - if (p1 != p2) { - break; - } - - overlap++; - } - - /* TODO: Special case, need to figure out how it coincides with knots. */ - if (overlap == 0 && num_verts > 2 && (*positions)[start] == (*positions)[end - 1]) { - overlap = 1; - } - - /* There is no real cycles. */ - if (overlap == 0) { - nu->flagu &= ~CU_NURB_CYCLIC; - nu->flagu |= CU_NURB_ENDPOINT; - } - nu->pntsu -= overlap; } @@ -274,12 +298,39 @@ void AbcCurveReader::read_curve_sample(Curve *cu, } } -Mesh *AbcCurveReader::read_mesh(Mesh *existing_mesh, - const ISampleSelector &sample_sel, - int /*read_flag*/, - const char * /*velocity_name*/, - const float /*velocity_scale*/, - const char **err_str) +static bool topology_changed(CurveEval *curve_eval, const Int32ArraySamplePtr &num_vertices) +{ + if (!curve_eval) { + return true; + } + + const size_t num_curves = num_vertices->size(); + if (num_curves != curve_eval->splines().size()) { + return true; + } + + for (size_t i = 0; i < num_vertices->size(); i++) { + const Spline *spline = curve_eval->splines()[i].get(); + + if (!spline) { + return true; + } + + // TODO(kevindietrich) : this should check for the overlap. + if (spline->positions().size() != (*num_vertices)[i]) { + return true; + } + } + + return false; +} + +void AbcCurveReader::read_geometry(GeometrySet &geometry_set, + const Alembic::Abc::ISampleSelector &sample_sel, + const AttributeSelector * /*attribute_selector*/, + int /*read_flag*/, + const float /*velocity_scale*/, + const char **err_str) { ICurvesSchema::Sample sample; @@ -293,61 +344,49 @@ Mesh *AbcCurveReader::read_mesh(Mesh *existing_mesh, m_curves_schema.getName().c_str(), sample_sel.getRequestedTime(), ex.what()); - return existing_mesh; + return; } - const P3fArraySamplePtr &positions = sample.getPositions(); - const Int32ArraySamplePtr num_vertices = sample.getCurvesNumVertices(); - - int vertex_idx = 0; - int curve_idx; - Curve *curve = static_cast<Curve *>(m_object->data); - - const int curve_count = BLI_listbase_count(&curve->nurb); - bool same_topology = curve_count == num_vertices->size(); + CurveEval *curve_eval = geometry_set.get_curve_for_write(); - if (same_topology) { - Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); - for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { - const int num_in_alembic = (*num_vertices)[curve_idx]; - const int num_in_blender = nurbs->pntsu; - - if (num_in_alembic != num_in_blender) { - same_topology = false; - break; - } - } - } + const Int32ArraySamplePtr num_vertices = sample.getCurvesNumVertices(); + const P3fArraySamplePtr &positions = sample.getPositions(); - if (!same_topology) { + if (blender::io::alembic::topology_changed(curve_eval, num_vertices)) { + Curve *curve = static_cast<Curve *>(m_object->data); BKE_nurbList_free(&curve->nurb); read_curve_sample(curve, m_curves_schema, sample_sel); + + std::unique_ptr<CurveEval> new_curve_eval = curve_eval_from_dna_curve(*curve); + geometry_set.replace_curve(new_curve_eval.release(), GeometryOwnershipType::Editable); } else { - Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); - for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { - const int totpoint = (*num_vertices)[curve_idx]; + const IFloatGeomParam widths_param = m_curves_schema.getWidthsParam(); + FloatArraySamplePtr radiuses; - if (nurbs->bp) { - BPoint *point = nurbs->bp; + if (widths_param.valid()) { + IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(sample_sel); + radiuses = wsample.getVals(); + } - for (int i = 0; i < totpoint; i++, point++, vertex_idx++) { - const Imath::V3f &pos = (*positions)[vertex_idx]; - copy_zup_from_yup(point->vec, pos.getValue()); - } + const bool do_radius = (radiuses != nullptr) && (radiuses->size() > 1); + float radius = (radiuses && radiuses->size() == 1) ? (*radiuses)[0] : 1.0f; + + size_t position_index = 0; + size_t radius_index = 0; + + for (size_t i = 0; i < num_vertices->size(); i++) { + Spline *spline = curve_eval->splines()[i].get(); + + for (float3 &position : spline->positions()) { + copy_zup_from_yup(position, (*positions)[position_index++].getValue()); } - else if (nurbs->bezt) { - BezTriple *bezier = nurbs->bezt; - for (int i = 0; i < totpoint; i++, bezier++, vertex_idx++) { - const Imath::V3f &pos = (*positions)[vertex_idx]; - copy_zup_from_yup(bezier->vec[1], pos.getValue()); - } + for (float &r : spline->radii()) { + r = (do_radius) ? (*radiuses)[radius_index++] : radius; } } } - - return BKE_mesh_new_nomain_from_curve(m_object); } } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_curves.h b/source/blender/io/alembic/intern/abc_reader_curves.h index e7e6fa9f0b2..88956c4415c 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.h +++ b/source/blender/io/alembic/intern/abc_reader_curves.h @@ -42,7 +42,10 @@ class AbcCurveReader final : public AbcObjectReader { const Object *const ob, const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; + void readObjectData(Main *bmain, + const AbcReaderManager &manager, + const Alembic::Abc::ISampleSelector &sample_sel) override; + /** * \note Alembic only stores data about control points, but the Mesh * passed from the cache modifier contains the #DispList, which has more data @@ -50,12 +53,12 @@ class AbcCurveReader final : public AbcObjectReader { * object directly and create a new Mesh from that. Also we might need to * create new or delete existing NURBS in the curve. */ - struct Mesh *read_mesh(struct Mesh *existing_mesh, - const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, - const char *velocity_name, - float velocity_scale, - const char **err_str) override; + void read_geometry(GeometrySet &geometry_set, + const Alembic::Abc::ISampleSelector &sample_sel, + const AttributeSelector *attribute_selector, + int read_flag, + const float velocity_scale, + const char **err_str) override; void read_curve_sample(Curve *cu, const Alembic::AbcGeom::ICurvesSchema &schema, diff --git a/source/blender/io/alembic/intern/abc_reader_instance.cc b/source/blender/io/alembic/intern/abc_reader_instance.cc new file mode 100644 index 00000000000..ee7c42a5ec1 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_instance.cc @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#include "abc_reader_instance.h" + +#include "DNA_object_types.h" + +#include "BLI_assert.h" + +#include "BKE_lib_id.h" +#include "BKE_lib_remap.h" +#include "BKE_object.h" + +#include "abc_reader_manager.h" + +namespace blender::io::alembic { + +AbcInstanceReader::AbcInstanceReader(const Alembic::Abc::IObject &object, ImportSettings &settings) + : AbcObjectReader(object, settings) +{ +} + +bool AbcInstanceReader::valid() const +{ + // TODO(kevindietrich) + return true; +} + +bool AbcInstanceReader::accepts_object_type( + const Alembic::AbcCoreAbstract::ObjectHeader & /*alembic_header*/, + const Object *const /*ob*/, + const char ** /*err_str*/) const +{ + /* TODO(kevindietrich) */ + return true; +} + +void AbcInstanceReader::readObjectData(Main *bmain, + const AbcReaderManager &manager, + const Alembic::Abc::ISampleSelector & /* sample_sel */) +{ + /* For reference on duplication, see ED_object_add_duplicate_linked. + * + * In this function, we only duplicate the object, as the rest (adding to view layer, tagging the + * depsgraph, etc.) is done at the end of the import. + */ + + Object *ob = manager.get_blender_object_for_path(m_iobject.instanceSourcePath()); + BLI_assert(ob); + /* 0 = linked */ + uint dupflag = 0; + uint duplicate_options = LIB_ID_DUPLICATE_IS_SUBPROCESS | LIB_ID_DUPLICATE_IS_ROOT_ID; + + m_object = static_cast<Object *>( + ID_NEW_SET(ob, BKE_object_duplicate(bmain, ob, dupflag, duplicate_options))); + + /* link own references to the newly duplicated data T26816. */ + BKE_libblock_relink_to_newid(bmain, &m_object->id, 0); +} + +} // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_instance.h b/source/blender/io/alembic/intern/abc_reader_instance.h new file mode 100644 index 00000000000..9901cbf62ff --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_instance.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup balembic + */ + +#include "abc_reader_object.h" + +namespace blender::io::alembic { + +class AbcInstanceReader final : public AbcObjectReader { + public: + AbcInstanceReader(const Alembic::Abc::IObject &object, ImportSettings &settings); + + bool valid() const override; + + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const override; + + void readObjectData(Main *bmain, + const AbcReaderManager &manager, + const Alembic::Abc::ISampleSelector &sample_sel) override; +}; + +} // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_manager.cc b/source/blender/io/alembic/intern/abc_reader_manager.cc new file mode 100644 index 00000000000..486c417cb26 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_manager.cc @@ -0,0 +1,55 @@ +/* + * 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. + */ + +/** \file + * \ingroup balembic + */ + +#include "abc_reader_manager.h" + +#include "abc_reader_instance.h" + +namespace blender::io::alembic { + +AbcObjectReader *AbcReaderManager::create_instance_reader(Alembic::Abc::v12::IObject iobject, + ImportSettings &settings) +{ + std::cerr << "Creating an instance reader...\n"; + AbcObjectReader *reader = new AbcInstanceReader(iobject, settings); + m_instance_readers.push_back(reader); + m_readers_all.push_back(reader); + return reader; +} + +Object *AbcReaderManager::get_blender_object_for_path(const std::string &path) const +{ + AbcObjectReader *reader = get_object_reader_for_path(path); + if (!reader) { + return nullptr; + } + return reader->object(); +} + +AbcObjectReader *AbcReaderManager::get_object_reader_for_path(const std::string &path) const +{ + MapIteratorType iter = m_readers_map.find(path); + if (iter == m_readers_map.end()) { + return nullptr; + } + return iter->second; +} + +} // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_manager.h b/source/blender/io/alembic/intern/abc_reader_manager.h new file mode 100644 index 00000000000..5f990f4b8f9 --- /dev/null +++ b/source/blender/io/alembic/intern/abc_reader_manager.h @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup balembic + */ + +#include "abc_reader_object.h" + +namespace blender::io::alembic { + +class AbcReaderManager { + using MapType = std::map<std::string, AbcObjectReader *>; + using MapIteratorType = MapType::const_iterator; + std::map<std::string, AbcObjectReader *> m_readers_map{}; + + AbcObjectReader::ptr_vector m_readers; + AbcObjectReader::ptr_vector m_readers_all; + AbcObjectReader::ptr_vector m_instance_readers; + + public: + template<typename ReaderType> + AbcObjectReader *create(Alembic::Abc::IObject iobject, ImportSettings &settings) + { + static_assert( + std::is_base_of_v<AbcObjectReader, ReaderType>, + "Trying to create a reader from a class which does not derive from AbcObjectReader !"); + + if (iobject.isInstanceRoot()) { + return create_instance_reader(iobject, settings); + } + + ReaderType *reader = new ReaderType(iobject, settings); + m_readers_map[iobject.getFullName()] = reader; + m_readers.push_back(reader); + m_readers_all.push_back(reader); + return reader; + } + + AbcObjectReader *create_instance_reader(Alembic::Abc::IObject iobject, ImportSettings &settings); + + Object *get_blender_object_for_path(const std::string &path) const; + + const AbcObjectReader::ptr_vector &all_readers() const + { + return m_readers_all; + } + + const AbcObjectReader::ptr_vector &instance_readers() const + { + return m_instance_readers; + } + + const AbcObjectReader::ptr_vector &data_readers() const + { + return m_readers; + } + + private: + AbcObjectReader *get_object_reader_for_path(const std::string &path) const; +}; + +} // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index 43581ad48d9..4234fb34015 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -38,7 +38,7 @@ #include "BLI_listbase.h" #include "BLI_math_geom.h" -#include "BKE_attribute.h" +#include "BKE_geometry_set.hh" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_mesh.h" @@ -126,10 +126,6 @@ struct AbcMeshData { P3fArraySamplePtr positions; P3fArraySamplePtr ceil_positions; - - AbcUvScope uv_scope; - V2fArraySamplePtr uvs; - UInt32ArraySamplePtr uvs_indices; }; static void read_mverts_interp(MVert *mverts, @@ -189,21 +185,12 @@ static void read_mpolys(CDStreamConfig &config, const AbcMeshData &mesh_data) { MPoly *mpolys = config.mpoly; MLoop *mloops = config.mloop; - MLoopUV *mloopuvs = config.mloopuv; const Int32ArraySamplePtr &face_indices = mesh_data.face_indices; const Int32ArraySamplePtr &face_counts = mesh_data.face_counts; - const V2fArraySamplePtr &uvs = mesh_data.uvs; - const size_t uvs_size = uvs == nullptr ? 0 : uvs->size(); - - const UInt32ArraySamplePtr &uvs_indices = mesh_data.uvs_indices; - const bool do_uvs = (mloopuvs && uvs && uvs_indices); - const bool do_uvs_per_loop = do_uvs && mesh_data.uv_scope == ABC_UV_SCOPE_LOOP; - BLI_assert(!do_uvs || mesh_data.uv_scope != ABC_UV_SCOPE_NONE); unsigned int loop_index = 0; unsigned int rev_loop_index = 0; - unsigned int uv_index = 0; bool seen_invalid_geometry = false; for (int i = 0; i < face_counts->size(); i++) { @@ -231,19 +218,6 @@ static void read_mpolys(CDStreamConfig &config, const AbcMeshData &mesh_data) seen_invalid_geometry = true; } last_vertex_index = loop.v; - - if (do_uvs) { - MLoopUV &loopuv = mloopuvs[rev_loop_index]; - uv_index = (*uvs_indices)[do_uvs_per_loop ? loop_index : loop.v]; - - /* Some Alembic files are broken (or at least export UVs in a way we don't expect). */ - if (uv_index >= uvs_size) { - continue; - } - - loopuv.uv[0] = (*uvs)[uv_index][0]; - loopuv.uv[1] = (*uvs)[uv_index][1]; - } } } @@ -351,68 +325,6 @@ static void process_normals(CDStreamConfig &config, } } -BLI_INLINE void read_uvs_params(CDStreamConfig &config, - AbcMeshData &abc_data, - const IV2fGeomParam &uv, - const ISampleSelector &selector) -{ - if (!uv.valid()) { - return; - } - - IV2fGeomParam::Sample uvsamp; - uv.getIndexed(uvsamp, selector); - - UInt32ArraySamplePtr uvs_indices = uvsamp.getIndices(); - - const AbcUvScope uv_scope = get_uv_scope(uv.getScope(), config, uvs_indices); - - if (uv_scope == ABC_UV_SCOPE_NONE) { - return; - } - - abc_data.uv_scope = uv_scope; - abc_data.uvs = uvsamp.getVals(); - abc_data.uvs_indices = uvs_indices; - - std::string name = Alembic::Abc::GetSourceName(uv.getMetaData()); - - /* According to the convention, primary UVs should have had their name - * set using Alembic::Abc::SetSourceName, but you can't expect everyone - * to follow it! :) */ - if (name.empty()) { - name = uv.getName(); - } - - void *cd_ptr = config.add_customdata_cb(config.mesh, name.c_str(), CD_MLOOPUV); - config.mloopuv = static_cast<MLoopUV *>(cd_ptr); -} - -static void *add_customdata_cb(Mesh *mesh, const char *name, int data_type) -{ - CustomDataType cd_data_type = static_cast<CustomDataType>(data_type); - void *cd_ptr; - CustomData *loopdata; - int numloops; - - /* unsupported custom data type -- don't do anything. */ - if (!ELEM(cd_data_type, CD_MLOOPUV, CD_MLOOPCOL)) { - return nullptr; - } - - loopdata = &mesh->ldata; - cd_ptr = CustomData_get_layer_named(loopdata, cd_data_type, name); - if (cd_ptr != nullptr) { - /* layer already exists, so just return it. */ - return cd_ptr; - } - - /* Create a new layer. */ - numloops = mesh->totloop; - cd_ptr = CustomData_add_layer_named(loopdata, cd_data_type, CD_DEFAULT, nullptr, numloops, name); - return cd_ptr; -} - static void get_weight_and_index(CDStreamConfig &config, Alembic::AbcCoreAbstract::TimeSamplingPtr time_sampling, size_t samples_number) @@ -425,65 +337,7 @@ static void get_weight_and_index(CDStreamConfig &config, config.ceil_index = i1; } -static V3fArraySamplePtr get_velocity_prop(const ICompoundProperty &schema, - const ISampleSelector &selector, - const std::string &name) -{ - for (size_t i = 0; i < schema.getNumProperties(); i++) { - const PropertyHeader &header = schema.getPropertyHeader(i); - - if (header.isCompound()) { - const ICompoundProperty &prop = ICompoundProperty(schema, header.getName()); - - if (has_property(prop, name)) { - /* Header cannot be null here, as its presence is checked via has_property, so it is safe - * to dereference. */ - const PropertyHeader *header = prop.getPropertyHeader(name); - if (!IV3fArrayProperty::matches(*header)) { - continue; - } - - const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(prop, name, 0); - if (velocity_prop) { - return velocity_prop.getValue(selector); - } - } - } - else if (header.isArray()) { - if (header.getName() == name && IV3fArrayProperty::matches(header)) { - const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(schema, name, 0); - return velocity_prop.getValue(selector); - } - } - } - - return V3fArraySamplePtr(); -} - -static void read_velocity(const V3fArraySamplePtr &velocities, - const CDStreamConfig &config, - const float velocity_scale) -{ - const int num_velocity_vectors = static_cast<int>(velocities->size()); - if (num_velocity_vectors != config.mesh->totvert) { - /* Files containing videogrammetry data may be malformed and export velocity data on missing - * frames (most likely by copying the last valid data). */ - return; - } - - CustomDataLayer *velocity_layer = BKE_id_attribute_new( - &config.mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT, nullptr); - float(*velocity)[3] = (float(*)[3])velocity_layer->data; - - for (int i = 0; i < num_velocity_vectors; i++) { - const Imath::V3f &vel_in = (*velocities)[i]; - copy_zup_from_yup(velocity[i], vel_in.getValue()); - mul_v3_fl(velocity[i], velocity_scale); - } -} - -static void read_mesh_sample(const std::string &iobject_full_name, - ImportSettings *settings, +static void read_mesh_sample(ImportSettings *settings, const IPolyMeshSchema &schema, const ISampleSelector &selector, CDStreamConfig &config) @@ -503,13 +357,8 @@ static void read_mesh_sample(const std::string &iobject_full_name, abc_mesh_data.ceil_positions = ceil_sample.getPositions(); } - if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) { - read_uvs_params(config, abc_mesh_data, schema.getUVsParam(), selector); - } - if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { read_mverts(config, abc_mesh_data); - read_generated_coordinates(schema.getArbGeomParams(), config, selector); } if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) { @@ -517,25 +366,21 @@ static void read_mesh_sample(const std::string &iobject_full_name, process_normals(config, schema.getNormalsParam(), selector); } - if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) { - read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector); - } - - if (!settings->velocity_name.empty() && settings->velocity_scale != 0.0f) { - V3fArraySamplePtr velocities = get_velocity_prop(schema, selector, settings->velocity_name); - if (velocities) { - read_velocity(velocities, config, settings->velocity_scale); - } - } + read_arbitrary_attributes( + config, schema, schema.getUVsParam(), selector, settings->velocity_scale); } -CDStreamConfig get_config(Mesh *mesh, const bool use_vertex_interpolation) +CDStreamConfig get_config(Mesh *mesh, + const AttributeSelector *attr_selector, + const std::string &iobject_full_name, + const bool use_vertex_interpolation) { CDStreamConfig config; BLI_assert(mesh->mvert || mesh->totvert == 0); config.mesh = mesh; + config.id = &mesh->id; config.mvert = mesh->mvert; config.mloop = mesh->mloop; config.mpoly = mesh->mpoly; @@ -543,8 +388,11 @@ CDStreamConfig get_config(Mesh *mesh, const bool use_vertex_interpolation) config.totloop = mesh->totloop; config.totpoly = mesh->totpoly; config.loopdata = &mesh->ldata; - config.add_customdata_cb = add_customdata_cb; config.use_vertex_interpolation = use_vertex_interpolation; + config.attr_selector = attr_selector; + config.iobject_full_name = iobject_full_name; + config.scope_sizes = {mesh->totvert, mesh->totpoly, mesh->totloop}; + config.custom_data_pointers = {&mesh->vdata, &mesh->pdata, &mesh->ldata}; return config; } @@ -567,39 +415,6 @@ bool AbcMeshReader::valid() const return m_schema.valid(); } -template<class typedGeomParam> -bool is_valid_animated(const ICompoundProperty arbGeomParams, const PropertyHeader &prop_header) -{ - if (!typedGeomParam::matches(prop_header)) { - return false; - } - - typedGeomParam geom_param(arbGeomParams, prop_header.getName()); - return geom_param.valid() && !geom_param.isConstant(); -} - -static bool has_animated_geom_params(const ICompoundProperty arbGeomParams) -{ - if (!arbGeomParams.valid()) { - return false; - } - - const int num_props = arbGeomParams.getNumProperties(); - for (int i = 0; i < num_props; i++) { - const PropertyHeader &prop_header = arbGeomParams.getPropertyHeader(i); - - /* These are interpreted as vertex colors later (see 'read_custom_data'). */ - if (is_valid_animated<IC3fGeomParam>(arbGeomParams, prop_header)) { - return true; - } - if (is_valid_animated<IC4fGeomParam>(arbGeomParams, prop_header)) { - return true; - } - } - - return false; -} - /* Specialization of #has_animations() as defined in abc_reader_object.h. */ template<> bool has_animations(Alembic::AbcGeom::IPolyMeshSchema &schema, ImportSettings *settings) { @@ -618,21 +433,30 @@ template<> bool has_animations(Alembic::AbcGeom::IPolyMeshSchema &schema, Import } ICompoundProperty arbGeomParams = schema.getArbGeomParams(); - if (has_animated_geom_params(arbGeomParams)) { + if (has_animated_attributes(arbGeomParams)) { return true; } return false; } -void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +void AbcMeshReader::readObjectData(Main *bmain, + const AbcReaderManager & /*manager*/, + const Alembic::Abc::ISampleSelector &sample_sel) { Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str()); m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); m_object->data = mesh; - Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, "", 0.0f, nullptr); + /* Default AttributeSelector to ensure that at least UVs and vertex colors are read. To load + * other attributes, a modifier should be added as there are no clear conventions for them. */ + ListBase lb = {nullptr, nullptr}; + AttributeSelector attr_selector(&lb); + attr_selector.set_read_flags(MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR); + + Mesh *read_mesh = this->read_mesh( + mesh, sample_sel, &attr_selector, MOD_MESHSEQ_READ_ALL, 0.0f, nullptr); if (read_mesh != mesh) { /* XXX FIXME: after 2.80; mesh->flag isn't copied by #BKE_mesh_nomain_to_mesh(). */ /* read_mesh can be freed by BKE_mesh_nomain_to_mesh(), so get the flag before that happens. */ @@ -697,10 +521,31 @@ bool AbcMeshReader::topology_changed(Mesh *existing_mesh, const ISampleSelector face_indices->size() != existing_mesh->totloop; } +void AbcMeshReader::read_geometry(GeometrySet &geometry_set, + const Alembic::Abc::ISampleSelector &sample_sel, + const AttributeSelector *attribute_selector, + int read_flag, + const float velocity_scale, + const char **err_str) +{ + Mesh *mesh = geometry_set.get_mesh_for_write(); + + if (mesh == nullptr) { + return; + } + + Mesh *new_mesh = read_mesh( + mesh, sample_sel, attribute_selector, read_flag, velocity_scale, err_str); + + if (new_mesh != mesh) { + geometry_set.replace_mesh(new_mesh); + } +} + Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh, const ISampleSelector &sample_sel, + const AttributeSelector *attribute_selector, const int read_flag, - const char *velocity_name, const float velocity_scale, const char **err_str) { @@ -744,7 +589,6 @@ Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh, /* Only read point data when streaming meshes, unless we need to create new ones. */ ImportSettings settings; settings.read_flag |= read_flag; - settings.velocity_name = velocity_name; settings.velocity_scale = velocity_scale; if (topology_changed(existing_mesh, sample_sel)) { @@ -771,11 +615,12 @@ Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh, Mesh *mesh_to_export = new_mesh ? new_mesh : existing_mesh; const bool use_vertex_interpolation = read_flag & MOD_MESHSEQ_INTERPOLATE_VERTICES; - CDStreamConfig config = get_config(mesh_to_export, use_vertex_interpolation); + CDStreamConfig config = get_config( + mesh_to_export, attribute_selector, m_iobject.getFullName(), use_vertex_interpolation); config.time = sample_sel.getRequestedTime(); config.modifier_error_message = err_str; - read_mesh_sample(m_iobject.getFullName(), &settings, m_schema, sample_sel, config); + read_mesh_sample(&settings, m_schema, sample_sel, config); if (new_mesh) { /* Here we assume that the number of materials doesn't change, i.e. that @@ -862,8 +707,7 @@ BLI_INLINE MEdge *find_edge(MEdge *edges, int totedge, int v1, int v2) return nullptr; } -static void read_subd_sample(const std::string &iobject_full_name, - ImportSettings *settings, +static void read_subd_sample(ImportSettings *settings, const ISubDSchema &schema, const ISampleSelector &selector, CDStreamConfig &config) @@ -883,10 +727,6 @@ static void read_subd_sample(const std::string &iobject_full_name, abc_mesh_data.ceil_positions = ceil_sample.getPositions(); } - if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) { - read_uvs_params(config, abc_mesh_data, schema.getUVsParam(), selector); - } - if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { read_mverts(config, abc_mesh_data); } @@ -899,16 +739,8 @@ static void read_subd_sample(const std::string &iobject_full_name, process_no_normals(config); } - if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) { - read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector); - } - - if (!settings->velocity_name.empty() && settings->velocity_scale != 0.0f) { - V3fArraySamplePtr velocities = get_velocity_prop(schema, selector, settings->velocity_name); - if (velocities) { - read_velocity(velocities, config, settings->velocity_scale); - } - } + read_arbitrary_attributes( + config, schema, schema.getUVsParam(), selector, settings->velocity_scale); } static void read_vertex_creases(Mesh *mesh, @@ -1009,14 +841,23 @@ bool AbcSubDReader::accepts_object_type( return true; } -void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +void AbcSubDReader::readObjectData(Main *bmain, + const AbcReaderManager & /*manager*/, + const Alembic::Abc::ISampleSelector &sample_sel) { Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str()); m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); m_object->data = mesh; - Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, "", 0.0f, nullptr); + /* Default AttributeSelector to ensure that at least UVs and vertex colors are read. To load + * other attributes, a modifier should be added as there are no clear conventions for them. */ + ListBase lb = {nullptr, nullptr}; + AttributeSelector attr_selector(&lb); + attr_selector.set_read_flags(MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR); + + Mesh *read_mesh = this->read_mesh( + mesh, sample_sel, &attr_selector, MOD_MESHSEQ_READ_ALL, 0.0f, nullptr); if (read_mesh != mesh) { BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_EVERYTHING, true); } @@ -1049,8 +890,8 @@ void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh, const ISampleSelector &sample_sel, + const AttributeSelector *attribute_selector, const int read_flag, - const char *velocity_name, const float velocity_scale, const char **err_str) { @@ -1078,7 +919,6 @@ Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh, ImportSettings settings; settings.read_flag |= read_flag; - settings.velocity_name = velocity_name; settings.velocity_scale = velocity_scale; if (existing_mesh->totvert != positions->size()) { @@ -1106,11 +946,33 @@ Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh, /* Only read point data when streaming meshes, unless we need to create new ones. */ Mesh *mesh_to_export = new_mesh ? new_mesh : existing_mesh; const bool use_vertex_interpolation = read_flag & MOD_MESHSEQ_INTERPOLATE_VERTICES; - CDStreamConfig config = get_config(mesh_to_export, use_vertex_interpolation); + CDStreamConfig config = get_config( + mesh_to_export, attribute_selector, m_iobject.getFullName(), use_vertex_interpolation); config.time = sample_sel.getRequestedTime(); - read_subd_sample(m_iobject.getFullName(), &settings, m_schema, sample_sel, config); + read_subd_sample(&settings, m_schema, sample_sel, config); return mesh_to_export; } +void AbcSubDReader::read_geometry(GeometrySet &geometry_set, + const Alembic::Abc::ISampleSelector &sample_sel, + const AttributeSelector *attribute_selector, + int read_flag, + const float velocity_scale, + const char **err_str) +{ + Mesh *mesh = geometry_set.get_mesh_for_write(); + + if (mesh == nullptr) { + return; + } + + Mesh *new_mesh = read_mesh( + mesh, sample_sel, attribute_selector, read_flag, velocity_scale, err_str); + + if (new_mesh != mesh) { + geometry_set.replace_mesh(new_mesh); + } +} + } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.h b/source/blender/io/alembic/intern/abc_reader_mesh.h index 1123616943e..ac5939155dc 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.h +++ b/source/blender/io/alembic/intern/abc_reader_mesh.h @@ -38,17 +38,20 @@ class AbcMeshReader final : public AbcObjectReader { bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; + void readObjectData(Main *bmain, + const AbcReaderManager &manager, + const Alembic::Abc::ISampleSelector &sample_sel) override; - struct Mesh *read_mesh(struct Mesh *existing_mesh, - const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, - const char *velocity_name, - float velocity_scale, - const char **err_str) override; bool topology_changed(Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel) override; + void read_geometry(GeometrySet &geometry_set, + const Alembic::Abc::ISampleSelector &sample_sel, + const AttributeSelector *attribute_selector, + int read_flag, + const float velocity_scale, + const char **err_str) override; + private: void readFaceSetsSample(Main *bmain, Mesh *mesh, @@ -58,6 +61,13 @@ class AbcMeshReader final : public AbcObjectReader { MPoly *mpoly, int totpoly, std::map<std::string, int> &r_mat_map); + + struct Mesh *read_mesh(struct Mesh *existing_mesh, + const Alembic::Abc::ISampleSelector &sample_sel, + const AttributeSelector *attribute_selector, + const int read_flag, + const float velocity_scale, + const char **err_str); }; class AbcSubDReader final : public AbcObjectReader { @@ -72,19 +82,33 @@ class AbcSubDReader final : public AbcObjectReader { bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; + void readObjectData(Main *bmain, + const AbcReaderManager &manager, + const Alembic::Abc::ISampleSelector &sample_sel) override; + + void read_geometry(GeometrySet &geometry_set, + const Alembic::Abc::ISampleSelector &sample_sel, + const AttributeSelector *attribute_selector, + int read_flag, + const float velocity_scale, + const char **err_str) override; + + private: struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, - const char *velocity_name, - float velocity_scale, - const char **err_str) override; + const AttributeSelector *attribute_selector, + const int read_flag, + const float velocity_scale, + const char **err_str); }; void read_mverts(Mesh &mesh, const Alembic::AbcGeom::P3fArraySamplePtr positions, const Alembic::AbcGeom::N3fArraySamplePtr normals); -CDStreamConfig get_config(struct Mesh *mesh, bool use_vertex_interpolation); +CDStreamConfig get_config(struct Mesh *mesh, + const AttributeSelector *attr_selector, + const std::string &iobject_full_name, + bool use_vertex_interpolation); } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.cc b/source/blender/io/alembic/intern/abc_reader_nurbs.cc index 4492d1e1ca0..fd3c1a708ec 100644 --- a/source/blender/io/alembic/intern/abc_reader_nurbs.cc +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.cc @@ -108,7 +108,9 @@ static bool set_knots(const FloatArraySamplePtr &knots, float *&nu_knots) return true; } -void AbcNurbsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +void AbcNurbsReader::readObjectData(Main *bmain, + const AbcReaderManager & /*manager*/, + const Alembic::Abc::ISampleSelector &sample_sel) { Curve *cu = static_cast<Curve *>(BKE_curve_add(bmain, m_data_name.c_str(), OB_SURF)); cu->actvert = CU_ACT_NONE; diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.h b/source/blender/io/alembic/intern/abc_reader_nurbs.h index 66e68cf6942..e775cc8af8e 100644 --- a/source/blender/io/alembic/intern/abc_reader_nurbs.h +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.h @@ -35,7 +35,9 @@ class AbcNurbsReader final : public AbcObjectReader { const Object *const ob, const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; + void readObjectData(Main *bmain, + const AbcReaderManager &manager, + const Alembic::Abc::ISampleSelector &sample_sel) override; private: void getNurbsPatches(const Alembic::Abc::IObject &obj); diff --git a/source/blender/io/alembic/intern/abc_reader_object.cc b/source/blender/io/alembic/intern/abc_reader_object.cc index 86fa580bf1f..68979f3a076 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.cc +++ b/source/blender/io/alembic/intern/abc_reader_object.cc @@ -144,16 +144,6 @@ Imath::M44d get_matrix(const IXformSchema &schema, const float time) return s0.getMatrix(); } -struct Mesh *AbcObjectReader::read_mesh(struct Mesh *existing_mesh, - const Alembic::Abc::ISampleSelector &UNUSED(sample_sel), - int UNUSED(read_flag), - const char *UNUSED(velocity_name), - const float UNUSED(velocity_scale), - const char **UNUSED(err_str)) -{ - return existing_mesh; -} - bool AbcObjectReader::topology_changed(Mesh * /*existing_mesh*/, const Alembic::Abc::ISampleSelector & /*sample_sel*/) { @@ -162,6 +152,16 @@ bool AbcObjectReader::topology_changed(Mesh * /*existing_mesh*/, return false; } +void AbcObjectReader::read_geometry(GeometrySet &UNUSED(geometry_set), + const Alembic::Abc::ISampleSelector &UNUSED(sample_sel), + const AttributeSelector *UNUSED(attribute_selector), + int UNUSED(read_flag), + const float UNUSED(velocity_scale), + const char **UNUSED(err_str)) +{ + return; +} + void AbcObjectReader::setupObjectTransform(const float time) { bool is_constant = false; @@ -180,14 +180,30 @@ void AbcObjectReader::setupObjectTransform(const float time) BKE_object_to_mat4(m_object, m_object->obmat); if (!is_constant || m_settings->always_add_cache_reader) { - bConstraint *con = BKE_constraint_add_for_object( - m_object, nullptr, CONSTRAINT_TYPE_TRANSFORM_CACHE); - bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data); - BLI_strncpy(data->object_path, m_iobject.getFullName().c_str(), FILE_MAX); + bTransformCacheConstraint *constraint = getOrCreateConstraint(); + BLI_strncpy(constraint->object_path, m_iobject.getFullName().c_str(), FILE_MAX); + } +} - data->cache_file = m_settings->cache_file; - id_us_plus(&data->cache_file->id); +bTransformCacheConstraint *AbcObjectReader::getOrCreateConstraint() +{ + if (m_iobject.isInstanceRoot()) { + /* As this is an instance we may already have created a constraint when duplicating the source + * object, if so return it. Note that it is possible for an instance to have an animated + * transform, but not for the source object. */ + bConstraint *constraint = static_cast<bConstraint *>(m_object->constraints.last); + if (constraint && constraint->type == CONSTRAINT_TYPE_TRANSFORM_CACHE) { + return static_cast<bTransformCacheConstraint *>(constraint->data); + } } + + /* Create a new constraint. */ + bConstraint *constraint = BKE_constraint_add_for_object( + m_object, nullptr, CONSTRAINT_TYPE_TRANSFORM_CACHE); + bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(constraint->data); + data->cache_file = m_settings->cache_file; + id_us_plus(&data->cache_file->id); + return data; } Alembic::AbcGeom::IXform AbcObjectReader::xform() diff --git a/source/blender/io/alembic/intern/abc_reader_object.h b/source/blender/io/alembic/intern/abc_reader_object.h index 5b327a9ddd9..33c7a98b091 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.h +++ b/source/blender/io/alembic/intern/abc_reader_object.h @@ -24,7 +24,9 @@ #include "DNA_ID.h" +struct bTransformCacheConstraint; struct CacheFile; +struct GeometrySet; struct Main; struct Mesh; struct Object; @@ -33,6 +35,9 @@ using Alembic::AbcCoreAbstract::chrono_t; namespace blender::io::alembic { +class AbcReaderManager; +class AttributeSelector; + struct ImportSettings { bool do_convert_mat; float conversion_mat[4][4]; @@ -51,7 +56,6 @@ struct ImportSettings { int read_flag; /* From CacheFile and MeshSeqCacheModifierData */ - std::string velocity_name; float velocity_scale; bool validate_meshes; @@ -69,7 +73,6 @@ struct ImportSettings { sequence_len(1), sequence_offset(0), read_flag(0), - velocity_name(""), velocity_scale(1.0f), validate_meshes(false), always_add_cache_reader(false), @@ -145,17 +148,20 @@ class AbcObjectReader { const Object *const ob, const char **err_str) const = 0; - virtual void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) = 0; + virtual void readObjectData(Main *bmain, + const AbcReaderManager &manager, + const Alembic::Abc::ISampleSelector &sample_sel) = 0; - virtual struct Mesh *read_mesh(struct Mesh *mesh, - const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, - const char *velocity_name, - float velocity_scale, - const char **err_str); virtual bool topology_changed(Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel); + virtual void read_geometry(GeometrySet &geometry_set, + const Alembic::Abc::ISampleSelector &sample_sel, + const AttributeSelector *attribute_selector, + int read_flag, + const float velocity_scale, + const char **err_str); + /** Reads the object matrix and sets up an object transform if animated. */ void setupObjectTransform(float time); @@ -173,6 +179,9 @@ class AbcObjectReader { protected: /** Determine whether we can inherit our parent's XForm. */ void determine_inherits_xform(); + + private: + bTransformCacheConstraint *getOrCreateConstraint(); }; Imath::M44d get_matrix(const Alembic::AbcGeom::IXformSchema &schema, float time); diff --git a/source/blender/io/alembic/intern/abc_reader_points.cc b/source/blender/io/alembic/intern/abc_reader_points.cc index 0135e150097..a1e5c0afa78 100644 --- a/source/blender/io/alembic/intern/abc_reader_points.cc +++ b/source/blender/io/alembic/intern/abc_reader_points.cc @@ -22,6 +22,7 @@ */ #include "abc_reader_points.h" +#include "abc_axis_conversion.h" #include "abc_reader_mesh.h" #include "abc_reader_transform.h" #include "abc_util.h" @@ -29,15 +30,21 @@ #include "DNA_mesh_types.h" #include "DNA_modifier_types.h" #include "DNA_object_types.h" +#include "DNA_pointcloud_types.h" #include "BKE_customdata.h" +#include "BKE_geometry_set.hh" +#include "BKE_lib_id.h" #include "BKE_mesh.h" #include "BKE_object.h" +#include "BKE_pointcloud.h" using Alembic::AbcGeom::kWrapExisting; using Alembic::AbcGeom::N3fArraySamplePtr; using Alembic::AbcGeom::P3fArraySamplePtr; +using namespace Alembic::AbcGeom; + using Alembic::AbcGeom::ICompoundProperty; using Alembic::AbcGeom::IN3fArrayProperty; using Alembic::AbcGeom::IPoints; @@ -71,7 +78,7 @@ bool AbcPointsReader::accepts_object_type( return false; } - if (ob->type != OB_MESH) { + if (ob->type != OB_POINTCLOUD) { *err_str = "Object type mismatch, Alembic object path points to Points."; return false; } @@ -79,58 +86,89 @@ bool AbcPointsReader::accepts_object_type( return true; } -void AbcPointsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) +void AbcPointsReader::readObjectData(Main *bmain, + const AbcReaderManager & /*manager*/, + const Alembic::Abc::ISampleSelector &sample_sel) { - Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str()); - Mesh *read_mesh = this->read_mesh(mesh, sample_sel, 0, "", 0.0f, nullptr); + PointCloud *point_cloud = static_cast<PointCloud *>( + BKE_pointcloud_add_default(bmain, m_data_name.c_str())); - if (read_mesh != mesh) { - BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); - } + GeometrySet geometry_set = GeometrySet::create_with_pointcloud(point_cloud, + GeometryOwnershipType::Editable); + read_geometry(geometry_set, sample_sel, nullptr, 0, 0.0f, nullptr); - if (m_settings->validate_meshes) { - BKE_mesh_validate(mesh, false, false); + PointCloud *read_point_cloud = + geometry_set.get_component_for_write<PointCloudComponent>().release(); + + if (read_point_cloud != point_cloud) { + BKE_pointcloud_nomain_to_pointcloud(read_point_cloud, point_cloud, true); } - m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); - m_object->data = mesh; + m_object = BKE_object_add_only_object(bmain, OB_POINTCLOUD, m_object_name.c_str()); + m_object->data = point_cloud; if (m_settings->always_add_cache_reader || has_animations(m_schema, m_settings)) { addCacheModifier(); } } -void read_points_sample(const IPointsSchema &schema, - const ISampleSelector &selector, - CDStreamConfig &config) +static void read_points_interp(const P3fArraySamplePtr positions, + const P3fArraySamplePtr ceil_positions, + const float weight, + float3 *r_points) +{ + float3 tmp; + for (size_t i = 0; i < positions->size(); i++) { + const Imath::V3f &floor_pos = (*positions)[i]; + const Imath::V3f &ceil_pos = (*ceil_positions)[i]; + interp_v3_v3v3(tmp, floor_pos.getValue(), ceil_pos.getValue(), weight); + copy_zup_from_yup(r_points[i], (*positions)[i].getValue()); + } +} + +static void read_points(const P3fArraySamplePtr positions, float3 *r_points) +{ + for (size_t i = 0; i < positions->size(); i++) { + copy_zup_from_yup(r_points[i], (*positions)[i].getValue()); + } +} + +static void read_points_sample(const IPointsSchema &schema, + const ISampleSelector &selector, + CDStreamConfig &config, + float3 *r_points) { Alembic::AbcGeom::IPointsSchema::Sample sample = schema.getValue(selector); const P3fArraySamplePtr &positions = sample.getPositions(); ICompoundProperty prop = schema.getArbGeomParams(); - N3fArraySamplePtr vnormals; - if (has_property(prop, "N")) { - const Alembic::Util::uint32_t itime = static_cast<Alembic::Util::uint32_t>( - selector.getRequestedTime()); - const IN3fArrayProperty &normals_prop = IN3fArrayProperty(prop, "N", itime); + Alembic::AbcGeom::index_t i0, i1; + const float weight = get_weight_and_index( + config.time, schema.getTimeSampling(), schema.getNumSamples(), i0, i1); - if (normals_prop) { - vnormals = normals_prop.getValue(selector); - } + if (config.use_vertex_interpolation && weight != 0.0f) { + Alembic::AbcGeom::IPointsSchema::Sample ceil_sample; + schema.get(ceil_sample, Alembic::Abc::ISampleSelector(i1)); + P3fArraySamplePtr ceil_positions = ceil_sample.getPositions(); + + read_points_interp(positions, ceil_positions, weight, r_points); + return; } - read_mverts(*config.mesh, positions, vnormals); + read_points(positions, r_points); } -struct Mesh *AbcPointsReader::read_mesh(struct Mesh *existing_mesh, - const ISampleSelector &sample_sel, - int read_flag, - const char * /*velocity_name*/, - const float /*velocity_scale*/, - const char **err_str) +void AbcPointsReader::read_geometry(GeometrySet &geometry_set, + const Alembic::Abc::ISampleSelector &sample_sel, + const AttributeSelector *attribute_selector, + int read_flag, + const float velocity_scale, + const char **err_str) { + assert(geometry_set.has_pointcloud()); + IPointsSchema::Sample sample; try { sample = m_schema.getValue(sample_sel); @@ -142,23 +180,66 @@ struct Mesh *AbcPointsReader::read_mesh(struct Mesh *existing_mesh, m_schema.getName().c_str(), sample_sel.getRequestedTime(), ex.what()); - return existing_mesh; + return; } + PointCloud *existing_point_cloud = geometry_set.get_pointcloud_for_write(); + PointCloud *point_cloud = existing_point_cloud; + const P3fArraySamplePtr &positions = sample.getPositions(); - Mesh *new_mesh = nullptr; + const IFloatGeomParam widths_param = m_schema.getWidthsParam(); + FloatArraySamplePtr radiuses; + + if (widths_param.valid()) { + IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(sample_sel); + radiuses = wsample.getVals(); + } - if (existing_mesh->totvert != positions->size()) { - new_mesh = BKE_mesh_new_nomain(positions->size(), 0, 0, 0, 0); + if (point_cloud->totpoint != positions->size()) { + point_cloud = BKE_pointcloud_new_nomain(positions->size()); } - Mesh *mesh_to_export = new_mesh ? new_mesh : existing_mesh; - const bool use_vertex_interpolation = read_flag & MOD_MESHSEQ_INTERPOLATE_VERTICES; - CDStreamConfig config = get_config(mesh_to_export, use_vertex_interpolation); - read_points_sample(m_schema, sample_sel, config); + ScopeCustomDataPointers custom_data_pointers = {&point_cloud->pdata, nullptr, nullptr}; + ScopeSizeInfo scope_sizes = {point_cloud->totpoint, 0, 0}; + CDStreamConfig config; + config.custom_data_pointers = custom_data_pointers; + config.scope_sizes = scope_sizes; + config.id = &point_cloud->id; + config.attr_selector = attribute_selector; + config.time = sample_sel.getRequestedTime(); + config.use_vertex_interpolation = (read_flag & MOD_MESHSEQ_INTERPOLATE_VERTICES) != 0; + + read_points_sample(m_schema, sample_sel, config, reinterpret_cast<float3 *>(point_cloud->co)); + + if (radiuses) { + for (size_t i = 0; i < radiuses->size(); i++) { + point_cloud->radius[i] = (*radiuses)[i]; + } + } + else { + for (int i = 0; i < point_cloud->totpoint; i++) { + point_cloud->radius[i] = 0.01f; + } + } - return mesh_to_export; + UInt64ArraySamplePtr ids = sample.getIds(); + if (ids && ids->size() == positions->size()) { + CustomDataLayer *ids_layer = BKE_id_attribute_ensure( + &point_cloud->id, "ids", CD_PROP_INT32, ATTR_DOMAIN_POINT, nullptr); + int *ids_layer_data = static_cast<int *>(ids_layer->data); + + for (size_t i = 0; i < ids->size(); i++) { + ids_layer_data[i] = static_cast<int>((*ids)[i]); + } + } + + /* Attributes */ + read_arbitrary_attributes(config, m_schema, {}, sample_sel, velocity_scale); + + if (point_cloud != existing_point_cloud) { + geometry_set.replace_pointcloud(point_cloud); + } } } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_points.h b/source/blender/io/alembic/intern/abc_reader_points.h index 2f91e07d26a..33742235720 100644 --- a/source/blender/io/alembic/intern/abc_reader_points.h +++ b/source/blender/io/alembic/intern/abc_reader_points.h @@ -39,18 +39,16 @@ class AbcPointsReader final : public AbcObjectReader { const Object *const ob, const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; - - struct Mesh *read_mesh(struct Mesh *existing_mesh, - const Alembic::Abc::ISampleSelector &sample_sel, - int read_flag, - const char *velocity_name, - float velocity_scale, - const char **err_str) override; + void readObjectData(Main *bmain, + const AbcReaderManager &manager, + const Alembic::Abc::ISampleSelector &sample_sel) override; + + void read_geometry(GeometrySet &geometry_set, + const Alembic::Abc::ISampleSelector &sample_sel, + const AttributeSelector *attribute_selector, + int read_flag, + const float velocity_scale, + const char **err_str) override; }; -void read_points_sample(const Alembic::AbcGeom::IPointsSchema &schema, - const Alembic::AbcGeom::ISampleSelector &selector, - CDStreamConfig &config); - } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_transform.cc b/source/blender/io/alembic/intern/abc_reader_transform.cc index 625cd255dba..25cd7c6e5f8 100644 --- a/source/blender/io/alembic/intern/abc_reader_transform.cc +++ b/source/blender/io/alembic/intern/abc_reader_transform.cc @@ -69,7 +69,9 @@ bool AbcEmptyReader::accepts_object_type( return true; } -void AbcEmptyReader::readObjectData(Main *bmain, const ISampleSelector &UNUSED(sample_sel)) +void AbcEmptyReader::readObjectData(Main *bmain, + const AbcReaderManager & /*manager*/, + const ISampleSelector &UNUSED(sample_sel)) { m_object = BKE_object_add_only_object(bmain, OB_EMPTY, m_object_name.c_str()); m_object->data = nullptr; diff --git a/source/blender/io/alembic/intern/abc_reader_transform.h b/source/blender/io/alembic/intern/abc_reader_transform.h index 6810cc214b7..e4d67320881 100644 --- a/source/blender/io/alembic/intern/abc_reader_transform.h +++ b/source/blender/io/alembic/intern/abc_reader_transform.h @@ -36,7 +36,9 @@ class AbcEmptyReader final : public AbcObjectReader { const Object *const ob, const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; + void readObjectData(Main *bmain, + const AbcReaderManager &manager, + const Alembic::Abc::ISampleSelector &sample_sel) override; }; } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc index 58fc4dd599d..03e9ce6547e 100644 --- a/source/blender/io/alembic/intern/alembic_capi.cc +++ b/source/blender/io/alembic/intern/alembic_capi.cc @@ -27,6 +27,7 @@ #include "abc_reader_archive.h" #include "abc_reader_camera.h" #include "abc_reader_curves.h" +#include "abc_reader_manager.h" #include "abc_reader_mesh.h" #include "abc_reader_nurbs.h" #include "abc_reader_points.h" @@ -225,7 +226,7 @@ static void find_iobject(const IObject &object, IObject &ret, const std::string * Generates an AbcObjectReader for this Alembic object and its children. * * \param object: The Alembic IObject to visit. - * \param readers: The created AbcObjectReader * will be appended to this vector. + * \param manager: The manager responsible for creating the readers. * \param settings: Import settings, not used directly but passed to the * AbcObjectReader subclass constructors. * \param r_assign_as_parent: Return parameter, contains a list of reader @@ -243,7 +244,7 @@ static void find_iobject(const IObject &object, IObject &ret, const std::string * them in sync. */ static std::pair<bool, AbcObjectReader *> visit_object( const IObject &object, - AbcObjectReader::ptr_vector &readers, + AbcReaderManager &manager, ImportSettings &settings, AbcObjectReader::ptr_vector &r_assign_as_parent) { @@ -268,7 +269,7 @@ static std::pair<bool, AbcObjectReader *> visit_object( /* TODO: When we only support C++11, use std::tie() instead. */ std::pair<bool, AbcObjectReader *> child_result; - child_result = visit_object(ichild, readers, settings, assign_as_parent); + child_result = visit_object(ichild, manager, settings, assign_as_parent); bool child_claims_this_object = child_result.first; AbcObjectReader *child_reader = child_result.second; @@ -313,15 +314,15 @@ static std::pair<bool, AbcObjectReader *> visit_object( } if (create_empty) { - reader = new AbcEmptyReader(object, settings); + reader = manager.create<AbcEmptyReader>(object, settings); } } else if (IPolyMesh::matches(md)) { - reader = new AbcMeshReader(object, settings); + reader = manager.create<AbcMeshReader>(object, settings); parent_is_part_of_this_object = true; } else if (ISubD::matches(md)) { - reader = new AbcSubDReader(object, settings); + reader = manager.create<AbcSubDReader>(object, settings); parent_is_part_of_this_object = true; } else if (INuPatch::matches(md)) { @@ -332,16 +333,16 @@ static std::pair<bool, AbcObjectReader *> visit_object( * Blender. Need to figure out exactly how these points are * duplicated, in all cases (cyclic U, cyclic V, and cyclic UV). * Until this is fixed, disabling NURBS reading. */ - reader = new AbcNurbsReader(object, settings); + reader = manager.create<AbcNurbsReader>(object, settings); parent_is_part_of_this_object = true; #endif } else if (ICamera::matches(md)) { - reader = new AbcCameraReader(object, settings); + reader = manager.create<AbcCameraReader>(object, settings); parent_is_part_of_this_object = true; } else if (IPoints::matches(md)) { - reader = new AbcPointsReader(object, settings); + reader = manager.create<AbcPointsReader>(object, settings); parent_is_part_of_this_object = true; } else if (IMaterial::matches(md)) { @@ -354,7 +355,7 @@ static std::pair<bool, AbcObjectReader *> visit_object( /* Pass, those are handled in the mesh reader. */ } else if (ICurves::matches(md)) { - reader = new AbcCurveReader(object, settings); + reader = manager.create<AbcCurveReader>(object, settings); parent_is_part_of_this_object = true; } else { @@ -367,7 +368,6 @@ static std::pair<bool, AbcObjectReader *> visit_object( * not claimed as part of any child Alembic object. */ BLI_assert(claiming_child_readers.empty()); - readers.push_back(reader); reader->incref(); CacheObjectPath *abc_path = static_cast<CacheObjectPath *>( @@ -437,7 +437,7 @@ struct ImportJobData { ImportSettings settings; ArchiveReader *archive; - std::vector<AbcObjectReader *> readers; + AbcReaderManager reader_manager; short *stop; short *do_update; @@ -489,7 +489,8 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa /* Parse Alembic Archive. */ AbcObjectReader::ptr_vector assign_as_parent; - visit_object(archive->getTop(), data->readers, data->settings, assign_as_parent); + IObject root = archive->getTop(); + visit_object(archive->getTop(), data->reader_manager, data->settings, assign_as_parent); /* There shouldn't be any orphans. */ BLI_assert(assign_as_parent.empty()); @@ -504,19 +505,16 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa /* Create objects and set scene frame range. */ - const float size = static_cast<float>(data->readers.size()); + const float size = static_cast<float>(data->reader_manager.all_readers().size()); size_t i = 0; chrono_t min_time = std::numeric_limits<chrono_t>::max(); chrono_t max_time = std::numeric_limits<chrono_t>::min(); ISampleSelector sample_sel(0.0f); - std::vector<AbcObjectReader *>::iterator iter; - for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { - AbcObjectReader *reader = *iter; - + for (AbcObjectReader *reader : data->reader_manager.data_readers()) { if (reader->valid()) { - reader->readObjectData(data->bmain, sample_sel); + reader->readObjectData(data->bmain, data->reader_manager, sample_sel); min_time = std::min(min_time, reader->minTime()); max_time = std::max(max_time, reader->maxTime()); @@ -551,8 +549,7 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa } /* Setup parenthood. */ - for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { - const AbcObjectReader *reader = *iter; + for (AbcObjectReader *reader : data->reader_manager.data_readers()) { const AbcObjectReader *parent_reader = reader->parent_reader; Object *ob = reader->object(); @@ -566,8 +563,7 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa /* Setup transformations and constraints. */ i = 0; - for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { - AbcObjectReader *reader = *iter; + for (AbcObjectReader *reader : data->reader_manager.data_readers()) { reader->setupObjectTransform(0.0f); *data->progress = 0.7f + 0.3f * (++i / size); @@ -586,12 +582,10 @@ static void import_endjob(void *user_data) ImportJobData *data = static_cast<ImportJobData *>(user_data); - std::vector<AbcObjectReader *>::iterator iter; - /* Delete objects on cancellation. */ if (data->was_cancelled) { - for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { - Object *ob = (*iter)->object(); + for (AbcObjectReader *reader : data->reader_manager.all_readers()) { + Object *ob = reader->object(); /* It's possible that cancellation occurred between the creation of * the reader and the creation of the Blender object. */ @@ -612,8 +606,8 @@ static void import_endjob(void *user_data) lc = BKE_layer_collection_get_active(view_layer); - for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { - Object *ob = (*iter)->object(); + for (AbcObjectReader *reader : data->reader_manager.data_readers()) { + Object *ob = reader->object(); BKE_collection_object_add(data->bmain, lc->collection, ob); @@ -628,6 +622,24 @@ static void import_endjob(void *user_data) ID_RECALC_BASE_FLAGS); } + /* Finally, create instances. */ + for (AbcObjectReader *reader : data->reader_manager.instance_readers()) { + if (!reader->valid()) { + continue; + } + + reader->readObjectData(data->bmain, data->reader_manager, {}); + /* We assume that the instance's parent is the same as that of the source object, so we do + * not setup parenthood. */ + reader->setupObjectTransform(0.0f); + + Object *ob = reader->object(); + BKE_collection_object_add(data->bmain, lc->collection, ob); + DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + + BKE_main_id_newptr_and_tag_clear(data->bmain); + } + DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS); DEG_relations_tag_update(data->bmain); @@ -638,8 +650,7 @@ static void import_endjob(void *user_data) } } - for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) { - AbcObjectReader *reader = *iter; + for (AbcObjectReader *reader : data->reader_manager.all_readers()) { reader->decref(); if (reader->refcount() == 0) { @@ -795,23 +806,28 @@ static ISampleSelector sample_selector_for_time(float time) return ISampleSelector(time, ISampleSelector::kFloorIndex); } -Mesh *ABC_read_mesh(CacheReader *reader, - Object *ob, - Mesh *existing_mesh, - const float time, - const char **err_str, - const int read_flag, - const char *velocity_name, - const float velocity_scale) +void ABC_read_geometry(CacheReader *reader, + Object *ob, + GeometrySet &geometry_set, + const ABCReadParams *params, + const char **err_str) { AbcObjectReader *abc_reader = get_abc_reader(reader, ob, err_str); if (abc_reader == nullptr) { - return nullptr; + return; } - ISampleSelector sample_sel = sample_selector_for_time(time); - return abc_reader->read_mesh( - existing_mesh, sample_sel, read_flag, velocity_name, velocity_scale, err_str); + AttributeSelector attribute_selector(params->mappings); + attribute_selector.set_velocity_attribute(params->velocity_name); + attribute_selector.set_read_flags(params->read_flags); + + ISampleSelector sample_sel = sample_selector_for_time(params->time); + abc_reader->read_geometry(geometry_set, + sample_sel, + &attribute_selector, + params->read_flags, + params->velocity_scale, + err_str); } bool ABC_mesh_topology_changed( diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt index 980f33fffa1..773518cb817 100644 --- a/source/blender/io/usd/CMakeLists.txt +++ b/source/blender/io/usd/CMakeLists.txt @@ -42,6 +42,7 @@ set(INC ../../bmesh ../../depsgraph ../../editors/include + ../../functions ../../makesdna ../../makesrna ../../windowmanager diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc index 789ff20ba82..0d8fb1ee557 100644 --- a/source/blender/io/usd/intern/usd_capi_import.cc +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -436,20 +436,20 @@ static USDPrimReader *get_usd_reader(CacheReader *reader, Object * /* ob */, con return usd_reader; } -struct Mesh *USD_read_mesh(struct CacheReader *reader, - struct Object *ob, - struct Mesh *existing_mesh, - const float time, - const char **err_str, - const int read_flag) +void USD_read_geometry(CacheReader *reader, + Object *ob, + GeometrySet &geometry_set, + const float time, + const char **err_str, + const int read_flag) { USDGeomReader *usd_reader = dynamic_cast<USDGeomReader *>(get_usd_reader(reader, ob, err_str)); if (usd_reader == nullptr) { - return nullptr; + return; } - return usd_reader->read_mesh(existing_mesh, time, read_flag, err_str); + return usd_reader->read_geometry(geometry_set, time, read_flag, err_str); } bool USD_mesh_topology_changed( diff --git a/source/blender/io/usd/intern/usd_reader_curve.cc b/source/blender/io/usd/intern/usd_reader_curve.cc index 5007d204020..f8a8d30948a 100644 --- a/source/blender/io/usd/intern/usd_reader_curve.cc +++ b/source/blender/io/usd/intern/usd_reader_curve.cc @@ -23,8 +23,10 @@ #include "usd_reader_curve.h" #include "BKE_curve.h" +#include "BKE_geometry_set.hh" #include "BKE_mesh.h" #include "BKE_object.h" +#include "BKE_spline.hh" #include "BLI_listbase.h" @@ -179,13 +181,36 @@ void USDCurvesReader::read_curve_sample(Curve *cu, const double motionSampleTime } } -Mesh *USDCurvesReader::read_mesh(struct Mesh *existing_mesh, - const double motionSampleTime, - const int /* read_flag */, - const char ** /* err_str */) +static bool topology_changed(CurveEval *curve_eval, const pxr::VtIntArray &usdCounts) +{ + if (!curve_eval) { + return true; + } + + if (curve_eval->splines().size() != usdCounts.size()) { + return true; + } + + int curve_idx = 0; + for (const SplinePtr &spline : curve_eval->splines()) { + const int num_in_usd = usdCounts[curve_idx++]; + const int num_in_blender = spline->positions().size(); + + if (num_in_usd != num_in_blender) { + return true; + } + } + + return false; +} + +void USDCurvesReader::read_geometry(GeometrySet &geometry_set, + double motionSampleTime, + int /* read_flag */, + const char ** /* err_str */) { if (!curve_prim_) { - return existing_mesh; + return; } pxr::UsdAttribute widthsAttr = curve_prim_.GetWidthsAttr(); @@ -195,62 +220,36 @@ Mesh *USDCurvesReader::read_mesh(struct Mesh *existing_mesh, pxr::VtIntArray usdCounts; vertexAttr.Get(&usdCounts, motionSampleTime); - int num_subcurves = usdCounts.size(); pxr::VtVec3fArray usdPoints; pointsAttr.Get(&usdPoints, motionSampleTime); int vertex_idx = 0; int curve_idx; - Curve *curve = static_cast<Curve *>(object_->data); - - const int curve_count = BLI_listbase_count(&curve->nurb); - bool same_topology = curve_count == num_subcurves; + CurveEval *curve_eval = geometry_set.get_curve_for_write(); - if (same_topology) { - Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); - for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { - const int num_in_usd = usdCounts[curve_idx]; - const int num_in_blender = nurbs->pntsu; - - if (num_in_usd != num_in_blender) { - same_topology = false; - break; - } - } - } - - if (!same_topology) { + if (blender::io::usd::topology_changed(curve_eval, usdCounts)) { + Curve *curve = static_cast<Curve *>(object_->data); BKE_nurbList_free(&curve->nurb); read_curve_sample(curve, motionSampleTime); + + std::unique_ptr<CurveEval> new_curve_eval = curve_eval_from_dna_curve(*curve); + geometry_set.replace_curve(new_curve_eval.release(), GeometryOwnershipType::Editable); } else { - Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); - for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { - const int totpoint = usdCounts[curve_idx]; - - if (nurbs->bp) { - BPoint *point = nurbs->bp; - - for (int i = 0; i < totpoint; i++, point++, vertex_idx++) { - point->vec[0] = usdPoints[vertex_idx][0]; - point->vec[1] = usdPoints[vertex_idx][1]; - point->vec[2] = usdPoints[vertex_idx][2]; - } - } - else if (nurbs->bezt) { - BezTriple *bezier = nurbs->bezt; - - for (int i = 0; i < totpoint; i++, bezier++, vertex_idx++) { - bezier->vec[1][0] = usdPoints[vertex_idx][0]; - bezier->vec[1][1] = usdPoints[vertex_idx][1]; - bezier->vec[1][2] = usdPoints[vertex_idx][2]; - } + BLI_assert_msg(curve_eval, "curve_eval is null although the topology is the same !"); + const int curve_count = curve_eval->splines().size(); + for (curve_idx = 0; curve_idx < curve_count; curve_idx++) { + Spline *spline = curve_eval->splines()[curve_idx].get(); + + for (float3 &position : spline->positions()) { + position.x = usdPoints[vertex_idx][0]; + position.y = usdPoints[vertex_idx][0]; + position.z = usdPoints[vertex_idx][0]; + vertex_idx++; } } } - - return BKE_mesh_new_nomain_from_curve(object_); } } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_curve.h b/source/blender/io/usd/intern/usd_reader_curve.h index 1e676bbbd02..ab27f2efaae 100644 --- a/source/blender/io/usd/intern/usd_reader_curve.h +++ b/source/blender/io/usd/intern/usd_reader_curve.h @@ -53,10 +53,10 @@ class USDCurvesReader : public USDGeomReader { void read_curve_sample(Curve *cu, double motionSampleTime); - Mesh *read_mesh(struct Mesh *existing_mesh, - double motionSampleTime, - int read_flag, - const char **err_str) override; + void read_geometry(GeometrySet &geometry_set, + double motionSampleTime, + int read_flag, + const char **err_str) override; }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_geom.h b/source/blender/io/usd/intern/usd_reader_geom.h index 99e22248f2b..cde0f3842cf 100644 --- a/source/blender/io/usd/intern/usd_reader_geom.h +++ b/source/blender/io/usd/intern/usd_reader_geom.h @@ -21,6 +21,7 @@ #include "usd.h" #include "usd_reader_xform.h" +struct GeometrySet; struct Mesh; namespace blender::io::usd { @@ -35,10 +36,10 @@ class USDGeomReader : public USDXformReader { { } - virtual Mesh *read_mesh(struct Mesh *existing_mesh, - double motionSampleTime, - int read_flag, - const char **err_str) = 0; + virtual void read_geometry(GeometrySet &geometry_set, + double motionSampleTime, + int read_flag, + const char **err_str) = 0; virtual bool topology_changed(Mesh * /* existing_mesh */, double /* motionSampleTime */) { diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 95b73c85da6..4e5b67a54a9 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -23,6 +23,7 @@ #include "usd_reader_material.h" #include "BKE_customdata.h" +#include "BKE_geometry_set.hh" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_mesh.h" @@ -882,4 +883,17 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh, return active_mesh; } +void USDMeshReader::read_geometry(GeometrySet &geometry_set, + double motionSampleTime, + int read_flag, + const char **err_str) +{ + Mesh *existing_mesh = geometry_set.get_mesh_for_write(); + Mesh *new_mesh = read_mesh(existing_mesh, motionSampleTime, read_flag, err_str); + + if (new_mesh != existing_mesh) { + geometry_set.replace_mesh(new_mesh); + } +} + } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_mesh.h b/source/blender/io/usd/intern/usd_reader_mesh.h index c5869fcbd32..cd3f2cc97a4 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.h +++ b/source/blender/io/usd/intern/usd_reader_mesh.h @@ -65,10 +65,10 @@ class USDMeshReader : public USDGeomReader { void create_object(Main *bmain, double motionSampleTime) override; void read_object_data(Main *bmain, double motionSampleTime) override; - struct Mesh *read_mesh(struct Mesh *existing_mesh, - double motionSampleTime, - int read_flag, - const char **err_str) override; + void read_geometry(GeometrySet &geometry_set, + double motionSampleTime, + int read_flag, + const char **err_str) override; bool topology_changed(Mesh *existing_mesh, double motionSampleTime) override; @@ -92,6 +92,11 @@ class USDMeshReader : public USDGeomReader { Mesh *mesh, double motionSampleTime, bool new_mesh); + + struct Mesh *read_mesh(struct Mesh *existing_mesh, + double motionSampleTime, + int read_flag, + const char **err_str); }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_nurbs.cc b/source/blender/io/usd/intern/usd_reader_nurbs.cc index 8370804b776..d724536856e 100644 --- a/source/blender/io/usd/intern/usd_reader_nurbs.cc +++ b/source/blender/io/usd/intern/usd_reader_nurbs.cc @@ -22,8 +22,10 @@ #include "usd_reader_nurbs.h" #include "BKE_curve.h" +#include "BKE_geometry_set.hh" #include "BKE_mesh.h" #include "BKE_object.h" +#include "BKE_spline.hh" #include "BLI_listbase.h" @@ -181,10 +183,33 @@ void USDNurbsReader::read_curve_sample(Curve *cu, const double motionSampleTime) } } -Mesh *USDNurbsReader::read_mesh(struct Mesh * /* existing_mesh */, - const double motionSampleTime, - const int /* read_flag */, - const char ** /* err_str */) +static bool topology_changed(CurveEval *curve_eval, const pxr::VtIntArray &usdCounts) +{ + if (!curve_eval) { + return true; + } + + if (curve_eval->splines().size() != usdCounts.size()) { + return true; + } + + int curve_idx = 0; + for (const SplinePtr &spline : curve_eval->splines()) { + const int num_in_usd = usdCounts[curve_idx++]; + const int num_in_blender = spline->positions().size(); + + if (num_in_usd != num_in_blender) { + return true; + } + } + + return false; +} + +void USDNurbsReader::read_geometry(GeometrySet &geometry_set, + double motionSampleTime, + int /* read_flag */, + const char ** /* err_str */) { pxr::UsdGeomCurves curve_prim_(prim_); @@ -195,62 +220,36 @@ Mesh *USDNurbsReader::read_mesh(struct Mesh * /* existing_mesh */, pxr::VtIntArray usdCounts; vertexAttr.Get(&usdCounts, motionSampleTime); - int num_subcurves = usdCounts.size(); pxr::VtVec3fArray usdPoints; pointsAttr.Get(&usdPoints, motionSampleTime); int vertex_idx = 0; int curve_idx; - Curve *curve = static_cast<Curve *>(object_->data); - - const int curve_count = BLI_listbase_count(&curve->nurb); - bool same_topology = curve_count == num_subcurves; - - if (same_topology) { - Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); - for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { - const int num_in_usd = usdCounts[curve_idx]; - const int num_in_blender = nurbs->pntsu; - - if (num_in_usd != num_in_blender) { - same_topology = false; - break; - } - } - } + CurveEval *curve_eval = geometry_set.get_curve_for_write(); - if (!same_topology) { + if (blender::io::usd::topology_changed(curve_eval, usdCounts)) { + Curve *curve = static_cast<Curve *>(object_->data); BKE_nurbList_free(&curve->nurb); read_curve_sample(curve, motionSampleTime); + + std::unique_ptr<CurveEval> new_curve_eval = curve_eval_from_dna_curve(*curve); + geometry_set.replace_curve(new_curve_eval.release(), GeometryOwnershipType::Editable); } else { - Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); - for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { - const int totpoint = usdCounts[curve_idx]; - - if (nurbs->bp) { - BPoint *point = nurbs->bp; - - for (int i = 0; i < totpoint; i++, point++, vertex_idx++) { - point->vec[0] = usdPoints[vertex_idx][0]; - point->vec[1] = usdPoints[vertex_idx][1]; - point->vec[2] = usdPoints[vertex_idx][2]; - } - } - else if (nurbs->bezt) { - BezTriple *bezier = nurbs->bezt; - - for (int i = 0; i < totpoint; i++, bezier++, vertex_idx++) { - bezier->vec[1][0] = usdPoints[vertex_idx][0]; - bezier->vec[1][1] = usdPoints[vertex_idx][1]; - bezier->vec[1][2] = usdPoints[vertex_idx][2]; - } + BLI_assert_msg(curve_eval, "curve_eval is null although the topology is the same !"); + const int curve_count = curve_eval->splines().size(); + for (curve_idx = 0; curve_idx < curve_count; curve_idx++) { + Spline *spline = curve_eval->splines()[curve_idx].get(); + + for (float3 &position : spline->positions()) { + position.x = usdPoints[vertex_idx][0]; + position.y = usdPoints[vertex_idx][0]; + position.z = usdPoints[vertex_idx][0]; + vertex_idx++; } } } - - return BKE_mesh_new_nomain_from_curve(object_); } } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_nurbs.h b/source/blender/io/usd/intern/usd_reader_nurbs.h index 33a4acf503e..ac118791c72 100644 --- a/source/blender/io/usd/intern/usd_reader_nurbs.h +++ b/source/blender/io/usd/intern/usd_reader_nurbs.h @@ -52,10 +52,10 @@ class USDNurbsReader : public USDGeomReader { void read_curve_sample(Curve *cu, double motionSampleTime); - Mesh *read_mesh(struct Mesh *existing_mesh, - double motionSampleTime, - int read_flag, - const char **err_str) override; + void read_geometry(GeometrySet &geometry_set, + double motionSampleTime, + int read_flag, + const char **err_str) override; }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h index 505f917ede5..c9267d78572 100644 --- a/source/blender/io/usd/usd.h +++ b/source/blender/io/usd/usd.h @@ -28,6 +28,8 @@ extern "C" { struct CacheArchiveHandle; struct CacheFile; struct CacheReader; +struct GeometrySet; +struct Mesh; struct Object; struct bContext; @@ -106,13 +108,14 @@ void USD_free_handle(struct CacheArchiveHandle *handle); void USD_get_transform(struct CacheReader *reader, float r_mat[4][4], float time, float scale); -/* Either modifies current_mesh in-place or constructs a new mesh. */ -struct Mesh *USD_read_mesh(struct CacheReader *reader, - struct Object *ob, - struct Mesh *existing_mesh, - float time, - const char **err_str, - int read_flag); +#ifdef __cplusplus +void USD_read_geometry(CacheReader *reader, + Object *ob, + GeometrySet &geometry_set, + const float time, + const char **err_str, + const int read_flag); +#endif bool USD_mesh_topology_changed(struct CacheReader *reader, struct Object *ob, diff --git a/source/blender/makesdna/DNA_cachefile_types.h b/source/blender/makesdna/DNA_cachefile_types.h index 65e1dd6c096..e5216bbc21e 100644 --- a/source/blender/makesdna/DNA_cachefile_types.h +++ b/source/blender/makesdna/DNA_cachefile_types.h @@ -71,6 +71,43 @@ typedef struct CacheFileLayer { int _pad; } CacheFileLayer; +/* CacheAttributeMapping::mapping */ +enum { + /* Default mapping, so we do not make an arbitrary decision as to what is the default. Also used + * to mark the mapping as ignored. */ + CACHEFILE_ATTRIBUTE_MAP_NONE, + CACHEFILE_ATTRIBUTE_MAP_TO_UVS, + CACHEFILE_ATTRIBUTE_MAP_TO_VERTEX_COLORS, + CACHEFILE_ATTRIBUTE_MAP_TO_WEIGHT_GROUPS, + CACHEFILE_ATTRIBUTE_MAP_TO_FLOAT2, + CACHEFILE_ATTRIBUTE_MAP_TO_FLOAT3, + CACHEFILE_ATTRIBUTE_MAP_TO_COLOR, +}; + +/* CacheAttributeMapping::domain */ +enum { + /* Try to automatically map the attribute to the right domain (default behavior without mapping.) + */ + CACHEFILE_ATTR_MAP_DOMAIN_AUTO, + CACHEFILE_ATTR_MAP_DOMAIN_POINT, + CACHEFILE_ATTR_MAP_DOMAIN_FACE_CORNER, + CACHEFILE_ATTR_MAP_DOMAIN_FACE, +}; + +/* Custom data mapping for the attributes in the CacheFile. Since there might not be a standard way + * of expressing what an attribute should be (e.g. is this float2 attribute a UV map?), and since + * some software might write multi-dimensionnal data as arrays of 1D element (although the size of + * the array will be N-time its expected size were it written as N-D data), we delegate to the user + * the task of telling us what is supposed to be what through these mappings. */ +typedef struct CacheAttributeMapping { + struct CacheAttributeMapping *next, *prev; + + char name[64]; + char mapping; + char domain; + char _pad[6]; +} CacheAttributeMapping; + /* CacheFile::velocity_unit * Determines what temporal unit is used to interpret velocity vectors for motion blur effects. */ enum { @@ -101,7 +138,8 @@ typedef struct CacheFile { /** The frame offset to subtract. */ float frame_offset; - char _pad[4]; + /** Index of the currently selected attribute mapping in the UI, starts at 1 */ + int active_attribute_mapping; /** Animation flag. */ short flag; @@ -132,6 +170,8 @@ typedef struct CacheFile { /* Name of the velocity property in the archive. */ char velocity_name[64]; + ListBase attribute_mappings; + /* Runtime */ struct CacheArchiveHandle *handle; char handle_filepath[1024]; diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 47afa0f9a13..410e3a97f2a 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -106,6 +106,7 @@ extern StructRNA RNA_ByteColorAttribute; extern StructRNA RNA_ByteColorAttributeValue; extern StructRNA RNA_ByteIntAttribute; extern StructRNA RNA_ByteIntAttributeValue; +extern StructRNA RNA_CacheAttributeMapping; extern StructRNA RNA_CacheFile; extern StructRNA RNA_CacheFileLayer; extern StructRNA RNA_Camera; diff --git a/source/blender/makesrna/intern/rna_cachefile.c b/source/blender/makesrna/intern/rna_cachefile.c index 2f8fc004d85..6d5b4dcd63f 100644 --- a/source/blender/makesrna/intern/rna_cachefile.c +++ b/source/blender/makesrna/intern/rna_cachefile.c @@ -63,6 +63,16 @@ static void rna_CacheFileLayer_update(Main *UNUSED(bmain), Scene *UNUSED(scene), WM_main_add_notifier(NC_OBJECT | ND_DRAW, NULL); } +static void rna_CacheFile_attribute_mapping_update(Main *UNUSED(bmain), + Scene *UNUSED(scene), + PointerRNA *ptr) +{ + CacheFile *cache_file = (CacheFile *)ptr->owner_id; + + DEG_id_tag_update(&cache_file->id, ID_RECALC_COPY_ON_WRITE); + WM_main_add_notifier(NC_OBJECT | ND_DRAW, NULL); +} + static void rna_CacheFile_dependency_update(Main *bmain, Scene *scene, PointerRNA *ptr) { rna_CacheFile_update(bmain, scene, ptr); @@ -160,6 +170,53 @@ static void rna_CacheFile_layer_remove(CacheFile *cache_file, bContext *C, Point WM_main_add_notifier(NC_OBJECT | ND_DRAW, NULL); } +static PointerRNA rna_CacheFile_active_attribute_mapping_get(PointerRNA *ptr) +{ + CacheFile *cache_file = (CacheFile *)ptr->owner_id; + return rna_pointer_inherit_refine( + ptr, &RNA_CacheAttributeMapping, BKE_cachefile_get_active_attribute_mapping(cache_file)); +} + +static void rna_CacheFile_active_attribute_mapping_set(PointerRNA *ptr, + PointerRNA value, + struct ReportList *reports) +{ + CacheFile *cache_file = (CacheFile *)ptr->owner_id; + int index = BLI_findindex(&cache_file->attribute_mappings, value.data); + if (index == -1) { + BKE_reportf(reports, + RPT_ERROR, + "Attribute mapping '%s' not found in object '%s'", + ((CacheAttributeMapping *)value.data)->name, + cache_file->id.name + 2); + cache_file->active_attribute_mapping = 0; + return; + } + + cache_file->active_attribute_mapping = index + 1; +} + +static int rna_CacheFile_active_attribute_mapping_index_get(PointerRNA *ptr) +{ + CacheFile *cache_file = (CacheFile *)ptr->owner_id; + return cache_file->active_attribute_mapping - 1; +} + +static void rna_CacheFile_active_attribute_mapping_index_set(PointerRNA *ptr, int value) +{ + CacheFile *cache_file = (CacheFile *)ptr->owner_id; + cache_file->active_attribute_mapping = value + 1; +} + +static void rna_CacheFile_active_attribute_mapping_index_range( + PointerRNA *ptr, int *min, int *max, int *UNUSED(softmin), int *UNUSED(softmax)) +{ + CacheFile *cache_file = (CacheFile *)ptr->owner_id; + + *min = 0; + *max = max_ii(0, BLI_listbase_count(&cache_file->attribute_mappings) - 1); +} + #else /* cachefile.object_paths */ @@ -179,6 +236,107 @@ static void rna_def_alembic_object_path(BlenderRNA *brna) RNA_define_lib_overridable(false); } +static void rna_def_cachefile_attribute_mapping(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "CacheAttributeMapping", NULL); + RNA_def_struct_sdna(srna, "CacheAttributeMapping"); + RNA_def_struct_ui_text( + srna, + "Cache Attribute Mapping", + "Attribute Mappin of the cache, used to define how to interpret certain attributes"); + + PropertyRNA *prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text(prop, "Name", "Name of the attribute to map"); + RNA_def_property_update(prop, 0, "rna_CacheFile_attribute_mapping_update"); + + static const EnumPropertyItem rna_enum_cache_attribute_mapping_items[] = { + {CACHEFILE_ATTRIBUTE_MAP_NONE, "MAP_NONE", 0, "None", ""}, + {CACHEFILE_ATTRIBUTE_MAP_TO_UVS, + "MAP_TO_UVS", + 0, + "UVs", + "Read the attribute as a UV map of the same name"}, + {CACHEFILE_ATTRIBUTE_MAP_TO_VERTEX_COLORS, + "MAP_TO_VERTEX_COLORS", + 0, + "Vertex Colors", + "Read the attribute as a vertex color layer of the same name"}, + {CACHEFILE_ATTRIBUTE_MAP_TO_WEIGHT_GROUPS, + "MAP_TO_WEIGHT_GROUPS", + 0, + "Weight Group", + "Read the attribute as a weight group channel of the same name"}, + {CACHEFILE_ATTRIBUTE_MAP_TO_FLOAT2, + "MAP_TO_FLOAT2", + 0, + "2D Vector", + "Interpret the attribute's data as generic 2D vectors"}, + {CACHEFILE_ATTRIBUTE_MAP_TO_FLOAT3, + "MAP_TO_FLOAT3", + 0, + "3D Vector", + "Interpret the attribute's data as generic 3D vectors"}, + {CACHEFILE_ATTRIBUTE_MAP_TO_COLOR, + "MAP_TO_COLOR", + 0, + "Color", + "Interpret the attribute's data as colors (RGBA)"}, + {0, NULL, 0, NULL, NULL}, + }; + + prop = RNA_def_property(srna, "mapping", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_cache_attribute_mapping_items); + RNA_def_property_update(prop, 0, "rna_CacheFile_attribute_mapping_update"); + RNA_def_property_ui_text(prop, "Data Type", "Define the data type of the attribute"); + + static const EnumPropertyItem rna_enum_cache_attribute_domain_items[] = { + {CACHEFILE_ATTR_MAP_DOMAIN_AUTO, + "AUTO", + 0, + "Automatic", + "Try to automatically determine the domain of the attribute"}, + {CACHEFILE_ATTR_MAP_DOMAIN_POINT, + "POINT", + 0, + "Point", + "The attribute is defined on the points"}, + {CACHEFILE_ATTR_MAP_DOMAIN_FACE_CORNER, + "FACE_CORNER", + 0, + "Face Corner", + "The attribute is defined on the face corners"}, + {CACHEFILE_ATTR_MAP_DOMAIN_FACE, "FACE", 0, "Face", "The attribute is defined on the faces"}, + {0, NULL, 0, NULL, NULL}, + }; + + prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_cache_attribute_domain_items); + RNA_def_property_update(prop, 0, "rna_CacheFile_attribute_mapping_update"); + RNA_def_property_ui_text(prop, "Domain", "Define the domain on which the attribute is written"); +} + +static void rna_def_cachefile_attribute_mappings(BlenderRNA *brna, PropertyRNA *cprop) +{ + RNA_def_property_srna(cprop, "CacheAttributeMappings"); + StructRNA *srna = RNA_def_struct(brna, "CacheAttributeMappings", NULL); + RNA_def_struct_sdna(srna, "CacheFile"); + RNA_def_struct_ui_text( + srna, "Cache Attribute Mappings", "Collection of cache attribute mappings"); + + PropertyRNA *prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "CacheAttributeMapping"); + RNA_def_property_pointer_funcs(prop, + "rna_CacheFile_active_attribute_mapping_get", + "rna_CacheFile_active_attribute_mapping_set", + NULL, + NULL); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text( + prop, "Active Attribute Mapping", "Active attribute mapping of the CacheFile"); + RNA_def_property_ui_text( + prop, "Active Attribute Mapping Index", "Active index in attribute mappings array"); +} + /* cachefile.object_paths */ static void rna_def_cachefile_object_paths(BlenderRNA *brna, PropertyRNA *cprop) { @@ -271,6 +429,18 @@ static void rna_def_cachefile(BlenderRNA *brna) "Alembic data themselves if possible"); RNA_def_property_update(prop, 0, "rna_CacheFile_dependency_update"); + static const EnumPropertyItem cache_file_type_items[] = { + {CACHE_FILE_TYPE_INVALID, "INVALID", 0, "Invalid", ""}, + {CACHEFILE_TYPE_ALEMBIC, "ALEMBIC", 0, "Alembic", ""}, + {CACHEFILE_TYPE_USD, "USD", 0, "USD", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, cache_file_type_items); + RNA_def_property_ui_text(prop, "Type", "Type of the file used for storing data"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + /* ----------------- For Scene time ------------------- */ prop = RNA_def_property(srna, "override_frame", PROP_BOOLEAN, PROP_NONE); @@ -400,6 +570,23 @@ static void rna_def_cachefile(BlenderRNA *brna) "rna_CacheFile_active_layer_index_set", "rna_CacheFile_active_layer_index_range"); + /* ----------------- Attribute Mappings ----------------- */ + + prop = RNA_def_property(srna, "attribute_mappings", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "attribute_mappings", NULL); + RNA_def_property_struct_type(prop, "CacheAttributeMapping"); + RNA_def_property_override_clear_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_ui_text(prop, "Cache Attribute Mappings", "Attribute mappings of the cache"); + rna_def_cachefile_attribute_mappings(brna, prop); + + prop = RNA_def_property(srna, "active_attribute_mapping_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_int_sdna(prop, NULL, "active_attribute_mapping"); + RNA_def_property_int_funcs(prop, + "rna_CacheFile_active_attribute_mapping_index_get", + "rna_CacheFile_active_attribute_mapping_index_set", + "rna_CacheFile_active_attribute_mapping_index_range"); + RNA_define_lib_overridable(false); rna_def_cachefile_object_paths(brna, prop); @@ -412,6 +599,7 @@ void RNA_def_cachefile(BlenderRNA *brna) rna_def_cachefile(brna); rna_def_alembic_object_path(brna); rna_def_cachefile_layer(brna); + rna_def_cachefile_attribute_mapping(brna); } #endif diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 719a07adbab..55c4b3e9705 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -76,7 +76,7 @@ set(SRC intern/MOD_meshcache_pc2.c intern/MOD_meshcache_util.c intern/MOD_meshdeform.c - intern/MOD_meshsequencecache.c + intern/MOD_meshsequencecache.cc intern/MOD_mirror.c intern/MOD_multires.c intern/MOD_nodes.cc diff --git a/source/blender/modifiers/intern/MOD_meshsequencecache.c b/source/blender/modifiers/intern/MOD_meshsequencecache.cc index 8bde0cab3dc..7d07e854ae7 100644 --- a/source/blender/modifiers/intern/MOD_meshsequencecache.c +++ b/source/blender/modifiers/intern/MOD_meshsequencecache.cc @@ -39,11 +39,14 @@ #include "BKE_cachefile.h" #include "BKE_context.h" +#include "BKE_curve_to_mesh.hh" +#include "BKE_geometry_set.hh" #include "BKE_lib_query.h" #include "BKE_mesh.h" #include "BKE_object.h" #include "BKE_scene.h" #include "BKE_screen.h" +#include "BKE_spline.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -93,7 +96,7 @@ static void copyData(const ModifierData *md, ModifierData *target, const int fla BKE_modifier_copydata_generic(md, target, flag); - tmcmd->reader = NULL; + tmcmd->reader = nullptr; tmcmd->reader_object_path[0] = '\0'; } @@ -114,7 +117,41 @@ static bool isDisabled(const struct Scene *UNUSED(scene), MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)md; /* leave it up to the modifier to check the file is valid on calculation */ - return (mcmd->cache_file == NULL) || (mcmd->object_path[0] == '\0'); + return (mcmd->cache_file == nullptr) || (mcmd->object_path[0] == '\0'); +} + +/* If this invocation is for the ORCO mesh, and the mesh hasn't changed topology, we + * must return the mesh as-is instead of deforming it. */ +static bool need_orco_mesh(MeshSeqCacheModifierData *mcmd, + const ModifierEvalContext *ctx, + Mesh *mesh, + float time, + const char **err_str) +{ + CacheFile *cache_file = mcmd->cache_file; + + if (ctx->flag & MOD_APPLY_ORCO) { + switch (cache_file->type) { + case CACHEFILE_TYPE_ALEMBIC: +#ifdef WITH_ALEMBIC + if (!ABC_mesh_topology_changed(mcmd->reader, ctx->object, mesh, time, err_str)) { + return true; + } +#endif + break; + case CACHEFILE_TYPE_USD: +#ifdef WITH_USD + if (!USD_mesh_topology_changed(mcmd->reader, ctx->object, mesh, time, err_str)) { + return true; + } +#endif + break; + case CACHE_FILE_TYPE_INVALID: + break; + } + } + + return false; } static Mesh *generate_bounding_box_mesh(Object *object, Mesh *org_mesh) @@ -155,20 +192,103 @@ static Mesh *generate_bounding_box_mesh(Object *object, Mesh *org_mesh) return result; } +static void modifyGeometry(ModifierData *md, + const ModifierEvalContext *ctx, + GeometrySet &geometry_set) +{ +#ifdef WITH_ALEMBIC + MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md); + + Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); + CacheFile *cache_file = mcmd->cache_file; + const float frame = DEG_get_ctime(ctx->depsgraph); + const float time = BKE_cachefile_time_offset(cache_file, frame, FPS); + const char *err_str = nullptr; + + if (!mcmd->reader || !STREQ(mcmd->reader_object_path, mcmd->object_path)) { + STRNCPY(mcmd->reader_object_path, mcmd->object_path); + BKE_cachefile_reader_open(cache_file, &mcmd->reader, ctx->object, mcmd->object_path); + if (!mcmd->reader) { + BKE_modifier_set_error( + ctx->object, md, "Could not create Alembic reader for file %s", cache_file->filepath); + return; + } + } + + if (geometry_set.has_mesh()) { + Mesh *mesh = geometry_set.get_mesh_for_write(); + if (need_orco_mesh(mcmd, ctx, mesh, time, &err_str)) { + return; + } + } + + /* Do not process data if using a render procedural, return a box instead for displaying in the + * viewport. */ + if (BKE_cache_file_uses_render_procedural(cache_file, scene, DEG_get_mode(ctx->depsgraph))) { + Mesh *org_mesh = nullptr; + if (geometry_set.has_mesh()) { + org_mesh = geometry_set.get_mesh_for_write(); + } + + Mesh *bbox = generate_bounding_box_mesh(ctx->object, org_mesh); + geometry_set = GeometrySet::create_with_mesh(bbox, GeometryOwnershipType::Editable); + return; + } + + /* Time (in frames or seconds) between two velocity samples. Automatically computed to + * scale the velocity vectors at render time for generating proper motion blur data. */ + float velocity_scale = mcmd->velocity_scale; + if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_FRAME) { + velocity_scale *= FPS; + } + + switch (cache_file->type) { + default: { + break; + } +# ifdef WITH_ALEMBIC + case CACHEFILE_TYPE_ALEMBIC: { + ABCReadParams params; + params.time = time; + params.read_flags = mcmd->read_flag; + params.velocity_name = mcmd->cache_file->velocity_name; + params.velocity_scale = velocity_scale; + params.mappings = &mcmd->cache_file->attribute_mappings; + ABC_read_geometry(mcmd->reader, ctx->object, geometry_set, ¶ms, &err_str); + break; + } +# endif +# ifdef WITH_USD + case CACHEFILE_TYPE_USD: { + USD_read_geometry(mcmd->reader, ctx->object, geometry_set, time, &err_str, mcmd->read_flag); + } +# endif + } + + if (err_str) { + BKE_modifier_set_error(ctx->object, md, "%s", err_str); + } + +#else + UNUSED_VARS(ctx, md, geometry_set); + return; +#endif +} + static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) { #if defined(WITH_USD) || defined(WITH_ALEMBIC) MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)md; /* Only used to check whether we are operating on org data or not... */ - Mesh *me = (ctx->object->type == OB_MESH) ? ctx->object->data : NULL; + Mesh *me = (ctx->object->type == OB_MESH) ? static_cast<Mesh *>(ctx->object->data) : nullptr; Mesh *org_mesh = mesh; Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); CacheFile *cache_file = mcmd->cache_file; const float frame = DEG_get_ctime(ctx->depsgraph); const float time = BKE_cachefile_time_offset(cache_file, frame, FPS); - const char *err_str = NULL; + const char *err_str = nullptr; if (!mcmd->reader || !STREQ(mcmd->reader_object_path, mcmd->object_path)) { STRNCPY(mcmd->reader_object_path, mcmd->object_path); @@ -186,30 +306,11 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * return generate_bounding_box_mesh(ctx->object, org_mesh); } - /* If this invocation is for the ORCO mesh, and the mesh hasn't changed topology, we - * must return the mesh as-is instead of deforming it. */ - if (ctx->flag & MOD_APPLY_ORCO) { - switch (cache_file->type) { - case CACHEFILE_TYPE_ALEMBIC: -# ifdef WITH_ALEMBIC - if (!ABC_mesh_topology_changed(mcmd->reader, ctx->object, mesh, time, &err_str)) { - return mesh; - } -# endif - break; - case CACHEFILE_TYPE_USD: -# ifdef WITH_USD - if (!USD_mesh_topology_changed(mcmd->reader, ctx->object, mesh, time, &err_str)) { - return mesh; - } -# endif - break; - case CACHE_FILE_TYPE_INVALID: - break; - } + if (need_orco_mesh(mcmd, ctx, mesh, time, &err_str)) { + return mesh; } - if (me != NULL) { + if (me != nullptr) { MVert *mvert = mesh->mvert; MEdge *medge = mesh->medge; MPoly *mpoly = mesh->mpoly; @@ -218,53 +319,44 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * * flags) and duplicate those too. */ if ((me->mvert == mvert) || (me->medge == medge) || (me->mpoly == mpoly)) { /* We need to duplicate data here, otherwise we'll modify org mesh, see T51701. */ - mesh = (Mesh *)BKE_id_copy_ex(NULL, + mesh = (Mesh *)BKE_id_copy_ex(nullptr, &mesh->id, - NULL, + nullptr, LIB_ID_CREATE_NO_MAIN | LIB_ID_CREATE_NO_USER_REFCOUNT | LIB_ID_CREATE_NO_DEG_TAG | LIB_ID_COPY_NO_PREVIEW); } } - Mesh *result = NULL; + Mesh *result = nullptr; - switch (cache_file->type) { - case CACHEFILE_TYPE_ALEMBIC: { -# ifdef WITH_ALEMBIC - /* Time (in frames or seconds) between two velocity samples. Automatically computed to - * scale the velocity vectors at render time for generating proper motion blur data. */ - float velocity_scale = mcmd->velocity_scale; - if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_FRAME) { - velocity_scale *= FPS; - } - - result = ABC_read_mesh(mcmd->reader, - ctx->object, - mesh, - time, - &err_str, - mcmd->read_flag, - mcmd->cache_file->velocity_name, - velocity_scale); -# endif - break; - } - case CACHEFILE_TYPE_USD: -# ifdef WITH_USD - result = USD_read_mesh( - mcmd->reader, ctx->object, mesh, time * FPS, &err_str, mcmd->read_flag); -# endif - break; - case CACHE_FILE_TYPE_INVALID: - break; + GeometrySet geometry_set; + + if (ctx->object->type == OB_CURVE) { + std::unique_ptr<CurveEval> curve_eval = curve_eval_from_dna_curve( + *static_cast<Curve *>(ctx->object->data)); + geometry_set = GeometrySet::create_with_curve(curve_eval.release(), + GeometryOwnershipType::Editable); + } + else { + geometry_set = GeometrySet::create_with_mesh(mesh, GeometryOwnershipType::Editable); } - if (err_str) { - BKE_modifier_set_error(ctx->object, md, "%s", err_str); + modifyGeometry(md, ctx, geometry_set); + + if (ctx->object->type == OB_CURVE) { + CurveEval *curve_eval = geometry_set.get_component_for_write<CurveComponent>().release(); + + if (curve_eval) { + result = blender::bke::curve_to_wire_mesh(*curve_eval); + delete curve_eval; + } + } + else { + result = geometry_set.get_component_for_write<MeshComponent>().release(); } - if (!ELEM(result, NULL, mesh) && (mesh != org_mesh)) { - BKE_id_free(NULL, mesh); + if (!ELEM(result, nullptr, mesh) && (mesh != org_mesh)) { + BKE_id_free(nullptr, mesh); mesh = org_mesh; } @@ -275,6 +367,13 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * #endif } +static void modifyGeometrySet(ModifierData *md, + const ModifierEvalContext *ctx, + GeometrySet *geometry_set) +{ + modifyGeometry(md, ctx, *geometry_set); +} + static bool dependsOnTime(Scene *scene, ModifierData *md, const int dag_eval_mode) { #if defined(WITH_USD) || defined(WITH_ALEMBIC) @@ -299,7 +398,7 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte { MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)md; - if (mcmd->cache_file != NULL) { + if (mcmd->cache_file != nullptr) { DEG_add_object_cache_relation( ctx->node, mcmd->cache_file, DEG_OB_COMP_CACHE, "Mesh Cache File"); } @@ -320,12 +419,13 @@ static void panel_draw(const bContext *C, Panel *panel) uiTemplateCacheFile(layout, C, ptr, "cache_file"); if (has_cache_file) { - uiItemPointerR(layout, ptr, "object_path", &cache_file_ptr, "object_paths", NULL, ICON_NONE); + uiItemPointerR( + layout, ptr, "object_path", &cache_file_ptr, "object_paths", nullptr, ICON_NONE); } if (RNA_enum_get(&ob_ptr, "type") == OB_MESH) { - uiItemR(layout, ptr, "read_data", UI_ITEM_R_EXPAND, NULL, ICON_NONE); - uiItemR(layout, ptr, "use_vertex_interpolation", 0, NULL, ICON_NONE); + uiItemR(layout, ptr, "read_data", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "use_vertex_interpolation", 0, nullptr, ICON_NONE); } modifier_panel_end(layout, ptr); @@ -343,16 +443,12 @@ static void velocity_panel_draw(const bContext *UNUSED(C), Panel *panel) return; } - if (RNA_pointer_is_null(&fileptr)) { - return; - } - uiLayoutSetPropSep(layout, true); uiTemplateCacheFileVelocity(layout, &fileptr); - uiItemR(layout, ptr, "velocity_scale", 0, NULL, ICON_NONE); + uiItemR(layout, ptr, "velocity_scale", 0, nullptr, ICON_NONE); } -static void time_panel_draw(const bContext *UNUSED(C), Panel *panel) +static void attribute_remapping_panel_draw(const bContext *C, Panel *panel) { uiLayout *layout = panel->layout; @@ -364,15 +460,11 @@ static void time_panel_draw(const bContext *UNUSED(C), Panel *panel) return; } - if (RNA_pointer_is_null(&fileptr)) { - return; - } - uiLayoutSetPropSep(layout, true); - uiTemplateCacheFileTimeSettings(layout, &fileptr); + uiTemplateCacheFileAttributeRemapping(layout, C, &fileptr); } -static void render_procedural_panel_draw(const bContext *C, Panel *panel) +static void override_layers_panel_draw(const bContext *C, Panel *panel) { uiLayout *layout = panel->layout; @@ -384,15 +476,27 @@ static void render_procedural_panel_draw(const bContext *C, Panel *panel) return; } - if (RNA_pointer_is_null(&fileptr)) { + uiLayoutSetPropSep(layout, true); + uiTemplateCacheFileLayers(layout, C, &fileptr); +} + +static void time_settings_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA ob_ptr; + PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr); + + PointerRNA fileptr; + if (!uiTemplateCacheFilePointer(ptr, "cache_file", &fileptr)) { return; } uiLayoutSetPropSep(layout, true); - uiTemplateCacheFileProcedural(layout, C, &fileptr); + uiTemplateCacheFileTimeSettings(layout, &fileptr); } -static void override_layers_panel_draw(const bContext *C, Panel *panel) +static void render_procedural_panel_draw(const bContext *C, Panel *panel) { uiLayout *layout = panel->layout; @@ -409,34 +513,45 @@ static void override_layers_panel_draw(const bContext *C, Panel *panel) } uiLayoutSetPropSep(layout, true); - uiTemplateCacheFileLayers(layout, C, &fileptr); + uiTemplateCacheFileProcedural(layout, C, &fileptr); } static void panelRegister(ARegionType *region_type) { PanelType *panel_type = modifier_panel_register( region_type, eModifierType_MeshSequenceCache, panel_draw); - modifier_subpanel_register(region_type, "time", "Time", NULL, time_panel_draw, panel_type); + modifier_subpanel_register(region_type, + "time_settings", + "Time Settings", + nullptr, + time_settings_panel_draw, + panel_type); modifier_subpanel_register(region_type, "render_procedural", "Render Procedural", - NULL, + nullptr, render_procedural_panel_draw, panel_type); - modifier_subpanel_register( - region_type, "velocity", "Velocity", NULL, velocity_panel_draw, panel_type); modifier_subpanel_register(region_type, "override_layers", "Override Layers", - NULL, + nullptr, override_layers_panel_draw, panel_type); + modifier_subpanel_register( + region_type, "velocity", "Velocity", nullptr, velocity_panel_draw, panel_type); + modifier_subpanel_register(region_type, + "attribute_remapping", + "Attribute Remapping", + nullptr, + attribute_remapping_panel_draw, + panel_type); } static void blendRead(BlendDataReader *UNUSED(reader), ModifierData *md) { MeshSeqCacheModifierData *msmcd = (MeshSeqCacheModifierData *)md; - msmcd->reader = NULL; + msmcd->reader = nullptr; msmcd->reader_object_path[0] = '\0'; } @@ -446,29 +561,30 @@ ModifierTypeInfo modifierType_MeshSequenceCache = { /* structSize */ sizeof(MeshSeqCacheModifierData), /* srna */ &RNA_MeshSequenceCacheModifier, /* type */ eModifierTypeType_Constructive, - /* flags */ eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs, + /* flags */ + static_cast<ModifierTypeFlag>(eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs), /* icon */ ICON_MOD_MESHDEFORM, /* TODO: Use correct icon. */ /* copyData */ copyData, - /* deformVerts */ NULL, - /* deformMatrices */ NULL, - /* deformVertsEM */ NULL, - /* deformMatricesEM */ NULL, + /* deformVerts */ nullptr, + /* deformMatrices */ nullptr, + /* deformVertsEM */ nullptr, + /* deformMatricesEM */ nullptr, /* modifyMesh */ modifyMesh, - /* modifyGeometrySet */ NULL, + /* modifyGeometrySet */ modifyGeometrySet, /* initData */ initData, - /* requiredDataMask */ NULL, + /* requiredDataMask */ nullptr, /* freeData */ freeData, /* isDisabled */ isDisabled, /* updateDepsgraph */ updateDepsgraph, /* dependsOnTime */ dependsOnTime, - /* dependsOnNormals */ NULL, + /* dependsOnNormals */ nullptr, /* foreachIDLink */ foreachIDLink, - /* foreachTexLink */ NULL, - /* freeRuntimeData */ NULL, + /* foreachTexLink */ nullptr, + /* freeRuntimeData */ nullptr, /* panelRegister */ panelRegister, - /* blendWrite */ NULL, + /* blendWrite */ nullptr, /* blendRead */ blendRead, }; |