diff options
Diffstat (limited to 'source/blender/io/usd/intern')
9 files changed, 258 insertions, 12 deletions
diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc index d7c1a5adc7c..c52d791f3b3 100644 --- a/source/blender/io/usd/intern/usd_capi_export.cc +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -97,7 +97,7 @@ static void export_startjob(void *customdata, usd_stage->SetEndTimeCode(scene->r.efra); } - USDHierarchyIterator iter(data->depsgraph, usd_stage, data->params); + USDHierarchyIterator iter(data->bmain, 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. */ diff --git a/source/blender/io/usd/intern/usd_exporter_context.h b/source/blender/io/usd/intern/usd_exporter_context.h index 5a3f94d01f8..a636d849296 100644 --- a/source/blender/io/usd/intern/usd_exporter_context.h +++ b/source/blender/io/usd/intern/usd_exporter_context.h @@ -8,12 +8,14 @@ #include <pxr/usd/usd/common.h> struct Depsgraph; +struct Main; namespace blender::io::usd { class USDHierarchyIterator; struct USDExporterContext { + Main *bmain; Depsgraph *depsgraph; const pxr::UsdStageRefPtr stage; const pxr::SdfPath usd_path; diff --git a/source/blender/io/usd/intern/usd_hierarchy_iterator.cc b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc index 7a0d896fb3e..51261c4d91e 100644 --- a/source/blender/io/usd/intern/usd_hierarchy_iterator.cc +++ b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc @@ -10,6 +10,7 @@ #include "usd_writer_mesh.h" #include "usd_writer_metaball.h" #include "usd_writer_transform.h" +#include "usd_writer_volume.h" #include <string> @@ -28,10 +29,11 @@ namespace blender::io::usd { -USDHierarchyIterator::USDHierarchyIterator(Depsgraph *depsgraph, +USDHierarchyIterator::USDHierarchyIterator(Main *bmain, + Depsgraph *depsgraph, pxr::UsdStageRefPtr stage, const USDExportParams ¶ms) - : AbstractHierarchyIterator(depsgraph), stage_(stage), params_(params) + : AbstractHierarchyIterator(bmain, depsgraph), stage_(stage), params_(params) { } @@ -59,6 +61,15 @@ void USDHierarchyIterator::set_export_frame(float frame_nr) export_time_ = pxr::UsdTimeCode(frame_nr); } +std::string USDHierarchyIterator::get_export_file_path() const +{ + /* Returns the same path that was passed to `stage_` object during it's creation (via + * `pxr::UsdStage::CreateNew` function). */ + const pxr::SdfLayerHandle root_layer = stage_->GetRootLayer(); + const std::string usd_export_file_path = root_layer->GetRealPath(); + return usd_export_file_path; +} + const pxr::UsdTimeCode &USDHierarchyIterator::get_export_time_code() const { return export_time_; @@ -66,7 +77,8 @@ const pxr::UsdTimeCode &USDHierarchyIterator::get_export_time_code() const USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context) { - return USDExporterContext{depsgraph_, stage_, pxr::SdfPath(context->export_path), this, params_}; + return USDExporterContext{ + bmain_, depsgraph_, stage_, pxr::SdfPath(context->export_path), this, params_}; } AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer( @@ -93,6 +105,9 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch case OB_MBALL: data_writer = new USDMetaballWriter(usd_export_context); break; + case OB_VOLUME: + data_writer = new USDVolumeWriter(usd_export_context); + break; case OB_EMPTY: case OB_CURVES_LEGACY: diff --git a/source/blender/io/usd/intern/usd_hierarchy_iterator.h b/source/blender/io/usd/intern/usd_hierarchy_iterator.h index d5566103af4..445c37238be 100644 --- a/source/blender/io/usd/intern/usd_hierarchy_iterator.h +++ b/source/blender/io/usd/intern/usd_hierarchy_iterator.h @@ -12,6 +12,7 @@ #include <pxr/usd/usd/timeCode.h> struct Depsgraph; +struct Main; struct Object; namespace blender::io::usd { @@ -27,11 +28,13 @@ class USDHierarchyIterator : public AbstractHierarchyIterator { const USDExportParams ¶ms_; public: - USDHierarchyIterator(Depsgraph *depsgraph, + USDHierarchyIterator(Main *bmain, + Depsgraph *depsgraph, pxr::UsdStageRefPtr stage, const USDExportParams ¶ms); void set_export_frame(float frame_nr); + std::string get_export_file_path() const; const pxr::UsdTimeCode &get_export_time_code() const; virtual std::string make_valid_name(const std::string &name) const override; diff --git a/source/blender/io/usd/intern/usd_writer_abstract.cc b/source/blender/io/usd/intern/usd_writer_abstract.cc index dbeb9df1ee8..2be9b1c065a 100644 --- a/source/blender/io/usd/intern/usd_writer_abstract.cc +++ b/source/blender/io/usd/intern/usd_writer_abstract.cc @@ -47,6 +47,11 @@ bool USDAbstractWriter::is_supported(const HierarchyContext * /*context*/) const return true; } +std::string USDAbstractWriter::get_export_file_path() const +{ + return usd_export_context_.hierarchy_iterator->get_export_file_path(); +} + pxr::UsdTimeCode USDAbstractWriter::get_export_time_code() const { if (is_animated_) { diff --git a/source/blender/io/usd/intern/usd_writer_abstract.h b/source/blender/io/usd/intern/usd_writer_abstract.h index 66feb6e1070..8adc054c2c2 100644 --- a/source/blender/io/usd/intern/usd_writer_abstract.h +++ b/source/blender/io/usd/intern/usd_writer_abstract.h @@ -51,6 +51,7 @@ class USDAbstractWriter : public AbstractHierarchyWriter { protected: virtual void do_write(HierarchyContext &context) = 0; + std::string get_export_file_path() const; pxr::UsdTimeCode get_export_time_code() const; pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material); diff --git a/source/blender/io/usd/intern/usd_writer_material.cc b/source/blender/io/usd/intern/usd_writer_material.cc index 1bfc0e50f69..a24877a20bd 100644 --- a/source/blender/io/usd/intern/usd_writer_material.cc +++ b/source/blender/io/usd/intern/usd_writer_material.cc @@ -576,7 +576,7 @@ static std::string get_tex_image_asset_path(bNode *node, char file_path[FILE_MAX]; BLI_split_file_part(path.c_str(), file_path, FILE_MAX); - if (export_params.relative_texture_paths) { + if (export_params.relative_paths) { BLI_path_join(exp_path, FILE_MAX, ".", "textures", file_path, nullptr); } else { @@ -594,7 +594,7 @@ static std::string get_tex_image_asset_path(bNode *node, return exp_path; } - if (export_params.relative_texture_paths) { + if (export_params.relative_paths) { /* Get the path relative to the USD. */ pxr::SdfLayerHandle layer = stage->GetRootLayer(); std::string stage_path = layer->GetRealPath(); @@ -606,11 +606,7 @@ static std::string get_tex_image_asset_path(bNode *node, strcpy(rel_path, path.c_str()); BLI_path_rel(rel_path, stage_path.c_str()); - - /* BLI_path_rel adds '//' as a prefix to the path, if - * generating the relative path was successful. */ - if (rel_path[0] != '/' || rel_path[1] != '/') { - /* No relative path generated. */ + if (!BLI_path_is_rel(rel_path)) { return path; } diff --git a/source/blender/io/usd/intern/usd_writer_volume.cc b/source/blender/io/usd/intern/usd_writer_volume.cc new file mode 100644 index 00000000000..b83ded1e5d0 --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_volume.cc @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "usd_writer_volume.h" + +#include <pxr/base/tf/pathUtils.h> +#include <pxr/usd/usdVol/openVDBAsset.h> +#include <pxr/usd/usdVol/volume.h> + +#include "DNA_volume_types.h" +#include "DNA_windowmanager_types.h" + +#include "BKE_volume.h" + +#include "BLI_fileops.h" +#include "BLI_index_range.hh" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "WM_api.h" + +#include "usd_hierarchy_iterator.h" + +namespace blender::io::usd { + +USDVolumeWriter::USDVolumeWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) +{ +} + +bool USDVolumeWriter::check_is_animated(const HierarchyContext &context) const +{ + const Volume *volume = static_cast<Volume *>(context.object->data); + return volume->is_sequence; +} + +void USDVolumeWriter::do_write(HierarchyContext &context) +{ + Volume *volume = static_cast<Volume *>(context.object->data); + if (!BKE_volume_load(volume, usd_export_context_.bmain)) { + return; + } + + const int num_grids = BKE_volume_num_grids(volume); + if (!num_grids) { + return; + } + + auto vdb_file_path = resolve_vdb_file(volume); + if (!vdb_file_path.has_value()) { + WM_reportf(RPT_WARNING, + "USD Export: failed to resolve .vdb file for object: %s", + volume->id.name + 2); + return; + } + + if (usd_export_context_.export_params.relative_paths) { + if (auto relative_vdb_file_path = construct_vdb_relative_file_path(vdb_file_path.value())) { + vdb_file_path = relative_vdb_file_path; + } + else { + WM_reportf(RPT_WARNING, + "USD Export: couldn't construct relative file path for .vdb file, absolute path " + "will be used instead"); + } + } + + const pxr::UsdTimeCode timecode = get_export_time_code(); + const pxr::SdfPath &volume_path = usd_export_context_.usd_path; + pxr::UsdStageRefPtr stage = usd_export_context_.stage; + pxr::UsdVolVolume usd_volume = pxr::UsdVolVolume::Define(stage, volume_path); + + for (const int i : IndexRange(num_grids)) { + const VolumeGrid *grid = BKE_volume_grid_get_for_read(volume, i); + const std::string grid_name = BKE_volume_grid_name(grid); + const std::string grid_id = pxr::TfMakeValidIdentifier(grid_name); + const pxr::SdfPath grid_path = volume_path.AppendPath(pxr::SdfPath(grid_id)); + pxr::UsdVolOpenVDBAsset usd_grid = pxr::UsdVolOpenVDBAsset::Define(stage, grid_path); + usd_grid.GetFieldNameAttr().Set(pxr::TfToken(grid_name), timecode); + usd_grid.GetFilePathAttr().Set(pxr::SdfAssetPath(vdb_file_path.value()), timecode); + usd_volume.CreateFieldRelationship(pxr::TfToken(grid_id), grid_path); + } + + float3 volume_bound_min(std::numeric_limits<float>::max()); + float3 volume_bound_max(std::numeric_limits<float>::min()); + if (BKE_volume_min_max(volume, volume_bound_min, volume_bound_max)) { + const pxr::VtArray<pxr::GfVec3f> volume_extent = {pxr::GfVec3f(&volume_bound_min[0]), + pxr::GfVec3f(&volume_bound_max[0])}; + usd_volume.GetExtentAttr().Set(volume_extent, timecode); + } + + BKE_volume_unload(volume); +} + +std::optional<std::string> USDVolumeWriter::resolve_vdb_file(const Volume *volume) const +{ + std::optional<std::string> vdb_file_path; + if (volume->filepath[0] == '\0') { + /* Entering this section should mean that Volume object contains OpenVDB data that is not + * obtained from external .vdb file but rather generated inside of Blender (i.e. by 'Mesh to + * Volume' modifier). Try to save this data to a .vdb file. */ + + vdb_file_path = construct_vdb_file_path(volume); + if (!BKE_volume_save( + volume, usd_export_context_.bmain, NULL, vdb_file_path.value_or("").c_str())) { + return std::nullopt; + } + } + + if (!vdb_file_path.has_value()) { + vdb_file_path = BKE_volume_grids_frame_filepath(volume); + if (vdb_file_path.value().empty()) { + return std::nullopt; + } + } + + return vdb_file_path; +} + +std::optional<std::string> USDVolumeWriter::construct_vdb_file_path(const Volume *volume) const +{ + const std::string usd_file_path = get_export_file_path(); + if (usd_file_path.empty()) { + return std::nullopt; + } + + char usd_directory_path[FILE_MAX]; + char usd_file_name[FILE_MAXFILE]; + BLI_split_dirfile(usd_file_path.c_str(), + usd_directory_path, + usd_file_name, + sizeof(usd_directory_path), + sizeof(usd_file_name)); + + if (usd_directory_path[0] == '\0' || usd_file_name[0] == '\0') { + return std::nullopt; + } + + const char *vdb_directory_name = "volumes"; + + char vdb_directory_path[FILE_MAX]; + BLI_strncpy(vdb_directory_path, usd_directory_path, FILE_MAX); + strcat(vdb_directory_path, vdb_directory_name); + BLI_dir_create_recursive(vdb_directory_path); + + char vdb_file_name[FILE_MAXFILE]; + BLI_strncpy(vdb_file_name, volume->id.name + 2, FILE_MAXFILE); + const pxr::UsdTimeCode timecode = get_export_time_code(); + if (!timecode.IsDefault()) { + const int frame = (int)timecode.GetValue(); + const int num_frame_digits = frame == 0 ? 1 : integer_digits_i(abs(frame)); + BLI_path_frame(vdb_file_name, frame, num_frame_digits); + } + strcat(vdb_file_name, ".vdb"); + + char vdb_file_path[FILE_MAX]; + BLI_path_join(vdb_file_path, sizeof(vdb_file_path), vdb_directory_path, vdb_file_name, NULL); + + return vdb_file_path; +} + +std::optional<std::string> USDVolumeWriter::construct_vdb_relative_file_path( + const std::string &vdb_file_path) const +{ + const std::string usd_file_path = get_export_file_path(); + if (usd_file_path.empty()) { + return std::nullopt; + } + + char relative_path[FILE_MAX]; + BLI_strncpy(relative_path, vdb_file_path.c_str(), FILE_MAX); + BLI_path_rel(relative_path, usd_file_path.c_str()); + if (!BLI_path_is_rel(relative_path)) { + return std::nullopt; + } + + /* Following code was written with an assumption that Blender's relative paths start with + * // characters as well as have OS dependent slashes. Inside of USD files those relative + * paths should start with either ./ or ../ characters and have always forward slashes (/) + * separating directories. This is the convention used in USD documentation (and it seems + * to be used in other DCC packages as well). */ + std::string relative_path_processed = pxr::TfNormPath(relative_path + 2); + if (relative_path_processed[0] != '.') { + relative_path_processed.insert(0, "./"); + } + + return relative_path_processed; +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_writer_volume.h b/source/blender/io/usd/intern/usd_writer_volume.h new file mode 100644 index 00000000000..8c1e36b7e53 --- /dev/null +++ b/source/blender/io/usd/intern/usd_writer_volume.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +#include <optional> + +#include "BLI_math_vec_types.hh" +#include "usd_writer_abstract.h" + +struct Volume; + +namespace blender::io::usd { + +/* Writer for writing OpenVDB assets to UsdVolVolume. Volume data is stored in separate .vdb files + * which are referenced in USD file. */ +class USDVolumeWriter : public USDAbstractWriter { + public: + USDVolumeWriter(const USDExporterContext &ctx); + + protected: + virtual bool check_is_animated(const HierarchyContext &context) const override; + virtual void do_write(HierarchyContext &context) override; + + private: + /* Try to ensure that external .vdb file is available for USD to be referenced. Blender can + * either reference external OpenVDB data or generate such data internally. Latter option will + * mean that `resolve_vdb_file` method will try to export volume data to a new .vdb file. If + * successful, this method returns absolute file path to the resolved .vdb file, if not, returns + * `std::nullopt`. */ + std::optional<std::string> resolve_vdb_file(const Volume *volume) const; + + std::optional<std::string> construct_vdb_file_path(const Volume *volume) const; + std::optional<std::string> construct_vdb_relative_file_path( + const std::string &vdb_file_path) const; +}; + +} // namespace blender::io::usd |