Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPiotr Makal <pmakal>2022-05-06 12:36:27 +0300
committerSybren A. Stüvel <sybren@blender.org>2022-05-06 12:43:43 +0300
commitce3dd12371f5bd02eb64488712d8c529ffe62f6b (patch)
treef489357ce47ec4fa1e2ba0fb02a8f8a11955192d /source/blender/io
parentf3b56246d1ef113a434cc647c6865f91631bc18d (diff)
USD: add volume/VDB export
Add support for volume (OpenVDB) USD export: - Allows to export both static and animated volumes. - Supports volumes that have OpenVDB data from files or are generated in Blender with 'Mesh to Volume' modifier. - For volumes that have generated data in Blender it also exports corresponding .vdb files. Those files are saved in a new folder named "volumes". - Slightly changes the USD export UI panel. "Relative Texture Paths" becomes "Relative Paths" (and has separate UI box) as the functionality will now apply to both textures and volumes. Disabling of this option due to "Materials" checkbox being turned off has been removed. Reviewed By: sybren, makowalski Differential Revision: https://developer.blender.org/D14193 Manifest Task: T95407
Diffstat (limited to 'source/blender/io')
-rw-r--r--source/blender/io/alembic/exporter/abc_export_capi.cc2
-rw-r--r--source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc5
-rw-r--r--source/blender/io/alembic/exporter/abc_hierarchy_iterator.h4
-rw-r--r--source/blender/io/common/IO_abstract_hierarchy_iterator.h4
-rw-r--r--source/blender/io/common/intern/abstract_hierarchy_iterator.cc4
-rw-r--r--source/blender/io/common/intern/abstract_hierarchy_iterator_test.cc5
-rw-r--r--source/blender/io/usd/CMakeLists.txt13
-rw-r--r--source/blender/io/usd/intern/usd_capi_export.cc2
-rw-r--r--source/blender/io/usd/intern/usd_exporter_context.h2
-rw-r--r--source/blender/io/usd/intern/usd_hierarchy_iterator.cc21
-rw-r--r--source/blender/io/usd/intern/usd_hierarchy_iterator.h5
-rw-r--r--source/blender/io/usd/intern/usd_writer_abstract.cc5
-rw-r--r--source/blender/io/usd/intern/usd_writer_abstract.h1
-rw-r--r--source/blender/io/usd/intern/usd_writer_material.cc10
-rw-r--r--source/blender/io/usd/intern/usd_writer_volume.cc188
-rw-r--r--source/blender/io/usd/intern/usd_writer_volume.h36
-rw-r--r--source/blender/io/usd/usd.h2
17 files changed, 285 insertions, 24 deletions
diff --git a/source/blender/io/alembic/exporter/abc_export_capi.cc b/source/blender/io/alembic/exporter/abc_export_capi.cc
index c1ff7df0c37..edaf53b3efa 100644
--- a/source/blender/io/alembic/exporter/abc_export_capi.cc
+++ b/source/blender/io/alembic/exporter/abc_export_capi.cc
@@ -115,7 +115,7 @@ static void export_startjob(void *customdata,
return;
}
- ABCHierarchyIterator iter(data->depsgraph, abc_archive.get(), data->params);
+ ABCHierarchyIterator iter(data->bmain, data->depsgraph, abc_archive.get(), data->params);
if (export_animation) {
CLOG_INFO(&LOG, 2, "Exporting animation");
diff --git a/source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc b/source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc
index d33adfdb4b9..ab62694b802 100644
--- a/source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc
+++ b/source/blender/io/alembic/exporter/abc_hierarchy_iterator.cc
@@ -26,10 +26,11 @@
namespace blender::io::alembic {
-ABCHierarchyIterator::ABCHierarchyIterator(Depsgraph *depsgraph,
+ABCHierarchyIterator::ABCHierarchyIterator(Main *bmain,
+ Depsgraph *depsgraph,
ABCArchive *abc_archive,
const AlembicExportParams &params)
- : AbstractHierarchyIterator(depsgraph), abc_archive_(abc_archive), params_(params)
+ : AbstractHierarchyIterator(bmain, depsgraph), abc_archive_(abc_archive), params_(params)
{
}
diff --git a/source/blender/io/alembic/exporter/abc_hierarchy_iterator.h b/source/blender/io/alembic/exporter/abc_hierarchy_iterator.h
index 24d3f319fbd..f7814c28a55 100644
--- a/source/blender/io/alembic/exporter/abc_hierarchy_iterator.h
+++ b/source/blender/io/alembic/exporter/abc_hierarchy_iterator.h
@@ -13,6 +13,7 @@
#include <Alembic/Abc/OObject.h>
struct Depsgraph;
+struct Main;
struct Object;
namespace blender::io::alembic {
@@ -36,7 +37,8 @@ class ABCHierarchyIterator : public AbstractHierarchyIterator {
const AlembicExportParams &params_;
public:
- ABCHierarchyIterator(Depsgraph *depsgraph,
+ ABCHierarchyIterator(Main *bmain,
+ Depsgraph *depsgraph,
ABCArchive *abc_archive_,
const AlembicExportParams &params);
diff --git a/source/blender/io/common/IO_abstract_hierarchy_iterator.h b/source/blender/io/common/IO_abstract_hierarchy_iterator.h
index 3c6b6cc631e..3371501db95 100644
--- a/source/blender/io/common/IO_abstract_hierarchy_iterator.h
+++ b/source/blender/io/common/IO_abstract_hierarchy_iterator.h
@@ -30,6 +30,7 @@
struct Depsgraph;
struct DupliObject;
struct ID;
+struct Main;
struct Object;
struct ParticleSystem;
@@ -204,12 +205,13 @@ class AbstractHierarchyIterator {
protected:
ExportGraph export_graph_;
ExportPathMap duplisource_export_path_;
+ Main *bmain_;
Depsgraph *depsgraph_;
WriterMap writers_;
ExportSubset export_subset_;
public:
- explicit AbstractHierarchyIterator(Depsgraph *depsgraph);
+ explicit AbstractHierarchyIterator(Main *bmain, Depsgraph *depsgraph);
virtual ~AbstractHierarchyIterator();
/* Iterate over the depsgraph, create writers, and tell the writers to write.
diff --git a/source/blender/io/common/intern/abstract_hierarchy_iterator.cc b/source/blender/io/common/intern/abstract_hierarchy_iterator.cc
index f0501d4cf62..82bb1c57833 100644
--- a/source/blender/io/common/intern/abstract_hierarchy_iterator.cc
+++ b/source/blender/io/common/intern/abstract_hierarchy_iterator.cc
@@ -161,8 +161,8 @@ bool AbstractHierarchyWriter::check_has_deforming_physics(const HierarchyContext
return rbo != nullptr && rbo->type == RBO_TYPE_ACTIVE && (rbo->flag & RBO_FLAG_USE_DEFORM) != 0;
}
-AbstractHierarchyIterator::AbstractHierarchyIterator(Depsgraph *depsgraph)
- : depsgraph_(depsgraph), export_subset_({true, true})
+AbstractHierarchyIterator::AbstractHierarchyIterator(Main *bmain, Depsgraph *depsgraph)
+ : bmain_(bmain), depsgraph_(depsgraph), export_subset_({true, true})
{
}
diff --git a/source/blender/io/common/intern/abstract_hierarchy_iterator_test.cc b/source/blender/io/common/intern/abstract_hierarchy_iterator_test.cc
index 52cae89c2e8..81a3546259e 100644
--- a/source/blender/io/common/intern/abstract_hierarchy_iterator_test.cc
+++ b/source/blender/io/common/intern/abstract_hierarchy_iterator_test.cc
@@ -54,7 +54,8 @@ class TestingHierarchyIterator : public AbstractHierarchyIterator {
used_writers hair_writers;
used_writers particle_writers;
- explicit TestingHierarchyIterator(Depsgraph *depsgraph) : AbstractHierarchyIterator(depsgraph)
+ explicit TestingHierarchyIterator(Main *bmain, Depsgraph *depsgraph)
+ : AbstractHierarchyIterator(bmain, depsgraph)
{
}
~TestingHierarchyIterator() override
@@ -105,7 +106,7 @@ class AbstractHierarchyIteratorTest : public BlendfileLoadingBaseTest {
/* Create a test iterator. */
void iterator_create()
{
- iterator = new TestingHierarchyIterator(depsgraph);
+ iterator = new TestingHierarchyIterator(bfile->main, depsgraph);
}
/* Free the test iterator if it is not nullptr. */
void iterator_free()
diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt
index e2e959814fa..7a7c95b29f9 100644
--- a/source/blender/io/usd/CMakeLists.txt
+++ b/source/blender/io/usd/CMakeLists.txt
@@ -69,6 +69,7 @@ set(SRC
intern/usd_writer_mesh.cc
intern/usd_writer_metaball.cc
intern/usd_writer_transform.cc
+ intern/usd_writer_volume.cc
intern/usd_reader_camera.cc
intern/usd_reader_curve.cc
@@ -95,6 +96,7 @@ set(SRC
intern/usd_writer_mesh.h
intern/usd_writer_metaball.h
intern/usd_writer_transform.h
+ intern/usd_writer_volume.h
intern/usd_reader_camera.h
intern/usd_reader_curve.h
@@ -119,8 +121,15 @@ list(APPEND LIB
${BOOST_LIBRARIES}
)
-list(APPEND LIB
-)
+if(WITH_OPENVDB)
+ add_definitions(-DWITH_OPENVDB ${OPENVDB_DEFINITIONS})
+ list(APPEND INC_SYS
+ ${OPENVDB_INCLUDE_DIRS}
+ )
+ list(APPEND LIB
+ ${OPENVDB_LIBRARIES}
+ )
+endif()
blender_add_lib(bf_usd "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
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 &params)
- : 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 &params_;
public:
- USDHierarchyIterator(Depsgraph *depsgraph,
+ USDHierarchyIterator(Main *bmain,
+ Depsgraph *depsgraph,
pxr::UsdStageRefPtr stage,
const USDExportParams &params);
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
diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h
index e63cd0a4e04..deaa27a5252 100644
--- a/source/blender/io/usd/usd.h
+++ b/source/blender/io/usd/usd.h
@@ -28,7 +28,7 @@ struct USDExportParams {
bool generate_preview_surface;
bool export_textures;
bool overwrite_textures;
- bool relative_texture_paths;
+ bool relative_paths;
};
struct USDImportParams {