Blender V5.0
usd_writer_volume.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
7#include "usd_utils.hh"
8
9#include <pxr/base/gf/vec3f.h>
10#include <pxr/base/tf/pathUtils.h>
11#include <pxr/base/vt/array.h>
12#include <pxr/base/vt/value.h>
13#include <pxr/usd/usdVol/openVDBAsset.h>
14#include <pxr/usd/usdVol/volume.h>
15
16#include "DNA_scene_types.h"
17#include "DNA_volume_types.h"
19
20#include "BKE_report.hh"
21#include "BKE_volume.hh"
22
23#include "BLI_fileops.h"
24#include "BLI_index_range.hh"
25#include "BLI_math_base.h"
26#include "BLI_path_utils.hh"
27#include "BLI_string.h"
28
30
31namespace blender::io::usd {
32
33static bool has_varying_modifiers(const Object *ob)
34{
35 /* These modifiers may vary the Volume either over time or by deformation/transformation. */
36 ModifierData *md = static_cast<ModifierData *>(ob->modifiers.first);
37 while (md) {
38 if (ELEM(md->type,
42 {
43 return true;
44 }
45 md = md->next;
46 }
47
48 return false;
49}
50
52
54{
55 const Volume *volume = static_cast<Volume *>(context.object->data);
56 return volume->is_sequence || has_varying_modifiers(context.object);
57}
58
60{
61 Volume *volume = static_cast<Volume *>(context.object->data);
62 if (!BKE_volume_load(volume, usd_export_context_.bmain)) {
63 return;
64 }
65
66 const int num_grids = BKE_volume_num_grids(volume);
67 if (!num_grids) {
68 return;
69 }
70
71 const bool has_modifiers = has_varying_modifiers(context.object);
72 auto vdb_file_path = resolve_vdb_file(volume, has_modifiers);
73 if (!vdb_file_path.has_value()) {
76 "USD Export: failed to resolve .vdb file for object: %s",
77 volume->id.name + 2);
78 return;
79 }
80
81 if (usd_export_context_.export_params.relative_paths) {
82 if (auto relative_vdb_file_path = construct_vdb_relative_file_path(*vdb_file_path)) {
83 vdb_file_path = relative_vdb_file_path;
84 }
85 else {
88 "USD Export: couldn't construct relative file path for .vdb file, absolute path "
89 "will be used instead");
90 }
91 }
92
93 const pxr::UsdTimeCode time = get_export_time_code();
94 const pxr::SdfPath &volume_path = usd_export_context_.usd_path;
95 pxr::UsdStageRefPtr stage = usd_export_context_.stage;
96 pxr::UsdVolVolume usd_volume = pxr::UsdVolVolume::Define(stage, volume_path);
97
98 for (const int i : IndexRange(num_grids)) {
99 const bke::VolumeGridData *grid = BKE_volume_grid_get(volume, i);
100 const std::string grid_name = bke::volume_grid::get_name(*grid);
101 const std::string grid_id = make_safe_name(grid_name,
102 usd_export_context_.export_params.allow_unicode);
103 const pxr::SdfPath grid_path = volume_path.AppendPath(pxr::SdfPath(grid_id));
104 pxr::UsdVolOpenVDBAsset usd_grid = pxr::UsdVolOpenVDBAsset::Define(stage, grid_path);
105
106 pxr::TfToken grid_name_token = pxr::TfToken(grid_name);
107 pxr::SdfAssetPath asset_path = pxr::SdfAssetPath(*vdb_file_path);
108 pxr::UsdAttribute attr_field = usd_grid.CreateFieldNameAttr(pxr::VtValue(), true);
109 pxr::UsdAttribute attr_file = usd_grid.CreateFilePathAttr(pxr::VtValue(), true);
110 if (!attr_field.HasValue()) {
111 attr_field.Set(grid_name_token, pxr::UsdTimeCode::Default());
112 }
113 if (!attr_file.HasValue()) {
114 attr_file.Set(asset_path, pxr::UsdTimeCode::Default());
115 }
116
117 usd_value_writer_.SetAttribute(attr_field, grid_name_token, time);
118 usd_value_writer_.SetAttribute(attr_file, asset_path, time);
119
120 usd_volume.CreateFieldRelationship(pxr::TfToken(grid_id), grid_path);
121 }
122
123 this->author_extent(usd_volume, BKE_volume_min_max(volume), time);
124
125 BKE_volume_unload(volume);
126}
127
128std::optional<std::string> USDVolumeWriter::resolve_vdb_file(const Volume *volume,
129 bool has_modifiers) const
130{
131 std::optional<std::string> vdb_file_path;
132
133 const bool needs_vdb_save = volume->filepath[0] == '\0' || has_modifiers;
134 if (needs_vdb_save) {
135 /* Entering this section means that the Volume object contains OpenVDB data that is not
136 * obtained solely from external `.vdb` files but is generated or modified inside of Blender.
137 * Write this data as a new `.vdb` files. */
138
139 vdb_file_path = construct_vdb_file_path(volume);
140 if (!BKE_volume_save(
141 volume, usd_export_context_.bmain, nullptr, vdb_file_path.value_or("").c_str()))
142 {
143 return std::nullopt;
144 }
145 }
146
147 if (!vdb_file_path.has_value()) {
148 vdb_file_path = BKE_volume_grids_frame_filepath(volume);
149 if (vdb_file_path->empty()) {
150 return std::nullopt;
151 }
152 }
153
154 return vdb_file_path;
155}
156
157std::optional<std::string> USDVolumeWriter::construct_vdb_file_path(const Volume *volume) const
158{
159 const std::string usd_file_path = get_export_file_path();
160 if (usd_file_path.empty()) {
161 return std::nullopt;
162 }
163
164 char usd_directory_path[FILE_MAX];
165 char usd_file_name[FILE_MAXFILE];
166 BLI_path_split_dir_file(usd_file_path.c_str(),
167 usd_directory_path,
168 sizeof(usd_directory_path),
169 usd_file_name,
170 sizeof(usd_file_name));
171
172 if (usd_directory_path[0] == '\0' || usd_file_name[0] == '\0') {
173 return std::nullopt;
174 }
175
176 const char *vdb_directory_name = "volumes";
177
178 char vdb_directory_path[FILE_MAX];
179 STRNCPY(vdb_directory_path, usd_directory_path);
180 BLI_strncat(vdb_directory_path, vdb_directory_name, sizeof(vdb_directory_path));
181 BLI_dir_create_recursive(vdb_directory_path);
182
183 const Scene *scene = DEG_get_input_scene(usd_export_context_.depsgraph);
184 const int max_frame_digits = std::max(2, integer_digits_i(abs(scene->r.efra)));
185
186 char vdb_file_name[FILE_MAXFILE];
187 STRNCPY(vdb_file_name, volume->id.name + 2);
188 const pxr::UsdTimeCode time = get_export_time_code();
189 if (!time.IsDefault()) {
190 const int frame = int(time.GetValue());
191 BLI_path_frame(vdb_file_name, sizeof(vdb_file_name), frame, max_frame_digits);
192 }
193 BLI_strncat(vdb_file_name, ".vdb", sizeof(vdb_file_name));
194
195 char vdb_file_path[FILE_MAX];
196 BLI_path_join(vdb_file_path, sizeof(vdb_file_path), vdb_directory_path, vdb_file_name);
197
198 return vdb_file_path;
199}
200
201std::optional<std::string> USDVolumeWriter::construct_vdb_relative_file_path(
202 const std::string &vdb_file_path) const
203{
204 const std::string usd_file_path = get_export_file_path();
205 if (usd_file_path.empty()) {
206 return std::nullopt;
207 }
208
209 char relative_path[FILE_MAX];
210 STRNCPY(relative_path, vdb_file_path.c_str());
211 BLI_path_rel(relative_path, usd_file_path.c_str());
212 if (!BLI_path_is_rel(relative_path)) {
213 return std::nullopt;
214 }
215
216 /* Following code was written with an assumption that Blender's relative paths start with
217 * `//` characters as well as have OS dependent slashes. Inside of USD files those relative
218 * paths should start with either `./` or `../` characters and have always forward slashes (`/`)
219 * separating directories. This is the convention used in USD documentation (and it seems
220 * to be used in other DCC packages as well). */
221 std::string relative_path_processed = pxr::TfNormPath(relative_path + 2);
222 if (relative_path_processed[0] != '.') {
223 relative_path_processed.insert(0, "./");
224 }
225
226 return relative_path_processed;
227}
228
229} // namespace blender::io::usd
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_WARNING
Definition BKE_report.hh:38
Volume data-block.
int BKE_volume_num_grids(const Volume *volume)
bool BKE_volume_save(const Volume *volume, const Main *bmain, ReportList *reports, const char *filepath)
bool BKE_volume_load(const Volume *volume, const Main *bmain)
std::optional< blender::Bounds< blender::float3 > > BKE_volume_min_max(const Volume *volume)
const char * BKE_volume_grids_frame_filepath(const Volume *volume)
const blender::bke::VolumeGridData * BKE_volume_grid_get(const Volume *volume, int grid_index)
void BKE_volume_unload(Volume *volume)
File and directory operations.
bool BLI_dir_create_recursive(const char *dirname) ATTR_NONNULL()
Definition fileops_c.cc:414
MINLINE int integer_digits_i(int i)
#define FILE_MAXFILE
#define FILE_MAX
#define BLI_path_join(...)
void BLI_path_split_dir_file(const char *filepath, char *dir, size_t dir_maxncpy, char *file, size_t file_maxncpy) ATTR_NONNULL(1
bool BLI_path_is_rel(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
bool void BLI_path_rel(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1)
bool BLI_path_frame(char *path, size_t path_maxncpy, int frame, int digits) ATTR_NONNULL(1)
char char size_t char * BLI_strncat(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
#define ELEM(...)
Scene * DEG_get_input_scene(const Depsgraph *graph)
@ eModifierType_MeshToVolume
@ eModifierType_VolumeDisplace
@ eModifierType_Nodes
struct Scene Scene
struct Volume Volume
void author_extent(const pxr::UsdGeomBoundable &boundable, const pxr::UsdTimeCode time)
pxr::UsdTimeCode get_export_time_code() const
pxr::UsdUtilsSparseValueWriter usd_value_writer_
USDAbstractWriter(const USDExporterContext &usd_export_context)
const USDExporterContext usd_export_context_
bool check_is_animated(const HierarchyContext &context) const override
void do_write(HierarchyContext &context) override
USDVolumeWriter(const USDExporterContext &ctx)
#define abs
std::string get_name(const VolumeGridData &grid)
static bool has_varying_modifiers(const Object *ob)
std::string make_safe_name(const StringRef name, bool allow_unicode)
Definition usd_utils.cc:18
char name[258]
Definition DNA_ID.h:432
void * first
struct ModifierData * next
ListBase modifiers
struct RenderData r
char filepath[1024]
char is_sequence
i
Definition text_draw.cc:230