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