diff options
author | Michael Kowalski <makowalski> | 2021-08-03 12:55:53 +0300 |
---|---|---|
committer | Sybren A. Stüvel <sybren@blender.org> | 2021-08-03 13:33:36 +0300 |
commit | ea54cbe1b42efb3107285c89685f555c06997062 (patch) | |
tree | bfaf0cb2315172e330c982ae081a4299cc3dc82b /source/blender | |
parent | 28b9dd7b1f0a8d4ae56eafca188796936a6e3723 (diff) |
USD: add USD importer
This is an initial implementation of a USD importer.
This work is comprised of Tangent Animation's open source USD importer,
combined with features @makowalski had implemented.
The design is very similar to the approach taken in the Alembic
importer. The core functionality resides in a collection of "reader"
classes, each of which is responsible for converting an instance of a
USD prim to the corresponding Blender Object representation.
The flow of control for the conversion can be followed in the
`import_startjob()` and `import_endjob()` functions in `usd_capi.cc`.
The `USDStageReader` class is responsible for traversing the USD stage
and instantiating the appropriate readers.
Reviewed By: sybren, HooglyBoogly
Differential Revision: https://developer.blender.org/D10700
Diffstat (limited to 'source/blender')
46 files changed, 5397 insertions, 89 deletions
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 78bfe8c9afb..adaef22d5bc 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -698,6 +698,13 @@ if(WITH_ALEMBIC) add_definitions(-DWITH_ALEMBIC) endif() +if(WITH_USD) + list(APPEND INC + ../io/usd + ) + add_definitions(-DWITH_USD) +endif() + if(WITH_OPENSUBDIV) list(APPEND INC_SYS ${OPENSUBDIV_INCLUDE_DIRS} diff --git a/source/blender/blenkernel/intern/cachefile.c b/source/blender/blenkernel/intern/cachefile.c index eaba5d33a20..75180de94d8 100644 --- a/source/blender/blenkernel/intern/cachefile.c +++ b/source/blender/blenkernel/intern/cachefile.c @@ -55,6 +55,10 @@ # include "ABC_alembic.h" #endif +#ifdef WITH_USD +# include "usd.h" +#endif + static void cachefile_handle_free(CacheFile *cache_file); static void cache_file_init_data(ID *id) @@ -166,15 +170,30 @@ void BKE_cachefile_reader_open(CacheFile *cache_file, Object *object, const char *object_path) { -#ifdef WITH_ALEMBIC +#if defined(WITH_ALEMBIC) || defined(WITH_USD) + BLI_assert(cache_file->id.tag & LIB_TAG_COPIED_ON_WRITE); if (cache_file->handle == NULL) { return; } - /* Open Alembic cache reader. */ - *reader = CacheReader_open_alembic_object(cache_file->handle, *reader, object, object_path); + switch (cache_file->type) { + case CACHEFILE_TYPE_ALEMBIC: +# ifdef WITH_ALEMBIC + /* Open Alembic cache reader. */ + *reader = CacheReader_open_alembic_object(cache_file->handle, *reader, object, object_path); +# endif + break; + case CACHEFILE_TYPE_USD: +# ifdef WITH_USD + /* Open USD cache reader. */ + *reader = CacheReader_open_usd_object(cache_file->handle, *reader, object, object_path); +# endif + break; + case CACHE_FILE_TYPE_INVALID: + break; + } /* Multiple modifiers and constraints can call this function concurrently. */ BLI_spin_lock(&spin); @@ -197,16 +216,30 @@ void BKE_cachefile_reader_open(CacheFile *cache_file, void BKE_cachefile_reader_free(CacheFile *cache_file, struct CacheReader **reader) { -#ifdef WITH_ALEMBIC +#if defined(WITH_ALEMBIC) || defined(WITH_USD) /* Multiple modifiers and constraints can call this function concurrently, and * cachefile_handle_free() can also be called at the same time. */ BLI_spin_lock(&spin); if (*reader != NULL) { if (cache_file) { BLI_assert(cache_file->id.tag & LIB_TAG_COPIED_ON_WRITE); + + switch (cache_file->type) { + case CACHEFILE_TYPE_ALEMBIC: +# ifdef WITH_ALEMBIC + ABC_CacheReader_free(*reader); +# endif + break; + case CACHEFILE_TYPE_USD: +# ifdef WITH_USD + USD_CacheReader_free(*reader); +# endif + break; + case CACHE_FILE_TYPE_INVALID: + break; + } } - CacheReader_free(*reader); *reader = NULL; if (cache_file && cache_file->handle_readers) { @@ -221,7 +254,8 @@ void BKE_cachefile_reader_free(CacheFile *cache_file, struct CacheReader **reade static void cachefile_handle_free(CacheFile *cache_file) { -#ifdef WITH_ALEMBIC +#if defined(WITH_ALEMBIC) || defined(WITH_USD) + /* Free readers in all modifiers and constraints that use the handle, before * we free the handle itself. */ BLI_spin_lock(&spin); @@ -230,7 +264,21 @@ static void cachefile_handle_free(CacheFile *cache_file) GSET_ITER (gs_iter, cache_file->handle_readers) { struct CacheReader **reader = BLI_gsetIterator_getKey(&gs_iter); if (*reader != NULL) { - CacheReader_free(*reader); + switch (cache_file->type) { + case CACHEFILE_TYPE_ALEMBIC: +# ifdef WITH_ALEMBIC + ABC_CacheReader_free(*reader); +# endif + break; + case CACHEFILE_TYPE_USD: +# ifdef WITH_USD + USD_CacheReader_free(*reader); +# endif + break; + case CACHE_FILE_TYPE_INVALID: + break; + } + *reader = NULL; } } @@ -242,7 +290,22 @@ static void cachefile_handle_free(CacheFile *cache_file) /* Free handle. */ if (cache_file->handle) { - ABC_free_handle(cache_file->handle); + + switch (cache_file->type) { + case CACHEFILE_TYPE_ALEMBIC: +# ifdef WITH_ALEMBIC + ABC_free_handle(cache_file->handle); +# endif + break; + case CACHEFILE_TYPE_USD: +# ifdef WITH_USD + USD_free_handle(cache_file->handle); +# endif + break; + case CACHE_FILE_TYPE_INVALID: + break; + } + cache_file->handle = NULL; } @@ -289,8 +352,18 @@ void BKE_cachefile_eval(Main *bmain, Depsgraph *depsgraph, CacheFile *cache_file BLI_freelistN(&cache_file->object_paths); #ifdef WITH_ALEMBIC - cache_file->handle = ABC_create_handle(bmain, filepath, &cache_file->object_paths); - BLI_strncpy(cache_file->handle_filepath, filepath, FILE_MAX); + if (BLI_path_extension_check_glob(filepath, "*abc")) { + cache_file->type = CACHEFILE_TYPE_ALEMBIC; + cache_file->handle = ABC_create_handle(bmain, filepath, &cache_file->object_paths); + BLI_strncpy(cache_file->handle_filepath, filepath, FILE_MAX); + } +#endif +#ifdef WITH_USD + if (BLI_path_extension_check_glob(filepath, "*.usd;*.usda;*.usdc")) { + cache_file->type = CACHEFILE_TYPE_USD; + cache_file->handle = USD_create_handle(bmain, filepath, &cache_file->object_paths); + BLI_strncpy(cache_file->handle_filepath, filepath, FILE_MAX); + } #endif if (DEG_is_active(depsgraph)) { diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index 47df31e3a2c..022073b0f12 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -95,6 +95,10 @@ # include "ABC_alembic.h" #endif +#ifdef WITH_USD +# include "usd.h" +#endif + /* ---------------------------------------------------------------------------- */ /* Useful macros for testing various common flag combinations */ @@ -5403,7 +5407,7 @@ static void transformcache_id_looper(bConstraint *con, ConstraintIDFunc func, vo static void transformcache_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *targets) { -#ifdef WITH_ALEMBIC +#if defined(WITH_ALEMBIC) || defined(WITH_USD) bTransformCacheConstraint *data = con->data; Scene *scene = cob->scene; @@ -5421,7 +5425,20 @@ static void transformcache_evaluate(bConstraint *con, bConstraintOb *cob, ListBa BKE_cachefile_reader_open(cache_file, &data->reader, cob->ob, data->object_path); } - ABC_get_transform(data->reader, cob->matrix, time, cache_file->scale); + switch (cache_file->type) { + case CACHEFILE_TYPE_ALEMBIC: +# ifdef WITH_ALEMBIC + ABC_get_transform(data->reader, cob->matrix, time, cache_file->scale); +# endif + break; + case CACHEFILE_TYPE_USD: +# ifdef WITH_USD + USD_get_transform(data->reader, cob->matrix, time * FPS, cache_file->scale); +# endif + break; + case CACHE_FILE_TYPE_INVALID: + break; + } #else UNUSED_VARS(con, cob); #endif diff --git a/source/blender/editors/io/io_ops.c b/source/blender/editors/io/io_ops.c index 9fa34a1c55d..b2788ee49a2 100644 --- a/source/blender/editors/io/io_ops.c +++ b/source/blender/editors/io/io_ops.c @@ -52,6 +52,7 @@ void ED_operatortypes_io(void) WM_operatortype_append(WM_OT_alembic_export); #endif #ifdef WITH_USD + WM_operatortype_append(WM_OT_usd_import); WM_operatortype_append(WM_OT_usd_export); #endif diff --git a/source/blender/editors/io/io_usd.c b/source/blender/editors/io/io_usd.c index 0eadb38abb5..d0007d9e5be 100644 --- a/source/blender/editors/io/io_usd.c +++ b/source/blender/editors/io/io_usd.c @@ -22,23 +22,30 @@ */ #ifdef WITH_USD +# include "DNA_modifier_types.h" # include "DNA_space_types.h" +# include <string.h> # include "BKE_context.h" # include "BKE_main.h" # include "BKE_report.h" +# include "BLI_blenlib.h" # include "BLI_path_util.h" # include "BLI_string.h" # include "BLI_utildefines.h" # include "BLT_translation.h" +# include "ED_object.h" + # include "MEM_guardedalloc.h" # include "RNA_access.h" # include "RNA_define.h" +# include "RNA_enum_types.h" + # include "UI_interface.h" # include "UI_resources.h" @@ -50,6 +57,8 @@ # include "io_usd.h" # include "usd.h" +# include "stdio.h" + const EnumPropertyItem rna_enum_usd_export_evaluation_mode_items[] = { {DAG_EVAL_RENDER, "RENDER", @@ -242,4 +251,274 @@ void WM_OT_usd_export(struct wmOperatorType *ot) "are different settings for viewport and rendering"); } +/* ====== USD Import ====== */ + +static int wm_usd_import_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + eUSDOperatorOptions *options = MEM_callocN(sizeof(eUSDOperatorOptions), "eUSDOperatorOptions"); + options->as_background_job = true; + op->customdata = options; + + return WM_operator_filesel(C, op, event); +} + +static int wm_usd_import_exec(bContext *C, wmOperator *op) +{ + if (!RNA_struct_property_is_set(op->ptr, "filepath")) { + BKE_report(op->reports, RPT_ERROR, "No filename given"); + return OPERATOR_CANCELLED; + } + + char filename[FILE_MAX]; + RNA_string_get(op->ptr, "filepath", filename); + + eUSDOperatorOptions *options = (eUSDOperatorOptions *)op->customdata; + const bool as_background_job = (options != NULL && options->as_background_job); + MEM_SAFE_FREE(op->customdata); + + const float scale = RNA_float_get(op->ptr, "scale"); + + const bool set_frame_range = RNA_boolean_get(op->ptr, "set_frame_range"); + + const bool read_mesh_uvs = RNA_boolean_get(op->ptr, "read_mesh_uvs"); + const bool read_mesh_colors = RNA_boolean_get(op->ptr, "read_mesh_colors"); + + char mesh_read_flag = MOD_MESHSEQ_READ_VERT | MOD_MESHSEQ_READ_POLY; + if (read_mesh_uvs) { + mesh_read_flag |= MOD_MESHSEQ_READ_UV; + } + if (read_mesh_colors) { + mesh_read_flag |= MOD_MESHSEQ_READ_COLOR; + } + + const bool import_cameras = RNA_boolean_get(op->ptr, "import_cameras"); + const bool import_curves = RNA_boolean_get(op->ptr, "import_curves"); + const bool import_lights = RNA_boolean_get(op->ptr, "import_lights"); + const bool import_materials = RNA_boolean_get(op->ptr, "import_materials"); + const bool import_meshes = RNA_boolean_get(op->ptr, "import_meshes"); + const bool import_volumes = RNA_boolean_get(op->ptr, "import_volumes"); + + const bool import_subdiv = RNA_boolean_get(op->ptr, "import_subdiv"); + + const bool import_instance_proxies = RNA_boolean_get(op->ptr, "import_instance_proxies"); + + const bool import_visible_only = RNA_boolean_get(op->ptr, "import_visible_only"); + + const bool create_collection = RNA_boolean_get(op->ptr, "create_collection"); + + char *prim_path_mask = malloc(1024); + RNA_string_get(op->ptr, "prim_path_mask", prim_path_mask); + + const bool import_guide = RNA_boolean_get(op->ptr, "import_guide"); + const bool import_proxy = RNA_boolean_get(op->ptr, "import_proxy"); + const bool import_render = RNA_boolean_get(op->ptr, "import_render"); + + const bool import_usd_preview = RNA_boolean_get(op->ptr, "import_usd_preview"); + const bool set_material_blend = RNA_boolean_get(op->ptr, "set_material_blend"); + + const float light_intensity_scale = RNA_float_get(op->ptr, "light_intensity_scale"); + + /* TODO(makowalski): Add support for sequences. */ + const bool is_sequence = false; + int offset = 0; + int sequence_len = 1; + + /* Switch out of edit mode to avoid being stuck in it (T54326). */ + Object *obedit = CTX_data_edit_object(C); + if (obedit) { + ED_object_mode_set(C, OB_MODE_EDIT); + } + + const bool validate_meshes = false; + const bool use_instancing = false; + + struct USDImportParams params = {.scale = scale, + .is_sequence = is_sequence, + .set_frame_range = set_frame_range, + .sequence_len = sequence_len, + .offset = offset, + .validate_meshes = validate_meshes, + .mesh_read_flag = mesh_read_flag, + .import_cameras = import_cameras, + .import_curves = import_curves, + .import_lights = import_lights, + .import_materials = import_materials, + .import_meshes = import_meshes, + .import_volumes = import_volumes, + .prim_path_mask = prim_path_mask, + .import_subdiv = import_subdiv, + .import_instance_proxies = import_instance_proxies, + .create_collection = create_collection, + .import_guide = import_guide, + .import_proxy = import_proxy, + .import_render = import_render, + .import_visible_only = import_visible_only, + .use_instancing = use_instancing, + .import_usd_preview = import_usd_preview, + .set_material_blend = set_material_blend, + .light_intensity_scale = light_intensity_scale}; + + const bool ok = USD_import(C, filename, ¶ms, as_background_job); + + return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; +} + +static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op) +{ + uiLayout *layout = op->layout; + struct PointerRNA *ptr = op->ptr; + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + uiLayout *box = uiLayoutBox(layout); + uiLayout *col = uiLayoutColumnWithHeading(box, true, IFACE_("Data Types")); + uiItemR(col, ptr, "import_cameras", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "import_curves", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "import_lights", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "import_materials", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "import_meshes", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "import_volumes", 0, NULL, ICON_NONE); + uiItemR(box, ptr, "prim_path_mask", 0, NULL, ICON_NONE); + uiItemR(box, ptr, "scale", 0, NULL, ICON_NONE); + + box = uiLayoutBox(layout); + col = uiLayoutColumnWithHeading(box, true, IFACE_("Mesh Data")); + uiItemR(col, ptr, "read_mesh_uvs", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "read_mesh_colors", 0, NULL, ICON_NONE); + col = uiLayoutColumnWithHeading(box, true, IFACE_("Include")); + uiItemR(col, ptr, "import_subdiv", 0, IFACE_("Subdivision"), ICON_NONE); + uiItemR(col, ptr, "import_instance_proxies", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "import_visible_only", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "import_guide", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "import_proxy", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "import_render", 0, NULL, ICON_NONE); + + col = uiLayoutColumnWithHeading(box, true, IFACE_("Options")); + uiItemR(col, ptr, "set_frame_range", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "relative_path", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "create_collection", 0, NULL, ICON_NONE); + uiItemR(box, ptr, "light_intensity_scale", 0, NULL, ICON_NONE); + + box = uiLayoutBox(layout); + col = uiLayoutColumnWithHeading(box, true, IFACE_("Experimental")); + uiItemR(col, ptr, "import_usd_preview", 0, NULL, ICON_NONE); + uiLayoutSetEnabled(col, RNA_boolean_get(ptr, "import_materials")); + uiLayout *row = uiLayoutRow(col, true); + uiItemR(row, ptr, "set_material_blend", 0, NULL, ICON_NONE); + uiLayoutSetEnabled(row, RNA_boolean_get(ptr, "import_usd_preview")); +} + +void WM_OT_usd_import(struct wmOperatorType *ot) +{ + ot->name = "Import USD"; + ot->description = "Import USD stage into current scene"; + ot->idname = "WM_OT_usd_import"; + + ot->invoke = wm_usd_import_invoke; + ot->exec = wm_usd_import_exec; + ot->poll = WM_operator_winactive; + ot->ui = wm_usd_import_draw; + + WM_operator_properties_filesel(ot, + FILE_TYPE_FOLDER | FILE_TYPE_USD, + FILE_BLENDER, + FILE_OPENFILE, + WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS, + FILE_DEFAULTDISPLAY, + FILE_SORT_ALPHA); + + RNA_def_float( + ot->srna, + "scale", + 1.0f, + 0.0001f, + 1000.0f, + "Scale", + "Value by which to enlarge or shrink the objects with respect to the world's origin", + 0.0001f, + 1000.0f); + + RNA_def_boolean(ot->srna, + "set_frame_range", + true, + "Set Frame Range", + "Update the scene's start and end frame to match those of the USD archive"); + + RNA_def_boolean(ot->srna, "import_cameras", true, "Cameras", ""); + RNA_def_boolean(ot->srna, "import_curves", true, "Curves", ""); + RNA_def_boolean(ot->srna, "import_lights", true, "Lights", ""); + RNA_def_boolean(ot->srna, "import_materials", true, "Materials", ""); + RNA_def_boolean(ot->srna, "import_meshes", true, "Meshes", ""); + RNA_def_boolean(ot->srna, "import_volumes", true, "Volumes", ""); + + RNA_def_boolean(ot->srna, + "import_subdiv", + false, + "Import Subdivision Scheme", + "Create subdivision surface modifiers based on the USD " + "SubdivisionScheme attribute"); + + RNA_def_boolean(ot->srna, + "import_instance_proxies", + true, + "Import Instance Proxies", + "Create unique Blender objects for USD instances"); + + RNA_def_boolean(ot->srna, + "import_visible_only", + true, + "Visible Primitives Only", + "Do not import invisible USD primitives. " + "Only applies to primitives with a non-animated visibility attribute. " + "Primitives with animated visibility will always be imported"); + + RNA_def_boolean(ot->srna, + "create_collection", + false, + "Create Collection", + "Add all imported objects to a new collection"); + + RNA_def_boolean(ot->srna, "read_mesh_uvs", true, "UV Coordinates", "Read mesh UV coordinates"); + + RNA_def_boolean(ot->srna, "read_mesh_colors", false, "Vertex Colors", "Read mesh vertex colors"); + + RNA_def_string(ot->srna, + "prim_path_mask", + NULL, + 1024, + "Path Mask", + "Import only the subset of the USD scene rooted at the given primitive"); + + RNA_def_boolean(ot->srna, "import_guide", false, "Guide", "Import guide geometry"); + + RNA_def_boolean(ot->srna, "import_proxy", true, "Proxy", "Import proxy geometry"); + + RNA_def_boolean(ot->srna, "import_render", true, "Render", "Import final render geometry"); + + RNA_def_boolean(ot->srna, + "import_usd_preview", + false, + "Import USD Preview", + "Convert UsdPreviewSurface shaders to Principled BSDF shader networks"); + + RNA_def_boolean(ot->srna, + "set_material_blend", + true, + "Set Material Blend", + "If the Import USD Preview option is enabled, " + "the material blend method will automatically be set based on the " + "shader's opacity and opacityThreshold inputs"); + + RNA_def_float(ot->srna, + "light_intensity_scale", + 1.0f, + 0.0001f, + 10000.0f, + "Light Intensity Scale", + "Scale for the intensity of imported lights", + 0.0001f, + 1000.0f); +} + #endif /* WITH_USD */ diff --git a/source/blender/editors/io/io_usd.h b/source/blender/editors/io/io_usd.h index 671984b6f34..7424cc0df32 100644 --- a/source/blender/editors/io/io_usd.h +++ b/source/blender/editors/io/io_usd.h @@ -26,3 +26,5 @@ struct wmOperatorType; void WM_OT_usd_export(struct wmOperatorType *ot); + +void WM_OT_usd_import(struct wmOperatorType *ot); diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h index 3d1391ac2a4..0dbebb1e4c4 100644 --- a/source/blender/io/alembic/ABC_alembic.h +++ b/source/blender/io/alembic/ABC_alembic.h @@ -25,6 +25,7 @@ extern "C" { #endif +struct CacheArchiveHandle; struct CacheReader; struct ListBase; struct Main; @@ -33,8 +34,6 @@ struct Object; struct Scene; struct bContext; -typedef struct AbcArchiveHandle AbcArchiveHandle; - int ABC_get_version(void); struct AlembicExportParams { @@ -100,11 +99,11 @@ bool ABC_import(struct bContext *C, bool validate_meshes, bool as_background_job); -AbcArchiveHandle *ABC_create_handle(struct Main *bmain, - const char *filename, - struct ListBase *object_paths); +struct CacheArchiveHandle *ABC_create_handle(struct Main *bmain, + const char *filename, + struct ListBase *object_paths); -void ABC_free_handle(AbcArchiveHandle *handle); +void ABC_free_handle(struct CacheArchiveHandle *handle); void ABC_get_transform(struct CacheReader *reader, float r_mat_world[4][4], @@ -125,10 +124,10 @@ bool ABC_mesh_topology_changed(struct CacheReader *reader, const float time, const char **err_str); -void CacheReader_incref(struct CacheReader *reader); -void CacheReader_free(struct CacheReader *reader); +void ABC_CacheReader_incref(struct CacheReader *reader); +void ABC_CacheReader_free(struct CacheReader *reader); -struct CacheReader *CacheReader_open_alembic_object(struct AbcArchiveHandle *handle, +struct CacheReader *CacheReader_open_alembic_object(struct CacheArchiveHandle *handle, struct CacheReader *reader, struct Object *object, const char *object_path); diff --git a/source/blender/io/alembic/intern/abc_util.h b/source/blender/io/alembic/intern/abc_util.h index 98f4b0376a7..ced9fde0f85 100644 --- a/source/blender/io/alembic/intern/abc_util.h +++ b/source/blender/io/alembic/intern/abc_util.h @@ -22,15 +22,6 @@ #include <Alembic/Abc/All.h> #include <Alembic/AbcGeom/All.h> -/** - * \brief The CacheReader struct is only used for anonymous pointers, - * to interface between C and C++ code. This library only creates - * pointers to AbcObjectReader (or subclasses thereof). - */ -struct CacheReader { - int unused; -}; - using Alembic::Abc::chrono_t; struct ID; diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc index e8d70bf3edb..b94b75b2216 100644 --- a/source/blender/io/alembic/intern/alembic_capi.cc +++ b/source/blender/io/alembic/intern/alembic_capi.cc @@ -19,6 +19,7 @@ */ #include "../ABC_alembic.h" +#include "IO_types.h" #include <Alembic/AbcMaterial/IMaterial.h> @@ -89,18 +90,14 @@ using Alembic::AbcMaterial::IMaterial; using namespace blender::io::alembic; -struct AbcArchiveHandle { - int unused; -}; - -BLI_INLINE ArchiveReader *archive_from_handle(AbcArchiveHandle *handle) +BLI_INLINE ArchiveReader *archive_from_handle(CacheArchiveHandle *handle) { return reinterpret_cast<ArchiveReader *>(handle); } -BLI_INLINE AbcArchiveHandle *handle_from_archive(ArchiveReader *archive) +BLI_INLINE CacheArchiveHandle *handle_from_archive(ArchiveReader *archive) { - return reinterpret_cast<AbcArchiveHandle *>(archive); + return reinterpret_cast<CacheArchiveHandle *>(archive); } //#define USE_NURBS @@ -150,8 +147,8 @@ static bool gather_objects_paths(const IObject &object, ListBase *object_paths) } if (get_path) { - void *abc_path_void = MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath"); - AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>(abc_path_void); + void *abc_path_void = MEM_callocN(sizeof(CacheObjectPath), "CacheObjectPath"); + CacheObjectPath *abc_path = static_cast<CacheObjectPath *>(abc_path_void); BLI_strncpy(abc_path->path, object.getFullName().c_str(), sizeof(abc_path->path)); BLI_addtail(object_paths, abc_path); @@ -160,9 +157,9 @@ static bool gather_objects_paths(const IObject &object, ListBase *object_paths) return parent_is_part_of_this_object; } -AbcArchiveHandle *ABC_create_handle(struct Main *bmain, - const char *filename, - ListBase *object_paths) +CacheArchiveHandle *ABC_create_handle(struct Main *bmain, + const char *filename, + ListBase *object_paths) { ArchiveReader *archive = new ArchiveReader(bmain, filename); @@ -178,7 +175,7 @@ AbcArchiveHandle *ABC_create_handle(struct Main *bmain, return handle_from_archive(archive); } -void ABC_free_handle(AbcArchiveHandle *handle) +void ABC_free_handle(CacheArchiveHandle *handle) { delete archive_from_handle(handle); } @@ -359,8 +356,8 @@ static std::pair<bool, AbcObjectReader *> visit_object( readers.push_back(reader); reader->incref(); - AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>( - MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath")); + CacheObjectPath *abc_path = static_cast<CacheObjectPath *>( + MEM_callocN(sizeof(CacheObjectPath), "CacheObjectPath")); BLI_strncpy(abc_path->path, full_name.c_str(), sizeof(abc_path->path)); BLI_addtail(&settings.cache_file->object_paths, abc_path); @@ -812,7 +809,7 @@ bool ABC_mesh_topology_changed( /* ************************************************************************** */ -void CacheReader_free(CacheReader *reader) +void ABC_CacheReader_free(CacheReader *reader) { AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); abc_reader->decref(); @@ -822,13 +819,13 @@ void CacheReader_free(CacheReader *reader) } } -void CacheReader_incref(CacheReader *reader) +void ABC_CacheReader_incref(CacheReader *reader) { AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); abc_reader->incref(); } -CacheReader *CacheReader_open_alembic_object(AbcArchiveHandle *handle, +CacheReader *CacheReader_open_alembic_object(CacheArchiveHandle *handle, CacheReader *reader, Object *object, const char *object_path) @@ -847,7 +844,7 @@ CacheReader *CacheReader_open_alembic_object(AbcArchiveHandle *handle, find_iobject(archive->getTop(), iobject, object_path); if (reader) { - CacheReader_free(reader); + ABC_CacheReader_free(reader); } ImportSettings settings; diff --git a/source/blender/io/common/CMakeLists.txt b/source/blender/io/common/CMakeLists.txt index 7e39af32f11..2aaf5d57fd6 100644 --- a/source/blender/io/common/CMakeLists.txt +++ b/source/blender/io/common/CMakeLists.txt @@ -37,6 +37,7 @@ set(SRC IO_abstract_hierarchy_iterator.h IO_dupli_persistent_id.hh + IO_types.h intern/dupli_parent_finder.hh ) diff --git a/source/blender/io/common/IO_types.h b/source/blender/io/common/IO_types.h new file mode 100644 index 00000000000..4570e29f6ed --- /dev/null +++ b/source/blender/io/common/IO_types.h @@ -0,0 +1,34 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ +#pragma once + +/* The CacheArchiveHandle struct is only used for anonymous pointers, + * to interface between C and C++ code. This is currently used + * to hide pointers to alembic ArchiveReader and USDStageReader. */ +struct CacheArchiveHandle { + int unused; +}; + +/* The CacheReader struct is only used for anonymous pointers, + * to interface between C and C++ code. This is currently used + * to hide pointers to AbcObjectReader and USDPrimReader + * (or subclasses thereof). */ +struct CacheReader { + int unused; +}; diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt index 6ea30f48a13..5499fe36898 100644 --- a/source/blender/io/usd/CMakeLists.txt +++ b/source/blender/io/usd/CMakeLists.txt @@ -56,7 +56,9 @@ set(INC_SYS ) set(SRC - intern/usd_capi.cc + intern/usd_capi_export.cc + intern/usd_capi_import.cc + intern/usd_common.cc intern/usd_hierarchy_iterator.cc intern/usd_writer_abstract.cc intern/usd_writer_camera.cc @@ -66,7 +68,21 @@ set(SRC intern/usd_writer_metaball.cc intern/usd_writer_transform.cc + intern/usd_reader_camera.cc + intern/usd_reader_curve.cc + intern/usd_reader_geom.cc + intern/usd_reader_light.cc + intern/usd_reader_material.cc + intern/usd_reader_mesh.cc + intern/usd_reader_nurbs.cc + intern/usd_reader_prim.cc + intern/usd_reader_stage.cc + intern/usd_reader_xform.cc + intern/usd_reader_volume.cc + usd.h + + intern/usd_common.h intern/usd_exporter_context.h intern/usd_hierarchy_iterator.h intern/usd_writer_abstract.h @@ -76,6 +92,18 @@ set(SRC intern/usd_writer_mesh.h intern/usd_writer_metaball.h intern/usd_writer_transform.h + + intern/usd_reader_camera.h + intern/usd_reader_curve.h + intern/usd_reader_geom.h + intern/usd_reader_light.h + intern/usd_reader_material.h + intern/usd_reader_mesh.h + intern/usd_reader_nurbs.h + intern/usd_reader_prim.h + intern/usd_reader_stage.h + intern/usd_reader_xform.h + intern/usd_reader_volume.h ) set(LIB diff --git a/source/blender/io/usd/intern/usd_capi.cc b/source/blender/io/usd/intern/usd_capi_export.cc index dc2b46e5cea..25f12e683cf 100644 --- a/source/blender/io/usd/intern/usd_capi.cc +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -18,6 +18,7 @@ */ #include "usd.h" +#include "usd_common.h" #include "usd_hierarchy_iterator.h" #include <pxr/base/plug/registry.h> @@ -59,21 +60,6 @@ struct ExportJobData { bool export_ok; }; -static void ensure_usd_plugin_path_registered() -{ - static bool plugin_path_registered = false; - if (plugin_path_registered) { - return; - } - plugin_path_registered = true; - - /* Tell USD which directory to search for its JSON files. If 'datafiles/usd' - * does not exist, the USD library will not be able to read or write any files. */ - const std::string blender_usd_datafiles = BKE_appdir_folder_id(BLENDER_DATAFILES, "usd"); - /* The trailing slash indicates to the USD library that the path is a directory. */ - pxr::PlugRegistry::GetInstance().RegisterPlugins(blender_usd_datafiles + "/"); -} - static void export_startjob(void *customdata, /* Cannot be const, this function implements wm_jobs_start_callback. * NOLINTNEXTLINE: readability-non-const-parameter. */ diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc new file mode 100644 index 00000000000..8255fca284c --- /dev/null +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -0,0 +1,578 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2019 Blender Foundation. + * All rights reserved. + */ + +#include "IO_types.h" +#include "usd.h" +#include "usd_common.h" +#include "usd_hierarchy_iterator.h" +#include "usd_reader_geom.h" +#include "usd_reader_prim.h" +#include "usd_reader_stage.h" + +#include "BKE_appdir.h" +#include "BKE_blender_version.h" +#include "BKE_cachefile.h" +#include "BKE_cdderivedmesh.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_library.h" +#include "BKE_main.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_scene.h" +#include "BKE_world.h" + +#include "BLI_fileops.h" +#include "BLI_listbase.h" +#include "BLI_math_matrix.h" +#include "BLI_math_rotation.h" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" + +#include "DNA_cachefile_types.h" +#include "DNA_collection_types.h" +#include "DNA_node_types.h" +#include "DNA_scene_types.h" +#include "DNA_world_types.h" + +#include "MEM_guardedalloc.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include <pxr/usd/usd/stage.h> +#include <pxr/usd/usdGeom/metrics.h> +#include <pxr/usd/usdGeom/scope.h> +#include <pxr/usd/usdGeom/tokens.h> +#include <pxr/usd/usdGeom/xformCommonAPI.h> + +#include <iostream> + +namespace blender::io::usd { + +static CacheArchiveHandle *handle_from_stage_reader(USDStageReader *reader) +{ + return reinterpret_cast<CacheArchiveHandle *>(reader); +} + +static USDStageReader *stage_reader_from_handle(CacheArchiveHandle *handle) +{ + return reinterpret_cast<USDStageReader *>(handle); +} + +static bool gather_objects_paths(const pxr::UsdPrim &object, ListBase *object_paths) +{ + if (!object.IsValid()) { + return false; + } + + for (const pxr::UsdPrim &childPrim : object.GetChildren()) { + gather_objects_paths(childPrim, object_paths); + } + + void *usd_path_void = MEM_callocN(sizeof(CacheObjectPath), "CacheObjectPath"); + CacheObjectPath *usd_path = static_cast<CacheObjectPath *>(usd_path_void); + + BLI_strncpy(usd_path->path, object.GetPrimPath().GetString().c_str(), sizeof(usd_path->path)); + BLI_addtail(object_paths, usd_path); + + return true; +} + +/* Update the given import settings with the global rotation matrix to orient + * imported objects with Z-up, if necessary */ +static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings *r_settings) +{ + if (!stage || pxr::UsdGeomGetStageUpAxis(stage) == pxr::UsdGeomTokens->z) { + return; + } + + if (!r_settings) { + return; + } + + r_settings->do_convert_mat = true; + + /* Rotate 90 degrees about the X-axis. */ + float rmat[3][3]; + float axis[3] = {1.0f, 0.0f, 0.0f}; + axis_angle_normalized_to_mat3(rmat, axis, M_PI / 2.0f); + + unit_m4(r_settings->conversion_mat); + copy_m4_m3(r_settings->conversion_mat, rmat); +} + +enum { + USD_NO_ERROR = 0, + USD_ARCHIVE_FAIL, +}; + +struct ImportJobData { + Main *bmain; + Scene *scene; + ViewLayer *view_layer; + wmWindowManager *wm; + + char filename[1024]; + USDImportParams params; + ImportSettings settings; + + USDStageReader *archive; + + short *stop; + short *do_update; + float *progress; + + char error_code; + bool was_canceled; + bool import_ok; +}; + +static void import_startjob(void *customdata, short *stop, short *do_update, float *progress) +{ + ImportJobData *data = static_cast<ImportJobData *>(customdata); + + data->stop = stop; + data->do_update = do_update; + data->progress = progress; + data->was_canceled = false; + data->archive = nullptr; + + WM_set_locked_interface(data->wm, true); + G.is_break = false; + + if (data->params.create_collection) { + char display_name[1024]; + BLI_path_to_display_name( + display_name, strlen(data->filename), BLI_path_basename(data->filename)); + Collection *import_collection = BKE_collection_add( + data->bmain, data->scene->master_collection, display_name); + id_fake_user_set(&import_collection->id); + + DEG_id_tag_update(&import_collection->id, ID_RECALC_COPY_ON_WRITE); + DEG_relations_tag_update(data->bmain); + + WM_main_add_notifier(NC_SCENE | ND_LAYER, nullptr); + + data->view_layer->active_collection = BKE_layer_collection_first_from_scene_collection( + data->view_layer, import_collection); + } + + BLI_path_abs(data->filename, BKE_main_blendfile_path_from_global()); + + CacheFile *cache_file = static_cast<CacheFile *>( + BKE_cachefile_add(data->bmain, BLI_path_basename(data->filename))); + + /* Decrement the ID ref-count because it is going to be incremented for each + * modifier and constraint that it will be attached to, so since currently + * it is not used by anyone, its use count will off by one. */ + id_us_min(&cache_file->id); + + cache_file->is_sequence = data->params.is_sequence; + cache_file->scale = data->params.scale; + STRNCPY(cache_file->filepath, data->filename); + + data->settings.cache_file = cache_file; + + *data->do_update = true; + *data->progress = 0.05f; + + if (G.is_break) { + data->was_canceled = true; + return; + } + + *data->do_update = true; + *data->progress = 0.1f; + + pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(data->filename); + + if (!stage) { + WM_reportf(RPT_ERROR, "USD Import: unable to open stage to read %s", data->filename); + data->import_ok = false; + return; + } + + convert_to_z_up(stage, &data->settings); + + /* Set up the stage for animated data. */ + if (data->params.set_frame_range) { + data->scene->r.sfra = stage->GetStartTimeCode(); + data->scene->r.efra = stage->GetEndTimeCode(); + } + + *data->progress = 0.15f; + + USDStageReader *archive = new USDStageReader(stage, data->params, data->settings); + + data->archive = archive; + + archive->collect_readers(data->bmain); + + *data->progress = 0.2f; + + const float size = static_cast<float>(archive->readers().size()); + size_t i = 0; + + /* Setup parenthood */ + + for (USDPrimReader *reader : archive->readers()) { + + if (!reader) { + continue; + } + + Object *ob = reader->object(); + + reader->read_object_data(data->bmain, 0.0); + + USDPrimReader *parent = reader->parent(); + + if (parent == nullptr) { + ob->parent = nullptr; + } + else { + ob->parent = parent->object(); + } + + *data->progress = 0.2f + 0.8f * (++i / size); + *data->do_update = true; + + if (G.is_break) { + data->was_canceled = true; + return; + } + } + + data->import_ok = !data->was_canceled; + + *progress = 1.0f; + *do_update = true; +} + +static void import_endjob(void *customdata) +{ + ImportJobData *data = static_cast<ImportJobData *>(customdata); + + /* Delete objects on cancellation. */ + if (data->was_canceled && data->archive) { + + for (USDPrimReader *reader : data->archive->readers()) { + + if (!reader) { + continue; + } + + /* It's possible that cancellation occurred between the creation of + * the reader and the creation of the Blender object. */ + if (Object *ob = reader->object()) { + BKE_id_free_us(data->bmain, ob); + } + } + } + else if (data->archive) { + /* Add object to scene. */ + Base *base; + LayerCollection *lc; + ViewLayer *view_layer = data->view_layer; + + BKE_view_layer_base_deselect_all(view_layer); + + lc = BKE_layer_collection_get_active(view_layer); + + for (USDPrimReader *reader : data->archive->readers()) { + + if (!reader) { + continue; + } + + Object *ob = reader->object(); + + if (!ob) { + continue; + } + + BKE_collection_object_add(data->bmain, lc->collection, ob); + + base = BKE_view_layer_base_find(view_layer, ob); + /* TODO: is setting active needed? */ + BKE_view_layer_base_select_and_set_active(view_layer, base); + + DEG_id_tag_update(&lc->collection->id, ID_RECALC_COPY_ON_WRITE); + DEG_id_tag_update_ex(data->bmain, + &ob->id, + ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION | + ID_RECALC_BASE_FLAGS); + } + + DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS); + DEG_relations_tag_update(data->bmain); + } + + WM_set_locked_interface(data->wm, false); + + switch (data->error_code) { + default: + case USD_NO_ERROR: + data->import_ok = !data->was_canceled; + break; + case USD_ARCHIVE_FAIL: + WM_report(RPT_ERROR, "Could not open USD archive for reading! See console for detail."); + break; + } + + WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene); +} + +static void import_freejob(void *user_data) +{ + ImportJobData *data = static_cast<ImportJobData *>(user_data); + + delete data->archive; + delete data; +} + +} // namespace blender::io::usd + +using namespace blender::io::usd; + +bool USD_import(struct bContext *C, + const char *filepath, + const USDImportParams *params, + bool as_background_job) +{ + blender::io::usd::ensure_usd_plugin_path_registered(); + + /* Using new here since MEM_* funcs do not call ctor to properly initialize + * data. */ + ImportJobData *job = new ImportJobData(); + job->bmain = CTX_data_main(C); + job->scene = CTX_data_scene(C); + job->view_layer = CTX_data_view_layer(C); + job->wm = CTX_wm_manager(C); + job->import_ok = false; + BLI_strncpy(job->filename, filepath, 1024); + + job->settings.scale = params->scale; + job->settings.sequence_offset = params->offset; + job->settings.is_sequence = params->is_sequence; + job->settings.sequence_len = params->sequence_len; + job->settings.validate_meshes = params->validate_meshes; + job->settings.sequence_len = params->sequence_len; + job->error_code = USD_NO_ERROR; + job->was_canceled = false; + job->archive = nullptr; + + job->params = *params; + + G.is_break = false; + + bool import_ok = false; + if (as_background_job) { + wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + job->scene, + "USD Import", + WM_JOB_PROGRESS, + WM_JOB_TYPE_ALEMBIC); + + /* setup job */ + WM_jobs_customdata_set(wm_job, job, import_freejob); + WM_jobs_timer(wm_job, 0.1, NC_SCENE, NC_SCENE); + WM_jobs_callbacks(wm_job, import_startjob, nullptr, nullptr, import_endjob); + + WM_jobs_start(CTX_wm_manager(C), wm_job); + } + else { + /* Fake a job context, so that we don't need NULL pointer checks while importing. */ + short stop = 0, do_update = 0; + float progress = 0.f; + + import_startjob(job, &stop, &do_update, &progress); + import_endjob(job); + import_ok = job->import_ok; + + import_freejob(job); + } + + return import_ok; +} + +/* TODO(makowalski): Extend this function with basic validation that the + * USD reader is compatible with the type of the given (currently unused) 'ob' + * Object parameter, similar to the logic in get_abc_reader() in the + * Alembic importer code. */ +static USDPrimReader *get_usd_reader(CacheReader *reader, Object * /* ob */, const char **err_str) +{ + USDPrimReader *usd_reader = reinterpret_cast<USDPrimReader *>(reader); + pxr::UsdPrim iobject = usd_reader->prim(); + + if (!iobject.IsValid()) { + *err_str = "Invalid object: verify object path"; + return nullptr; + } + + 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) +{ + USDGeomReader *usd_reader = dynamic_cast<USDGeomReader *>(get_usd_reader(reader, ob, err_str)); + + if (usd_reader == nullptr) { + return nullptr; + } + + return usd_reader->read_mesh(existing_mesh, time, read_flag, err_str); +} + +bool USD_mesh_topology_changed( + CacheReader *reader, Object *ob, Mesh *existing_mesh, const float time, const char **err_str) +{ + USDGeomReader *usd_reader = dynamic_cast<USDGeomReader *>(get_usd_reader(reader, ob, err_str)); + + if (usd_reader == nullptr) { + return false; + } + + return usd_reader->topology_changed(existing_mesh, time); +} + +void USD_CacheReader_incref(CacheReader *reader) +{ + USDPrimReader *usd_reader = reinterpret_cast<USDPrimReader *>(reader); + usd_reader->incref(); +} + +CacheReader *CacheReader_open_usd_object(CacheArchiveHandle *handle, + CacheReader *reader, + Object *object, + const char *object_path) +{ + if (object_path[0] == '\0') { + return reader; + } + + USDStageReader *archive = stage_reader_from_handle(handle); + + if (!archive || !archive->valid()) { + return reader; + } + + pxr::UsdPrim prim = archive->stage()->GetPrimAtPath(pxr::SdfPath(object_path)); + + if (reader) { + USD_CacheReader_free(reader); + } + + /* TODO(makowalski): The handle does not have the proper import params or settings. */ + USDPrimReader *usd_reader = archive->create_reader(prim); + + if (usd_reader == nullptr) { + /* This object is not supported */ + return nullptr; + } + usd_reader->object(object); + usd_reader->incref(); + + return reinterpret_cast<CacheReader *>(usd_reader); +} + +void USD_CacheReader_free(CacheReader *reader) +{ + USDPrimReader *usd_reader = reinterpret_cast<USDPrimReader *>(reader); + usd_reader->decref(); + + if (usd_reader->refcount() == 0) { + delete usd_reader; + } +} + +CacheArchiveHandle *USD_create_handle(struct Main * /*bmain*/, + const char *filename, + ListBase *object_paths) +{ + pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(filename); + + if (!stage) { + return nullptr; + } + + USDImportParams params{}; + + blender::io::usd::ImportSettings settings{}; + convert_to_z_up(stage, &settings); + + USDStageReader *stage_reader = new USDStageReader(stage, params, settings); + + if (object_paths) { + gather_objects_paths(stage->GetPseudoRoot(), object_paths); + } + + return handle_from_stage_reader(stage_reader); +} + +void USD_free_handle(CacheArchiveHandle *handle) +{ + USDStageReader *stage_reader = stage_reader_from_handle(handle); + delete stage_reader; +} + +void USD_get_transform(struct CacheReader *reader, + float r_mat_world[4][4], + float time, + float scale) +{ + if (!reader) { + return; + } + USDXformReader *usd_reader = reinterpret_cast<USDXformReader *>(reader); + + bool is_constant = false; + + /* Convert from the local matrix we obtain from USD to world coordinates + * for Blender. This conversion is done here rather than by Blender due to + * work around the non-standard interpretation of CONSTRAINT_SPACE_LOCAL in + * BKE_constraint_mat_convertspace(). */ + Object *object = usd_reader->object(); + if (object->parent == nullptr) { + /* No parent, so local space is the same as world space. */ + usd_reader->read_matrix(r_mat_world, time, scale, &is_constant); + return; + } + + float mat_parent[4][4]; + BKE_object_get_parent_matrix(object, object->parent, mat_parent); + + float mat_local[4][4]; + usd_reader->read_matrix(mat_local, time, scale, &is_constant); + mul_m4_m4m4(r_mat_world, mat_parent, object->parentinv); + mul_m4_m4m4(r_mat_world, r_mat_world, mat_local); +} diff --git a/source/blender/io/usd/intern/usd_common.cc b/source/blender/io/usd/intern/usd_common.cc new file mode 100644 index 00000000000..0cd9c3019ef --- /dev/null +++ b/source/blender/io/usd/intern/usd_common.cc @@ -0,0 +1,43 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ + +#include "usd_common.h" + +#include <pxr/base/plug/registry.h> + +#include "BKE_appdir.h" + +namespace blender::io::usd { + +void ensure_usd_plugin_path_registered() +{ + static bool plugin_path_registered = false; + if (plugin_path_registered) { + return; + } + plugin_path_registered = true; + + /* Tell USD which directory to search for its JSON files. If 'datafiles/usd' + * does not exist, the USD library will not be able to read or write any files. */ + const std::string blender_usd_datafiles = BKE_appdir_folder_id(BLENDER_DATAFILES, "usd"); + /* The trailing slash indicates to the USD library that the path is a directory. */ + pxr::PlugRegistry::GetInstance().RegisterPlugins(blender_usd_datafiles + "/"); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_common.h b/source/blender/io/usd/intern/usd_common.h new file mode 100644 index 00000000000..36667bbc6b1 --- /dev/null +++ b/source/blender/io/usd/intern/usd_common.h @@ -0,0 +1,25 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ +#pragma once + +namespace blender::io::usd { + +void ensure_usd_plugin_path_registered(); + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_camera.cc b/source/blender/io/usd/intern/usd_reader_camera.cc new file mode 100644 index 00000000000..2732ed5770d --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_camera.cc @@ -0,0 +1,100 @@ +/* + * 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. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_camera.h" + +#include "DNA_camera_types.h" +#include "DNA_object_types.h" + +#include "BKE_camera.h" +#include "BKE_object.h" + +#include "BLI_math.h" + +#include <pxr/pxr.h> +#include <pxr/usd/usdGeom/camera.h> + +namespace blender::io::usd { + +void USDCameraReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + Camera *bcam = static_cast<Camera *>(BKE_camera_add(bmain, name_.c_str())); + + object_ = BKE_object_add_only_object(bmain, OB_CAMERA, name_.c_str()); + object_->data = bcam; +} + +void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTime) +{ + Camera *bcam = (Camera *)object_->data; + + pxr::UsdGeomCamera cam_prim(prim_); + + if (!cam_prim) { + return; + } + + pxr::VtValue val; + cam_prim.GetFocalLengthAttr().Get(&val, motionSampleTime); + pxr::VtValue verApOffset; + cam_prim.GetVerticalApertureOffsetAttr().Get(&verApOffset, motionSampleTime); + pxr::VtValue horApOffset; + cam_prim.GetHorizontalApertureOffsetAttr().Get(&horApOffset, motionSampleTime); + pxr::VtValue clippingRangeVal; + cam_prim.GetClippingRangeAttr().Get(&clippingRangeVal, motionSampleTime); + pxr::VtValue focalDistanceVal; + cam_prim.GetFocusDistanceAttr().Get(&focalDistanceVal, motionSampleTime); + pxr::VtValue fstopVal; + cam_prim.GetFStopAttr().Get(&fstopVal, motionSampleTime); + pxr::VtValue projectionVal; + cam_prim.GetProjectionAttr().Get(&projectionVal, motionSampleTime); + pxr::VtValue verAp; + cam_prim.GetVerticalApertureAttr().Get(&verAp, motionSampleTime); + pxr::VtValue horAp; + cam_prim.GetHorizontalApertureAttr().Get(&horAp, motionSampleTime); + + bcam->lens = val.Get<float>(); + /* TODO(makowalski) */ +#if 0 + bcam->sensor_x = 0.0f; + bcam->sensor_y = 0.0f; +#endif + bcam->shiftx = verApOffset.Get<float>(); + bcam->shifty = horApOffset.Get<float>(); + + bcam->type = (projectionVal.Get<pxr::TfToken>().GetString() == "perspective") ? CAM_PERSP : + CAM_ORTHO; + + /* Calling UncheckedGet() to silence compiler warnings. */ + bcam->clip_start = max_ff(0.1f, clippingRangeVal.UncheckedGet<pxr::GfVec2f>()[0]); + bcam->clip_end = clippingRangeVal.UncheckedGet<pxr::GfVec2f>()[1]; + + bcam->dof.focus_distance = focalDistanceVal.Get<float>(); + bcam->dof.aperture_fstop = static_cast<float>(fstopVal.Get<float>()); + + if (bcam->type == CAM_ORTHO) { + bcam->ortho_scale = max_ff(verAp.Get<float>(), horAp.Get<float>()); + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_camera.h b/source/blender/io/usd/intern/usd_reader_camera.h new file mode 100644 index 00000000000..a4156aa8be2 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_camera.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. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_xform.h" + +namespace blender::io::usd { + +class USDCameraReader : public USDXformReader { + + public: + USDCameraReader(const pxr::UsdPrim &object, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDXformReader(object, import_params, settings) + { + } + + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_curve.cc b/source/blender/io/usd/intern/usd_reader_curve.cc new file mode 100644 index 00000000000..31ecf27cf7e --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_curve.cc @@ -0,0 +1,256 @@ +/* + * 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. + * + * Adapted from the Blender Alembic importer implementation, + * Copyright (C) 2016 Kévin Dietrich. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_curve.h" + +#include "BKE_curve.h" +#include "BKE_mesh.h" +#include "BKE_object.h" + +#include "BLI_listbase.h" + +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "MEM_guardedalloc.h" + +#include <pxr/base/vt/array.h> +#include <pxr/base/vt/types.h> +#include <pxr/base/vt/value.h> + +#include <pxr/usd/usdGeom/basisCurves.h> +#include <pxr/usd/usdGeom/curves.h> + +namespace blender::io::usd { + +void USDCurvesReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE); + + curve_->flag |= CU_DEFORM_FILL | CU_3D; + curve_->actvert = CU_ACT_NONE; + curve_->resolu = 2; + + object_ = BKE_object_add_only_object(bmain, OB_CURVE, name_.c_str()); + object_->data = curve_; +} + +void USDCurvesReader::read_object_data(Main *bmain, double motionSampleTime) +{ + Curve *cu = (Curve *)object_->data; + read_curve_sample(cu, motionSampleTime); + + if (curve_prim_.GetPointsAttr().ValueMightBeTimeVarying()) { + add_cache_modifier(); + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +void USDCurvesReader::read_curve_sample(Curve *cu, const double motionSampleTime) +{ + curve_prim_ = pxr::UsdGeomBasisCurves(prim_); + + if (!curve_prim_) { + return; + } + + pxr::UsdAttribute widthsAttr = curve_prim_.GetWidthsAttr(); + pxr::UsdAttribute vertexAttr = curve_prim_.GetCurveVertexCountsAttr(); + pxr::UsdAttribute pointsAttr = curve_prim_.GetPointsAttr(); + + pxr::VtIntArray usdCounts; + + vertexAttr.Get(&usdCounts, motionSampleTime); + int num_subcurves = usdCounts.size(); + + pxr::VtVec3fArray usdPoints; + pointsAttr.Get(&usdPoints, motionSampleTime); + + pxr::VtFloatArray usdWidths; + widthsAttr.Get(&usdWidths, motionSampleTime); + + pxr::UsdAttribute basisAttr = curve_prim_.GetBasisAttr(); + pxr::TfToken basis; + basisAttr.Get(&basis, motionSampleTime); + + pxr::UsdAttribute typeAttr = curve_prim_.GetTypeAttr(); + pxr::TfToken type; + typeAttr.Get(&type, motionSampleTime); + + pxr::UsdAttribute wrapAttr = curve_prim_.GetWrapAttr(); + pxr::TfToken wrap; + wrapAttr.Get(&wrap, motionSampleTime); + + pxr::VtVec3fArray usdNormals; + curve_prim_.GetNormalsAttr().Get(&usdNormals, motionSampleTime); + + /* If normals, extrude, else bevel. + * Perhaps to be replaced by Blender/USD Schema. */ + if (!usdNormals.empty()) { + /* Set extrusion to 1.0f. */ + curve_->ext1 = 1.0f; + } + else { + /* Set bevel depth to 1.0f. */ + curve_->ext2 = 1.0f; + } + + size_t idx = 0; + for (size_t i = 0; i < num_subcurves; i++) { + const int num_verts = usdCounts[i]; + Nurb *nu = static_cast<Nurb *>(MEM_callocN(sizeof(Nurb), __func__)); + + if (basis == pxr::UsdGeomTokens->bspline) { + nu->flag = CU_SMOOTH; + nu->type = CU_NURBS; + } + else if (basis == pxr::UsdGeomTokens->bezier) { + /* TODO(makowalski): Beziers are not properly imported as beziers. */ + nu->type = CU_POLY; + } + else if (basis.IsEmpty()) { + nu->type = CU_POLY; + } + nu->resolu = cu->resolu; + nu->resolv = cu->resolv; + + nu->pntsu = num_verts; + nu->pntsv = 1; + + if (type == pxr::UsdGeomTokens->cubic) { + nu->orderu = 4; + } + else if (type == pxr::UsdGeomTokens->linear) { + nu->orderu = 2; + } + + if (wrap == pxr::UsdGeomTokens->periodic) { + nu->flagu |= CU_NURB_CYCLIC; + } + else if (wrap == pxr::UsdGeomTokens->pinned) { + nu->flagu |= CU_NURB_ENDPOINT; + } + + float weight = 1.0f; + + nu->bp = static_cast<BPoint *>(MEM_callocN(sizeof(BPoint) * nu->pntsu, __func__)); + BPoint *bp = nu->bp; + + for (int j = 0; j < nu->pntsu; j++, bp++, idx++) { + bp->vec[0] = (float)usdPoints[idx][0]; + bp->vec[1] = (float)usdPoints[idx][1]; + bp->vec[2] = (float)usdPoints[idx][2]; + bp->vec[3] = weight; + bp->f1 = SELECT; + bp->weight = weight; + + float radius = curve_->width; + if (idx < usdWidths.size()) { + radius = usdWidths[idx]; + } + + bp->radius = radius; + } + + BKE_nurb_knot_calc_u(nu); + BKE_nurb_knot_calc_v(nu); + + BLI_addtail(BKE_curve_nurbs_get(cu), nu); + } +} + +Mesh *USDCurvesReader::read_mesh(struct Mesh *existing_mesh, + const double motionSampleTime, + const int /* read_flag */, + const char ** /* err_str */) +{ + if (!curve_prim_) { + return existing_mesh; + } + + pxr::UsdAttribute widthsAttr = curve_prim_.GetWidthsAttr(); + pxr::UsdAttribute vertexAttr = curve_prim_.GetCurveVertexCountsAttr(); + pxr::UsdAttribute pointsAttr = curve_prim_.GetPointsAttr(); + + 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; + } + } + } + + if (!same_topology) { + BKE_nurbList_free(&curve->nurb); + read_curve_sample(curve, motionSampleTime); + } + 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]; + } + } + } + } + + 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 new file mode 100644 index 00000000000..1e676bbbd02 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_curve.h @@ -0,0 +1,62 @@ +/* + * 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. + * + * Adapted from the Blender Alembic importer implementation, + * Copyright (C) 2016 Kévin Dietrich. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_geom.h" + +#include "pxr/usd/usdGeom/basisCurves.h" + +struct Curve; + +namespace blender::io::usd { + +class USDCurvesReader : public USDGeomReader { + protected: + pxr::UsdGeomBasisCurves curve_prim_; + Curve *curve_; + + public: + USDCurvesReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDGeomReader(prim, import_params, settings), curve_prim_(prim), curve_(nullptr) + { + } + + bool valid() const override + { + return static_cast<bool>(curve_prim_); + } + + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; + + 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; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_geom.cc b/source/blender/io/usd/intern/usd_reader_geom.cc new file mode 100644 index 00000000000..23c5f57120c --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_geom.cc @@ -0,0 +1,59 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_geom.h" + +#include "BKE_lib_id.h" +#include "BKE_modifier.h" +#include "BKE_object.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_math_geom.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "DNA_cachefile_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_space_types.h" /* for FILE_MAX */ + +namespace blender::io::usd { + +void USDGeomReader::add_cache_modifier() +{ + ModifierData *md = BKE_modifier_new(eModifierType_MeshSequenceCache); + BLI_addtail(&object_->modifiers, md); + + MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md); + + mcmd->cache_file = settings_->cache_file; + id_us_plus(&mcmd->cache_file->id); + mcmd->read_flag = import_params_.mesh_read_flag; + + BLI_strncpy(mcmd->object_path, prim_.GetPath().GetString().c_str(), FILE_MAX); +} + +void USDGeomReader::add_subdiv_modifier() +{ + ModifierData *md = BKE_modifier_new(eModifierType_Subsurf); + BLI_addtail(&object_->modifiers, md); +} + +} // 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 new file mode 100644 index 00000000000..99e22248f2b --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_geom.h @@ -0,0 +1,52 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_xform.h" + +struct Mesh; + +namespace blender::io::usd { + +class USDGeomReader : public USDXformReader { + + public: + USDGeomReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDXformReader(prim, import_params, settings) + { + } + + virtual Mesh *read_mesh(struct Mesh *existing_mesh, + double motionSampleTime, + int read_flag, + const char **err_str) = 0; + + virtual bool topology_changed(Mesh * /* existing_mesh */, double /* motionSampleTime */) + { + return true; + } + + void add_cache_modifier(); + void add_subdiv_modifier(); +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_instance.cc b/source/blender/io/usd/intern/usd_reader_instance.cc new file mode 100644 index 00000000000..e645b0237b9 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_instance.cc @@ -0,0 +1,64 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ + +#include "usd_reader_instance.h" + +#include "BKE_object.h" +#include "DNA_object_types.h" + +#include <iostream> + +namespace blender::io::usd { + +USDInstanceReader::USDInstanceReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDXformReader(prim, import_params, settings) +{ +} + +bool USDInstanceReader::valid() const +{ + return prim_.IsValid() && prim_.IsInstance(); +} + +void USDInstanceReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + this->object_ = BKE_object_add_only_object(bmain, OB_EMPTY, name_.c_str()); + this->object_->data = nullptr; + this->object_->transflag |= OB_DUPLICOLLECTION; +} + +void USDInstanceReader::set_instance_collection(Collection *coll) +{ + if (this->object_) { + this->object_->instance_collection = coll; + } +} + +pxr::SdfPath USDInstanceReader::proto_path() const +{ + if (pxr::UsdPrim master = prim_.GetMaster()) { + return master.GetPath(); + } + + return pxr::SdfPath(); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_instance.h b/source/blender/io/usd/intern/usd_reader_instance.h new file mode 100644 index 00000000000..efc1c69a7dd --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_instance.h @@ -0,0 +1,47 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ +#pragma once + +#include "usd_reader_xform.h" + +#include <pxr/usd/usdGeom/xform.h> + +struct Collection; + +namespace blender::io::usd { + +/* Wraps the UsdGeomXform schema. Creates a Blender Empty object. */ + +class USDInstanceReader : public USDXformReader { + + public: + USDInstanceReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings); + + bool valid() const override; + + void create_object(Main *bmain, double motionSampleTime) override; + + void set_instance_collection(Collection *coll); + + pxr::SdfPath proto_path() const; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_light.cc b/source/blender/io/usd/intern/usd_reader_light.cc new file mode 100644 index 00000000000..fda0c17968a --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_light.cc @@ -0,0 +1,252 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_light.h" + +#include "BKE_light.h" +#include "BKE_object.h" + +#include "DNA_light_types.h" +#include "DNA_object_types.h" + +#include <pxr/usd/usdLux/light.h> + +#include <pxr/usd/usdLux/diskLight.h> +#include <pxr/usd/usdLux/distantLight.h> +#include <pxr/usd/usdLux/rectLight.h> +#include <pxr/usd/usdLux/shapingAPI.h> +#include <pxr/usd/usdLux/sphereLight.h> + +#include <iostream> + +namespace blender::io::usd { + +void USDLightReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + Light *blight = static_cast<Light *>(BKE_light_add(bmain, name_.c_str())); + + object_ = BKE_object_add_only_object(bmain, OB_LAMP, name_.c_str()); + object_->data = blight; +} + +void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime) +{ + Light *blight = (Light *)object_->data; + + if (blight == nullptr) { + return; + } + + if (!prim_) { + return; + } + + pxr::UsdLuxLight light_prim(prim_); + + if (!light_prim) { + return; + } + + pxr::UsdLuxShapingAPI shaping_api(light_prim); + + /* Set light type. */ + + if (prim_.IsA<pxr::UsdLuxDiskLight>()) { + blight->type = LA_AREA; + blight->area_shape = LA_AREA_DISK; + /* Ellipse lights are not currently supported */ + } + else if (prim_.IsA<pxr::UsdLuxRectLight>()) { + blight->type = LA_AREA; + blight->area_shape = LA_AREA_RECT; + } + else if (prim_.IsA<pxr::UsdLuxSphereLight>()) { + blight->type = LA_LOCAL; + + if (shaping_api && shaping_api.GetShapingConeAngleAttr().IsAuthored()) { + blight->type = LA_SPOT; + } + } + else if (prim_.IsA<pxr::UsdLuxDistantLight>()) { + blight->type = LA_SUN; + } + + /* Set light values. */ + + if (pxr::UsdAttribute intensity_attr = light_prim.GetIntensityAttr()) { + float intensity = 0.0f; + if (intensity_attr.Get(&intensity, motionSampleTime)) { + blight->energy = intensity * this->import_params_.light_intensity_scale; + } + } + + /* TODO(makowalsk): Not currently supported. */ +#if 0 + pxr::VtValue exposure; + light_prim.GetExposureAttr().Get(&exposure, motionSampleTime); +#endif + + /* TODO(makowalsk): Not currently supported */ +#if 0 + pxr::VtValue diffuse; + light_prim.GetDiffuseAttr().Get(&diffuse, motionSampleTime); +#endif + + if (pxr::UsdAttribute spec_attr = light_prim.GetSpecularAttr()) { + float spec = 0.0f; + if (spec_attr.Get(&spec, motionSampleTime)) { + blight->spec_fac = spec; + } + } + + if (pxr::UsdAttribute color_attr = light_prim.GetColorAttr()) { + pxr::GfVec3f color; + if (color_attr.Get(&color, motionSampleTime)) { + blight->r = color[0]; + blight->g = color[1]; + blight->b = color[2]; + } + } + + /* TODO(makowalski): Not currently supported. */ +#if 0 + pxr::VtValue use_color_temp; + light_prim.GetEnableColorTemperatureAttr().Get(&use_color_temp, motionSampleTime); +#endif + + /* TODO(makowalski): Not currently supported. */ +#if 0 + pxr::VtValue color_temp; + light_prim.GetColorTemperatureAttr().Get(&color_temp, motionSampleTime); +#endif + + switch (blight->type) { + case LA_AREA: + if (blight->area_shape == LA_AREA_RECT && prim_.IsA<pxr::UsdLuxRectLight>()) { + + pxr::UsdLuxRectLight rect_light(prim_); + + if (!rect_light) { + break; + } + + if (pxr::UsdAttribute width_attr = rect_light.GetWidthAttr()) { + float width = 0.0f; + if (width_attr.Get(&width, motionSampleTime)) { + blight->area_size = width; + } + } + + if (pxr::UsdAttribute height_attr = rect_light.GetHeightAttr()) { + float height = 0.0f; + if (height_attr.Get(&height, motionSampleTime)) { + blight->area_sizey = height; + } + } + } + else if (blight->area_shape == LA_AREA_DISK && prim_.IsA<pxr::UsdLuxDiskLight>()) { + + pxr::UsdLuxDiskLight disk_light(prim_); + + if (!disk_light) { + break; + } + + if (pxr::UsdAttribute radius_attr = disk_light.GetRadiusAttr()) { + float radius = 0.0f; + if (radius_attr.Get(&radius, motionSampleTime)) { + blight->area_size = radius * 2.0f; + } + } + } + break; + case LA_LOCAL: + if (prim_.IsA<pxr::UsdLuxSphereLight>()) { + + pxr::UsdLuxSphereLight sphere_light(prim_); + + if (!sphere_light) { + break; + } + + if (pxr::UsdAttribute radius_attr = sphere_light.GetRadiusAttr()) { + float radius = 0.0f; + if (radius_attr.Get(&radius, motionSampleTime)) { + blight->area_size = radius; + } + } + } + break; + case LA_SPOT: + if (prim_.IsA<pxr::UsdLuxSphereLight>()) { + + pxr::UsdLuxSphereLight sphere_light(prim_); + + if (!sphere_light) { + break; + } + + if (pxr::UsdAttribute radius_attr = sphere_light.GetRadiusAttr()) { + float radius = 0.0f; + if (radius_attr.Get(&radius, motionSampleTime)) { + blight->area_size = radius; + } + } + + if (!shaping_api) { + break; + } + + if (pxr::UsdAttribute cone_angle_attr = shaping_api.GetShapingConeAngleAttr()) { + float cone_angle = 0.0f; + if (cone_angle_attr.Get(&cone_angle, motionSampleTime)) { + blight->spotsize = cone_angle * ((float)M_PI / 180.0f) * 2.0f; + } + } + + if (pxr::UsdAttribute cone_softness_attr = shaping_api.GetShapingConeSoftnessAttr()) { + float cone_softness = 0.0f; + if (cone_softness_attr.Get(&cone_softness, motionSampleTime)) { + blight->spotblend = cone_softness; + } + } + } + break; + case LA_SUN: + if (prim_.IsA<pxr::UsdLuxDistantLight>()) { + pxr::UsdLuxDistantLight distant_light(prim_); + + if (!distant_light) { + break; + } + + if (pxr::UsdAttribute angle_attr = distant_light.GetAngleAttr()) { + float angle = 0.0f; + if (angle_attr.Get(&angle, motionSampleTime)) { + blight->sun_angle = angle * (float)M_PI / 180.0f; + } + } + } + break; + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_light.h b/source/blender/io/usd/intern/usd_reader_light.h new file mode 100644 index 00000000000..e7860fd2c80 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_light.h @@ -0,0 +1,41 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_xform.h" + +namespace blender::io::usd { + +class USDLightReader : public USDXformReader { + + public: + USDLightReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDXformReader(prim, import_params, settings) + { + } + + void create_object(Main *bmain, double motionSampleTime) override; + + void read_object_data(Main *bmain, double motionSampleTime) override; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_material.cc b/source/blender/io/usd/intern/usd_reader_material.cc new file mode 100644 index 00000000000..02ed7c35e57 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_material.cc @@ -0,0 +1,703 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 NVIDIA Corporation. + * All rights reserved. + */ + +#include "usd_reader_material.h" + +#include "BKE_image.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_node.h" + +#include "BLI_math_vector.h" +#include "BLI_string.h" + +#include "DNA_material_types.h" + +#include <pxr/base/gf/vec3f.h> +#include <pxr/usd/usdShade/material.h> +#include <pxr/usd/usdShade/shader.h> + +#include <iostream> +#include <vector> + +namespace usdtokens { + +/* Parameter names. */ +static const pxr::TfToken a("a", pxr::TfToken::Immortal); +static const pxr::TfToken b("b", pxr::TfToken::Immortal); +static const pxr::TfToken clearcoat("clearcoat", pxr::TfToken::Immortal); +static const pxr::TfToken clearcoatRoughness("clearcoatRoughness", pxr::TfToken::Immortal); +static const pxr::TfToken diffuseColor("diffuseColor", pxr::TfToken::Immortal); +static const pxr::TfToken emissiveColor("emissiveColor", pxr::TfToken::Immortal); +static const pxr::TfToken file("file", pxr::TfToken::Immortal); +static const pxr::TfToken g("g", pxr::TfToken::Immortal); +static const pxr::TfToken ior("ior", pxr::TfToken::Immortal); +static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal); +static const pxr::TfToken normal("normal", pxr::TfToken::Immortal); +static const pxr::TfToken occlusion("occlusion", pxr::TfToken::Immortal); +static const pxr::TfToken opacity("opacity", pxr::TfToken::Immortal); +static const pxr::TfToken opacityThreshold("opacityThreshold", pxr::TfToken::Immortal); +static const pxr::TfToken r("r", pxr::TfToken::Immortal); +static const pxr::TfToken result("result", pxr::TfToken::Immortal); +static const pxr::TfToken rgb("rgb", pxr::TfToken::Immortal); +static const pxr::TfToken rgba("rgba", pxr::TfToken::Immortal); +static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal); +static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal); +static const pxr::TfToken specularColor("specularColor", pxr::TfToken::Immortal); +static const pxr::TfToken st("st", pxr::TfToken::Immortal); +static const pxr::TfToken varname("varname", pxr::TfToken::Immortal); + +/* Color space names. */ +static const pxr::TfToken raw("raw", pxr::TfToken::Immortal); +static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal); + +/* USD shader names. */ +static const pxr::TfToken UsdPreviewSurface("UsdPreviewSurface", pxr::TfToken::Immortal); +static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2", + pxr::TfToken::Immortal); +static const pxr::TfToken UsdUVTexture("UsdUVTexture", pxr::TfToken::Immortal); +} // namespace usdtokens + +/* Add a node of the given type at the given location coordinates. */ +static bNode *add_node( + const bContext *C, bNodeTree *ntree, const int type, const float locx, const float locy) +{ + bNode *new_node = nodeAddStaticNode(C, ntree, type); + + if (new_node) { + new_node->locx = locx; + new_node->locy = locy; + } + + return new_node; +} + +/* Connect the output socket of node 'source' to the input socket of node 'dest'. */ +static void link_nodes( + bNodeTree *ntree, bNode *source, const char *sock_out, bNode *dest, const char *sock_in) +{ + bNodeSocket *source_socket = nodeFindSocket(source, SOCK_OUT, sock_out); + + if (!source_socket) { + std::cerr << "PROGRAMMER ERROR: Couldn't find output socket " << sock_out << std::endl; + return; + } + + bNodeSocket *dest_socket = nodeFindSocket(dest, SOCK_IN, sock_in); + + if (!dest_socket) { + std::cerr << "PROGRAMMER ERROR: Couldn't find input socket " << sock_in << std::endl; + return; + } + + nodeAddLink(ntree, source, source_socket, dest, dest_socket); +} + +/* Returns true if the given shader may have opacity < 1.0, based + * on heuristics. */ +static bool needs_blend(const pxr::UsdShadeShader &usd_shader) +{ + if (!usd_shader) { + return false; + } + + bool needs_blend = false; + + if (pxr::UsdShadeInput opacity_input = usd_shader.GetInput(usdtokens::opacity)) { + + if (opacity_input.HasConnectedSource()) { + needs_blend = true; + } + else { + pxr::VtValue val; + if (opacity_input.GetAttr().HasAuthoredValue() && opacity_input.GetAttr().Get(&val)) { + float opacity = val.Get<float>(); + needs_blend = opacity < 1.0f; + } + } + } + + return needs_blend; +} + +/* Returns the given shader's opacityThreshold input value, if this input has an + * authored value. Otherwise, returns the given default value. */ +static float get_opacity_threshold(const pxr::UsdShadeShader &usd_shader, + float default_value = 0.0f) +{ + if (!usd_shader) { + return default_value; + } + + pxr::UsdShadeInput opacity_threshold_input = usd_shader.GetInput(usdtokens::opacityThreshold); + + if (!opacity_threshold_input) { + return default_value; + } + + pxr::VtValue val; + if (opacity_threshold_input.GetAttr().HasAuthoredValue() && + opacity_threshold_input.GetAttr().Get(&val)) { + return val.Get<float>(); + } + + return default_value; +} + +static pxr::TfToken get_source_color_space(const pxr::UsdShadeShader &usd_shader) +{ + if (!usd_shader) { + return pxr::TfToken(); + } + + pxr::UsdShadeInput color_space_input = usd_shader.GetInput(usdtokens::sourceColorSpace); + + if (!color_space_input) { + return pxr::TfToken(); + } + + pxr::VtValue color_space_val; + if (color_space_input.Get(&color_space_val) && color_space_val.IsHolding<pxr::TfToken>()) { + return color_space_val.Get<pxr::TfToken>(); + } + + return pxr::TfToken(); +} + +/* Attempts to return in r_preview_surface the UsdPreviewSurface shader source + * of the given material. Returns true if a UsdPreviewSurface source was found + * and returns false otherwise. */ +static bool get_usd_preview_surface(const pxr::UsdShadeMaterial &usd_material, + pxr::UsdShadeShader &r_preview_surface) +{ + if (!usd_material) { + return false; + } + + if (pxr::UsdShadeShader surf_shader = usd_material.ComputeSurfaceSource()) { + /* Check if we have a UsdPreviewSurface shader. */ + pxr::TfToken shader_id; + if (surf_shader.GetShaderId(&shader_id) && shader_id == usdtokens::UsdPreviewSurface) { + r_preview_surface = surf_shader; + return true; + } + } + + return false; +} + +/* Set the Blender material's viewport display color, metallic and roughness + * properties from the given USD preview surface shader's inputs. */ +static void set_viewport_material_props(Material *mtl, const pxr::UsdShadeShader &usd_preview) +{ + if (!(mtl && usd_preview)) { + return; + } + + if (pxr::UsdShadeInput diffuse_color_input = usd_preview.GetInput(usdtokens::diffuseColor)) { + pxr::VtValue val; + if (diffuse_color_input.GetAttr().HasAuthoredValue() && + diffuse_color_input.GetAttr().Get(&val) && val.IsHolding<pxr::GfVec3f>()) { + pxr::GfVec3f color = val.UncheckedGet<pxr::GfVec3f>(); + mtl->r = color[0]; + mtl->g = color[1]; + mtl->b = color[2]; + } + } + + if (pxr::UsdShadeInput metallic_input = usd_preview.GetInput(usdtokens::metallic)) { + pxr::VtValue val; + if (metallic_input.GetAttr().HasAuthoredValue() && metallic_input.GetAttr().Get(&val) && + val.IsHolding<float>()) { + mtl->metallic = val.Get<float>(); + } + } + + if (pxr::UsdShadeInput roughness_input = usd_preview.GetInput(usdtokens::roughness)) { + pxr::VtValue val; + if (roughness_input.GetAttr().HasAuthoredValue() && roughness_input.GetAttr().Get(&val) && + val.IsHolding<float>()) { + mtl->roughness = val.Get<float>(); + } + } +} + +namespace blender::io::usd { + +namespace { + +/* Compute the x- and y-coordinates for placing a new node in an unoccupied region of + * the column with the given index. Returns the coordinates in r_locx and r_locy and + * updates the column-occupancy information in r_ctx. */ +void compute_node_loc(const int column, float *r_locx, float *r_locy, NodePlacementContext *r_ctx) +{ + if (!(r_locx && r_locy && r_ctx)) { + return; + } + + (*r_locx) = r_ctx->origx - column * r_ctx->horizontal_step; + + if (column >= r_ctx->column_offsets.size()) { + r_ctx->column_offsets.push_back(0.0f); + } + + (*r_locy) = r_ctx->origy - r_ctx->column_offsets[column]; + + /* Record the y-offset of the occupied region in + * the column, including padding. */ + r_ctx->column_offsets[column] += r_ctx->vertical_step + 10.0f; +} + +} // End anonymous namespace. + +USDMaterialReader::USDMaterialReader(const USDImportParams ¶ms, Main *bmain) + : params_(params), bmain_(bmain) +{ +} + +Material *USDMaterialReader::add_material(const pxr::UsdShadeMaterial &usd_material) const +{ + if (!(bmain_ && usd_material)) { + return nullptr; + } + + std::string mtl_name = usd_material.GetPrim().GetName().GetString(); + + /* Create the material. */ + Material *mtl = BKE_material_add(bmain_, mtl_name.c_str()); + + /* Get the UsdPreviewSurface shader source for the material, + * if there is one. */ + pxr::UsdShadeShader usd_preview; + if (get_usd_preview_surface(usd_material, usd_preview)) { + + set_viewport_material_props(mtl, usd_preview); + + /* Optionally, create shader nodes to represent a UsdPreviewSurface. */ + if (params_.import_usd_preview) { + import_usd_preview(mtl, usd_preview); + } + } + + return mtl; +} + +/* Create the Principled BSDF shader node network. */ +void USDMaterialReader::import_usd_preview(Material *mtl, + const pxr::UsdShadeShader &usd_shader) const +{ + if (!(bmain_ && mtl && usd_shader)) { + return; + } + + /* Create the Material's node tree containing the principled BSDF + * and output shaders. */ + + /* Add the node tree. */ + bNodeTree *ntree = ntreeAddTree(nullptr, "Shader Nodetree", "ShaderNodeTree"); + mtl->nodetree = ntree; + mtl->use_nodes = true; + + /* Create the Principled BSDF shader node. */ + bNode *principled = add_node(nullptr, ntree, SH_NODE_BSDF_PRINCIPLED, 0.0f, 300.0f); + + if (!principled) { + std::cerr << "ERROR: Couldn't create SH_NODE_BSDF_PRINCIPLED node for USD shader " + << usd_shader.GetPath() << std::endl; + return; + } + + /* Create the material output node. */ + bNode *output = add_node(nullptr, ntree, SH_NODE_OUTPUT_MATERIAL, 300.0f, 300.0f); + + if (!output) { + std::cerr << "ERROR: Couldn't create SH_NODE_OUTPUT_MATERIAL node for USD shader " + << usd_shader.GetPath() << std::endl; + return; + } + + /* Connect the Principled BSDF node to the output node. */ + link_nodes(ntree, principled, "BSDF", output, "Surface"); + + /* Recursively create the principled shader input networks. */ + set_principled_node_inputs(principled, ntree, usd_shader); + + nodeSetActive(ntree, output); + + ntreeUpdateTree(bmain_, ntree); + + /* Optionally, set the material blend mode. */ + + if (params_.set_material_blend) { + if (needs_blend(usd_shader)) { + float opacity_threshold = get_opacity_threshold(usd_shader, 0.0f); + if (opacity_threshold > 0.0f) { + mtl->blend_method = MA_BM_CLIP; + mtl->alpha_threshold = opacity_threshold; + } + else { + mtl->blend_method = MA_BM_BLEND; + } + } + } +} + +void USDMaterialReader::set_principled_node_inputs(bNode *principled, + bNodeTree *ntree, + const pxr::UsdShadeShader &usd_shader) const +{ + /* The context struct keeps track of the locations for adding + * input nodes. */ + NodePlacementContext context(0.0f, 300.0); + + /* The column index (from right to left relative to the principled + * node) where we're adding the nodes. */ + int column = 0; + + /* Recursively set the principled shader inputs. */ + + if (pxr::UsdShadeInput diffuse_input = usd_shader.GetInput(usdtokens::diffuseColor)) { + set_node_input(diffuse_input, principled, "Base Color", ntree, column, &context); + } + + if (pxr::UsdShadeInput emissive_input = usd_shader.GetInput(usdtokens::emissiveColor)) { + set_node_input(emissive_input, principled, "Emission", ntree, column, &context); + } + + if (pxr::UsdShadeInput specular_input = usd_shader.GetInput(usdtokens::specularColor)) { + set_node_input(specular_input, principled, "Specular", ntree, column, &context); + } + + if (pxr::UsdShadeInput metallic_input = usd_shader.GetInput(usdtokens::metallic)) { + ; + set_node_input(metallic_input, principled, "Metallic", ntree, column, &context); + } + + if (pxr::UsdShadeInput roughness_input = usd_shader.GetInput(usdtokens::roughness)) { + set_node_input(roughness_input, principled, "Roughness", ntree, column, &context); + } + + if (pxr::UsdShadeInput clearcoat_input = usd_shader.GetInput(usdtokens::clearcoat)) { + set_node_input(clearcoat_input, principled, "Clearcoat", ntree, column, &context); + } + + if (pxr::UsdShadeInput clearcoat_roughness_input = usd_shader.GetInput( + usdtokens::clearcoatRoughness)) { + set_node_input( + clearcoat_roughness_input, principled, "Clearcoat Roughness", ntree, column, &context); + } + + if (pxr::UsdShadeInput opacity_input = usd_shader.GetInput(usdtokens::opacity)) { + set_node_input(opacity_input, principled, "Alpha", ntree, column, &context); + } + + if (pxr::UsdShadeInput ior_input = usd_shader.GetInput(usdtokens::ior)) { + set_node_input(ior_input, principled, "IOR", ntree, column, &context); + } + + if (pxr::UsdShadeInput normal_input = usd_shader.GetInput(usdtokens::normal)) { + set_node_input(normal_input, principled, "Normal", ntree, column, &context); + } +} + +/* Convert the given USD shader input to an input on the given Blender node. */ +void USDMaterialReader::set_node_input(const pxr::UsdShadeInput &usd_input, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + const int column, + NodePlacementContext *r_ctx) const +{ + if (!(usd_input && dest_node && r_ctx)) { + return; + } + + if (usd_input.HasConnectedSource()) { + /* The USD shader input has a connected source shader. Follow the connection + * and attempt to convert the connected USD shader to a Blender node. */ + follow_connection(usd_input, dest_node, dest_socket_name, ntree, column, r_ctx); + } + else { + /* Set the destination node socket value from the USD shader input value. */ + + bNodeSocket *sock = nodeFindSocket(dest_node, SOCK_IN, dest_socket_name); + if (!sock) { + std::cerr << "ERROR: couldn't get destination node socket " << dest_socket_name << std::endl; + return; + } + + pxr::VtValue val; + if (!usd_input.Get(&val)) { + std::cerr << "ERROR: couldn't get value for usd shader input " + << usd_input.GetPrim().GetPath() << std::endl; + return; + } + + switch (sock->type) { + case SOCK_FLOAT: + if (val.IsHolding<float>()) { + ((bNodeSocketValueFloat *)sock->default_value)->value = val.UncheckedGet<float>(); + } + else if (val.IsHolding<pxr::GfVec3f>()) { + pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>(); + float average = (v3f[0] + v3f[1] + v3f[2]) / 3.0f; + ((bNodeSocketValueFloat *)sock->default_value)->value = average; + } + break; + case SOCK_RGBA: + if (val.IsHolding<pxr::GfVec3f>()) { + pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>(); + copy_v3_v3(((bNodeSocketValueRGBA *)sock->default_value)->value, v3f.data()); + } + break; + case SOCK_VECTOR: + if (val.IsHolding<pxr::GfVec3f>()) { + pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>(); + copy_v3_v3(((bNodeSocketValueVector *)sock->default_value)->value, v3f.data()); + } + else if (val.IsHolding<pxr::GfVec2f>()) { + pxr::GfVec2f v2f = val.UncheckedGet<pxr::GfVec2f>(); + copy_v2_v2(((bNodeSocketValueVector *)sock->default_value)->value, v2f.data()); + } + break; + default: + std::cerr << "WARNING: unexpected type " << sock->idname << " for destination node socket " + << dest_socket_name << std::endl; + break; + } + } +} + +/* Follow the connected source of the USD input to create corresponding inputs + * for the given Blender node. */ +void USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + int column, + NodePlacementContext *r_ctx) const +{ + if (!(usd_input && dest_node && dest_socket_name && ntree && r_ctx)) { + return; + } + + pxr::UsdShadeConnectableAPI source; + pxr::TfToken source_name; + pxr::UsdShadeAttributeType source_type; + + usd_input.GetConnectedSource(&source, &source_name, &source_type); + + if (!(source && source.GetPrim().IsA<pxr::UsdShadeShader>())) { + return; + } + + pxr::UsdShadeShader source_shader(source.GetPrim()); + + if (!source_shader) { + return; + } + + pxr::TfToken shader_id; + if (!source_shader.GetShaderId(&shader_id)) { + std::cerr << "ERROR: couldn't get shader id for source shader " + << source_shader.GetPrim().GetPath() << std::endl; + return; + } + + /* For now, only convert UsdUVTexture and UsdPrimvarReader_float2 inputs. */ + if (shader_id == usdtokens::UsdUVTexture) { + + if (strcmp(dest_socket_name, "Normal") == 0) { + + /* The normal texture input requires creating a normal map node. */ + float locx = 0.0f; + float locy = 0.0f; + compute_node_loc(column + 1, &locx, &locy, r_ctx); + + bNode *normal_map = add_node(nullptr, ntree, SH_NODE_NORMAL_MAP, locx, locy); + + /* Currently, the Normal Map node has Tangent Space as the default, + * which is what we need, so we don't need to explicitly set it. */ + + /* Connect the Normal Map to the Normal input. */ + link_nodes(ntree, normal_map, "Normal", dest_node, "Normal"); + + /* Now, create the Texture Image node input to the Normal Map "Color" input. */ + convert_usd_uv_texture( + source_shader, source_name, normal_map, "Color", ntree, column + 2, r_ctx); + } + else { + convert_usd_uv_texture( + source_shader, source_name, dest_node, dest_socket_name, ntree, column + 1, r_ctx); + } + } + else if (shader_id == usdtokens::UsdPrimvarReader_float2) { + convert_usd_primvar_reader_float2( + source_shader, source_name, dest_node, dest_socket_name, ntree, column + 1, r_ctx); + } +} + +void USDMaterialReader::convert_usd_uv_texture(const pxr::UsdShadeShader &usd_shader, + const pxr::TfToken &usd_source_name, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + const int column, + NodePlacementContext *r_ctx) const +{ + if (!usd_shader || !dest_node || !ntree || !dest_socket_name || !bmain_ || !r_ctx) { + return; + } + + float locx = 0.0f; + float locy = 0.0f; + compute_node_loc(column, &locx, &locy, r_ctx); + + /* Create the Texture Image node. */ + bNode *tex_image = add_node(nullptr, ntree, SH_NODE_TEX_IMAGE, locx, locy); + + if (!tex_image) { + std::cerr << "ERROR: Couldn't create SH_NODE_TEX_IMAGE for node input " << dest_socket_name + << std::endl; + return; + } + + /* Load the texture image. */ + load_tex_image(usd_shader, tex_image); + + /* Connect to destination node input. */ + + /* Get the source socket name. */ + std::string source_socket_name = usd_source_name == usdtokens::a ? "Alpha" : "Color"; + + link_nodes(ntree, tex_image, source_socket_name.c_str(), dest_node, dest_socket_name); + + /* Connect the texture image node "Vector" input. */ + if (pxr::UsdShadeInput st_input = usd_shader.GetInput(usdtokens::st)) { + set_node_input(st_input, tex_image, "Vector", ntree, column, r_ctx); + } +} + +/* Load the texture image node's texture from the path given by the USD shader's + * file input value. */ +void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader, + bNode *tex_image) const +{ + if (!(usd_shader && tex_image && tex_image->type == SH_NODE_TEX_IMAGE)) { + return; + } + + /* Try to load the texture image. */ + pxr::UsdShadeInput file_input = usd_shader.GetInput(usdtokens::file); + + if (!file_input) { + std::cerr << "WARNING: Couldn't get file input for USD shader " << usd_shader.GetPath() + << std::endl; + return; + } + + pxr::VtValue file_val; + if (!file_input.Get(&file_val) || !file_val.IsHolding<pxr::SdfAssetPath>()) { + std::cerr << "WARNING: Couldn't get file input value for USD shader " << usd_shader.GetPath() + << std::endl; + return; + } + + const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>(); + std::string file_path = asset_path.GetResolvedPath(); + if (file_path.empty()) { + std::cerr << "WARNING: Couldn't resolve image asset '" << asset_path + << "' for Texture Image node." << std::endl; + return; + } + + const char *im_file = file_path.c_str(); + Image *image = BKE_image_load_exists(bmain_, im_file); + if (!image) { + std::cerr << "WARNING: Couldn't open image file '" << im_file << "' for Texture Image node." + << std::endl; + return; + } + + tex_image->id = &image->id; + + /* Set texture color space. + * TODO(makowalski): For now, just checking for RAW color space, + * assuming sRGB otherwise, but more complex logic might be + * required if the color space is "auto". */ + + pxr::TfToken color_space = get_source_color_space(usd_shader); + + if (color_space.IsEmpty()) { + color_space = file_input.GetAttr().GetColorSpace(); + } + + if (color_space == usdtokens::RAW || color_space == usdtokens::raw) { + STRNCPY(image->colorspace_settings.name, "Raw"); + } +} + +/* This function creates a Blender UV Map node, under the simplifying assumption that + * UsdPrimvarReader_float2 shaders output UV coordinates. + * TODO(makowalski): investigate supporting conversion to other Blender node types + * (e.g., Attribute Nodes) if needed. */ +void USDMaterialReader::convert_usd_primvar_reader_float2( + const pxr::UsdShadeShader &usd_shader, + const pxr::TfToken & /* usd_source_name */, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + const int column, + NodePlacementContext *r_ctx) const +{ + if (!usd_shader || !dest_node || !ntree || !dest_socket_name || !bmain_ || !r_ctx) { + return; + } + + float locx = 0.0f; + float locy = 0.0f; + compute_node_loc(column, &locx, &locy, r_ctx); + + /* Create the UV Map node. */ + bNode *uv_map = add_node(nullptr, ntree, SH_NODE_UVMAP, locx, locy); + + if (!uv_map) { + std::cerr << "ERROR: Couldn't create SH_NODE_UVMAP for node input " << dest_socket_name + << std::endl; + return; + } + + /* Set the texmap name. */ + pxr::UsdShadeInput varname_input = usd_shader.GetInput(usdtokens::varname); + if (varname_input) { + pxr::VtValue varname_val; + if (varname_input.Get(&varname_val) && varname_val.IsHolding<pxr::TfToken>()) { + std::string varname = varname_val.Get<pxr::TfToken>().GetString(); + if (!varname.empty()) { + NodeShaderUVMap *storage = (NodeShaderUVMap *)uv_map->storage; + BLI_strncpy(storage->uv_map, varname.c_str(), sizeof(storage->uv_map)); + } + } + } + + /* Connect to destination node input. */ + link_nodes(ntree, uv_map, "UV", dest_node, dest_socket_name); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_material.h b/source/blender/io/usd/intern/usd_reader_material.h new file mode 100644 index 00000000000..3e8fc675931 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_material.h @@ -0,0 +1,131 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 NVIDIA Corporation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" + +#include <pxr/usd/usdShade/material.h> + +struct Main; +struct Material; +struct bNode; +struct bNodeTree; + +namespace blender::io::usd { + +/* Helper struct used when arranging nodes in columns, keeping track the + * occupancy information for a given column. I.e., for column n, + * column_offsets[n] is the y-offset (from top to bottom) of the occupied + * region in that column. */ +struct NodePlacementContext { + float origx; + float origy; + std::vector<float> column_offsets; + const float horizontal_step; + const float vertical_step; + + NodePlacementContext(float in_origx, + float in_origy, + float in_horizontal_step = 300.0f, + float in_vertical_step = 300.0f) + : origx(in_origx), + origy(in_origy), + column_offsets(64, 0.0f), + horizontal_step(in_horizontal_step), + vertical_step(in_vertical_step) + { + } +}; + +/* Converts USD materials to Blender representation. */ + +/* By default, the USDMaterialReader creates a Blender material with + * the same name as the USD material. If the USD material has a + * UsdPreviewSurface source, the Blender material's viewport display + * color, roughness and metallic properties are set to the corresponding + * UsdPreoviewSurface inputs. + * + * If the Import USD Preview option is enabled, the current implementation + * converts UsdPreviewSurface to Blender nodes as follows: + * + * UsdPreviewSurface -> Pricipled BSDF + * UsdUVTexture -> Texture Image + Normal Map + * UsdPrimvarReader_float2 -> UV Map + * + * Limitations: arbitrary primvar readers or UsdTransform2d not yet + * supported. For UsdUVTexture, only the file, st and sourceColorSpace + * inputs are handled. + * + * TODO(makowalski): Investigate adding support for converting additional + * shaders and inputs. Supporting certain types of inputs, such as texture + * scale and bias, will probably require creating Blender Group nodes with + * the corresponding inputs. */ + +class USDMaterialReader { + protected: + USDImportParams params_; + + Main *bmain_; + + public: + USDMaterialReader(const USDImportParams ¶ms, Main *bmain); + + Material *add_material(const pxr::UsdShadeMaterial &usd_material) const; + + protected: + void import_usd_preview(Material *mtl, const pxr::UsdShadeShader &usd_shader) const; + + void set_principled_node_inputs(bNode *principled_node, + bNodeTree *ntree, + const pxr::UsdShadeShader &usd_shader) const; + + void set_node_input(const pxr::UsdShadeInput &usd_input, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + int column, + NodePlacementContext *r_ctx) const; + + void follow_connection(const pxr::UsdShadeInput &usd_input, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + int column, + NodePlacementContext *r_ctx) const; + + void convert_usd_uv_texture(const pxr::UsdShadeShader &usd_shader, + const pxr::TfToken &usd_source_name, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + int column, + NodePlacementContext *r_ctx) const; + + void load_tex_image(const pxr::UsdShadeShader &usd_shader, bNode *tex_image) const; + + void convert_usd_primvar_reader_float2(const pxr::UsdShadeShader &usd_shader, + const pxr::TfToken &usd_source_name, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + int column, + NodePlacementContext *r_ctx) const; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc new file mode 100644 index 00000000000..f13da4680e2 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -0,0 +1,853 @@ +/* + * 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. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation and + * NVIDIA Corporation. All rights reserved. + */ + +#include "usd_reader_mesh.h" +#include "usd_reader_material.h" + +#include "BKE_customdata.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_object.h" + +#include "BLI_math.h" +#include "BLI_math_geom.h" +#include "BLI_string.h" + +#include "DNA_customdata_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" + +#include "MEM_guardedalloc.h" + +#include <pxr/base/vt/array.h> +#include <pxr/base/vt/types.h> +#include <pxr/base/vt/value.h> +#include <pxr/usd/sdf/types.h> +#include <pxr/usd/usdGeom/mesh.h> +#include <pxr/usd/usdGeom/subset.h> +#include <pxr/usd/usdShade/materialBindingAPI.h> + +#include <iostream> + +namespace usdtokens { +/* Materials */ +static const pxr::TfToken st("st", pxr::TfToken::Immortal); +static const pxr::TfToken UVMap("UVMap", pxr::TfToken::Immortal); +static const pxr::TfToken Cd("Cd", pxr::TfToken::Immortal); +static const pxr::TfToken displayColor("displayColor", pxr::TfToken::Immortal); +static const pxr::TfToken normalsPrimvar("normals", pxr::TfToken::Immortal); +} // namespace usdtokens + +namespace utils { +/* Very similar to abc mesh utils. */ +static void build_mat_map(const Main *bmain, std::map<std::string, Material *> *r_mat_map) +{ + if (r_mat_map == nullptr) { + return; + } + + Material *material = static_cast<Material *>(bmain->materials.first); + + for (; material; material = static_cast<Material *>(material->id.next)) { + /* We have to do this because the stored material name is coming directly from usd. */ + (*r_mat_map)[pxr::TfMakeValidIdentifier(material->id.name + 2)] = material; + } +} + +static void assign_materials(Main *bmain, + Object *ob, + const std::map<pxr::SdfPath, int> &mat_index_map, + const USDImportParams ¶ms, + pxr::UsdStageRefPtr stage) +{ + if (!(stage && bmain && ob)) { + return; + } + + bool can_assign = true; + std::map<pxr::SdfPath, int>::const_iterator it = mat_index_map.begin(); + + int matcount = 0; + for (; it != mat_index_map.end(); ++it, matcount++) { + if (!BKE_object_material_slot_add(bmain, ob)) { + can_assign = false; + break; + } + } + + if (!can_assign) { + return; + } + + /* TODO(kevin): use global map? */ + std::map<std::string, Material *> mat_map; + build_mat_map(bmain, &mat_map); + + blender::io::usd::USDMaterialReader mat_reader(params, bmain); + + for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) { + std::string mat_name = it->first.GetName(); + + std::map<std::string, Material *>::iterator mat_iter = mat_map.find(mat_name); + + Material *assigned_mat = nullptr; + + if (mat_iter == mat_map.end()) { + /* Blender material doesn't exist, so create it now. */ + + /* Look up the USD material. */ + pxr::UsdPrim prim = stage->GetPrimAtPath(it->first); + pxr::UsdShadeMaterial usd_mat(prim); + + if (!usd_mat) { + std::cout << "WARNING: Couldn't construct USD material from prim " << it->first + << std::endl; + continue; + } + + /* Add the Blender material. */ + assigned_mat = mat_reader.add_material(usd_mat); + + if (!assigned_mat) { + std::cout << "WARNING: Couldn't create Blender material from USD material " << it->first + << std::endl; + continue; + } + + mat_map[mat_name] = assigned_mat; + } + else { + /* We found an existing Blender material. */ + assigned_mat = mat_iter->second; + } + + if (assigned_mat) { + BKE_object_material_assign(bmain, ob, assigned_mat, it->second, BKE_MAT_ASSIGN_OBDATA); + } + else { + /* This shouldn't happen. */ + std::cout << "WARNING: Couldn't assign material " << mat_name << std::endl; + } + } +} + +} // namespace utils + +static void *add_customdata_cb(Mesh *mesh, const char *name, const 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; +} + +namespace blender::io::usd { + +USDMeshReader::USDMeshReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDGeomReader(prim, import_params, settings), + mesh_prim_(prim), + is_left_handed_(false), + has_uvs_(false), + is_time_varying_(false), + is_initial_load_(false) +{ +} + +void USDMeshReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + Mesh *mesh = BKE_mesh_add(bmain, name_.c_str()); + + object_ = BKE_object_add_only_object(bmain, OB_MESH, name_.c_str()); + object_->data = mesh; +} + +void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime) +{ + Mesh *mesh = (Mesh *)object_->data; + + is_initial_load_ = true; + Mesh *read_mesh = this->read_mesh( + mesh, motionSampleTime, import_params_.mesh_read_flag, nullptr); + + is_initial_load_ = false; + 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. */ + short autosmooth = (read_mesh->flag & ME_AUTOSMOOTH); + BKE_mesh_nomain_to_mesh(read_mesh, mesh, object_, &CD_MASK_MESH, true); + mesh->flag |= autosmooth; + } + + readFaceSetsSample(bmain, mesh, motionSampleTime); + + if (mesh_prim_.GetPointsAttr().ValueMightBeTimeVarying()) { + is_time_varying_ = true; + } + + if (is_time_varying_) { + add_cache_modifier(); + } + + if (import_params_.import_subdiv) { + pxr::TfToken subdivScheme; + mesh_prim_.GetSubdivisionSchemeAttr().Get(&subdivScheme, motionSampleTime); + + if (subdivScheme == pxr::UsdGeomTokens->catmullClark) { + add_subdiv_modifier(); + } + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +bool USDMeshReader::valid() const +{ + return static_cast<bool>(mesh_prim_); +} + +bool USDMeshReader::topology_changed(Mesh *existing_mesh, const double motionSampleTime) +{ + /* TODO(makowalski): Is it the best strategy to cache the mesh + * geometry in this function? This needs to be revisited. */ + + mesh_prim_.GetFaceVertexIndicesAttr().Get(&face_indices_, motionSampleTime); + mesh_prim_.GetFaceVertexCountsAttr().Get(&face_counts_, motionSampleTime); + mesh_prim_.GetPointsAttr().Get(&positions_, motionSampleTime); + + /* TODO(makowalski): Reading normals probably doesn't belong in this function, + * as this is not required to determine if the topology has changed. */ + + /* If 'normals' and 'primvars:normals' are both specified, the latter has precedence. */ + pxr::UsdGeomPrimvar primvar = mesh_prim_.GetPrimvar(usdtokens::normalsPrimvar); + if (primvar.HasValue()) { + primvar.ComputeFlattened(&normals_, motionSampleTime); + normal_interpolation_ = primvar.GetInterpolation(); + } + else { + mesh_prim_.GetNormalsAttr().Get(&normals_, motionSampleTime); + normal_interpolation_ = mesh_prim_.GetNormalsInterpolation(); + } + + return positions_.size() != existing_mesh->totvert || + face_counts_.size() != existing_mesh->totpoly || + face_indices_.size() != existing_mesh->totloop; +} + +void USDMeshReader::read_mpolys(Mesh *mesh) +{ + MPoly *mpolys = mesh->mpoly; + MLoop *mloops = mesh->mloop; + + int loop_index = 0; + + for (int i = 0; i < face_counts_.size(); i++) { + const int face_size = face_counts_[i]; + + MPoly &poly = mpolys[i]; + poly.loopstart = loop_index; + poly.totloop = face_size; + poly.mat_nr = 0; + + /* Polygons are always assumed to be smooth-shaded. If the mesh should be flat-shaded, + * this is encoded in custom loop normals. */ + poly.flag |= ME_SMOOTH; + + if (is_left_handed_) { + int loop_end_index = loop_index + (face_size - 1); + for (int f = 0; f < face_size; ++f, ++loop_index) { + mloops[loop_index].v = face_indices_[loop_end_index - f]; + } + } + else { + for (int f = 0; f < face_size; ++f, ++loop_index) { + mloops[loop_index].v = face_indices_[loop_index]; + } + } + } + + BKE_mesh_calc_edges(mesh, false, false); +} + +void USDMeshReader::read_uvs(Mesh *mesh, const double motionSampleTime, const bool load_uvs) +{ + unsigned int loop_index = 0; + unsigned int rev_loop_index = 0; + unsigned int uv_index = 0; + + const CustomData *ldata = &mesh->ldata; + + struct UVSample { + pxr::VtVec2fArray uvs; + pxr::TfToken interpolation; + }; + + std::vector<UVSample> uv_primvars(ldata->totlayer); + + if (has_uvs_) { + for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) { + const CustomDataLayer *layer = &ldata->layers[layer_idx]; + std::string layer_name = std::string(layer->name); + if (layer->type != CD_MLOOPUV) { + continue; + } + + pxr::TfToken uv_token; + + /* If first time seeing uv token, store in map of <layer->uid, TfToken> */ + if (uv_token_map_.find(layer_name) == uv_token_map_.end()) { + uv_token = pxr::TfToken(layer_name); + uv_token_map_.insert(std::make_pair(layer_name, uv_token)); + } + else { + uv_token = uv_token_map_.at(layer_name); + } + + /* Early out if no token found, this should never happen */ + if (uv_token.IsEmpty()) { + continue; + } + /* Early out if not first load and uvs arent animated. */ + if (!load_uvs && primvar_varying_map_.find(uv_token) != primvar_varying_map_.end() && + !primvar_varying_map_.at(uv_token)) { + continue; + } + + /* Early out if mesh doesn't have primvar. */ + if (!mesh_prim_.HasPrimvar(uv_token)) { + continue; + } + + if (pxr::UsdGeomPrimvar uv_primvar = mesh_prim_.GetPrimvar(uv_token)) { + uv_primvar.ComputeFlattened(&uv_primvars[layer_idx].uvs, motionSampleTime); + uv_primvars[layer_idx].interpolation = uv_primvar.GetInterpolation(); + } + } + } + + for (int i = 0; i < face_counts_.size(); i++) { + const int face_size = face_counts_[i]; + + rev_loop_index = loop_index + (face_size - 1); + + for (int f = 0; f < face_size; f++, loop_index++, rev_loop_index--) { + + for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) { + const CustomDataLayer *layer = &ldata->layers[layer_idx]; + if (layer->type != CD_MLOOPUV) { + continue; + } + + /* Early out if mismatched layer sizes. */ + if (layer_idx > uv_primvars.size()) { + continue; + } + + /* Early out if no uvs loaded. */ + if (uv_primvars[layer_idx].uvs.empty()) { + continue; + } + + const UVSample &sample = uv_primvars[layer_idx]; + + if (!(sample.interpolation == pxr::UsdGeomTokens->faceVarying || + sample.interpolation == pxr::UsdGeomTokens->vertex)) { + std::cerr << "WARNING: unexpected interpolation type " << sample.interpolation + << " for uv " << layer->name << std::endl; + continue; + } + + /* For Vertex interpolation, use the vertex index. */ + int usd_uv_index = sample.interpolation == pxr::UsdGeomTokens->vertex ? + mesh->mloop[loop_index].v : + loop_index; + + if (usd_uv_index >= sample.uvs.size()) { + std::cerr << "WARNING: out of bounds uv index " << usd_uv_index << " for uv " + << layer->name << " of size " << sample.uvs.size() << std::endl; + continue; + } + + MLoopUV *mloopuv = static_cast<MLoopUV *>(layer->data); + if (is_left_handed_) { + uv_index = rev_loop_index; + } + else { + uv_index = loop_index; + } + mloopuv[uv_index].uv[0] = sample.uvs[usd_uv_index][0]; + mloopuv[uv_index].uv[1] = sample.uvs[usd_uv_index][1]; + } + } + } +} + +void USDMeshReader::read_colors(Mesh *mesh, const double motionSampleTime) +{ + if (!(mesh && mesh_prim_ && mesh->totloop > 0)) { + return; + } + + /* Early out if we read the display color before and if this attribute isn't animated. */ + if (primvar_varying_map_.find(usdtokens::displayColor) != primvar_varying_map_.end() && + !primvar_varying_map_.at(usdtokens::displayColor)) { + return; + } + + pxr::UsdGeomPrimvar color_primvar = mesh_prim_.GetDisplayColorPrimvar(); + + if (!color_primvar.HasValue()) { + return; + } + + pxr::TfToken interp = color_primvar.GetInterpolation(); + + if (interp == pxr::UsdGeomTokens->varying) { + std::cerr << "WARNING: Unsupported varying interpolation for display colors\n" << std::endl; + return; + } + + if (primvar_varying_map_.find(usdtokens::displayColor) == primvar_varying_map_.end()) { + bool might_be_time_varying = color_primvar.ValueMightBeTimeVarying(); + primvar_varying_map_.insert(std::make_pair(usdtokens::displayColor, might_be_time_varying)); + if (might_be_time_varying) { + is_time_varying_ = true; + } + } + + pxr::VtArray<pxr::GfVec3f> display_colors; + + if (!color_primvar.ComputeFlattened(&display_colors, motionSampleTime)) { + std::cerr << "WARNING: Couldn't compute display colors\n" << std::endl; + return; + } + + if ((interp == pxr::UsdGeomTokens->faceVarying && display_colors.size() != mesh->totloop) || + (interp == pxr::UsdGeomTokens->vertex && display_colors.size() != mesh->totvert) || + (interp == pxr::UsdGeomTokens->constant && display_colors.size() != 1) || + (interp == pxr::UsdGeomTokens->uniform && display_colors.size() != mesh->totpoly)) { + std::cerr << "WARNING: display colors count mismatch\n" << std::endl; + return; + } + + void *cd_ptr = add_customdata_cb(mesh, "displayColors", CD_MLOOPCOL); + + if (!cd_ptr) { + std::cerr << "WARNING: Couldn't add displayColors custom data.\n"; + return; + } + + MLoopCol *colors = static_cast<MLoopCol *>(cd_ptr); + + mesh->mloopcol = colors; + + MPoly *poly = mesh->mpoly; + + for (int i = 0, e = mesh->totpoly; i < e; ++i, ++poly) { + for (int j = 0; j < poly->totloop; ++j) { + int loop_index = poly->loopstart + j; + + /* Default for constant varying interpolation. */ + int usd_index = 0; + + if (interp == pxr::UsdGeomTokens->vertex) { + usd_index = mesh->mloop[loop_index].v; + } + else if (interp == pxr::UsdGeomTokens->faceVarying) { + usd_index = poly->loopstart; + if (is_left_handed_) { + usd_index += poly->totloop - 1 - j; + } + else { + usd_index += j; + } + } + else if (interp == pxr::UsdGeomTokens->uniform) { + /* Uniform varying uses the poly index. */ + usd_index = i; + } + + if (usd_index >= display_colors.size()) { + continue; + } + + colors[loop_index].r = unit_float_to_uchar_clamp(display_colors[usd_index][0]); + colors[loop_index].g = unit_float_to_uchar_clamp(display_colors[usd_index][1]); + colors[loop_index].b = unit_float_to_uchar_clamp(display_colors[usd_index][2]); + colors[loop_index].a = unit_float_to_uchar_clamp(1.0); + } + } +} + +void USDMeshReader::process_normals_vertex_varying(Mesh *mesh) +{ + if (!mesh) { + return; + } + + if (normals_.empty()) { + BKE_mesh_calc_normals(mesh); + return; + } + + if (normals_.size() != mesh->totvert) { + std::cerr << "WARNING: vertex varying normals count mismatch for mesh " << prim_path_ + << std::endl; + BKE_mesh_calc_normals(mesh); + return; + } + + for (int i = 0; i < normals_.size(); i++) { + MVert &mvert = mesh->mvert[i]; + normal_float_to_short_v3(mvert.no, normals_[i].data()); + } +} + +void USDMeshReader::process_normals_face_varying(Mesh *mesh) +{ + if (normals_.empty()) { + BKE_mesh_calc_normals(mesh); + return; + } + + /* Check for normals count mismatches to prevent crashes. */ + if (normals_.size() != mesh->totloop) { + std::cerr << "WARNING: loop normal count mismatch for mesh " << mesh->id.name << std::endl; + BKE_mesh_calc_normals(mesh); + return; + } + + mesh->flag |= ME_AUTOSMOOTH; + + long int loop_count = normals_.size(); + + float(*lnors)[3] = static_cast<float(*)[3]>( + MEM_malloc_arrayN(loop_count, sizeof(float[3]), "USD::FaceNormals")); + + MPoly *mpoly = mesh->mpoly; + + for (int i = 0, e = mesh->totpoly; i < e; ++i, ++mpoly) { + for (int j = 0; j < mpoly->totloop; j++) { + int blender_index = mpoly->loopstart + j; + + int usd_index = mpoly->loopstart; + if (is_left_handed_) { + usd_index += mpoly->totloop - 1 - j; + } + else { + usd_index += j; + } + + lnors[blender_index][0] = normals_[usd_index][0]; + lnors[blender_index][1] = normals_[usd_index][1]; + lnors[blender_index][2] = normals_[usd_index][2]; + } + } + BKE_mesh_set_custom_normals(mesh, lnors); + + MEM_freeN(lnors); +} + +/* Set USD uniform (per-face) normals as Blender loop normals. */ +void USDMeshReader::process_normals_uniform(Mesh *mesh) +{ + if (normals_.empty()) { + BKE_mesh_calc_normals(mesh); + return; + } + + /* Check for normals count mismatches to prevent crashes. */ + if (normals_.size() != mesh->totpoly) { + std::cerr << "WARNING: uniform normal count mismatch for mesh " << mesh->id.name << std::endl; + BKE_mesh_calc_normals(mesh); + return; + } + + float(*lnors)[3] = static_cast<float(*)[3]>( + MEM_malloc_arrayN(mesh->totloop, sizeof(float[3]), "USD::FaceNormals")); + + MPoly *mpoly = mesh->mpoly; + + for (int i = 0, e = mesh->totpoly; i < e; ++i, ++mpoly) { + + for (int j = 0; j < mpoly->totloop; j++) { + int loop_index = mpoly->loopstart + j; + lnors[loop_index][0] = normals_[i][0]; + lnors[loop_index][1] = normals_[i][1]; + lnors[loop_index][2] = normals_[i][2]; + } + } + + mesh->flag |= ME_AUTOSMOOTH; + BKE_mesh_set_custom_normals(mesh, lnors); + + MEM_freeN(lnors); +} + +void USDMeshReader::read_mesh_sample(ImportSettings *settings, + Mesh *mesh, + const double motionSampleTime, + const bool new_mesh) +{ + /* Note that for new meshes we always want to read verts and polys, + * regradless of the value of the read_flag, to avoid a crash downstream + * in code that expect this data to be there. */ + + if (new_mesh || (settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { + for (int i = 0; i < positions_.size(); i++) { + MVert &mvert = mesh->mvert[i]; + mvert.co[0] = positions_[i][0]; + mvert.co[1] = positions_[i][1]; + mvert.co[2] = positions_[i][2]; + } + } + + if (new_mesh || (settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) { + read_mpolys(mesh); + if (normal_interpolation_ == pxr::UsdGeomTokens->faceVarying) { + process_normals_face_varying(mesh); + } + else if (normal_interpolation_ == pxr::UsdGeomTokens->uniform) { + process_normals_uniform(mesh); + } + else { + /* Default */ + BKE_mesh_calc_normals(mesh); + } + } + + /* Process point normals after reading polys. This + * is important in the case where the normals are empty + * and we invoke BKE_mesh_calc_normals(mesh), which requires + * edges to be defined. */ + if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0 && + normal_interpolation_ == pxr::UsdGeomTokens->vertex) { + process_normals_vertex_varying(mesh); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) { + read_uvs(mesh, motionSampleTime, new_mesh); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_COLOR) != 0) { + read_colors(mesh, motionSampleTime); + } +} + +void USDMeshReader::assign_facesets_to_mpoly(double motionSampleTime, + MPoly *mpoly, + const int /* totpoly */, + std::map<pxr::SdfPath, int> *r_mat_map) +{ + if (r_mat_map == nullptr) { + return; + } + + /* Find the geom subsets that have bound materials. + * We don't call pxr::UsdShadeMaterialBindingAPI::GetMaterialBindSubsets() + * because this function returns only those subsets that are in the 'materialBind' + * family, but, in practice, applications (like Houdini) might export subsets + * in different families that are bound to materials. + * TODO(makowalski): Reassess if the above is the best approach. */ + const std::vector<pxr::UsdGeomSubset> subsets = pxr::UsdGeomSubset::GetAllGeomSubsets( + mesh_prim_); + + int current_mat = 0; + if (!subsets.empty()) { + for (const pxr::UsdGeomSubset &subset : subsets) { + pxr::UsdShadeMaterialBindingAPI subset_api = pxr::UsdShadeMaterialBindingAPI( + subset.GetPrim()); + + pxr::UsdShadeMaterial subset_mtl = subset_api.ComputeBoundMaterial(); + + if (!subset_mtl) { + continue; + } + + pxr::SdfPath subset_mtl_path = subset_mtl.GetPath(); + + if (subset_mtl_path.IsEmpty()) { + continue; + } + + if (r_mat_map->find(subset_mtl_path) == r_mat_map->end()) { + (*r_mat_map)[subset_mtl_path] = 1 + current_mat++; + } + + const int mat_idx = (*r_mat_map)[subset_mtl_path] - 1; + + pxr::UsdAttribute indicesAttribute = subset.GetIndicesAttr(); + pxr::VtIntArray indices; + indicesAttribute.Get(&indices, motionSampleTime); + + for (int i = 0; i < indices.size(); i++) { + MPoly &poly = mpoly[indices[i]]; + poly.mat_nr = mat_idx; + } + } + } + + if (r_mat_map->empty()) { + pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(prim_); + + if (pxr::UsdShadeMaterial mtl = api.ComputeBoundMaterial()) { + + pxr::SdfPath mtl_path = mtl.GetPath(); + + if (!mtl_path.IsEmpty()) { + r_mat_map->insert(std::make_pair(mtl.GetPath(), 1)); + } + } + } +} + +void USDMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const double motionSampleTime) +{ + if (!import_params_.import_materials) { + return; + } + + std::map<pxr::SdfPath, int> mat_map; + assign_facesets_to_mpoly(motionSampleTime, mesh->mpoly, mesh->totpoly, &mat_map); + utils::assign_materials(bmain, object_, mat_map, this->import_params_, this->prim_.GetStage()); +} + +Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh, + const double motionSampleTime, + const int read_flag, + const char ** /* err_str */) +{ + if (!mesh_prim_) { + return existing_mesh; + } + + mesh_prim_.GetOrientationAttr().Get(&orientation_); + if (orientation_ == pxr::UsdGeomTokens->leftHanded) { + is_left_handed_ = true; + } + + std::vector<pxr::TfToken> uv_tokens; + + /* Currently we only handle UV primvars. */ + if (read_flag & MOD_MESHSEQ_READ_UV) { + + std::vector<pxr::UsdGeomPrimvar> primvars = mesh_prim_.GetPrimvars(); + + for (pxr::UsdGeomPrimvar p : primvars) { + + pxr::TfToken name = p.GetPrimvarName(); + pxr::SdfValueTypeName type = p.GetTypeName(); + + bool is_uv = false; + + /* Assume all uvs are stored in one of these primvar types */ + if (type == pxr::SdfValueTypeNames->TexCoord2hArray || + type == pxr::SdfValueTypeNames->TexCoord2fArray || + type == pxr::SdfValueTypeNames->TexCoord2dArray) { + is_uv = true; + } + /* In some cases, the st primvar is stored as float2 values. */ + else if (name == usdtokens::st && type == pxr::SdfValueTypeNames->Float2Array) { + is_uv = true; + } + + if (is_uv) { + + pxr::TfToken interp = p.GetInterpolation(); + + if (!(interp == pxr::UsdGeomTokens->faceVarying || interp == pxr::UsdGeomTokens->vertex)) { + continue; + } + + uv_tokens.push_back(p.GetBaseName()); + has_uvs_ = true; + + /* Record whether the UVs might be time varying. */ + if (primvar_varying_map_.find(name) == primvar_varying_map_.end()) { + bool might_be_time_varying = p.ValueMightBeTimeVarying(); + primvar_varying_map_.insert(std::make_pair(name, might_be_time_varying)); + if (might_be_time_varying) { + is_time_varying_ = true; + } + } + } + } + } + + Mesh *active_mesh = existing_mesh; + bool new_mesh = false; + + /* TODO(makowalski): inmplement the optimization of only updating the mesh points when + * the topology is consistent, as in the Alembic importer. */ + + ImportSettings settings; + settings.read_flag |= read_flag; + + if (topology_changed(existing_mesh, motionSampleTime)) { + new_mesh = true; + active_mesh = BKE_mesh_new_nomain_from_template( + existing_mesh, positions_.size(), 0, 0, face_indices_.size(), face_counts_.size()); + + for (pxr::TfToken token : uv_tokens) { + void *cd_ptr = add_customdata_cb(active_mesh, token.GetText(), CD_MLOOPUV); + active_mesh->mloopuv = static_cast<MLoopUV *>(cd_ptr); + } + } + + read_mesh_sample(&settings, active_mesh, motionSampleTime, new_mesh || is_initial_load_); + + if (new_mesh) { + /* Here we assume that the number of materials doesn't change, i.e. that + * the material slots that were created when the object was loaded from + * USD are still valid now. */ + size_t num_polys = active_mesh->totpoly; + if (num_polys > 0 && import_params_.import_materials) { + std::map<pxr::SdfPath, int> mat_map; + assign_facesets_to_mpoly(motionSampleTime, active_mesh->mpoly, num_polys, &mat_map); + } + } + + return active_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 new file mode 100644 index 00000000000..54ad144d191 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_mesh.h @@ -0,0 +1,95 @@ +/* + * 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. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation and + * NVIDIA Corporation. All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_geom.h" + +#include "pxr/usd/usdGeom/mesh.h" + +struct MPoly; + +namespace blender::io::usd { + +class USDMeshReader : public USDGeomReader { + private: + pxr::UsdGeomMesh mesh_prim_; + + std::unordered_map<std::string, pxr::TfToken> uv_token_map_; + std::map<const pxr::TfToken, bool> primvar_varying_map_; + + /* TODO(makowalski): Is it the best strategy to cache the + * mesh geometry in the following members? It appears these + * arrays are never cleared, so this might bloat memory. */ + pxr::VtIntArray face_indices_; + pxr::VtIntArray face_counts_; + pxr::VtVec3fArray positions_; + pxr::VtVec3fArray normals_; + + pxr::TfToken normal_interpolation_; + pxr::TfToken orientation_; + bool is_left_handed_; + bool has_uvs_; + bool is_time_varying_; + + /* This is to ensure we load all data once, because we reuse the read_mesh function + * in the mesh seq modifier, and in initial load. Ideally, a better fix would be + * implemented. Note this will break if faces or positions vary. */ + bool is_initial_load_; + + public: + USDMeshReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings); + + bool valid() const override; + + 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; + + bool topology_changed(Mesh *existing_mesh, double motionSampleTime) override; + + private: + void process_normals_vertex_varying(Mesh *mesh); + void process_normals_face_varying(Mesh *mesh); + void process_normals_uniform(Mesh *mesh); + void readFaceSetsSample(Main *bmain, Mesh *mesh, double motionSampleTime); + void assign_facesets_to_mpoly(double motionSampleTime, + struct MPoly *mpoly, + int totpoly, + std::map<pxr::SdfPath, int> *r_mat_map); + + void read_mpolys(Mesh *mesh); + void read_uvs(Mesh *mesh, double motionSampleTime, bool load_uvs = false); + void read_colors(Mesh *mesh, double motionSampleTime); + + void read_mesh_sample(ImportSettings *settings, + Mesh *mesh, + double motionSampleTime, + bool new_mesh); +}; + +} // 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 new file mode 100644 index 00000000000..9b30b524729 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_nurbs.cc @@ -0,0 +1,256 @@ +/* + * 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. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_nurbs.h" + +#include "BKE_curve.h" +#include "BKE_mesh.h" +#include "BKE_object.h" + +#include "BLI_listbase.h" + +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "MEM_guardedalloc.h" + +#include <pxr/base/vt/array.h> +#include <pxr/base/vt/types.h> +#include <pxr/base/vt/value.h> +#include <pxr/usd/sdf/types.h> + +#include <pxr/usd/usdGeom/curves.h> + +static bool set_knots(const pxr::VtDoubleArray &knots, float *&nu_knots) +{ + if (knots.empty()) { + return false; + } + + /* Skip first and last knots, as they are used for padding. */ + const size_t num_knots = knots.size(); + nu_knots = static_cast<float *>(MEM_callocN(num_knots * sizeof(float), __func__)); + + for (size_t i = 0; i < num_knots; i++) { + nu_knots[i] = (float)knots[i]; + } + + return true; +} + +namespace blender::io::usd { + +void USDNurbsReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE); + + curve_->flag |= CU_DEFORM_FILL | CU_3D; + curve_->actvert = CU_ACT_NONE; + curve_->resolu = 2; + + object_ = BKE_object_add_only_object(bmain, OB_CURVE, name_.c_str()); + object_->data = curve_; +} + +void USDNurbsReader::read_object_data(Main *bmain, const double motionSampleTime) +{ + Curve *cu = (Curve *)object_->data; + read_curve_sample(cu, motionSampleTime); + + if (curve_prim_.GetPointsAttr().ValueMightBeTimeVarying()) { + add_cache_modifier(); + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +void USDNurbsReader::read_curve_sample(Curve *cu, const double motionSampleTime) +{ + curve_prim_ = pxr::UsdGeomNurbsCurves(prim_); + + pxr::UsdAttribute widthsAttr = curve_prim_.GetWidthsAttr(); + pxr::UsdAttribute vertexAttr = curve_prim_.GetCurveVertexCountsAttr(); + pxr::UsdAttribute pointsAttr = curve_prim_.GetPointsAttr(); + + pxr::VtIntArray usdCounts; + vertexAttr.Get(&usdCounts, motionSampleTime); + + pxr::VtVec3fArray usdPoints; + pointsAttr.Get(&usdPoints, motionSampleTime); + + pxr::VtFloatArray usdWidths; + widthsAttr.Get(&usdWidths, motionSampleTime); + + pxr::VtIntArray orders; + curve_prim_.GetOrderAttr().Get(&orders, motionSampleTime); + + pxr::VtDoubleArray knots; + curve_prim_.GetKnotsAttr().Get(&knots, motionSampleTime); + + pxr::VtVec3fArray usdNormals; + curve_prim_.GetNormalsAttr().Get(&usdNormals, motionSampleTime); + + /* If normals, extrude, else bevel. + * Perhaps to be replaced by Blender USD Schema. */ + if (!usdNormals.empty()) { + /* Set extrusion to 1. */ + curve_->ext1 = 1.0f; + } + else { + /* Set bevel depth to 1. */ + curve_->ext2 = 1.0f; + } + + size_t idx = 0; + for (size_t i = 0; i < usdCounts.size(); i++) { + const int num_verts = usdCounts[i]; + + Nurb *nu = static_cast<Nurb *>(MEM_callocN(sizeof(Nurb), __func__)); + nu->flag = CU_SMOOTH; + nu->type = CU_NURBS; + + nu->resolu = cu->resolu; + nu->resolv = cu->resolv; + + nu->pntsu = num_verts; + nu->pntsv = 1; + + if (i < orders.size()) { + nu->orderu = static_cast<short>(orders[i]); + } + else { + nu->orderu = 4; + nu->orderv = 4; + } + + /* TODO(makowalski): investigate setting Cyclic U and Endpoint U options. */ +#if 0 + if (knots.size() > 3) { + if ((knots[0] == knots[1]) && (knots[knots.size()] == knots[knots.size() - 1])) { + nu->flagu |= CU_NURB_ENDPOINT; + } else { + nu->flagu |= CU_NURB_CYCLIC; + } + } +#endif + + float weight = 1.0f; + + nu->bp = static_cast<BPoint *>(MEM_callocN(sizeof(BPoint) * nu->pntsu, __func__)); + BPoint *bp = nu->bp; + + for (int j = 0; j < nu->pntsu; j++, bp++, idx++) { + bp->vec[0] = (float)usdPoints[idx][0]; + bp->vec[1] = (float)usdPoints[idx][1]; + bp->vec[2] = (float)usdPoints[idx][2]; + bp->vec[3] = weight; + bp->f1 = SELECT; + bp->weight = weight; + + float radius = 0.1f; + if (idx < usdWidths.size()) { + radius = usdWidths[idx]; + } + + bp->radius = radius; + } + + if (!set_knots(knots, nu->knotsu)) { + BKE_nurb_knot_calc_u(nu); + } + + BLI_addtail(BKE_curve_nurbs_get(cu), nu); + } +} + +Mesh *USDNurbsReader::read_mesh(struct Mesh * /* existing_mesh */, + const double motionSampleTime, + const int /* read_flag */, + const char ** /* err_str */) +{ + pxr::UsdGeomCurves curve_prim_(prim_); + + pxr::UsdAttribute widthsAttr = curve_prim_.GetWidthsAttr(); + pxr::UsdAttribute vertexAttr = curve_prim_.GetCurveVertexCountsAttr(); + pxr::UsdAttribute pointsAttr = curve_prim_.GetPointsAttr(); + + 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; + } + } + } + + if (!same_topology) { + BKE_nurbList_free(&curve->nurb); + read_curve_sample(curve, motionSampleTime); + } + 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]; + } + } + } + } + + 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 new file mode 100644 index 00000000000..33a4acf503e --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_nurbs.h @@ -0,0 +1,61 @@ +/* + * 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. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_geom.h" + +#include "pxr/usd/usdGeom/nurbsCurves.h" + +struct Curve; + +namespace blender::io::usd { + +class USDNurbsReader : public USDGeomReader { + protected: + pxr::UsdGeomNurbsCurves curve_prim_; + Curve *curve_; + + public: + USDNurbsReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDGeomReader(prim, import_params, settings), curve_prim_(prim), curve_(nullptr) + { + } + + bool valid() const override + { + return static_cast<bool>(curve_prim_); + } + + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; + + 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; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_prim.cc b/source/blender/io/usd/intern/usd_reader_prim.cc new file mode 100644 index 00000000000..abd70f49f23 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_prim.cc @@ -0,0 +1,80 @@ +/* + * 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. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_prim.h" + +#include "BLI_utildefines.h" + +namespace blender::io::usd { + +USDPrimReader::USDPrimReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : name_(prim.GetName().GetString()), + prim_path_(prim.GetPrimPath().GetString()), + object_(nullptr), + prim_(prim), + import_params_(import_params), + parent_reader_(nullptr), + settings_(&settings), + refcount_(0) +{ +} + +USDPrimReader::~USDPrimReader() = default; + +const pxr::UsdPrim &USDPrimReader::prim() const +{ + return prim_; +} + +Object *USDPrimReader::object() const +{ + return object_; +} + +void USDPrimReader::object(Object *ob) +{ + object_ = ob; +} + +bool USDPrimReader::valid() const +{ + return prim_.IsValid(); +} + +int USDPrimReader::refcount() const +{ + return refcount_; +} + +void USDPrimReader::incref() +{ + refcount_++; +} + +void USDPrimReader::decref() +{ + refcount_--; + BLI_assert(refcount_ >= 0); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_prim.h b/source/blender/io/usd/intern/usd_reader_prim.h new file mode 100644 index 00000000000..5aff52f011f --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_prim.h @@ -0,0 +1,131 @@ +/* + * 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. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" + +#include <pxr/usd/usd/prim.h> + +struct Main; +struct Object; + +namespace blender::io::usd { + +struct ImportSettings { + bool do_convert_mat; + float conversion_mat[4][4]; + + int from_up; + int from_forward; + float scale; + bool is_sequence; + bool set_frame_range; + + /* Length and frame offset of file sequences. */ + int sequence_len; + int sequence_offset; + + /* From MeshSeqCacheModifierData.read_flag */ + int read_flag; + + bool validate_meshes; + + CacheFile *cache_file; + + ImportSettings() + : do_convert_mat(false), + from_up(0), + from_forward(0), + scale(1.0f), + is_sequence(false), + set_frame_range(false), + sequence_len(1), + sequence_offset(0), + read_flag(0), + validate_meshes(false), + cache_file(NULL) + { + } +}; + +/* Most generic USD Reader. */ + +class USDPrimReader { + + protected: + std::string name_; + std::string prim_path_; + Object *object_; + pxr::UsdPrim prim_; + const USDImportParams &import_params_; + USDPrimReader *parent_reader_; + const ImportSettings *settings_; + int refcount_; + + public: + USDPrimReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings); + virtual ~USDPrimReader(); + + const pxr::UsdPrim &prim() const; + + virtual bool valid() const; + + virtual void create_object(Main *bmain, double motionSampleTime) = 0; + virtual void read_object_data(Main * /* bmain */, double /* motionSampleTime */){}; + + Object *object() const; + void object(Object *ob); + + USDPrimReader *parent() const + { + return parent_reader_; + } + void parent(USDPrimReader *parent) + { + parent_reader_ = parent; + } + + /* Since readers might be referenced through handles + * maintained by modifiers and constraints, we provide + * a reference count to facilitate managing the object + * lifetime. + * TODO(makowalski): investigate transitioning to using + * smart pointers for readers, or, alternatively look into + * making the lifetime management more robust, e.g., by + * making the destructors protected and implementing deletion + * in decref(), etc. */ + int refcount() const; + void incref(); + void decref(); + + const std::string &name() const + { + return name_; + } + const std::string &prim_path() const + { + return prim_path_; + } +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc new file mode 100644 index 00000000000..d3693f783ec --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -0,0 +1,324 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation and + * NVIDIA Corporation. All rights reserved. + */ + +#include "usd_reader_stage.h" +#include "usd_reader_camera.h" +#include "usd_reader_curve.h" +#include "usd_reader_instance.h" +#include "usd_reader_light.h" +#include "usd_reader_mesh.h" +#include "usd_reader_nurbs.h" +#include "usd_reader_prim.h" +#include "usd_reader_volume.h" +#include "usd_reader_xform.h" + +#include <pxr/pxr.h> +#include <pxr/usd/usdGeom/camera.h> +#include <pxr/usd/usdGeom/curves.h> +#include <pxr/usd/usdGeom/mesh.h> +#include <pxr/usd/usdGeom/nurbsCurves.h> +#include <pxr/usd/usdGeom/scope.h> +#include <pxr/usd/usdLux/light.h> + +#include <iostream> + +namespace blender::io::usd { + +USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage, + const USDImportParams ¶ms, + const ImportSettings &settings) + : stage_(stage), params_(params), settings_(settings) +{ +} + +USDStageReader::~USDStageReader() +{ + clear_readers(); +} + +bool USDStageReader::valid() const +{ + return stage_; +} + +USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim) +{ + if (params_.import_cameras && prim.IsA<pxr::UsdGeomCamera>()) { + return new USDCameraReader(prim, params_, settings_); + } + if (params_.import_curves && prim.IsA<pxr::UsdGeomBasisCurves>()) { + return new USDCurvesReader(prim, params_, settings_); + } + if (params_.import_curves && prim.IsA<pxr::UsdGeomNurbsCurves>()) { + return new USDNurbsReader(prim, params_, settings_); + } + if (params_.import_meshes && prim.IsA<pxr::UsdGeomMesh>()) { + return new USDMeshReader(prim, params_, settings_); + } + if (params_.import_lights && prim.IsA<pxr::UsdLuxLight>()) { + return new USDLightReader(prim, params_, settings_); + } + if (params_.import_volumes && prim.IsA<pxr::UsdVolVolume>()) { + return new USDVolumeReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdGeomImageable>()) { + return new USDXformReader(prim, params_, settings_); + } + + return nullptr; +} + +USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim) +{ + if (prim.IsA<pxr::UsdGeomCamera>()) { + return new USDCameraReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdGeomBasisCurves>()) { + return new USDCurvesReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdGeomNurbsCurves>()) { + return new USDNurbsReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdGeomMesh>()) { + return new USDMeshReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdLuxLight>()) { + return new USDLightReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdVolVolume>()) { + return new USDVolumeReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdGeomImageable>()) { + return new USDXformReader(prim, params_, settings_); + } + return nullptr; +} + +/* Returns true if the given prim should be included in the + * traversal based on the import options and the prim's visibility + * attribute. Note that the prim will be trivially included + * if it has no visibility attribute or if the visibility + * is inherited. */ +bool USDStageReader::include_by_visibility(const pxr::UsdGeomImageable &imageable) const +{ + if (!params_.import_visible_only) { + /* Invisible prims are allowed. */ + return true; + } + + pxr::UsdAttribute visibility_attr = imageable.GetVisibilityAttr(); + + if (!visibility_attr) { + /* No visibility attribute, so allow. */ + return true; + } + + /* Include if the prim has an animating visibility attribute or is not invisible. */ + + if (visibility_attr.ValueMightBeTimeVarying()) { + return true; + } + + pxr::TfToken visibility; + visibility_attr.Get(&visibility); + return visibility != pxr::UsdGeomTokens->invisible; +} + +/* Returns true if the given prim should be included in the + * traversal based on the import options and the prim's purpose + * attribute. E.g., return false (to exclude the prim) if the prim + * represents guide geometry and the 'Import Guide' option is + * toggled off. */ +bool USDStageReader::include_by_purpose(const pxr::UsdGeomImageable &imageable) const +{ + if (params_.import_guide && params_.import_proxy && params_.import_render) { + /* The options allow any purpose, so we trivially include the prim. */ + return true; + } + + pxr::UsdAttribute purpose_attr = imageable.GetPurposeAttr(); + + if (!purpose_attr) { + /* No purpose attribute, so trivially include the prim. */ + return true; + } + + pxr::TfToken purpose; + purpose_attr.Get(&purpose); + + if (purpose == pxr::UsdGeomTokens->guide) { + return params_.import_guide; + } + if (purpose == pxr::UsdGeomTokens->proxy) { + return params_.import_proxy; + } + if (purpose == pxr::UsdGeomTokens->render) { + return params_.import_render; + } + + return true; +} + +/* Determine if the given reader can use the parent of the encapsulated USD prim + * to compute the Blender object's transform. If so, the reader is appropriately + * flagged and the function returns true. Otherwise, the function returns false. */ +static bool merge_with_parent(USDPrimReader *reader) +{ + USDXformReader *xform_reader = dynamic_cast<USDXformReader *>(reader); + + if (!xform_reader) { + return false; + } + + /* Check if the Xform reader is already merged. */ + if (xform_reader->use_parent_xform()) { + return false; + } + + /* Only merge if the parent is an Xform. */ + if (!xform_reader->prim().GetParent().IsA<pxr::UsdGeomXform>()) { + return false; + } + + /* Don't merge Xform and Scope prims. */ + if (xform_reader->prim().IsA<pxr::UsdGeomXform>() || + xform_reader->prim().IsA<pxr::UsdGeomScope>()) { + return false; + } + + /* Don't merge if the prim has authored transform ops. */ + if (xform_reader->prim_has_xform_ops()) { + return false; + } + + /* Flag the Xform reader as merged. */ + xform_reader->set_use_parent_xform(true); + + return true; +} + +USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &prim) +{ + if (prim.IsA<pxr::UsdGeomImageable>()) { + pxr::UsdGeomImageable imageable(prim); + + if (!include_by_purpose(imageable)) { + return nullptr; + } + + if (!include_by_visibility(imageable)) { + return nullptr; + } + } + + pxr::Usd_PrimFlagsPredicate filter_predicate = pxr::UsdPrimDefaultPredicate; + + if (params_.import_instance_proxies) { + filter_predicate = pxr::UsdTraverseInstanceProxies(filter_predicate); + } + + pxr::UsdPrimSiblingRange children = prim.GetFilteredChildren(filter_predicate); + + std::vector<USDPrimReader *> child_readers; + + for (const auto &childPrim : children) { + if (USDPrimReader *child_reader = collect_readers(bmain, childPrim)) { + child_readers.push_back(child_reader); + } + } + + if (prim.IsPseudoRoot()) { + return nullptr; + } + + /* Check if we can merge an Xform with its child prim. */ + if (child_readers.size() == 1) { + + USDPrimReader *child_reader = child_readers.front(); + + if (merge_with_parent(child_reader)) { + return child_reader; + } + } + + USDPrimReader *reader = create_reader_if_allowed(prim); + + if (!reader) { + return nullptr; + } + + reader->create_object(bmain, 0.0); + + readers_.push_back(reader); + reader->incref(); + + /* Set each child reader's parent. */ + for (USDPrimReader *child_reader : child_readers) { + child_reader->parent(reader); + } + + return reader; +} + +void USDStageReader::collect_readers(Main *bmain) +{ + if (!valid()) { + return; + } + + clear_readers(); + + /* Iterate through the stage. */ + pxr::UsdPrim root = stage_->GetPseudoRoot(); + + std::string prim_path_mask(params_.prim_path_mask); + + if (!prim_path_mask.empty()) { + pxr::UsdPrim prim = stage_->GetPrimAtPath(pxr::SdfPath(prim_path_mask)); + if (prim.IsValid()) { + root = prim; + } + else { + std::cerr << "WARNING: Prim Path Mask " << prim_path_mask + << " does not specify a valid prim.\n"; + } + } + + stage_->SetInterpolationType(pxr::UsdInterpolationType::UsdInterpolationTypeHeld); + collect_readers(bmain, root); +} + +void USDStageReader::clear_readers() +{ + for (USDPrimReader *reader : readers_) { + if (!reader) { + continue; + } + + reader->decref(); + + if (reader->refcount() == 0) { + delete reader; + } + } + + readers_.clear(); +} + +} // Namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_stage.h b/source/blender/io/usd/intern/usd_reader_stage.h new file mode 100644 index 00000000000..7cc557f7802 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_stage.h @@ -0,0 +1,90 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation and + * NVIDIA Corporation. All rights reserved. + */ +#pragma once + +struct Main; + +#include "usd.h" +#include "usd_reader_prim.h" + +#include <pxr/usd/usd/stage.h> +#include <pxr/usd/usdGeom/imageable.h> + +#include <vector> + +struct ImportSettings; + +namespace blender::io::usd { + +typedef std::map<pxr::SdfPath, std::vector<USDPrimReader *>> ProtoReaderMap; + +class USDStageReader { + + protected: + pxr::UsdStageRefPtr stage_; + USDImportParams params_; + ImportSettings settings_; + + std::vector<USDPrimReader *> readers_; + + public: + USDStageReader(pxr::UsdStageRefPtr stage, + const USDImportParams ¶ms, + const ImportSettings &settings); + + ~USDStageReader(); + + USDPrimReader *create_reader_if_allowed(const pxr::UsdPrim &prim); + + USDPrimReader *create_reader(const pxr::UsdPrim &prim); + + void collect_readers(struct Main *bmain); + + bool valid() const; + + pxr::UsdStageRefPtr stage() + { + return stage_; + } + const USDImportParams ¶ms() const + { + return params_; + } + + const ImportSettings &settings() const + { + return settings_; + } + + void clear_readers(); + + const std::vector<USDPrimReader *> &readers() const + { + return readers_; + }; + + private: + USDPrimReader *collect_readers(Main *bmain, const pxr::UsdPrim &prim); + + bool include_by_visibility(const pxr::UsdGeomImageable &imageable) const; + + bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const; +}; + +}; // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_volume.cc b/source/blender/io/usd/intern/usd_reader_volume.cc new file mode 100644 index 00000000000..871f791c1dd --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_volume.cc @@ -0,0 +1,114 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_volume.h" + +#include "BKE_object.h" +#include "BKE_volume.h" + +#include "DNA_object_types.h" +#include "DNA_volume_types.h" + +#include <pxr/usd/usdVol/openVDBAsset.h> +#include <pxr/usd/usdVol/volume.h> + +#include <iostream> + +namespace usdtokens { + +static const pxr::TfToken density("density", pxr::TfToken::Immortal); + +} + +namespace blender::io::usd { + +void USDVolumeReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + Volume *volume = (Volume *)BKE_volume_add(bmain, name_.c_str()); + + object_ = BKE_object_add_only_object(bmain, OB_VOLUME, name_.c_str()); + object_->data = volume; +} + +void USDVolumeReader::read_object_data(Main *bmain, const double motionSampleTime) +{ + if (!volume_) { + return; + } + + Volume *volume = static_cast<Volume *>(object_->data); + + if (!volume) { + return; + } + + pxr::UsdVolVolume::FieldMap fields = volume_.GetFieldPaths(); + + for (pxr::UsdVolVolume::FieldMap::const_iterator it = fields.begin(); it != fields.end(); ++it) { + + pxr::UsdPrim fieldPrim = prim_.GetStage()->GetPrimAtPath(it->second); + + if (!fieldPrim.IsA<pxr::UsdVolOpenVDBAsset>()) { + continue; + } + + pxr::UsdVolOpenVDBAsset fieldBase(fieldPrim); + + pxr::UsdAttribute fieldNameAttr = fieldBase.GetFieldNameAttr(); + + if (fieldNameAttr.IsAuthored()) { + pxr::TfToken fieldName; + fieldNameAttr.Get(&fieldName, motionSampleTime); + + /* A Blender volume creates density by default. */ + if (fieldName != usdtokens::density) { + BKE_volume_grid_add(volume, fieldName.GetString().c_str(), VOLUME_GRID_FLOAT); + } + } + + pxr::UsdAttribute filepathAttr = fieldBase.GetFilePathAttr(); + + if (filepathAttr.IsAuthored()) { + pxr::SdfAssetPath fp; + filepathAttr.Get(&fp, motionSampleTime); + + if (filepathAttr.ValueMightBeTimeVarying()) { + std::vector<double> filePathTimes; + filepathAttr.GetTimeSamples(&filePathTimes); + + if (!filePathTimes.empty()) { + int start = static_cast<int>(filePathTimes.front()); + int end = static_cast<int>(filePathTimes.back()); + + volume->is_sequence = static_cast<char>(true); + volume->frame_start = start; + volume->frame_duration = (end - start) + 1; + } + } + + std::string filepath = fp.GetResolvedPath(); + + strcpy(volume->filepath, filepath.c_str()); + } + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_volume.h b/source/blender/io/usd/intern/usd_reader_volume.h new file mode 100644 index 00000000000..ca2fddb5531 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_volume.h @@ -0,0 +1,49 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_xform.h" + +#include "pxr/usd/usdVol/volume.h" + +namespace blender::io::usd { + +class USDVolumeReader : public USDXformReader { + private: + pxr::UsdVolVolume volume_; + + public: + USDVolumeReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDXformReader(prim, import_params, settings), volume_(prim) + { + } + + bool valid() const override + { + return static_cast<bool>(volume_); + } + + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_xform.cc b/source/blender/io/usd/intern/usd_reader_xform.cc new file mode 100644 index 00000000000..eebcc5eb3d5 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_xform.cc @@ -0,0 +1,184 @@ +/* + * 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. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_xform.h" + +#include "BKE_constraint.h" +#include "BKE_lib_id.h" +#include "BKE_library.h" +#include "BKE_modifier.h" +#include "BKE_object.h" + +#include "BLI_math_geom.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "DNA_cachefile_types.h" +#include "DNA_constraint_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_space_types.h" /* for FILE_MAX */ + +#include <pxr/base/gf/math.h> +#include <pxr/base/gf/matrix4f.h> + +#include <pxr/usd/usdGeom/xform.h> + +namespace blender::io::usd { + +void USDXformReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + object_ = BKE_object_add_only_object(bmain, OB_EMPTY, name_.c_str()); + object_->empty_drawsize = 0.1f; + object_->data = nullptr; +} + +void USDXformReader::read_object_data(Main * /* bmain */, const double motionSampleTime) +{ + bool is_constant; + float transform_from_usd[4][4]; + + read_matrix(transform_from_usd, motionSampleTime, import_params_.scale, &is_constant); + + if (!is_constant) { + bConstraint *con = BKE_constraint_add_for_object( + object_, nullptr, CONSTRAINT_TYPE_TRANSFORM_CACHE); + bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data); + + std::string prim_path = use_parent_xform_ ? prim_.GetParent().GetPath().GetAsString() : + prim_path_; + + BLI_strncpy(data->object_path, prim_path.c_str(), FILE_MAX); + + data->cache_file = settings_->cache_file; + id_us_plus(&data->cache_file->id); + } + + BKE_object_apply_mat4(object_, transform_from_usd, true, false); +} + +void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */, + const float time, + const float scale, + bool *r_is_constant) +{ + if (r_is_constant) { + *r_is_constant = true; + } + + unit_m4(r_mat); + + pxr::UsdGeomXformable xformable; + + if (use_parent_xform_) { + xformable = pxr::UsdGeomXformable(prim_.GetParent()); + } + else { + xformable = pxr::UsdGeomXformable(prim_); + } + + if (!xformable) { + /* This might happen if the prim is a Scope. */ + return; + } + + if (r_is_constant) { + *r_is_constant = !xformable.TransformMightBeTimeVarying(); + } + + pxr::GfMatrix4d usd_local_xf; + bool reset_xform_stack; + xformable.GetLocalTransformation(&usd_local_xf, &reset_xform_stack, time); + + /* Convert the result to a float matrix. */ + pxr::GfMatrix4f mat4f = pxr::GfMatrix4f(usd_local_xf); + mat4f.Get(r_mat); + + /* Apply global scaling and rotation only to root objects, parenting + * will propagate it. */ + if ((scale != 1.0 || settings_->do_convert_mat) && is_root_xform_) { + + if (scale != 1.0f) { + float scale_mat[4][4]; + scale_m4_fl(scale_mat, scale); + mul_m4_m4m4(r_mat, scale_mat, r_mat); + } + + if (settings_->do_convert_mat) { + mul_m4_m4m4(r_mat, settings_->conversion_mat, r_mat); + } + } +} + +bool USDXformReader::prim_has_xform_ops() const +{ + pxr::UsdGeomXformable xformable(prim_); + + if (!xformable) { + /* This might happen if the prim is a Scope. */ + return false; + } + + bool reset_xform_stack = false; + + return !xformable.GetOrderedXformOps(&reset_xform_stack).empty(); +} + +bool USDXformReader::is_root_xform_prim() const +{ + if (!prim_.IsValid()) { + return false; + } + + if (prim_.IsInMaster()) { + /* We don't consider prototypes to be root prims, + * because we never want to apply global scaling + * or rotations to the prototypes themselves. */ + return false; + } + + if (prim_.IsA<pxr::UsdGeomXformable>()) { + /* If this prim doesn't have an ancestor that's a + * UsdGeomXformable, then it's a root prim. Note + * that it's not sufficient to only check the immediate + * parent prim, since the immediate parent could be a + * UsdGeomScope that has an xformable ancestor. */ + pxr::UsdPrim cur_parent = prim_.GetParent(); + + if (use_parent_xform_) { + cur_parent = cur_parent.GetParent(); + } + + while (cur_parent && !cur_parent.IsPseudoRoot()) { + if (cur_parent.IsA<pxr::UsdGeomXformable>()) { + return false; + } + cur_parent = cur_parent.GetParent(); + } + + /* We didn't find an xformable ancestor. */ + return true; + } + + return false; +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_xform.h b/source/blender/io/usd/intern/usd_reader_xform.h new file mode 100644 index 00000000000..587ac373a4f --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_xform.h @@ -0,0 +1,68 @@ +/* + * 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. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_prim.h" + +namespace blender::io::usd { + +class USDXformReader : public USDPrimReader { + private: + bool use_parent_xform_; + + /* Indicates if the created object is the root of a + * transform hierarchy. */ + bool is_root_xform_; + + public: + USDXformReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDPrimReader(prim, import_params, settings), + use_parent_xform_(false), + is_root_xform_(is_root_xform_prim()) + { + } + + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; + + void read_matrix(float r_mat[4][4], const float time, const float scale, bool *r_is_constant); + + bool use_parent_xform() const + { + return use_parent_xform_; + } + void set_use_parent_xform(bool flag) + { + use_parent_xform_ = flag; + is_root_xform_ = is_root_xform_prim(); + } + + bool prim_has_xform_ops() const; + + protected: + /* Returns true if the contained USD prim is the root of a transform hierarchy. */ + bool is_root_xform_prim() const; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h index 40e2d0d8674..7a2d1b58c4d 100644 --- a/source/blender/io/usd/usd.h +++ b/source/blender/io/usd/usd.h @@ -26,6 +26,10 @@ extern "C" { #endif struct bContext; +struct Object; +struct CacheArchiveHandle; +struct CacheReader; +struct CacheFile; struct USDExportParams { bool export_animation; @@ -39,6 +43,34 @@ struct USDExportParams { enum eEvaluationMode evaluation_mode; }; +struct USDImportParams { + float scale; + bool is_sequence; + bool set_frame_range; + int sequence_len; + int offset; + bool validate_meshes; + char mesh_read_flag; + bool import_cameras; + bool import_curves; + bool import_lights; + bool import_materials; + bool import_meshes; + bool import_volumes; + char *prim_path_mask; + bool import_subdiv; + bool import_instance_proxies; + bool create_collection; + bool import_guide; + bool import_proxy; + bool import_render; + bool import_visible_only; + bool use_instancing; + bool import_usd_preview; + bool set_material_blend; + float light_intensity_scale; +}; + /* The USD_export takes a as_background_job parameter, and returns a boolean. * * When as_background_job=true, returns false immediately after scheduling @@ -53,8 +85,45 @@ bool USD_export(struct bContext *C, const struct USDExportParams *params, bool as_background_job); +bool USD_import(struct bContext *C, + const char *filepath, + const struct USDImportParams *params, + bool as_background_job); + int USD_get_version(void); +/* USD Import and Mesh Cache interface. */ + +struct CacheArchiveHandle *USD_create_handle(struct Main *bmain, + const char *filename, + struct ListBase *object_paths); + +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, + const float time, + const char **err_str, + int flags); + +bool USD_mesh_topology_changed(struct CacheReader *reader, + struct Object *ob, + struct Mesh *existing_mesh, + const float time, + const char **err_str); + +struct CacheReader *CacheReader_open_usd_object(struct CacheArchiveHandle *handle, + struct CacheReader *reader, + struct Object *object, + const char *object_path); + +void USD_CacheReader_incref(struct CacheReader *reader); +void USD_CacheReader_free(struct CacheReader *reader); + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/DNA_cachefile_defaults.h b/source/blender/makesdna/DNA_cachefile_defaults.h index d37994bb488..521b72567d4 100644 --- a/source/blender/makesdna/DNA_cachefile_defaults.h +++ b/source/blender/makesdna/DNA_cachefile_defaults.h @@ -36,6 +36,7 @@ .scale = 1.0f, \ .object_paths ={NULL, NULL}, \ \ + .type = 0, \ .handle = NULL, \ .handle_filepath[0] = '\0', \ .handle_readers = NULL, \ diff --git a/source/blender/makesdna/DNA_cachefile_types.h b/source/blender/makesdna/DNA_cachefile_types.h index 04c99c6c4b1..b38c7827ea5 100644 --- a/source/blender/makesdna/DNA_cachefile_types.h +++ b/source/blender/makesdna/DNA_cachefile_types.h @@ -31,6 +31,13 @@ extern "C" { struct GSet; +/* CacheFile::type */ +typedef enum { + CACHEFILE_TYPE_ALEMBIC = 1, + CACHEFILE_TYPE_USD = 2, + CACHE_FILE_TYPE_INVALID = 0, +} eCacheFileType; + /* CacheFile::flag */ enum { CACHEFILE_DS_EXPAND = (1 << 0), @@ -44,13 +51,13 @@ enum { }; #endif -/* Representation of an object's path inside the Alembic file. +/* Representation of an object's path inside the archive. * Note that this is not a file path. */ -typedef struct AlembicObjectPath { - struct AlembicObjectPath *next, *prev; +typedef struct CacheObjectPath { + struct CacheObjectPath *next, *prev; char path[4096]; -} AlembicObjectPath; +} CacheObjectPath; /* CacheFile::velocity_unit * Determines what temporal unit is used to interpret velocity vectors for motion blur effects. */ @@ -63,7 +70,7 @@ typedef struct CacheFile { ID id; struct AnimData *adt; - /** Paths of the objects inside of the Alembic archive referenced by this CacheFile. */ + /** Paths of the objects inside of the archive referenced by this CacheFile. */ ListBase object_paths; /** 1024 = FILE_MAX. */ @@ -84,14 +91,17 @@ typedef struct CacheFile { short flag; short draw_flag; /* UNUSED */ - char _pad[3]; + /* eCacheFileType enum. */ + char type; + + char _pad[2]; char velocity_unit; - /* Name of the velocity property in the Alembic file. */ + /* Name of the velocity property in the archive. */ char velocity_name[64]; /* Runtime */ - struct AbcArchiveHandle *handle; + struct CacheArchiveHandle *handle; char handle_filepath[1024]; struct GSet *handle_readers; } CacheFile; diff --git a/source/blender/makesrna/intern/rna_cachefile.c b/source/blender/makesrna/intern/rna_cachefile.c index c25cea1b4b3..b93f494072c 100644 --- a/source/blender/makesrna/intern/rna_cachefile.c +++ b/source/blender/makesrna/intern/rna_cachefile.c @@ -64,8 +64,8 @@ static void rna_CacheFile_object_paths_begin(CollectionPropertyIterator *iter, P /* cachefile.object_paths */ static void rna_def_alembic_object_path(BlenderRNA *brna) { - StructRNA *srna = RNA_def_struct(brna, "AlembicObjectPath", NULL); - RNA_def_struct_sdna(srna, "AlembicObjectPath"); + StructRNA *srna = RNA_def_struct(brna, "CacheObjectPath", NULL); + RNA_def_struct_sdna(srna, "CacheObjectPath"); RNA_def_struct_ui_text(srna, "Object Path", "Path of an object inside of an Alembic archive"); RNA_def_struct_ui_icon(srna, ICON_NONE); @@ -81,8 +81,8 @@ static void rna_def_alembic_object_path(BlenderRNA *brna) /* cachefile.object_paths */ static void rna_def_cachefile_object_paths(BlenderRNA *brna, PropertyRNA *cprop) { - RNA_def_property_srna(cprop, "AlembicObjectPaths"); - StructRNA *srna = RNA_def_struct(brna, "AlembicObjectPaths", NULL); + RNA_def_property_srna(cprop, "CacheObjectPaths"); + StructRNA *srna = RNA_def_struct(brna, "CacheObjectPaths", NULL); RNA_def_struct_sdna(srna, "CacheFile"); RNA_def_struct_ui_text(srna, "Object Paths", "Collection of object paths"); } @@ -169,8 +169,8 @@ static void rna_def_cachefile(BlenderRNA *brna) NULL, NULL, NULL); - RNA_def_property_struct_type(prop, "AlembicObjectPath"); - RNA_def_property_srna(prop, "AlembicObjectPaths"); + RNA_def_property_struct_type(prop, "CacheObjectPath"); + RNA_def_property_srna(prop, "CacheObjectPaths"); RNA_def_property_ui_text( prop, "Object Paths", "Paths of the objects inside the Alembic archive"); diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 0138dd0c3ad..d9b9fa96d04 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -141,6 +141,16 @@ if(WITH_ALEMBIC) ) endif() +if(WITH_USD) + add_definitions(-DWITH_USD) + list(APPEND INC + ../io/usd + ) + list(APPEND LIB + bf_usd + ) +endif() + if(WITH_MOD_REMESH) list(APPEND INC ../../../intern/dualcon diff --git a/source/blender/modifiers/intern/MOD_meshsequencecache.c b/source/blender/modifiers/intern/MOD_meshsequencecache.c index c2f9cd8c867..3e6081e0a18 100644 --- a/source/blender/modifiers/intern/MOD_meshsequencecache.c +++ b/source/blender/modifiers/intern/MOD_meshsequencecache.c @@ -55,18 +55,29 @@ #include "MOD_modifiertypes.h" #include "MOD_ui_common.h" -#ifdef WITH_ALEMBIC -# include "ABC_alembic.h" +#if defined(WITH_USD) || defined(WITH_ALEMBIC) # include "BKE_global.h" # include "BKE_lib_id.h" #endif +#ifdef WITH_ALEMBIC +# include "ABC_alembic.h" +#endif + +#ifdef WITH_USD +# include "usd.h" +#endif + static void initData(ModifierData *md) { MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)md; BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(mcmd, modifier)); + mcmd->cache_file = NULL; + mcmd->object_path[0] = '\0'; + mcmd->read_flag = MOD_MESHSEQ_READ_ALL; + MEMCPY_STRUCT_AFTER(mcmd, DNA_struct_default_get(MeshSeqCacheModifierData), modifier); } @@ -109,7 +120,7 @@ static bool isDisabled(const struct Scene *UNUSED(scene), static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) { -#ifdef WITH_ALEMBIC +#if defined(WITH_USD) || defined(WITH_ALEMBIC) MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)md; /* Only used to check whether we are operating on org data or not... */ @@ -127,16 +138,32 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * 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); + ctx->object, md, "Could not create reader for file %s", cache_file->filepath); return mesh; } } - /* If this invocation is for the ORCO mesh, and the mesh in Alembic hasn't changed topology, we + /* 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 && - !ABC_mesh_topology_changed(mcmd->reader, ctx->object, mesh, time, &err_str)) { - return mesh; + 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 (me != NULL) { @@ -156,7 +183,23 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * } } - Mesh *result = ABC_read_mesh(mcmd->reader, ctx->object, mesh, time, &err_str, mcmd->read_flag); + Mesh *result = NULL; + + switch (cache_file->type) { + case CACHEFILE_TYPE_ALEMBIC: +# ifdef WITH_ALEMBIC + result = ABC_read_mesh(mcmd->reader, ctx->object, mesh, time, &err_str, mcmd->read_flag); +# 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; + } mcmd->velocity_delta = 1.0f; if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_SECOND) { @@ -187,7 +230,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * static bool dependsOnTime(ModifierData *md) { -#ifdef WITH_ALEMBIC +#if defined(WITH_USD) || defined(WITH_ALEMBIC) MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)md; return (mcmd->cache_file != NULL); #else |