Blender V4.3
usd_writer_mesh.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2019 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4#include "usd_writer_mesh.hh"
5
9#include "usd_skel_convert.hh"
10#include "usd_utils.hh"
11
12#include <pxr/usd/usdGeom/mesh.h>
13#include <pxr/usd/usdGeom/primvarsAPI.h>
14#include <pxr/usd/usdShade/material.h>
15#include <pxr/usd/usdShade/materialBindingAPI.h>
16#include <pxr/usd/usdSkel/bindingAPI.h>
17
18#include "BLI_array_utils.hh"
19#include "BLI_assert.h"
21
22#include "BKE_attribute.hh"
23#include "BKE_customdata.hh"
24#include "BKE_lib_id.hh"
25#include "BKE_material.h"
26#include "BKE_mesh.hh"
27#include "BKE_mesh_wrapper.hh"
28#include "BKE_object.hh"
29#include "BKE_report.hh"
30
31#include "bmesh.hh"
32#include "bmesh_tools.hh"
33
34#include "DEG_depsgraph.hh"
35
36#include "DNA_key_types.h"
37#include "DNA_modifier_types.h"
38
39#include "CLG_log.h"
40static CLG_LogRef LOG = {"io.usd"};
41
42namespace usdtokens {
43static const pxr::TfToken Anim("Anim", pxr::TfToken::Immortal);
44} // namespace usdtokens
45
46namespace blender::io::usd {
47
51
53{
55 return context->is_object_visible(usd_export_context_.export_params.evaluation_mode);
56 }
57 return true;
58}
59
60/* Get the last subdiv modifier, regardless of enable/disable status */
62{
63 BLI_assert(obj);
64
65 /* Return the subdiv modifier if it is the last modifier and has
66 * the required mode enabled. */
67
69
70 if (!md) {
71 return nullptr;
72 }
73
74 /* Determine if the modifier is enabled for the current evaluation mode. */
75 ModifierMode mod_mode = (eval_mode == DAG_EVAL_RENDER) ? eModifierMode_Render :
77
78 if ((md->mode & mod_mode) != mod_mode) {
79 return nullptr;
80 }
81
82 if (md->type == eModifierType_Subsurf) {
83 return reinterpret_cast<SubsurfModifierData *>(md);
84 }
85
86 return nullptr;
87}
88
90{
91 Object *object_eval = context.object;
92 bool needsfree = false;
93 Mesh *mesh = get_export_mesh(object_eval, needsfree);
94
95 if (mesh == nullptr) {
96 return;
97 }
98
100 const bool tag_only = false;
101 const int quad_method = usd_export_context_.export_params.quad_method;
102 const int ngon_method = usd_export_context_.export_params.ngon_method;
103
104 BMeshCreateParams bmesh_create_params{};
105 BMeshFromMeshParams bmesh_from_mesh_params{};
106 bmesh_from_mesh_params.calc_face_normal = true;
107 bmesh_from_mesh_params.calc_vert_normal = true;
108 BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmesh_create_params, &bmesh_from_mesh_params);
109
110 BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, nullptr, nullptr, nullptr);
111
112 Mesh *triangulated_mesh = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh);
114
115 if (needsfree) {
116 free_export_mesh(mesh);
117 }
118 mesh = triangulated_mesh;
119 needsfree = true;
120 }
121
122 try {
123 /* Fetch the subdiv modifier, if one exists and it is the last modifier. */
126
127 write_mesh(context, mesh, subsurfData);
128
129 auto prim = usd_export_context_.stage->GetPrimAtPath(usd_export_context_.usd_path);
130 if (prim.IsValid() && object_eval) {
131 prim.SetActive((object_eval->duplicator_visibility_flag & OB_DUPLI_FLAG_RENDER) != 0);
132 write_id_properties(prim, mesh->id, get_export_time_code());
133 }
134
135 if (needsfree) {
136 free_export_mesh(mesh);
137 }
138 }
139 catch (...) {
140 if (needsfree) {
141 free_export_mesh(mesh);
142 }
143 throw;
144 }
145}
146
147void USDGenericMeshWriter::write_custom_data(const Object *obj,
148 const Mesh *mesh,
149 const pxr::UsdGeomMesh &usd_mesh)
150{
151 const bke::AttributeAccessor attributes = mesh->attributes();
152
153 const StringRef active_uvmap_name = CustomData_get_render_layer_name(&mesh->corner_data,
155
156 attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
157 /* Skip "internal" Blender properties and attributes processed elsewhere.
158 * Skip edge domain because USD doesn't have a good conversion for them. */
159 if (iter.name[0] == '.' || bke::attribute_name_is_anonymous(iter.name) ||
161 ELEM(iter.name, "position", "material_index", "velocity", "crease_vert"))
162 {
163 return;
164 }
165
168 iter.name.rfind("skel:") == 0)
169 {
170 /* If we're exporting armatures or shape keys to UsdSkel, we skip any
171 * attributes that have names with the "skel:" namespace, to avoid possible
172 * conflicts. Such attribute might have been previously imported into Blender
173 * from USD, but can no longer be considered valid. */
174 return;
175 }
176
179 {
180 /* This attribute is likely a vertex group for the armature modifier,
181 * and it may conflict with skinning data that will be written to
182 * the USD mesh, so we skip it. Such vertex groups will instead be
183 * handled in #export_deform_verts(). */
184 return;
185 }
186
187 /* UV Data. */
189 if (usd_export_context_.export_params.export_uvmaps) {
190 this->write_uv_data(usd_mesh, iter, active_uvmap_name);
191 }
192 }
193
194 else {
195 this->write_generic_data(mesh, usd_mesh, iter);
196 }
197 });
198}
199
200static std::optional<pxr::TfToken> convert_blender_domain_to_usd(
201 const bke::AttrDomain blender_domain)
202{
203 switch (blender_domain) {
205 return pxr::UsdGeomTokens->faceVarying;
207 return pxr::UsdGeomTokens->vertex;
209 return pxr::UsdGeomTokens->uniform;
210
211 /* Notice: Edge types are not supported in USD! */
212 default:
213 return std::nullopt;
214 }
215}
216
217void USDGenericMeshWriter::write_generic_data(const Mesh *mesh,
218 const pxr::UsdGeomMesh &usd_mesh,
219 const bke::AttributeIter &attr)
220{
221 const pxr::TfToken pv_name(
222 make_safe_name(attr.name, usd_export_context_.export_params.allow_unicode));
223 const bool use_color3f_type = pv_name == usdtokens::displayColor;
224 const std::optional<pxr::TfToken> pv_interp = convert_blender_domain_to_usd(attr.domain);
225 const std::optional<pxr::SdfValueTypeName> pv_type = convert_blender_type_to_usd(
226 attr.data_type, use_color3f_type);
227
228 if (!pv_interp || !pv_type) {
229 BKE_reportf(reports(),
231 "Mesh '%s', Attribute '%s' (domain %d, type %d) cannot be converted to USD",
232 BKE_id_name(mesh->id),
233 attr.name.c_str(),
234 int8_t(attr.domain),
235 attr.data_type);
236 return;
237 }
238
239 const GVArray attribute = *attr.get();
240 if (attribute.is_empty()) {
241 return;
242 }
243
244 const pxr::UsdTimeCode timecode = get_export_time_code();
245 const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_mesh);
246
247 pxr::UsdGeomPrimvar pv_attr = pv_api.CreatePrimvar(pv_name, *pv_type, *pv_interp);
248
249 copy_blender_attribute_to_primvar(
250 attribute, attr.data_type, timecode, pv_attr, usd_value_writer_);
251}
252
253void USDGenericMeshWriter::write_uv_data(const pxr::UsdGeomMesh &usd_mesh,
254 const bke::AttributeIter &attr,
255 const StringRef active_uvmap_name)
256{
257 const VArray<float2> buffer = *attr.get<float2>(bke::AttrDomain::Corner);
258 if (buffer.is_empty()) {
259 return;
260 }
261
262 /* Optionally rename active UV map to "st", to follow USD conventions
263 * and better work with MaterialX shader nodes. */
264 const StringRef name = usd_export_context_.export_params.rename_uvmaps &&
265 active_uvmap_name == attr.name ?
266 "st" :
267 attr.name;
268
269 const pxr::UsdTimeCode timecode = get_export_time_code();
270 const pxr::TfToken pv_name(
271 make_safe_name(name, usd_export_context_.export_params.allow_unicode));
272 const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_mesh);
273
274 pxr::UsdGeomPrimvar pv_uv = pv_api.CreatePrimvar(
275 pv_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
276
277 copy_blender_buffer_to_primvar<float2, pxr::GfVec2f>(buffer, timecode, pv_uv, usd_value_writer_);
278}
279
280void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
281{
282 BKE_id_free(nullptr, mesh);
283}
284
286 pxr::VtArray<pxr::GfVec3f> points;
287 pxr::VtIntArray face_vertex_counts;
288 pxr::VtIntArray face_indices;
290
291 /* The length of this array specifies the number of creases on the surface. Each element gives
292 * the number of (must be adjacent) vertices in each crease, whose indices are linearly laid out
293 * in the 'creaseIndices' attribute. Since each crease must be at least one edge long, each
294 * element of this array should be greater than one. */
295 pxr::VtIntArray crease_lengths;
296 /* The indices of all vertices forming creased edges. The size of this array must be equal to the
297 * sum of all elements of the 'creaseLengths' attribute. */
298 pxr::VtIntArray crease_vertex_indices;
299 /* The per-crease or per-edge sharpness for all creases (Usd.Mesh.SHARPNESS_INFINITE for a
300 * perfectly sharp crease). Since 'creaseLengths' encodes the number of vertices in each crease,
301 * the number of elements in this array will be either 'len(creaseLengths)' or the sum over all X
302 * of '(creaseLengths[X] - 1)'. Note that while the RI spec allows each crease to have either a
303 * single sharpness or a value per-edge, USD will encode either a single sharpness per crease on
304 * a mesh, or sharpness's for all edges making up the creases on a mesh. */
305 pxr::VtFloatArray crease_sharpnesses;
306
307 /* The lengths of this array specifies the number of sharp corners (or vertex crease) on the
308 * surface. Each value is the index of a vertex in the mesh's vertex list. */
309 pxr::VtIntArray corner_indices;
310 /* The per-vertex sharpnesses. The lengths of this array must match that of `corner_indices`. */
311 pxr::VtFloatArray corner_sharpnesses;
312};
313
314void USDGenericMeshWriter::write_mesh(HierarchyContext &context,
315 Mesh *mesh,
316 const SubsurfModifierData *subsurfData)
317{
318 pxr::UsdTimeCode timecode = get_export_time_code();
319 pxr::UsdStageRefPtr stage = usd_export_context_.stage;
320 const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
321
322 pxr::UsdGeomMesh usd_mesh = pxr::UsdGeomMesh::Define(stage, usd_path);
323 write_visibility(context, timecode, usd_mesh);
324
325 USDMeshData usd_mesh_data;
326 /* Ensure data exists if currently in edit mode. */
328 get_geometry_data(mesh, usd_mesh_data);
329
330 if (usd_export_context_.export_params.use_instancing && context.is_instance()) {
331 if (!mark_as_instance(context, usd_mesh.GetPrim())) {
332 return;
333 }
334
335 /* The material path will be of the form </_materials/{material name}>, which is outside the
336 * sub-tree pointed to by ref_path. As a result, the referenced data is not allowed to point
337 * out of its own sub-tree. It does work when we override the material with exactly the same
338 * path, though. */
339 if (usd_export_context_.export_params.export_materials) {
340 assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
341 }
342
343 return;
344 }
345
346 pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true);
347 pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(),
348 true);
349 pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(pxr::VtValue(),
350 true);
351
352 if (!attr_points.HasValue()) {
353 /* Provide the initial value as default. This makes USD write the value as constant if they
354 * don't change over time. */
355 attr_points.Set(usd_mesh_data.points, pxr::UsdTimeCode::Default());
356 attr_face_vertex_counts.Set(usd_mesh_data.face_vertex_counts, pxr::UsdTimeCode::Default());
357 attr_face_vertex_indices.Set(usd_mesh_data.face_indices, pxr::UsdTimeCode::Default());
358 }
359
360 usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(usd_mesh_data.points), timecode);
361 usd_value_writer_.SetAttribute(
362 attr_face_vertex_counts, pxr::VtValue(usd_mesh_data.face_vertex_counts), timecode);
363 usd_value_writer_.SetAttribute(
364 attr_face_vertex_indices, pxr::VtValue(usd_mesh_data.face_indices), timecode);
365
366 if (!usd_mesh_data.crease_lengths.empty()) {
367 pxr::UsdAttribute attr_crease_lengths = usd_mesh.CreateCreaseLengthsAttr(pxr::VtValue(), true);
368 pxr::UsdAttribute attr_crease_indices = usd_mesh.CreateCreaseIndicesAttr(pxr::VtValue(), true);
369 pxr::UsdAttribute attr_crease_sharpness = usd_mesh.CreateCreaseSharpnessesAttr(pxr::VtValue(),
370 true);
371
372 if (!attr_crease_lengths.HasValue()) {
373 attr_crease_lengths.Set(usd_mesh_data.crease_lengths, pxr::UsdTimeCode::Default());
374 attr_crease_indices.Set(usd_mesh_data.crease_vertex_indices, pxr::UsdTimeCode::Default());
375 attr_crease_sharpness.Set(usd_mesh_data.crease_sharpnesses, pxr::UsdTimeCode::Default());
376 }
377
378 usd_value_writer_.SetAttribute(
379 attr_crease_lengths, pxr::VtValue(usd_mesh_data.crease_lengths), timecode);
380 usd_value_writer_.SetAttribute(
381 attr_crease_indices, pxr::VtValue(usd_mesh_data.crease_vertex_indices), timecode);
382 usd_value_writer_.SetAttribute(
383 attr_crease_sharpness, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
384 }
385
386 if (!usd_mesh_data.corner_indices.empty() &&
387 usd_mesh_data.corner_indices.size() == usd_mesh_data.corner_sharpnesses.size())
388 {
389 pxr::UsdAttribute attr_corner_indices = usd_mesh.CreateCornerIndicesAttr(pxr::VtValue(), true);
390 pxr::UsdAttribute attr_corner_sharpnesses = usd_mesh.CreateCornerSharpnessesAttr(
391 pxr::VtValue(), true);
392
393 if (!attr_corner_indices.HasValue()) {
394 attr_corner_indices.Set(usd_mesh_data.corner_indices, pxr::UsdTimeCode::Default());
395 attr_corner_sharpnesses.Set(usd_mesh_data.corner_sharpnesses, pxr::UsdTimeCode::Default());
396 }
397
398 usd_value_writer_.SetAttribute(
399 attr_corner_indices, pxr::VtValue(usd_mesh_data.corner_indices), timecode);
400 usd_value_writer_.SetAttribute(
401 attr_corner_sharpnesses, pxr::VtValue(usd_mesh_data.corner_sharpnesses), timecode);
402 }
403
404 write_custom_data(context.object, mesh, usd_mesh);
405 write_surface_velocity(mesh, usd_mesh);
406
407 const pxr::TfToken subdiv_scheme = get_subdiv_scheme(subsurfData);
408
409 /* Normals can be animated, so ensure these are written for each frame,
410 * unless a subdiv modifier is used, in which case normals are computed,
411 * not stored with the mesh. */
412 if (usd_export_context_.export_params.export_normals &&
413 subdiv_scheme == pxr::UsdGeomTokens->none)
414 {
415 write_normals(mesh, usd_mesh);
416 }
417
418 /* TODO(Sybren): figure out what happens when the face groups change. */
419 if (frame_has_been_written_) {
420 return;
421 }
422
423 /* The subdivision scheme is a uniform according to spec,
424 * so this value cannot be animated. */
425 write_subdiv(subdiv_scheme, usd_mesh, subsurfData);
426
427 if (usd_export_context_.export_params.export_materials) {
428 assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
429 }
430
431 /* Blender grows its bounds cache to cover animated meshes, so only author once. */
432 if (const std::optional<Bounds<float3>> bounds = mesh->bounds_min_max()) {
433 pxr::VtArray<pxr::GfVec3f> extent{
434 pxr::GfVec3f{bounds->min[0], bounds->min[1], bounds->min[2]},
435 pxr::GfVec3f{bounds->max[0], bounds->max[1], bounds->max[2]}};
436 usd_mesh.CreateExtentAttr().Set(extent);
437 }
438}
439
440pxr::TfToken USDGenericMeshWriter::get_subdiv_scheme(const SubsurfModifierData *subsurfData)
441{
442 /* Default to setting the subdivision scheme to None. */
443 pxr::TfToken subdiv_scheme = pxr::UsdGeomTokens->none;
444
445 if (subsurfData) {
446 if (subsurfData->subdivType == SUBSURF_TYPE_CATMULL_CLARK) {
447 if (usd_export_context_.export_params.export_subdiv == USD_SUBDIV_BEST_MATCH) {
448 /* If a subdivision modifier exists, and it uses Catmull-Clark, then apply Catmull-Clark
449 * SubD scheme. */
450 subdiv_scheme = pxr::UsdGeomTokens->catmullClark;
451 }
452 }
453 else {
454 /* "Simple" is currently the only other subdivision type provided by Blender, */
455 /* and we do not yet provide a corresponding representation for USD export. */
456 BKE_reportf(reports(),
458 "USD export: Simple subdivision not supported, exporting subdivided mesh");
459 }
460 }
461
462 return subdiv_scheme;
463}
464
465void USDGenericMeshWriter::write_subdiv(const pxr::TfToken &subdiv_scheme,
466 const pxr::UsdGeomMesh &usd_mesh,
467 const SubsurfModifierData *subsurfData)
468{
469 usd_mesh.CreateSubdivisionSchemeAttr().Set(subdiv_scheme);
470 if (subdiv_scheme == pxr::UsdGeomTokens->catmullClark) {
471 /* For Catmull-Clark, also consider the various interpolation modes. */
472 /* For reference, see
473 * https://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html#face-varying-interpolation-rules
474 */
475 switch (subsurfData->uv_smooth) {
477 usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->all);
478 break;
480 usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersOnly);
481 break;
483 usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersPlus1);
484 break;
486 usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersPlus2);
487 break;
489 usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->boundaries);
490 break;
492 usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->none);
493 break;
494 default:
495 BLI_assert_msg(0, "Unsupported UV smoothing mode.");
496 }
497
498 /* For reference, see
499 * https://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html#boundary-interpolation-rules
500 */
501 switch (subsurfData->boundary_smooth) {
503 usd_mesh.CreateInterpolateBoundaryAttr().Set(pxr::UsdGeomTokens->edgeOnly);
504 break;
506 usd_mesh.CreateInterpolateBoundaryAttr().Set(pxr::UsdGeomTokens->edgeAndCorner);
507 break;
508 default:
509 BLI_assert_msg(0, "Unsupported boundary smoothing mode.");
510 }
511 }
512}
513
514static void get_positions(const Mesh *mesh, USDMeshData &usd_mesh_data)
515{
516 const Span<pxr::GfVec3f> positions = mesh->vert_positions().cast<pxr::GfVec3f>();
517 usd_mesh_data.points = pxr::VtArray<pxr::GfVec3f>(positions.begin(), positions.end());
518}
519
520static void get_loops_polys(const Mesh *mesh, USDMeshData &usd_mesh_data)
521{
522 /* Only construct face groups (a.k.a. geometry subsets) when we need them for material
523 * assignments. */
524 const bke::AttributeAccessor attributes = mesh->attributes();
525 const VArray<int> material_indices = *attributes.lookup_or_default<int>(
526 "material_index", bke::AttrDomain::Face, 0);
527 if (!material_indices.is_single() && mesh->totcol > 1) {
528 const VArraySpan<int> indices_span(material_indices);
529 for (const int i : indices_span.index_range()) {
530 usd_mesh_data.face_groups.lookup_or_add_default(indices_span[i]).push_back(i);
531 }
532 }
533
534 usd_mesh_data.face_vertex_counts.resize(mesh->faces_num);
535 const OffsetIndices faces = mesh->faces();
537 faces,
538 faces.index_range(),
539 MutableSpan(usd_mesh_data.face_vertex_counts.data(), mesh->faces_num));
540
541 const Span<int> corner_verts = mesh->corner_verts();
542 usd_mesh_data.face_indices = pxr::VtIntArray(corner_verts.begin(), corner_verts.end());
543}
544
545static void get_edge_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
546{
547 const bke::AttributeAccessor attributes = mesh->attributes();
548 const bke::AttributeReader attribute = attributes.lookup<float>("crease_edge",
550 if (!attribute) {
551 return;
552 }
553 const VArraySpan creases(*attribute);
554 const Span<int2> edges = mesh->edges();
555 for (const int i : edges.index_range()) {
556 const float crease = creases[i];
557 if (crease == 0.0f) {
558 continue;
559 }
560
561 const float sharpness = crease >= 1.0f ? pxr::UsdGeomMesh::SHARPNESS_INFINITE : crease;
562
563 usd_mesh_data.crease_vertex_indices.push_back(edges[i][0]);
564 usd_mesh_data.crease_vertex_indices.push_back(edges[i][1]);
565 usd_mesh_data.crease_lengths.push_back(2);
566 usd_mesh_data.crease_sharpnesses.push_back(sharpness);
567 }
568}
569
570static void get_vert_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
571{
572 const bke::AttributeAccessor attributes = mesh->attributes();
573 const bke::AttributeReader attribute = attributes.lookup<float>("crease_vert",
575 if (!attribute) {
576 return;
577 }
578 const VArraySpan creases(*attribute);
579 for (const int i : creases.index_range()) {
580 const float crease = creases[i];
581
582 if (crease > 0.0f) {
583 const float sharpness = crease >= 1.0f ? pxr::UsdGeomMesh::SHARPNESS_INFINITE : crease;
584 usd_mesh_data.corner_indices.push_back(i);
585 usd_mesh_data.corner_sharpnesses.push_back(sharpness);
586 }
587 }
588}
589
590void USDGenericMeshWriter::get_geometry_data(const Mesh *mesh, USDMeshData &usd_mesh_data)
591{
592 get_positions(mesh, usd_mesh_data);
593 get_loops_polys(mesh, usd_mesh_data);
594 get_edge_creases(mesh, usd_mesh_data);
595 get_vert_creases(mesh, usd_mesh_data);
596}
597
598void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
599 const pxr::UsdGeomMesh &usd_mesh,
600 const MaterialFaceGroups &usd_face_groups)
601{
602 if (context.object->totcol == 0) {
603 return;
604 }
605
606 /* Binding a material to a geometry subset isn't supported by the Hydra GL viewport yet,
607 * which is why we always bind the first material to the entire mesh. See
608 * https://github.com/PixarAnimationStudios/USD/issues/542 for more info. */
609 bool mesh_material_bound = false;
610 auto mesh_prim = usd_mesh.GetPrim();
611 pxr::UsdShadeMaterialBindingAPI material_binding_api(mesh_prim);
612 for (int mat_num = 0; mat_num < context.object->totcol; mat_num++) {
613 Material *material = BKE_object_material_get(context.object, mat_num + 1);
614 if (material == nullptr) {
615 continue;
616 }
617
618 pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
619 material_binding_api.Bind(usd_material);
620
621 /* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
622 * use the flag from the first non-empty material slot. */
623 usd_mesh.CreateDoubleSidedAttr(
624 pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
625
626 mesh_material_bound = true;
627 break;
628 }
629
630 if (mesh_material_bound) {
631 /* USD will require that prims with material bindings have the #MaterialBindingAPI applied
632 * schema. While Bind() above will create the binding attribute, Apply() needs to be called as
633 * well to add the #MaterialBindingAPI schema to the prim itself. */
634 pxr::UsdShadeMaterialBindingAPI::Apply(mesh_prim);
635 }
636 else {
637 /* Blender defaults to double-sided, but USD to single-sided. */
638 usd_mesh.CreateDoubleSidedAttr(pxr::VtValue(true));
639 }
640
641 if (!mesh_material_bound || usd_face_groups.size() < 2) {
642 /* Either all material slots were empty or there is only one material in use. As geometry
643 * subsets are only written when actually used to assign a material, and the mesh already has
644 * the material assigned, there is no need to continue. */
645 return;
646 }
647
648 /* Define a geometry subset per material. */
649 for (const MaterialFaceGroups::Item &face_group : usd_face_groups.items()) {
650 short material_number = face_group.key;
651 const pxr::VtIntArray &face_indices = face_group.value;
652
653 Material *material = BKE_object_material_get(context.object, material_number + 1);
654 if (material == nullptr) {
655 continue;
656 }
657
658 pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
659 pxr::TfToken material_name = usd_material.GetPath().GetNameToken();
660
661 pxr::UsdGeomSubset usd_face_subset = material_binding_api.CreateMaterialBindSubset(
662 material_name, face_indices);
663 auto subset_prim = usd_face_subset.GetPrim();
664 auto subset_material_api = pxr::UsdShadeMaterialBindingAPI(subset_prim);
665 subset_material_api.Bind(usd_material);
666 /* Apply the #MaterialBindingAPI applied schema, as required by USD. */
667 subset_material_api.Apply(subset_prim);
668 }
669}
670
671void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh &usd_mesh)
672{
673 pxr::UsdTimeCode timecode = get_export_time_code();
674
675 pxr::VtVec3fArray loop_normals;
676 loop_normals.resize(mesh->corners_num);
677
678 MutableSpan dst_normals(reinterpret_cast<float3 *>(loop_normals.data()), loop_normals.size());
679
680 switch (mesh->normals_domain()) {
682 array_utils::gather(mesh->vert_normals(), mesh->corner_verts(), dst_normals);
683 break;
684 }
686 const OffsetIndices faces = mesh->faces();
687 const Span<float3> face_normals = mesh->face_normals();
688 for (const int i : faces.index_range()) {
689 dst_normals.slice(faces[i]).fill(face_normals[i]);
690 }
691 break;
692 }
694 array_utils::copy(mesh->corner_normals(), dst_normals);
695 break;
696 }
697 }
698
699 pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true);
700 if (!attr_normals.HasValue()) {
701 attr_normals.Set(loop_normals, pxr::UsdTimeCode::Default());
702 }
703 usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), timecode);
704 usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
705}
706
707void USDGenericMeshWriter::write_surface_velocity(const Mesh *mesh,
708 const pxr::UsdGeomMesh &usd_mesh)
709{
710 /* Export velocity attribute output by fluid sim, sequence cache modifier
711 * and geometry nodes. */
712 const VArraySpan velocity = *mesh->attributes().lookup<float3>("velocity",
714 if (velocity.is_empty()) {
715 return;
716 }
717
718 /* Export per-vertex velocity vectors. */
719 Span<pxr::GfVec3f> data = velocity.cast<pxr::GfVec3f>();
720 pxr::VtVec3fArray usd_velocities;
721 usd_velocities.assign(data.begin(), data.end());
722
723 pxr::UsdTimeCode timecode = get_export_time_code();
724 pxr::UsdAttribute attr_vel = usd_mesh.CreateVelocitiesAttr(pxr::VtValue(), true);
725 if (!attr_vel.HasValue()) {
726 attr_vel.Set(usd_velocities, pxr::UsdTimeCode::Default());
727 }
728
729 usd_value_writer_.SetAttribute(attr_vel, usd_velocities, timecode);
730}
731
732USDMeshWriter::USDMeshWriter(const USDExporterContext &ctx)
733 : USDGenericMeshWriter(ctx), write_skinned_mesh_(false), write_blend_shapes_(false)
734{
735}
736
738{
739 write_skinned_mesh_ = false;
740 write_blend_shapes_ = false;
741
743
744 /* We can write a skinned mesh if exporting armatures is enabled and the object has an armature
745 * modifier. */
746 write_skinned_mesh_ = params.export_armatures &&
748
749 /* We can write blend shapes if exporting shape keys is enabled and the object has shape keys. */
750 write_blend_shapes_ = params.export_shapekeys && is_mesh_with_shape_keys(context.object);
751}
752
754{
755 pxr::UsdStageRefPtr stage = usd_export_context_.stage;
756
757 pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(usd_export_context_.usd_path);
758
759 if (!mesh_prim.IsValid()) {
760 CLOG_WARN(&LOG,
761 "%s: couldn't get valid mesh prim for mesh %s",
762 __func__,
763 usd_export_context_.usd_path.GetAsString().c_str());
764 return;
765 }
766
767 pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
768
769 if (!skel_api) {
770 CLOG_WARN(&LOG,
771 "Couldn't apply UsdSkelBindingAPI to mesh prim %s",
772 usd_export_context_.usd_path.GetAsString().c_str());
773 return;
774 }
775
776 const Object *arm_obj = get_armature_modifier_obj(*context.object,
778
779 if (!arm_obj) {
780 CLOG_WARN(&LOG,
781 "Couldn't get armature modifier object for skinned mesh %s",
782 usd_export_context_.usd_path.GetAsString().c_str());
783 return;
784 }
785
786 Vector<std::string> bone_names;
789
790 if (bone_names.is_empty()) {
791 CLOG_WARN(&LOG,
792 "No armature bones for skinned mesh %s",
793 usd_export_context_.usd_path.GetAsString().c_str());
794 return;
795 }
796
797 bool needsfree = false;
798 Mesh *mesh = get_export_mesh(context.object, needsfree);
799
800 if (mesh == nullptr) {
801 return;
802 }
803
804 try {
805 export_deform_verts(mesh, skel_api, bone_names);
806
807 if (needsfree) {
808 free_export_mesh(mesh);
809 }
810 }
811 catch (...) {
812 if (needsfree) {
813 free_export_mesh(mesh);
814 }
815 throw;
816 }
817}
818
820{
821 pxr::UsdStageRefPtr stage = usd_export_context_.stage;
822
823 pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(usd_export_context_.usd_path);
824
825 if (!mesh_prim.IsValid()) {
826 CLOG_WARN(&LOG,
827 "Couldn't get valid mesh prim for mesh %s",
828 mesh_prim.GetPath().GetAsString().c_str());
829 return;
830 }
831
833 context.object,
834 mesh_prim,
836}
837
839{
840 set_skel_export_flags(context);
841
842 if (frame_has_been_written_ && (write_skinned_mesh_ || write_blend_shapes_)) {
843 /* When writing skinned meshes or blend shapes, we only write the rest mesh once,
844 * so we return early after the first frame has been written. However, we still
845 * update blend shape weights if needed. */
846 if (write_blend_shapes_) {
847 add_shape_key_weights_sample(context.object);
848 }
849 return;
850 }
851
853
854 if (write_skinned_mesh_) {
855 init_skinned_mesh(context);
856 }
857
858 if (write_blend_shapes_) {
859 init_blend_shapes(context);
860 add_shape_key_weights_sample(context.object);
861 }
862}
863
864Mesh *USDMeshWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
865{
866 if (write_blend_shapes_) {
867 r_needsfree = true;
868 /* We return the pre-modified mesh with the verts in the shape key
869 * basis positions. */
870 return get_shape_key_basis_mesh(object_eval);
871 }
872
873 if (write_skinned_mesh_) {
874 r_needsfree = false;
875 /* We must export the skinned mesh in its rest pose. We therefore
876 * return the pre-modified mesh, so that the armature modifier isn't
877 * applied. */
878 return BKE_object_get_pre_modified_mesh(object_eval);
879 }
880
881 /* Return the fully evaluated mesh. */
882 r_needsfree = false;
883 return BKE_object_get_evaluated_mesh(object_eval);
884}
885
887{
888 if (!obj) {
889 return;
890 }
891
892 const Key *key = get_mesh_shape_key(obj);
893 if (!key) {
894 return;
895 }
896
897 pxr::UsdStageRefPtr stage = usd_export_context_.stage;
898
899 pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(usd_export_context_.usd_path);
900
901 if (!mesh_prim.IsValid()) {
902 CLOG_WARN(&LOG,
903 "Couldn't get valid mesh prim for mesh %s",
904 usd_export_context_.usd_path.GetAsString().c_str());
905 return;
906 }
907
908 pxr::VtFloatArray weights = get_blendshape_weights(key);
909 pxr::UsdTimeCode timecode = get_export_time_code();
910
911 /* Save the weights samples to a temporary privar which will be copied to
912 * a skeleton animation later. */
913 pxr::UsdAttribute temp_weights_attr = pxr::UsdGeomPrimvarsAPI(mesh_prim).CreatePrimvar(
914 TempBlendShapeWeightsPrimvarName, pxr::SdfValueTypeNames->FloatArray);
915
916 if (!temp_weights_attr) {
917 CLOG_WARN(&LOG,
918 "Couldn't create primvar %s on prim %s",
920 mesh_prim.GetPath().GetAsString().c_str());
921 return;
922 }
923
924 temp_weights_attr.Set(weights, timecode);
925}
926
927} // namespace blender::io::usd
CustomData interface, see also DNA_customdata_types.h.
const char * CustomData_get_render_layer_name(const CustomData *data, eCustomDataType type)
void BKE_id_free(Main *bmain, void *idv)
const char * BKE_id_name(const ID &id)
General operations, lookup, etc. for materials.
struct Material * BKE_object_material_get(struct Object *ob, short act)
Mesh * BKE_mesh_from_bmesh_for_eval_nomain(BMesh *bm, const CustomData_MeshMasks *cd_mask_extra, const Mesh *me_settings)
BMesh * BKE_mesh_to_bmesh_ex(const Mesh *mesh, const BMeshCreateParams *create_params, const BMeshFromMeshParams *convert_params)
void BKE_mesh_wrapper_ensure_mdata(Mesh *mesh)
General operations, lookup, etc. for blender objects.
Mesh * BKE_object_get_evaluated_mesh(const Object *object_eval)
Mesh * BKE_object_get_pre_modified_mesh(const Object *object)
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 ELEM(...)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
eEvaluationMode
@ DAG_EVAL_RENDER
@ CD_PROP_FLOAT2
@ MA_BL_CULL_BACKFACE
@ eModifierMode_Render
@ eModifierMode_Realtime
@ SUBSURF_TYPE_CATMULL_CLARK
@ SUBSURF_BOUNDARY_SMOOTH_ALL
@ SUBSURF_BOUNDARY_SMOOTH_PRESERVE_CORNERS
@ eModifierType_Subsurf
@ SUBSURF_UV_SMOOTH_PRESERVE_CORNERS_AND_JUNCTIONS
@ SUBSURF_UV_SMOOTH_ALL
@ SUBSURF_UV_SMOOTH_PRESERVE_CORNERS
@ SUBSURF_UV_SMOOTH_NONE
@ SUBSURF_UV_SMOOTH_PRESERVE_BOUNDARIES
@ SUBSURF_UV_SMOOTH_PRESERVE_CORNERS_JUNCTIONS_AND_CONCAVE
@ OB_DUPLI_FLAG_RENDER
ATTR_WARN_UNUSED_RESULT BMesh * bm
void BM_mesh_free(BMesh *bm)
BMesh Free Mesh.
void BM_mesh_triangulate(BMesh *bm, const int quad_method, const int ngon_method, const int min_vertices, const bool tag_only, BMOperator *op, BMOpSlot *slot_facemap_out, BMOpSlot *slot_facemap_double_out)
Value & lookup_or_add_default(const Key &key)
Definition BLI_map.hh:601
Span< NewT > constexpr cast() const
Definition BLI_span.hh:419
constexpr const T * end() const
Definition BLI_span.hh:225
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
constexpr const T * begin() const
Definition BLI_span.hh:221
constexpr int64_t rfind(char c, int64_t pos=INT64_MAX) const
constexpr const char * c_str() const
bool is_empty() const
GAttributeReader get() const
pxr::UsdTimeCode get_export_time_code() const
void write_id_properties(const pxr::UsdPrim &prim, const ID &id, pxr::UsdTimeCode=pxr::UsdTimeCode::Default()) const
const USDExporterContext usd_export_context_
USDGenericMeshWriter(const USDExporterContext &ctx)
virtual void do_write(HierarchyContext &context) override
virtual bool is_supported(const HierarchyContext *context) const override
virtual Mesh * get_export_mesh(Object *object_eval, bool &r_needsfree)=0
virtual void free_export_mesh(Mesh *mesh)
virtual void do_write(HierarchyContext &context) override
void add_shape_key_weights_sample(const Object *obj)
void init_skinned_mesh(const HierarchyContext &context)
void init_blend_shapes(const HierarchyContext &context)
virtual Mesh * get_export_mesh(Object *object_eval, bool &r_needsfree) override
void set_skel_export_flags(const HierarchyContext &context)
EvaluationStage stage
Definition deg_eval.cc:83
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
#define LOG(severity)
Definition log.h:33
void copy(const GVArray &src, GMutableSpan dst, int64_t grain_size=4096)
void gather(const GVArray &src, const IndexMask &indices, GMutableSpan dst, int64_t grain_size=4096)
bool attribute_name_is_anonymous(const StringRef name)
void write_custom_data(const OCompoundProperty &prop, CDStreamConfig &config, CustomData *data, int data_type)
static void get_loops_polys(const Mesh *mesh, USDMeshData &usd_mesh_data)
static const SubsurfModifierData * get_last_subdiv_modifier(eEvaluationMode eval_mode, Object *obj)
static void get_vert_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
static void get_positions(const Mesh *mesh, USDMeshData &usd_mesh_data)
std::string make_safe_name(const std::string &name, bool allow_unicode)
Definition usd_utils.cc:16
bool is_armature_modifier_bone_name(const Object &obj, const StringRefNull name, const Depsgraph *depsgraph)
void create_blend_shapes(pxr::UsdStageRefPtr stage, const Object *obj, const pxr::UsdPrim &mesh_prim, bool allow_unicode)
void export_deform_verts(const Mesh *mesh, const pxr::UsdSkelBindingAPI &skel_api, const Span< std::string > bone_names)
const Key * get_mesh_shape_key(const Object *obj)
static void get_edge_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
static std::optional< pxr::TfToken > convert_blender_domain_to_usd(const bke::AttrDomain blender_domain)
pxr::TfToken TempBlendShapeWeightsPrimvarName
void get_armature_bone_names(const Object *ob_arm, const bool use_deform, Vector< std::string > &r_names)
const Object * get_armature_modifier_obj(const Object &obj, const Depsgraph *depsgraph)
pxr::VtFloatArray get_blendshape_weights(const Key *key)
bool can_export_skinned_mesh(const Object &obj, const Depsgraph *depsgraph)
Mesh * get_shape_key_basis_mesh(Object *obj)
bool is_mesh_with_shape_keys(const Object *obj)
void copy_group_sizes(OffsetIndices< int > offsets, const IndexMask &mask, MutableSpan< int > sizes)
const pxr::TfToken displayColor("displayColor", pxr::TfToken::Immortal)
static const pxr::TfToken Anim("Anim", pxr::TfToken::Immortal)
signed char int8_t
Definition stdint.h:75
void * last
ListBase modifiers
char duplicator_visibility_flag
enum eEvaluationMode evaluation_mode
Definition usd.hh:146
Map< short, pxr::VtIntArray > face_groups
pxr::VtArray< pxr::GfVec3f > points
static CLG_LogRef LOG