From ea54cbe1b42efb3107285c89685f555c06997062 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Tue, 3 Aug 2021 11:55:53 +0200 Subject: 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 --- source/blender/io/alembic/ABC_alembic.h | 17 +- source/blender/io/alembic/intern/abc_util.h | 9 - source/blender/io/alembic/intern/alembic_capi.cc | 35 +- source/blender/io/common/CMakeLists.txt | 1 + source/blender/io/common/IO_types.h | 34 + source/blender/io/usd/CMakeLists.txt | 30 +- source/blender/io/usd/intern/usd_capi.cc | 252 ------ source/blender/io/usd/intern/usd_capi_export.cc | 238 ++++++ source/blender/io/usd/intern/usd_capi_import.cc | 578 ++++++++++++++ source/blender/io/usd/intern/usd_common.cc | 43 ++ source/blender/io/usd/intern/usd_common.h | 25 + source/blender/io/usd/intern/usd_reader_camera.cc | 100 +++ source/blender/io/usd/intern/usd_reader_camera.h | 42 + source/blender/io/usd/intern/usd_reader_curve.cc | 256 +++++++ source/blender/io/usd/intern/usd_reader_curve.h | 62 ++ source/blender/io/usd/intern/usd_reader_geom.cc | 59 ++ source/blender/io/usd/intern/usd_reader_geom.h | 52 ++ .../blender/io/usd/intern/usd_reader_instance.cc | 64 ++ source/blender/io/usd/intern/usd_reader_instance.h | 47 ++ source/blender/io/usd/intern/usd_reader_light.cc | 252 ++++++ source/blender/io/usd/intern/usd_reader_light.h | 41 + .../blender/io/usd/intern/usd_reader_material.cc | 703 +++++++++++++++++ source/blender/io/usd/intern/usd_reader_material.h | 131 ++++ source/blender/io/usd/intern/usd_reader_mesh.cc | 853 +++++++++++++++++++++ source/blender/io/usd/intern/usd_reader_mesh.h | 95 +++ source/blender/io/usd/intern/usd_reader_nurbs.cc | 256 +++++++ source/blender/io/usd/intern/usd_reader_nurbs.h | 61 ++ source/blender/io/usd/intern/usd_reader_prim.cc | 80 ++ source/blender/io/usd/intern/usd_reader_prim.h | 131 ++++ source/blender/io/usd/intern/usd_reader_stage.cc | 324 ++++++++ source/blender/io/usd/intern/usd_reader_stage.h | 90 +++ source/blender/io/usd/intern/usd_reader_volume.cc | 114 +++ source/blender/io/usd/intern/usd_reader_volume.h | 49 ++ source/blender/io/usd/intern/usd_reader_xform.cc | 184 +++++ source/blender/io/usd/intern/usd_reader_xform.h | 68 ++ source/blender/io/usd/usd.h | 69 ++ 36 files changed, 5155 insertions(+), 290 deletions(-) create mode 100644 source/blender/io/common/IO_types.h delete mode 100644 source/blender/io/usd/intern/usd_capi.cc create mode 100644 source/blender/io/usd/intern/usd_capi_export.cc create mode 100644 source/blender/io/usd/intern/usd_capi_import.cc create mode 100644 source/blender/io/usd/intern/usd_common.cc create mode 100644 source/blender/io/usd/intern/usd_common.h create mode 100644 source/blender/io/usd/intern/usd_reader_camera.cc create mode 100644 source/blender/io/usd/intern/usd_reader_camera.h create mode 100644 source/blender/io/usd/intern/usd_reader_curve.cc create mode 100644 source/blender/io/usd/intern/usd_reader_curve.h create mode 100644 source/blender/io/usd/intern/usd_reader_geom.cc create mode 100644 source/blender/io/usd/intern/usd_reader_geom.h create mode 100644 source/blender/io/usd/intern/usd_reader_instance.cc create mode 100644 source/blender/io/usd/intern/usd_reader_instance.h create mode 100644 source/blender/io/usd/intern/usd_reader_light.cc create mode 100644 source/blender/io/usd/intern/usd_reader_light.h create mode 100644 source/blender/io/usd/intern/usd_reader_material.cc create mode 100644 source/blender/io/usd/intern/usd_reader_material.h create mode 100644 source/blender/io/usd/intern/usd_reader_mesh.cc create mode 100644 source/blender/io/usd/intern/usd_reader_mesh.h create mode 100644 source/blender/io/usd/intern/usd_reader_nurbs.cc create mode 100644 source/blender/io/usd/intern/usd_reader_nurbs.h create mode 100644 source/blender/io/usd/intern/usd_reader_prim.cc create mode 100644 source/blender/io/usd/intern/usd_reader_prim.h create mode 100644 source/blender/io/usd/intern/usd_reader_stage.cc create mode 100644 source/blender/io/usd/intern/usd_reader_stage.h create mode 100644 source/blender/io/usd/intern/usd_reader_volume.cc create mode 100644 source/blender/io/usd/intern/usd_reader_volume.h create mode 100644 source/blender/io/usd/intern/usd_reader_xform.cc create mode 100644 source/blender/io/usd/intern/usd_reader_xform.h (limited to 'source/blender/io') 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 #include -/** - * \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 @@ -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(handle); } -BLI_INLINE AbcArchiveHandle *handle_from_archive(ArchiveReader *archive) +BLI_INLINE CacheArchiveHandle *handle_from_archive(ArchiveReader *archive) { - return reinterpret_cast(archive); + return reinterpret_cast(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(abc_path_void); + void *abc_path_void = MEM_callocN(sizeof(CacheObjectPath), "CacheObjectPath"); + CacheObjectPath *abc_path = static_cast(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 visit_object( readers.push_back(reader); reader->incref(); - AlembicObjectPath *abc_path = static_cast( - MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath")); + CacheObjectPath *abc_path = static_cast( + 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(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(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.cc deleted file mode 100644 index dc2b46e5cea..00000000000 --- a/source/blender/io/usd/intern/usd_capi.cc +++ /dev/null @@ -1,252 +0,0 @@ -/* - * 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 "usd.h" -#include "usd_hierarchy_iterator.h" - -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_build.h" -#include "DEG_depsgraph_query.h" - -#include "DNA_scene_types.h" - -#include "BKE_appdir.h" -#include "BKE_blender_version.h" -#include "BKE_context.h" -#include "BKE_global.h" -#include "BKE_scene.h" - -#include "BLI_fileops.h" -#include "BLI_path_util.h" -#include "BLI_string.h" - -#include "WM_api.h" -#include "WM_types.h" - -namespace blender::io::usd { - -struct ExportJobData { - Main *bmain; - Depsgraph *depsgraph; - wmWindowManager *wm; - - char filename[FILE_MAX]; - USDExportParams params; - - 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. */ - short *stop, - short *do_update, - float *progress) -{ - ExportJobData *data = static_cast(customdata); - data->export_ok = false; - - G.is_rendering = true; - WM_set_locked_interface(data->wm, true); - G.is_break = false; - - /* Construct the depsgraph for exporting. */ - Scene *scene = DEG_get_input_scene(data->depsgraph); - if (data->params.visible_objects_only) { - DEG_graph_build_from_view_layer(data->depsgraph); - } - else { - DEG_graph_build_for_all_objects(data->depsgraph); - } - BKE_scene_graph_update_tagged(data->depsgraph, data->bmain); - - *progress = 0.0f; - *do_update = true; - - /* For restoring the current frame after exporting animation is done. */ - const int orig_frame = CFRA; - - pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filename); - if (!usd_stage) { - /* This happens when the USD JSON files cannot be found. When that happens, - * the USD library doesn't know it has the functionality to write USDA and - * USDC files, and creating a new UsdStage fails. */ - WM_reportf( - RPT_ERROR, "USD Export: unable to find suitable USD plugin to write %s", data->filename); - return; - } - - usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z)); - usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit, - pxr::VtValue(scene->unit.scale_length)); - usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") + - BKE_blender_version_string()); - - /* Set up the stage for animated data. */ - if (data->params.export_animation) { - usd_stage->SetTimeCodesPerSecond(FPS); - usd_stage->SetStartTimeCode(scene->r.sfra); - usd_stage->SetEndTimeCode(scene->r.efra); - } - - USDHierarchyIterator iter(data->depsgraph, usd_stage, data->params); - - if (data->params.export_animation) { - /* Writing the animated frames is not 100% of the work, but it's our best guess. */ - float progress_per_frame = 1.0f / std::max(1, (scene->r.efra - scene->r.sfra + 1)); - - for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) { - if (G.is_break || (stop != nullptr && *stop)) { - break; - } - - /* Update the scene for the next frame to render. */ - scene->r.cfra = static_cast(frame); - scene->r.subframe = frame - scene->r.cfra; - BKE_scene_graph_update_for_newframe(data->depsgraph); - - iter.set_export_frame(frame); - iter.iterate_and_write(); - - *progress += progress_per_frame; - *do_update = true; - } - } - else { - /* If we're not animating, a single iteration over all objects is enough. */ - iter.iterate_and_write(); - } - - iter.release_writers(); - usd_stage->GetRootLayer()->Save(); - - /* Finish up by going back to the keyframe that was current before we started. */ - if (CFRA != orig_frame) { - CFRA = orig_frame; - BKE_scene_graph_update_for_newframe(data->depsgraph); - } - - data->export_ok = true; - *progress = 1.0f; - *do_update = true; -} - -static void export_endjob(void *customdata) -{ - ExportJobData *data = static_cast(customdata); - - DEG_graph_free(data->depsgraph); - - if (!data->export_ok && BLI_exists(data->filename)) { - BLI_delete(data->filename, false, false); - } - - G.is_rendering = false; - WM_set_locked_interface(data->wm, false); -} - -} // namespace blender::io::usd - -bool USD_export(bContext *C, - const char *filepath, - const USDExportParams *params, - bool as_background_job) -{ - ViewLayer *view_layer = CTX_data_view_layer(C); - Scene *scene = CTX_data_scene(C); - - blender::io::usd::ensure_usd_plugin_path_registered(); - - blender::io::usd::ExportJobData *job = static_cast( - MEM_mallocN(sizeof(blender::io::usd::ExportJobData), "ExportJobData")); - - job->bmain = CTX_data_main(C); - job->wm = CTX_wm_manager(C); - job->export_ok = false; - BLI_strncpy(job->filename, filepath, sizeof(job->filename)); - - job->depsgraph = DEG_graph_new(job->bmain, scene, view_layer, params->evaluation_mode); - job->params = *params; - - bool export_ok = false; - if (as_background_job) { - wmJob *wm_job = WM_jobs_get( - job->wm, CTX_wm_window(C), scene, "USD Export", WM_JOB_PROGRESS, WM_JOB_TYPE_ALEMBIC); - - /* setup job */ - WM_jobs_customdata_set(wm_job, job, MEM_freeN); - WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME); - WM_jobs_callbacks(wm_job, - blender::io::usd::export_startjob, - nullptr, - nullptr, - blender::io::usd::export_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 exporting. */ - short stop = 0, do_update = 0; - float progress = 0.0f; - - blender::io::usd::export_startjob(job, &stop, &do_update, &progress); - blender::io::usd::export_endjob(job); - export_ok = job->export_ok; - - MEM_freeN(job); - } - - return export_ok; -} - -int USD_get_version(void) -{ - /* USD 19.11 defines: - * - * #define PXR_MAJOR_VERSION 0 - * #define PXR_MINOR_VERSION 19 - * #define PXR_PATCH_VERSION 11 - * #define PXR_VERSION 1911 - * - * So the major version is implicit/invisible in the public version number. - */ - return PXR_VERSION; -} diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc new file mode 100644 index 00000000000..25f12e683cf --- /dev/null +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -0,0 +1,238 @@ +/* + * 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 "usd.h" +#include "usd_common.h" +#include "usd_hierarchy_iterator.h" + +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" + +#include "DNA_scene_types.h" + +#include "BKE_appdir.h" +#include "BKE_blender_version.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_scene.h" + +#include "BLI_fileops.h" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "WM_api.h" +#include "WM_types.h" + +namespace blender::io::usd { + +struct ExportJobData { + Main *bmain; + Depsgraph *depsgraph; + wmWindowManager *wm; + + char filename[FILE_MAX]; + USDExportParams params; + + bool export_ok; +}; + +static void export_startjob(void *customdata, + /* Cannot be const, this function implements wm_jobs_start_callback. + * NOLINTNEXTLINE: readability-non-const-parameter. */ + short *stop, + short *do_update, + float *progress) +{ + ExportJobData *data = static_cast(customdata); + data->export_ok = false; + + G.is_rendering = true; + WM_set_locked_interface(data->wm, true); + G.is_break = false; + + /* Construct the depsgraph for exporting. */ + Scene *scene = DEG_get_input_scene(data->depsgraph); + if (data->params.visible_objects_only) { + DEG_graph_build_from_view_layer(data->depsgraph); + } + else { + DEG_graph_build_for_all_objects(data->depsgraph); + } + BKE_scene_graph_update_tagged(data->depsgraph, data->bmain); + + *progress = 0.0f; + *do_update = true; + + /* For restoring the current frame after exporting animation is done. */ + const int orig_frame = CFRA; + + pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filename); + if (!usd_stage) { + /* This happens when the USD JSON files cannot be found. When that happens, + * the USD library doesn't know it has the functionality to write USDA and + * USDC files, and creating a new UsdStage fails. */ + WM_reportf( + RPT_ERROR, "USD Export: unable to find suitable USD plugin to write %s", data->filename); + return; + } + + usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z)); + usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit, + pxr::VtValue(scene->unit.scale_length)); + usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") + + BKE_blender_version_string()); + + /* Set up the stage for animated data. */ + if (data->params.export_animation) { + usd_stage->SetTimeCodesPerSecond(FPS); + usd_stage->SetStartTimeCode(scene->r.sfra); + usd_stage->SetEndTimeCode(scene->r.efra); + } + + USDHierarchyIterator iter(data->depsgraph, usd_stage, data->params); + + if (data->params.export_animation) { + /* Writing the animated frames is not 100% of the work, but it's our best guess. */ + float progress_per_frame = 1.0f / std::max(1, (scene->r.efra - scene->r.sfra + 1)); + + for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) { + if (G.is_break || (stop != nullptr && *stop)) { + break; + } + + /* Update the scene for the next frame to render. */ + scene->r.cfra = static_cast(frame); + scene->r.subframe = frame - scene->r.cfra; + BKE_scene_graph_update_for_newframe(data->depsgraph); + + iter.set_export_frame(frame); + iter.iterate_and_write(); + + *progress += progress_per_frame; + *do_update = true; + } + } + else { + /* If we're not animating, a single iteration over all objects is enough. */ + iter.iterate_and_write(); + } + + iter.release_writers(); + usd_stage->GetRootLayer()->Save(); + + /* Finish up by going back to the keyframe that was current before we started. */ + if (CFRA != orig_frame) { + CFRA = orig_frame; + BKE_scene_graph_update_for_newframe(data->depsgraph); + } + + data->export_ok = true; + *progress = 1.0f; + *do_update = true; +} + +static void export_endjob(void *customdata) +{ + ExportJobData *data = static_cast(customdata); + + DEG_graph_free(data->depsgraph); + + if (!data->export_ok && BLI_exists(data->filename)) { + BLI_delete(data->filename, false, false); + } + + G.is_rendering = false; + WM_set_locked_interface(data->wm, false); +} + +} // namespace blender::io::usd + +bool USD_export(bContext *C, + const char *filepath, + const USDExportParams *params, + bool as_background_job) +{ + ViewLayer *view_layer = CTX_data_view_layer(C); + Scene *scene = CTX_data_scene(C); + + blender::io::usd::ensure_usd_plugin_path_registered(); + + blender::io::usd::ExportJobData *job = static_cast( + MEM_mallocN(sizeof(blender::io::usd::ExportJobData), "ExportJobData")); + + job->bmain = CTX_data_main(C); + job->wm = CTX_wm_manager(C); + job->export_ok = false; + BLI_strncpy(job->filename, filepath, sizeof(job->filename)); + + job->depsgraph = DEG_graph_new(job->bmain, scene, view_layer, params->evaluation_mode); + job->params = *params; + + bool export_ok = false; + if (as_background_job) { + wmJob *wm_job = WM_jobs_get( + job->wm, CTX_wm_window(C), scene, "USD Export", WM_JOB_PROGRESS, WM_JOB_TYPE_ALEMBIC); + + /* setup job */ + WM_jobs_customdata_set(wm_job, job, MEM_freeN); + WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME); + WM_jobs_callbacks(wm_job, + blender::io::usd::export_startjob, + nullptr, + nullptr, + blender::io::usd::export_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 exporting. */ + short stop = 0, do_update = 0; + float progress = 0.0f; + + blender::io::usd::export_startjob(job, &stop, &do_update, &progress); + blender::io::usd::export_endjob(job); + export_ok = job->export_ok; + + MEM_freeN(job); + } + + return export_ok; +} + +int USD_get_version(void) +{ + /* USD 19.11 defines: + * + * #define PXR_MAJOR_VERSION 0 + * #define PXR_MINOR_VERSION 19 + * #define PXR_PATCH_VERSION 11 + * #define PXR_VERSION 1911 + * + * So the major version is implicit/invisible in the public version number. + */ + return PXR_VERSION; +} 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 +#include +#include +#include +#include + +#include + +namespace blender::io::usd { + +static CacheArchiveHandle *handle_from_stage_reader(USDStageReader *reader) +{ + return reinterpret_cast(reader); +} + +static USDStageReader *stage_reader_from_handle(CacheArchiveHandle *handle) +{ + return reinterpret_cast(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(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(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( + 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(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(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(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(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(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(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(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(usd_reader); +} + +void USD_CacheReader_free(CacheReader *reader) +{ + USDPrimReader *usd_reader = reinterpret_cast(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(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 + +#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 +#include + +namespace blender::io::usd { + +void USDCameraReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + Camera *bcam = static_cast(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(); + /* TODO(makowalski) */ +#if 0 + bcam->sensor_x = 0.0f; + bcam->sensor_y = 0.0f; +#endif + bcam->shiftx = verApOffset.Get(); + bcam->shifty = horApOffset.Get(); + + bcam->type = (projectionVal.Get().GetString() == "perspective") ? CAM_PERSP : + CAM_ORTHO; + + /* Calling UncheckedGet() to silence compiler warnings. */ + bcam->clip_start = max_ff(0.1f, clippingRangeVal.UncheckedGet()[0]); + bcam->clip_end = clippingRangeVal.UncheckedGet()[1]; + + bcam->dof.focus_distance = focalDistanceVal.Get(); + bcam->dof.aperture_fstop = static_cast(fstopVal.Get()); + + if (bcam->type == CAM_ORTHO) { + bcam->ortho_scale = max_ff(verAp.Get(), horAp.Get()); + } + + 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 +#include +#include + +#include +#include + +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(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(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(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(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(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(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(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 + +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 + +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 + +#include +#include +#include +#include +#include + +#include + +namespace blender::io::usd { + +void USDLightReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + Light *blight = static_cast(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()) { + blight->type = LA_AREA; + blight->area_shape = LA_AREA_DISK; + /* Ellipse lights are not currently supported */ + } + else if (prim_.IsA()) { + blight->type = LA_AREA; + blight->area_shape = LA_AREA_RECT; + } + else if (prim_.IsA()) { + blight->type = LA_LOCAL; + + if (shaping_api && shaping_api.GetShapingConeAngleAttr().IsAuthored()) { + blight->type = LA_SPOT; + } + } + else if (prim_.IsA()) { + 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 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 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 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 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 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 +#include +#include + +#include +#include + +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(); + 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(); + } + + 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()) { + return color_space_val.Get(); + } + + 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 color = val.UncheckedGet(); + 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()) { + mtl->metallic = val.Get(); + } + } + + 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()) { + mtl->roughness = val.Get(); + } + } +} + +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()) { + ((bNodeSocketValueFloat *)sock->default_value)->value = val.UncheckedGet(); + } + else if (val.IsHolding()) { + pxr::GfVec3f v3f = val.UncheckedGet(); + 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 v3f = val.UncheckedGet(); + copy_v3_v3(((bNodeSocketValueRGBA *)sock->default_value)->value, v3f.data()); + } + break; + case SOCK_VECTOR: + if (val.IsHolding()) { + pxr::GfVec3f v3f = val.UncheckedGet(); + copy_v3_v3(((bNodeSocketValueVector *)sock->default_value)->value, v3f.data()); + } + else if (val.IsHolding()) { + pxr::GfVec2f v2f = val.UncheckedGet(); + 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())) { + 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()) { + 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(); + 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()) { + std::string varname = varname_val.Get().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 + +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 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 +#include +#include +#include +#include +#include +#include + +#include + +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 *r_mat_map) +{ + if (r_mat_map == nullptr) { + return; + } + + Material *material = static_cast(bmain->materials.first); + + for (; material; material = static_cast(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 &mat_index_map, + const USDImportParams ¶ms, + pxr::UsdStageRefPtr stage) +{ + if (!(stage && bmain && ob)) { + return; + } + + bool can_assign = true; + std::map::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 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::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(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(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 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 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(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 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(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( + 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( + 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 *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 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 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 uv_tokens; + + /* Currently we only handle UV primvars. */ + if (read_flag & MOD_MESHSEQ_READ_UV) { + + std::vector 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(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 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 uv_token_map_; + std::map 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 *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 +#include +#include +#include + +#include + +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(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(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(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(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(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(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(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(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 + +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 +#include +#include +#include +#include +#include +#include + +#include + +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()) { + return new USDCameraReader(prim, params_, settings_); + } + if (params_.import_curves && prim.IsA()) { + return new USDCurvesReader(prim, params_, settings_); + } + if (params_.import_curves && prim.IsA()) { + return new USDNurbsReader(prim, params_, settings_); + } + if (params_.import_meshes && prim.IsA()) { + return new USDMeshReader(prim, params_, settings_); + } + if (params_.import_lights && prim.IsA()) { + return new USDLightReader(prim, params_, settings_); + } + if (params_.import_volumes && prim.IsA()) { + return new USDVolumeReader(prim, params_, settings_); + } + if (prim.IsA()) { + return new USDXformReader(prim, params_, settings_); + } + + return nullptr; +} + +USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim) +{ + if (prim.IsA()) { + return new USDCameraReader(prim, params_, settings_); + } + if (prim.IsA()) { + return new USDCurvesReader(prim, params_, settings_); + } + if (prim.IsA()) { + return new USDNurbsReader(prim, params_, settings_); + } + if (prim.IsA()) { + return new USDMeshReader(prim, params_, settings_); + } + if (prim.IsA()) { + return new USDLightReader(prim, params_, settings_); + } + if (prim.IsA()) { + return new USDVolumeReader(prim, params_, settings_); + } + if (prim.IsA()) { + 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(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()) { + return false; + } + + /* Don't merge Xform and Scope prims. */ + if (xform_reader->prim().IsA() || + xform_reader->prim().IsA()) { + 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 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 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 +#include + +#include + +struct ImportSettings; + +namespace blender::io::usd { + +typedef std::map> ProtoReaderMap; + +class USDStageReader { + + protected: + pxr::UsdStageRefPtr stage_; + USDImportParams params_; + ImportSettings settings_; + + std::vector 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 &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 +#include + +#include + +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(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()) { + 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 filePathTimes; + filepathAttr.GetTimeSamples(&filePathTimes); + + if (!filePathTimes.empty()) { + int start = static_cast(filePathTimes.front()); + int end = static_cast(filePathTimes.back()); + + volume->is_sequence = static_cast(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(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 +#include + +#include + +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(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()) { + /* 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()) { + 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 -- cgit v1.2.3