Blender V5.0
usd_writer_abstract.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2019 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
6#include "usd_utils.hh"
8
9#include <pxr/base/tf/stringUtils.h>
10#include <pxr/usd/usdGeom/bboxCache.h>
11#include <pxr/usd/usdGeom/scope.h>
12
13#include "BKE_customdata.hh"
14
15#include "BLI_assert.h"
16#include "BLI_bounds_types.hh"
17
18#include "DNA_material_types.h"
19#include "DNA_mesh_types.h"
20
21#include "CLG_log.h"
22static CLG_LogRef LOG = {"io.usd"};
23
24/* TfToken objects are not cheap to construct, so we do it once. */
25namespace usdtokens {
26static const pxr::TfToken blender_ns("userProperties:blender", pxr::TfToken::Immortal);
27} // namespace usdtokens
28
29static std::string get_mesh_active_uvlayer_name(const Object *ob)
30{
31 if (!ob || ob->type != OB_MESH || !ob->data) {
32 return "";
33 }
34
35 const Mesh *mesh = static_cast<Mesh *>(ob->data);
36
38
39 return name ? name : "";
40}
41
42template<typename USDT>
43bool set_vec_attrib(const pxr::UsdPrim &prim,
44 const IDProperty *prop,
45 const pxr::TfToken &prop_token,
46 const pxr::SdfValueTypeName &type_name,
47 const pxr::UsdTimeCode &time)
48{
49 if (!prim || !prop || !prop->data.pointer || prop_token.IsEmpty() || !type_name) {
50 return false;
51 }
52
53 pxr::UsdAttribute vec_attr = prim.CreateAttribute(prop_token, type_name, true);
54
55 if (!vec_attr) {
57 "Couldn't create USD attribute for array property %s",
58 prop_token.GetString().c_str());
59 return false;
60 }
61
62 USDT vec_value(static_cast<typename USDT::ScalarType *>(prop->data.pointer));
63
64 return vec_attr.Set(vec_value, time);
65}
66
67namespace blender::io::usd {
68
69static void create_vector_attrib(const pxr::UsdPrim &prim,
70 const IDProperty *prop,
71 const pxr::TfToken &prop_token,
72 const pxr::UsdTimeCode &time)
73{
74 if (!prim || !prop || prop_token.IsEmpty()) {
75 return;
76 }
77
78 if (prop->type != IDP_ARRAY) {
80 "Property %s is not an array type and can't be converted to a vector attribute",
81 prop->name);
82 return;
83 }
84
85 pxr::SdfValueTypeName type_name;
86 bool success = false;
87
88 if (prop->subtype == IDP_FLOAT) {
89 if (prop->len == 2) {
90 type_name = pxr::SdfValueTypeNames->Float2;
91 success = set_vec_attrib<pxr::GfVec2f>(prim, prop, prop_token, type_name, time);
92 }
93 else if (prop->len == 3) {
94 type_name = pxr::SdfValueTypeNames->Float3;
95 success = set_vec_attrib<pxr::GfVec3f>(prim, prop, prop_token, type_name, time);
96 }
97 else if (prop->len == 4) {
98 type_name = pxr::SdfValueTypeNames->Float4;
99 success = set_vec_attrib<pxr::GfVec4f>(prim, prop, prop_token, type_name, time);
100 }
101 }
102 else if (prop->subtype == IDP_DOUBLE) {
103 if (prop->len == 2) {
104 type_name = pxr::SdfValueTypeNames->Double2;
105 success = set_vec_attrib<pxr::GfVec2d>(prim, prop, prop_token, type_name, time);
106 }
107 else if (prop->len == 3) {
108 type_name = pxr::SdfValueTypeNames->Double3;
109 success = set_vec_attrib<pxr::GfVec3d>(prim, prop, prop_token, type_name, time);
110 }
111 else if (prop->len == 4) {
112 type_name = pxr::SdfValueTypeNames->Double4;
113 success = set_vec_attrib<pxr::GfVec4d>(prim, prop, prop_token, type_name, time);
114 }
115 }
116 else if (prop->subtype == IDP_INT) {
117 if (prop->len == 2) {
118 type_name = pxr::SdfValueTypeNames->Int2;
119 success = set_vec_attrib<pxr::GfVec2i>(prim, prop, prop_token, type_name, time);
120 }
121 else if (prop->len == 3) {
122 type_name = pxr::SdfValueTypeNames->Int3;
123 success = set_vec_attrib<pxr::GfVec3i>(prim, prop, prop_token, type_name, time);
124 }
125 else if (prop->len == 4) {
126 type_name = pxr::SdfValueTypeNames->Int4;
127 success = set_vec_attrib<pxr::GfVec4i>(prim, prop, prop_token, type_name, time);
128 }
129 }
130
131 if (!type_name) {
132 CLOG_WARN(&LOG,
133 "Couldn't determine USD type name for array property %s",
134 prop_token.GetString().c_str());
135 return;
136 }
137
138 if (!success) {
139 CLOG_WARN(
140 &LOG, "Couldn't set USD attribute from array property %s", prop_token.GetString().c_str());
141 return;
142 }
143}
144
149
151{
152 return true;
153}
154
156{
157 return usd_export_context_.export_file_path;
158}
159
161{
162 if (is_animated_) {
163 BLI_assert(usd_export_context_.get_time_code);
164 return usd_export_context_.get_time_code();
165 }
166 /* By using the default time-code USD won't even write a single `timeSample` for non-animated
167 * data. Instead, it writes it as non-time-sampled. */
168 return pxr::UsdTimeCode::Default();
169}
170
172{
174 is_animated_ = usd_export_context_.export_params.export_animation &&
175 check_is_animated(context);
176 }
177 else if (!is_animated_) {
178 /* A frame has already been written, and without animation one frame is enough. */
179 return;
180 }
181
182 do_write(context);
183
185}
186
187const pxr::SdfPath &USDAbstractWriter::usd_path() const
188{
189 return usd_export_context_.usd_path;
190}
191
193{
194 static std::string material_library_path("/_materials");
195
196 const std::string &root_prim_path = usd_export_context_.export_params.root_prim_path;
197
198 if (!root_prim_path.empty()) {
199 return pxr::SdfPath(root_prim_path + material_library_path);
200 }
201
202 return pxr::SdfPath(material_library_path);
203}
204
206{
207 static std::string material_library_path("/_materials");
208
209 std::string path_prefix(usd_export_context_.export_params.root_prim_path);
210
211 path_prefix += context.higher_up_export_path;
212
213 return pxr::SdfPath(path_prefix + material_library_path);
214}
215
217 const HierarchyContext &context, Material *material) const
218{
219 pxr::UsdStageRefPtr stage = usd_export_context_.stage;
220
221 /* Construct the material. */
222 pxr::TfToken material_name(
223 make_safe_name(material->id.name + 2, usd_export_context_.export_params.allow_unicode));
224 pxr::SdfPath usd_path = pxr::UsdGeomScope::Define(stage, get_material_library_path())
225 .GetPath()
226 .AppendChild(material_name);
227 pxr::UsdShadeMaterial usd_material = pxr::UsdShadeMaterial::Get(stage, usd_path);
228 if (usd_material) {
229 return usd_material;
230 }
231
232 std::string active_uv = get_mesh_active_uvlayer_name(context.object);
233
234 usd_material = create_usd_material(
235 usd_export_context_, usd_path, material, active_uv, reports());
236
237 auto prim = usd_material.GetPrim();
238 write_id_properties(prim, material->id, get_export_time_code());
239
240 return usd_material;
241}
242
243pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyContext &context,
244 Material *material) const
245{
246 pxr::UsdShadeMaterial library_material = ensure_usd_material_created(context, material);
247
248 /* If instancing is enabled and the object is an instancing prototype, create a material
249 * under the prototype root referencing the library material. This is considered a best
250 * practice and is required for certain renderers (e.g., karma). */
251
252 if (!(usd_export_context_.export_params.use_instancing && context.is_prototype())) {
253 /* We don't need to handle the material for the prototype. */
254 return library_material;
255 }
256
257 /* Create the prototype material. */
258
259 pxr::UsdStageRefPtr stage = usd_export_context_.stage;
260
261 pxr::SdfPath usd_path = pxr::UsdGeomScope::Define(stage, get_proto_material_root_path(context))
262 .GetPath()
263 .AppendChild(library_material.GetPath().GetNameToken());
264
265 pxr::UsdShadeMaterial proto_material = pxr::UsdShadeMaterial::Define(stage, usd_path);
266
267 if (!proto_material.GetPrim().GetReferences().AddInternalReference(library_material.GetPath())) {
268 CLOG_WARN(&LOG,
269 "Unable to add a material reference from %s to %s for prototype %s",
270 proto_material.GetPath().GetAsString().c_str(),
271 library_material.GetPath().GetAsString().c_str(),
272 context.export_path.c_str());
273 return library_material;
274 }
275
276 return proto_material;
277}
278
280 const pxr::UsdTimeCode time,
281 const pxr::UsdGeomImageable &usd_geometry)
282{
283 pxr::UsdAttribute attr_visibility = usd_geometry.CreateVisibilityAttr(pxr::VtValue(), true);
284
285 const bool is_visible = context.is_object_visible(
286 usd_export_context_.export_params.evaluation_mode);
287 const pxr::TfToken visibility = is_visible ? pxr::UsdGeomTokens->inherited :
288 pxr::UsdGeomTokens->invisible;
289
290 usd_value_writer_.SetAttribute(attr_visibility, pxr::VtValue(visibility), time);
291}
292
293bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const pxr::UsdPrim &prim)
294{
295 BLI_assert(context.is_instance());
296
297 if (context.export_path == context.original_export_path) {
299 "Reference error: export path matches reference path: %s",
300 context.export_path.c_str());
301 BLI_assert_msg(0, "USD reference error");
302 return false;
303 }
304
305 BLI_assert(!context.original_export_path.empty());
306 BLI_assert(context.original_export_path.front() == '/');
307
308 std::string ref_path_str(usd_export_context_.export_params.root_prim_path);
309 ref_path_str += context.original_export_path;
310
311 pxr::SdfPath ref_path(ref_path_str);
312
313 /* To avoid USD errors, make sure the referenced path exists. */
314 usd_export_context_.stage->DefinePrim(ref_path);
315
316 if (!prim.GetReferences().AddInternalReference(ref_path)) {
317 /* See this URL for a description for why referencing may fail"
318 * https://graphics.pixar.com/usd/docs/api/class_usd_references.html#Usd_Failing_References
319 */
320 CLOG_WARN(&LOG,
321 "Unable to add reference from %s to %s, not instancing object for export",
322 context.export_path.c_str(),
323 context.original_export_path.c_str());
324 return false;
325 }
326
327 prim.SetInstanceable(true);
328
329 return true;
330}
331
332void USDAbstractWriter::write_id_properties(const pxr::UsdPrim &prim,
333 const ID &id,
334 pxr::UsdTimeCode time) const
335{
336 if (!usd_export_context_.export_params.export_custom_properties) {
337 return;
338 }
339
340 if (usd_export_context_.export_params.author_blender_name) {
341 if (GS(id.name) == ID_OB) {
342 /* Author property of original blender Object name. */
343 prim.CreateAttribute(pxr::TfToken(usdtokens::blender_ns.GetString() + ":object_name"),
344 pxr::SdfValueTypeNames->String,
345 true)
346 .Set<std::string>(std::string(id.name + 2));
347 }
348 else {
349 prim.CreateAttribute(pxr::TfToken(usdtokens::blender_ns.GetString() + ":data_name"),
350 pxr::SdfValueTypeNames->String,
351 true)
352 .Set<std::string>(std::string(id.name + 2));
353 }
354 }
355
356 if (id.properties) {
357 write_user_properties(prim, id.properties, time);
358 }
359}
360
361void USDAbstractWriter::write_user_properties(const pxr::UsdPrim &prim,
362 IDProperty *properties,
363 pxr::UsdTimeCode time) const
364{
365 if (properties == nullptr) {
366 return;
367 }
368
369 if (properties->type != IDP_GROUP) {
370 return;
371 }
372
373 const StringRef displayName_identifier = "displayName";
374
375 const std::string default_namespace(
376 usd_export_context_.export_params.custom_properties_namespace);
377
378 for (IDProperty *prop = (IDProperty *)properties->data.group.first; prop; prop = prop->next) {
379 if (displayName_identifier == prop->name) {
380 if (prop->type == IDP_STRING && prop->data.pointer) {
381 prim.SetDisplayName(static_cast<char *>(prop->data.pointer));
382 }
383 continue;
384 }
385
386 std::vector<std::string> path_names = pxr::TfStringTokenize(prop->name, ":");
387
388 /* If the path does not already have a namespace prefix, prepend the default namespace
389 * specified by the user, if any. */
390 if (!default_namespace.empty() && path_names.size() < 2) {
391 path_names.insert(path_names.begin(), default_namespace);
392 }
393
394 std::vector<std::string> safe_names;
395 for (const std::string &name : path_names) {
396 safe_names.push_back(make_safe_name(name, usd_export_context_.export_params.allow_unicode));
397 }
398
399 std::string full_prop_name = pxr::SdfPath::JoinIdentifier(safe_names);
400 pxr::TfToken prop_token = pxr::TfToken(full_prop_name);
401
402 if (prim.HasAttribute(prop_token)) {
403 /* Don't overwrite existing attributes, as these may have been
404 * created by the exporter logic and shouldn't be changed. */
405 continue;
406 }
407
408 switch (prop->type) {
409 case IDP_INT:
410 if (pxr::UsdAttribute int_attr = prim.CreateAttribute(
411 prop_token, pxr::SdfValueTypeNames->Int, true))
412 {
413 int_attr.Set<int>(prop->data.val, time);
414 }
415 break;
416 case IDP_FLOAT:
417 if (pxr::UsdAttribute float_attr = prim.CreateAttribute(
418 prop_token, pxr::SdfValueTypeNames->Float, true))
419 {
420 float_attr.Set<float>(*reinterpret_cast<float *>(&prop->data.val), time);
421 }
422 break;
423 case IDP_DOUBLE:
424 if (pxr::UsdAttribute double_attr = prim.CreateAttribute(
425 prop_token, pxr::SdfValueTypeNames->Double, true))
426 {
427 double_attr.Set<double>(*reinterpret_cast<double *>(&prop->data.val), time);
428 }
429 break;
430 case IDP_STRING:
431 if (pxr::UsdAttribute str_attr = prim.CreateAttribute(
432 prop_token, pxr::SdfValueTypeNames->String, true))
433 {
434 str_attr.Set<std::string>(static_cast<const char *>(prop->data.pointer), time);
435 }
436 break;
437 case IDP_BOOLEAN:
438 if (pxr::UsdAttribute bool_attr = prim.CreateAttribute(
439 prop_token, pxr::SdfValueTypeNames->Bool, true))
440 {
441 bool_attr.Set<bool>(prop->data.val, time);
442 }
443 break;
444 case IDP_ARRAY:
445 create_vector_attrib(prim, prop, prop_token, time);
446 break;
447 }
448 }
449}
450
451void USDAbstractWriter::author_extent(const pxr::UsdGeomBoundable &boundable,
452 const pxr::UsdTimeCode time)
453{
454 /* Do not use any existing `extentsHint` that may be authored, instead recompute the extent when
455 * authoring it. */
456 const bool useExtentsHint = false;
457 const pxr::TfTokenVector includedPurposes{pxr::UsdGeomTokens->default_};
458 pxr::UsdGeomBBoxCache bboxCache(time, includedPurposes, useExtentsHint);
459 pxr::GfBBox3d bounds = bboxCache.ComputeLocalBound(boundable.GetPrim());
460
461 /* Note: An empty 'bounds' is still valid (e.g. a mesh with no vertices). */
462 pxr::VtArray<pxr::GfVec3f> extent{pxr::GfVec3f(bounds.GetRange().GetMin()),
463 pxr::GfVec3f(bounds.GetRange().GetMax())};
464
465 pxr::UsdAttribute attr_extent = boundable.CreateExtentAttr(pxr::VtValue(), true);
466 set_attribute(attr_extent, extent, time, usd_value_writer_);
467}
468
469void USDAbstractWriter::author_extent(const pxr::UsdGeomBoundable &boundable,
470 const std::optional<Bounds<float3>> &bounds,
471 const pxr::UsdTimeCode time)
472{
473 pxr::VtArray<pxr::GfVec3f> extent(2);
474 if (bounds) {
475 extent[0].Set(bounds->min);
476 extent[1].Set(bounds->max);
477 }
478
479 pxr::UsdAttribute attr_extent = boundable.CreateExtentAttr(pxr::VtValue(), true);
480 set_attribute(attr_extent, extent, time, usd_value_writer_);
481}
482
483} // namespace blender::io::usd
CustomData interface, see also DNA_customdata_types.h.
const char * CustomData_get_active_layer_name(const CustomData *data, eCustomDataType type)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:188
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:189
@ ID_OB
@ IDP_DOUBLE
@ IDP_FLOAT
@ IDP_STRING
@ IDP_BOOLEAN
@ IDP_INT
@ IDP_GROUP
@ IDP_ARRAY
@ CD_PROP_FLOAT2
@ OB_MESH
virtual bool check_is_animated(const HierarchyContext &context) const
pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material) const
virtual void do_write(HierarchyContext &context)=0
pxr::SdfPath get_proto_material_root_path(const HierarchyContext &context) const
void author_extent(const pxr::UsdGeomBoundable &boundable, const pxr::UsdTimeCode time)
const pxr::SdfPath & usd_path() const
void write_user_properties(const pxr::UsdPrim &prim, IDProperty *properties, pxr::UsdTimeCode=pxr::UsdTimeCode::Default()) const
pxr::UsdShadeMaterial ensure_usd_material_created(const HierarchyContext &context, Material *material) const
virtual bool mark_as_instance(const HierarchyContext &context, const pxr::UsdPrim &prim)
pxr::UsdTimeCode get_export_time_code() const
pxr::UsdUtilsSparseValueWriter usd_value_writer_
USDAbstractWriter(const USDExporterContext &usd_export_context)
void write_id_properties(const pxr::UsdPrim &prim, const ID &id, pxr::UsdTimeCode=pxr::UsdTimeCode::Default()) const
void write(HierarchyContext &context) override
void write_visibility(const HierarchyContext &context, const pxr::UsdTimeCode time, const pxr::UsdGeomImageable &usd_geometry)
virtual bool is_supported(const HierarchyContext *context) const
const USDExporterContext usd_export_context_
#define GS(x)
#define LOG(level)
Definition log.h:97
static void create_vector_attrib(const pxr::UsdPrim &prim, const IDProperty *prop, const pxr::TfToken &prop_token, const pxr::UsdTimeCode &time)
std::string make_safe_name(const StringRef name, bool allow_unicode)
Definition usd_utils.cc:18
pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_context, pxr::SdfPath usd_path, Material *material, const std::string &active_uvmap_name, ReportList *reports)
void set_attribute(const pxr::UsdAttribute &attr, const USDT value, pxr::UsdTimeCode time, pxr::UsdUtilsSparseValueWriter &value_writer)
static const pxr::TfToken blender_ns("userProperties:blender", pxr::TfToken::Immortal)
const char * name
ListBase group
Definition DNA_ID.h:143
void * pointer
Definition DNA_ID.h:142
int len
Definition DNA_ID.h:175
char name[64]
Definition DNA_ID.h:164
IDPropertyData data
Definition DNA_ID.h:169
char subtype
Definition DNA_ID.h:161
char type
Definition DNA_ID.h:156
Definition DNA_ID.h:414
char name[258]
Definition DNA_ID.h:432
void * first
CustomData corner_data
bool set_vec_attrib(const pxr::UsdPrim &prim, const IDProperty *prop, const pxr::TfToken &prop_token, const pxr::SdfValueTypeName &type_name, const pxr::UsdTimeCode &time)
static std::string get_mesh_active_uvlayer_name(const Object *ob)