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