Blender V4.3
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 */
5#include "usd_utils.hh"
7
8#include <pxr/base/tf/stringUtils.h>
9#include <pxr/usd/usdGeom/bboxCache.h>
10#include <pxr/usd/usdGeom/scope.h>
11
12#include "BKE_customdata.hh"
13#include "BKE_report.hh"
14
15#include "BLI_assert.h"
16
17#include "DNA_mesh_types.h"
18
19#include "CLG_log.h"
20static CLG_LogRef LOG = {"io.usd"};
21
22/* TfToken objects are not cheap to construct, so we do it once. */
23namespace usdtokens {
24/* Materials */
25static const pxr::TfToken diffuse_color("diffuseColor", pxr::TfToken::Immortal);
26static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal);
27static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal);
28static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
29static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
30static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
31static const pxr::TfToken blender_ns("userProperties:blender", pxr::TfToken::Immortal);
32} // namespace usdtokens
33
34static std::string get_mesh_active_uvlayer_name(const Object *ob)
35{
36 if (!ob || ob->type != OB_MESH || !ob->data) {
37 return "";
38 }
39
40 const Mesh *mesh = static_cast<Mesh *>(ob->data);
41
42 const char *name = CustomData_get_active_layer_name(&mesh->corner_data, CD_PROP_FLOAT2);
43
44 return name ? name : "";
45}
46
47template<typename USDT>
48bool set_vec_attrib(const pxr::UsdPrim &prim,
49 const IDProperty *prop,
50 const pxr::TfToken &prop_token,
51 const pxr::SdfValueTypeName &type_name,
52 const pxr::UsdTimeCode &timecode)
53{
54 if (!prim || !prop || !prop->data.pointer || prop_token.IsEmpty() || !type_name) {
55 return false;
56 }
57
58 pxr::UsdAttribute vec_attr = prim.CreateAttribute(prop_token, type_name, true);
59
60 if (!vec_attr) {
62 "Couldn't create USD attribute for array property %s",
63 prop_token.GetString().c_str());
64 return false;
65 }
66
67 USDT vec_value(static_cast<typename USDT::ScalarType *>(prop->data.pointer));
68
69 return vec_attr.Set(vec_value, timecode);
70}
71
72namespace blender::io::usd {
73
74static void create_vector_attrib(const pxr::UsdPrim &prim,
75 const IDProperty *prop,
76 const pxr::TfToken &prop_token,
77 const pxr::UsdTimeCode &timecode)
78{
79 if (!prim || !prop || prop_token.IsEmpty()) {
80 return;
81 }
82
83 if (prop->type != IDP_ARRAY) {
85 "Property %s is not an array type and can't be converted to a vector attribute",
86 prop->name);
87 return;
88 }
89
90 pxr::SdfValueTypeName type_name;
91 bool success = false;
92
93 if (prop->subtype == IDP_FLOAT) {
94 if (prop->len == 2) {
95 type_name = pxr::SdfValueTypeNames->Float2;
96 success = set_vec_attrib<pxr::GfVec2f>(prim, prop, prop_token, type_name, timecode);
97 }
98 else if (prop->len == 3) {
99 type_name = pxr::SdfValueTypeNames->Float3;
100 success = set_vec_attrib<pxr::GfVec3f>(prim, prop, prop_token, type_name, timecode);
101 }
102 else if (prop->len == 4) {
103 type_name = pxr::SdfValueTypeNames->Float4;
104 success = set_vec_attrib<pxr::GfVec4f>(prim, prop, prop_token, type_name, timecode);
105 }
106 }
107 else if (prop->subtype == IDP_DOUBLE) {
108 if (prop->len == 2) {
109 type_name = pxr::SdfValueTypeNames->Double2;
110 success = set_vec_attrib<pxr::GfVec2d>(prim, prop, prop_token, type_name, timecode);
111 }
112 else if (prop->len == 3) {
113 type_name = pxr::SdfValueTypeNames->Double3;
114 success = set_vec_attrib<pxr::GfVec3d>(prim, prop, prop_token, type_name, timecode);
115 }
116 else if (prop->len == 4) {
117 type_name = pxr::SdfValueTypeNames->Double4;
118 success = set_vec_attrib<pxr::GfVec4d>(prim, prop, prop_token, type_name, timecode);
119 }
120 }
121 else if (prop->subtype == IDP_INT) {
122 if (prop->len == 2) {
123 type_name = pxr::SdfValueTypeNames->Int2;
124 success = set_vec_attrib<pxr::GfVec2i>(prim, prop, prop_token, type_name, timecode);
125 }
126 else if (prop->len == 3) {
127 type_name = pxr::SdfValueTypeNames->Int3;
128 success = set_vec_attrib<pxr::GfVec3i>(prim, prop, prop_token, type_name, timecode);
129 }
130 else if (prop->len == 4) {
131 type_name = pxr::SdfValueTypeNames->Int4;
132 success = set_vec_attrib<pxr::GfVec4i>(prim, prop, prop_token, type_name, timecode);
133 }
134 }
135
136 if (!type_name) {
137 CLOG_WARN(&LOG,
138 "Couldn't determine USD type name for array property %s",
139 prop_token.GetString().c_str());
140 return;
141 }
142
143 if (!success) {
144 CLOG_WARN(
145 &LOG, "Couldn't set USD attribute from array property %s", prop_token.GetString().c_str());
146 return;
147 }
148}
149
151 : usd_export_context_(usd_export_context), frame_has_been_written_(false), is_animated_(false)
152{
153}
154
156{
157 return true;
158}
159
164
166{
167 if (is_animated_) {
170 }
171 /* By using the default time-code USD won't even write a single `timeSample` for non-animated
172 * data. Instead, it writes it as non-time-sampled. */
173 static pxr::UsdTimeCode default_timecode = pxr::UsdTimeCode::Default();
174 return default_timecode;
175}
176
178{
181 check_is_animated(context);
182 }
183 else if (!is_animated_) {
184 /* A frame has already been written, and without animation one frame is enough. */
185 return;
186 }
187
188 do_write(context);
189
191}
192
193const pxr::SdfPath &USDAbstractWriter::usd_path() const
194{
196}
197
199{
200 static std::string material_library_path("/_materials");
201
202 const char *root_prim_path = usd_export_context_.export_params.root_prim_path;
203
204 if (root_prim_path[0] != '\0') {
205 return pxr::SdfPath(root_prim_path + material_library_path);
206 }
207
208 return pxr::SdfPath(material_library_path);
209}
210
211pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyContext &context,
212 Material *material) const
213{
214 pxr::UsdStageRefPtr stage = usd_export_context_.stage;
215
216 /* Construct the material. */
217 pxr::TfToken material_name(
219 pxr::SdfPath usd_path = pxr::UsdGeomScope::Define(stage, get_material_library_path())
220 .GetPath()
221 .AppendChild(material_name);
222 pxr::UsdShadeMaterial usd_material = pxr::UsdShadeMaterial::Get(stage, usd_path);
223 if (usd_material) {
224 return usd_material;
225 }
226
227 std::string active_uv = get_mesh_active_uvlayer_name(context.object);
228
229 usd_material = create_usd_material(
230 usd_export_context_, usd_path, material, active_uv, reports());
231
232 auto prim = usd_material.GetPrim();
233 write_id_properties(prim, material->id, get_export_time_code());
234
235 return usd_material;
236}
237
239 const pxr::UsdTimeCode timecode,
240 const pxr::UsdGeomImageable &usd_geometry)
241{
242 pxr::UsdAttribute attr_visibility = usd_geometry.CreateVisibilityAttr(pxr::VtValue(), true);
243
244 const bool is_visible = context.is_object_visible(
246 const pxr::TfToken visibility = is_visible ? pxr::UsdGeomTokens->inherited :
247 pxr::UsdGeomTokens->invisible;
248
249 usd_value_writer_.SetAttribute(attr_visibility, pxr::VtValue(visibility), timecode);
250}
251
252bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const pxr::UsdPrim &prim)
253{
254 BLI_assert(context.is_instance());
255
256 if (context.export_path == context.original_export_path) {
258 "Reference error: export path matches reference path: %s",
259 context.export_path.c_str());
260 BLI_assert_msg(0, "USD reference error");
261 return false;
262 }
263
264 BLI_assert(!context.original_export_path.empty());
265 BLI_assert(context.original_export_path.front() == '/');
266
267 std::string ref_path_str(usd_export_context_.export_params.root_prim_path);
268 ref_path_str += context.original_export_path;
269
270 pxr::SdfPath ref_path(ref_path_str);
271
272 /* To avoid USD errors, make sure the referenced path exists. */
273 usd_export_context_.stage->DefinePrim(ref_path);
274
275 if (!prim.GetReferences().AddInternalReference(ref_path)) {
276 /* See this URL for a description for why referencing may fail"
277 * https://graphics.pixar.com/usd/docs/api/class_usd_references.html#Usd_Failing_References
278 */
279 CLOG_WARN(&LOG,
280 "Unable to add reference from %s to %s, not instancing object for export",
281 context.export_path.c_str(),
282 context.original_export_path.c_str());
283 return false;
284 }
285
286 return true;
287}
288
289void USDAbstractWriter::write_id_properties(const pxr::UsdPrim &prim,
290 const ID &id,
291 pxr::UsdTimeCode timecode) const
292{
294 return;
295 }
296
298 if (GS(id.name) == ID_OB) {
299 /* Author property of original blender Object name. */
300 prim.CreateAttribute(pxr::TfToken(usdtokens::blender_ns.GetString() + ":object_name"),
301 pxr::SdfValueTypeNames->String,
302 true)
303 .Set<std::string>(std::string(id.name + 2));
304 }
305 else {
306 prim.CreateAttribute(pxr::TfToken(usdtokens::blender_ns.GetString() + ":data_name"),
307 pxr::SdfValueTypeNames->String,
308 true)
309 .Set<std::string>(std::string(id.name + 2));
310 }
311 }
312
313 if (id.properties) {
314 write_user_properties(prim, id.properties, timecode);
315 }
316}
317
318void USDAbstractWriter::write_user_properties(const pxr::UsdPrim &prim,
319 IDProperty *properties,
320 pxr::UsdTimeCode timecode) const
321{
322 if (properties == nullptr) {
323 return;
324 }
325
326 if (properties->type != IDP_GROUP) {
327 return;
328 }
329
330 const StringRef displayName_identifier = "displayName";
331
332 const std::string default_namespace(
334
335 for (IDProperty *prop = (IDProperty *)properties->data.group.first; prop; prop = prop->next) {
336 if (displayName_identifier == prop->name) {
337 if (prop->type == IDP_STRING && prop->data.pointer) {
338 prim.SetDisplayName(static_cast<char *>(prop->data.pointer));
339 }
340 continue;
341 }
342
343 std::vector<std::string> path_names = pxr::TfStringTokenize(prop->name, ":");
344
345 /* If the path does not already have a namespace prefix, prepend the default namespace
346 * specified by the user, if any. */
347 if (!default_namespace.empty() && path_names.size() < 2) {
348 path_names.insert(path_names.begin(), default_namespace);
349 }
350
351 std::vector<std::string> safe_names;
352 for (const std::string &name : path_names) {
354 }
355
356 std::string full_prop_name = pxr::SdfPath::JoinIdentifier(safe_names);
357 pxr::TfToken prop_token = pxr::TfToken(full_prop_name);
358
359 if (prim.HasAttribute(prop_token)) {
360 /* Don't overwrite existing attributes, as these may have been
361 * created by the exporter logic and shouldn't be changed. */
362 continue;
363 }
364
365 switch (prop->type) {
366 case IDP_INT:
367 if (pxr::UsdAttribute int_attr = prim.CreateAttribute(
368 prop_token, pxr::SdfValueTypeNames->Int, true))
369 {
370 int_attr.Set<int>(prop->data.val, timecode);
371 }
372 break;
373 case IDP_FLOAT:
374 if (pxr::UsdAttribute float_attr = prim.CreateAttribute(
375 prop_token, pxr::SdfValueTypeNames->Float, true))
376 {
377 float_attr.Set<float>(*reinterpret_cast<float *>(&prop->data.val), timecode);
378 }
379 break;
380 case IDP_DOUBLE:
381 if (pxr::UsdAttribute double_attr = prim.CreateAttribute(
382 prop_token, pxr::SdfValueTypeNames->Double, true))
383 {
384 double_attr.Set<double>(*reinterpret_cast<double *>(&prop->data.val), timecode);
385 }
386 break;
387 case IDP_STRING:
388 if (pxr::UsdAttribute str_attr = prim.CreateAttribute(
389 prop_token, pxr::SdfValueTypeNames->String, true))
390 {
391 str_attr.Set<std::string>(static_cast<const char *>(prop->data.pointer), timecode);
392 }
393 break;
394 case IDP_BOOLEAN:
395 if (pxr::UsdAttribute bool_attr = prim.CreateAttribute(
396 prop_token, pxr::SdfValueTypeNames->Bool, true))
397 {
398 bool_attr.Set<bool>(prop->data.val, timecode);
399 }
400 break;
401 case IDP_ARRAY:
402 create_vector_attrib(prim, prop, prop_token, timecode);
403 break;
404 }
405 }
406}
407
408void USDAbstractWriter::author_extent(const pxr::UsdTimeCode timecode, pxr::UsdGeomBoundable &prim)
409{
410 /* Do not use any existing `extentsHint` that may be authored, instead recompute the extent when
411 * authoring it. */
412 const bool useExtentsHint = false;
413 const pxr::TfTokenVector includedPurposes{pxr::UsdGeomTokens->default_};
414 pxr::UsdGeomBBoxCache bboxCache(timecode, includedPurposes, useExtentsHint);
415 pxr::GfBBox3d bounds = bboxCache.ComputeLocalBound(prim.GetPrim());
416 if (pxr::GfBBox3d() == bounds) {
417 /* This will occur, for example, if a mesh does not have any vertices. */
420 "USD Export: no bounds could be computed for %s",
421 prim.GetPrim().GetName().GetText());
422 return;
423 }
424
425 pxr::VtArray<pxr::GfVec3f> extent{(pxr::GfVec3f)bounds.GetRange().GetMin(),
426 (pxr::GfVec3f)bounds.GetRange().GetMax()};
427 prim.CreateExtentAttr().Set(extent);
428}
429
430} // namespace blender::io::usd
CustomData interface, see also DNA_customdata_types.h.
const char * CustomData_get_active_layer_name(const CustomData *data, eCustomDataType type)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:182
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
@ ID_OB
@ IDP_DOUBLE
@ IDP_FLOAT
@ IDP_STRING
@ IDP_BOOLEAN
@ IDP_INT
@ IDP_GROUP
@ IDP_ARRAY
@ CD_PROP_FLOAT2
@ OB_MESH
Group Output data from inside of a node group A color picker Mix two input colors RGB to Convert a color s luminance to a grayscale value Generate a normal vector and a dot product Brightness Control the brightness and contrast of the input color Vector Map input vector components with curves Camera Retrieve information about the camera and how it relates to the current shading point s position Clamp a value between a minimum and a maximum Vector Perform vector math operation Invert Invert a producing a negative Combine Generate a color from its and blue Hue Saturation Apply a color transformation in the HSV color model Specular Similar to the Principled BSDF node but uses the specular workflow instead of metallic
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
const pxr::SdfPath & usd_path() const
void write_user_properties(const pxr::UsdPrim &prim, IDProperty *properties, pxr::UsdTimeCode=pxr::UsdTimeCode::Default()) const
void write_visibility(const HierarchyContext &context, const pxr::UsdTimeCode timecode, const pxr::UsdGeomImageable &usd_geometry)
virtual void author_extent(const pxr::UsdTimeCode timecode, pxr::UsdGeomBoundable &prim)
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
virtual void write(HierarchyContext &context) override
virtual bool is_supported(const HierarchyContext *context) const
const USDExporterContext usd_export_context_
EvaluationStage stage
Definition deg_eval.cc:83
#define GS(x)
Definition iris.cc:202
#define LOG(severity)
Definition log.h:33
std::string make_safe_name(const std::string &name, bool allow_unicode)
Definition usd_utils.cc:16
static void create_vector_attrib(const pxr::UsdPrim &prim, const IDProperty *prop, const pxr::TfToken &prop_token, const pxr::UsdTimeCode &timecode)
pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_context, pxr::SdfPath usd_path, Material *material, const std::string &active_uvmap_name, ReportList *reports)
static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal)
static const pxr::TfToken surface("surface", pxr::TfToken::Immortal)
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal)
static const pxr::TfToken blender_ns("userProperties:blender", pxr::TfToken::Immortal)
static const pxr::TfToken diffuse_color("diffuseColor", pxr::TfToken::Immortal)
static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal)
ListBase group
Definition DNA_ID.h:146
void * pointer
Definition DNA_ID.h:145
int len
Definition DNA_ID.h:174
char name[64]
Definition DNA_ID.h:163
IDPropertyData data
Definition DNA_ID.h:168
char subtype
Definition DNA_ID.h:159
char type
Definition DNA_ID.h:154
Definition DNA_ID.h:413
void * first
char custom_properties_namespace[MAX_IDPROP_NAME]
Definition usd.hh:169
enum eEvaluationMode evaluation_mode
Definition usd.hh:146
std::function< pxr::UsdTimeCode()> get_time_code
bool set_vec_attrib(const pxr::UsdPrim &prim, const IDProperty *prop, const pxr::TfToken &prop_token, const pxr::SdfValueTypeName &type_name, const pxr::UsdTimeCode &timecode)
static CLG_LogRef LOG
static std::string get_mesh_active_uvlayer_name(const Object *ob)