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{
54 if (usd_export_context_.export_params.visible_objects_only) {
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{
64
65 /* Return the subdiv modifier if it is the last modifier and has
66 * the required mode enabled. */
67
68 ModifierData *md = (ModifierData *)(obj->modifiers.last);
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
99 if (usd_export_context_.export_params.triangulate_meshes) {
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. */
125 usd_export_context_.export_params.evaluation_mode, object_eval);
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);
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. */
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(
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) {
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
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
278}
279
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. */
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. */
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. */
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
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
742 const USDExportParams &params = usd_export_context_.export_params;
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 &&
747 can_export_skinned_mesh(*context.object, usd_export_context_.depsgraph);
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,
777 usd_export_context_.depsgraph);
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;
788 arm_obj, usd_export_context_.export_params.only_deform_bones, 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,
835 usd_export_context_.export_params.allow_unicode);
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
struct Material Material
struct Mesh Mesh
@ eModifierMode_Render
@ eModifierMode_Realtime
@ SUBSURF_TYPE_CATMULL_CLARK
@ SUBSURF_BOUNDARY_SMOOTH_ALL
@ SUBSURF_BOUNDARY_SMOOTH_PRESERVE_CORNERS
@ eModifierType_Subsurf
struct SubsurfModifierData SubsurfModifierData
@ 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
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a or normal between and object coordinate space Combine Create a color from its and value channels Color Retrieve a color attribute
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)
AttributeSet attributes
MapItem< short, pxr::VtIntArray > Item
Definition BLI_map.hh:132
Value & lookup_or_add_default(const Key &key)
Definition BLI_map.hh:601
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
void foreach_attribute(const FunctionRef< void(const AttributeIter &)> fn) const
GAttributeReader lookup(const StringRef attribute_id) const
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type, const void *default_value=nullptr) const
GAttributeReader get() const
pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material) const
const pxr::SdfPath & usd_path() const
void write_visibility(const HierarchyContext &context, const pxr::UsdTimeCode timecode, const pxr::UsdGeomImageable &usd_geometry)
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
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
USDMeshWriter(const USDExporterContext &ctx)
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)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
#define LOG(severity)
Definition log.h:33
static char faces[256]
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)
int context(const bContext *C, const char *member, bContextDataResult *result)
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 copy_blender_attribute_to_primvar(const GVArray &attribute, const eCustomDataType data_type, const pxr::UsdTimeCode timecode, const pxr::UsdGeomPrimvar &primvar, pxr::UsdUtilsSparseValueWriter &value_writer)
static std::optional< pxr::TfToken > convert_blender_domain_to_usd(const bke::AttrDomain blender_domain, bool is_bezier)
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)
@ USD_SUBDIV_BEST_MATCH
Definition usd.hh:88
static void get_edge_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
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)
std::optional< pxr::SdfValueTypeName > convert_blender_type_to_usd(const eCustomDataType blender_type, bool use_color3f_type)
pxr::VtFloatArray get_blendshape_weights(const Key *key)
bool can_export_skinned_mesh(const Object &obj, const Depsgraph *depsgraph)
void copy_blender_buffer_to_primvar(const VArray< BlenderT > &buffer, const pxr::UsdTimeCode timecode, const pxr::UsdGeomPrimvar &primvar, pxr::UsdUtilsSparseValueWriter &value_writer)
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)
VecBase< float, 2 > float2
VecBase< float, 3 > float3
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
int corners_num
CustomData corner_data
short totcol
int faces_num
char duplicator_visibility_flag
Map< short, pxr::VtIntArray > face_groups
pxr::VtArray< pxr::GfVec3f > points