Blender V4.5
usd_reader_mesh.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later
4 * Adapted from the Blender Alembic importer implementation.
5 * Modifications Copyright 2021 Tangent Animation and
6 * NVIDIA Corporation. All rights reserved. */
7
8#include "usd_reader_mesh.hh"
9#include "usd.hh"
11#include "usd_hash_types.hh"
12#include "usd_mesh_utils.hh"
14#include "usd_skel_convert.hh"
15#include "usd_utils.hh"
16
17#include "BKE_attribute.hh"
18#include "BKE_customdata.hh"
19#include "BKE_geometry_set.hh"
20#include "BKE_main.hh"
21#include "BKE_material.hh"
22#include "BKE_mesh.hh"
23#include "BKE_object.hh"
24#include "BKE_report.hh"
25#include "BKE_subdiv.hh"
26
27#include "BLI_array.hh"
28#include "BLI_map.hh"
30#include "BLI_ordered_edge.hh"
31#include "BLI_set.hh"
32#include "BLI_span.hh"
33#include "BLI_task.hh"
34#include "BLI_vector_set.hh"
35
36#include "BLT_translation.hh"
37
39#include "DNA_material_types.h"
40#include "DNA_modifier_types.h"
41#include "DNA_object_types.h"
43
44#include <pxr/base/gf/matrix4f.h>
45#include <pxr/base/vt/array.h>
46#include <pxr/base/vt/types.h>
47#include <pxr/usd/sdf/types.h>
48#include <pxr/usd/usdGeom/primvarsAPI.h>
49#include <pxr/usd/usdGeom/subset.h>
50#include <pxr/usd/usdShade/materialBindingAPI.h>
51#include <pxr/usd/usdShade/tokens.h>
52#include <pxr/usd/usdSkel/bindingAPI.h>
53
54#include <fmt/core.h>
55
56#include <algorithm>
57
58#include "CLG_log.h"
59static CLG_LogRef LOG = {"io.usd"};
60
61namespace usdtokens {
62/* Materials */
63static const pxr::TfToken st("st", pxr::TfToken::Immortal);
64static const pxr::TfToken normalsPrimvar("normals", pxr::TfToken::Immortal);
65} // namespace usdtokens
66
67namespace blender::io::usd {
68
69namespace utils {
70
71static pxr::UsdShadeMaterial compute_bound_material(const pxr::UsdPrim &prim,
72 eUSDMtlPurpose mtl_purpose)
73{
74 const pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(prim);
75
76 /* See the following documentation for material resolution behavior:
77 * https://openusd.org/release/api/class_usd_shade_material_binding_a_p_i.html#UsdShadeMaterialBindingAPI_MaterialResolution
78 */
79
80 pxr::UsdShadeMaterial mtl;
81 switch (mtl_purpose) {
83 mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->full);
84 if (!mtl) {
85 /* Add an additional Blender-specific fallback to help with oddly authored USD files. */
86 mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview);
87 }
88 break;
90 mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview);
91 break;
93 mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->allPurpose);
94 break;
95 }
96
97 return mtl;
98}
99
100static void assign_materials(Main *bmain,
101 Object *ob,
102 const blender::Map<pxr::SdfPath, int> &mat_index_map,
103 const USDImportParams &params,
104 pxr::UsdStageRefPtr stage,
105 const ImportSettings &settings)
106{
107 if (!(stage && bmain && ob)) {
108 return;
109 }
110
111 if (mat_index_map.size() > MAXMAT) {
112 return;
113 }
114
115 USDMaterialReader mat_reader(params, *bmain);
116
117 for (const auto item : mat_index_map.items()) {
118 Material *assigned_mat = find_existing_material(
119 item.key, params, settings.mat_name_to_mat, settings.usd_path_to_mat);
120 if (!assigned_mat) {
121 /* Blender material doesn't exist, so create it now. */
122
123 /* Look up the USD material. */
124 pxr::UsdPrim prim = stage->GetPrimAtPath(item.key);
125 pxr::UsdShadeMaterial usd_mat(prim);
126
127 if (!usd_mat) {
128 CLOG_WARN(
129 &LOG, "Couldn't construct USD material from prim %s", item.key.GetAsString().c_str());
130 continue;
131 }
132
133 const bool have_import_hook = settings.mat_import_hook_sources.contains(item.key);
134
135 /* Add the Blender material. If we have an import hook which can handle this material
136 * we don't import USD Preview Surface shaders. */
137 assigned_mat = mat_reader.add_material(usd_mat, !have_import_hook);
138
139 if (!assigned_mat) {
140 CLOG_WARN(&LOG,
141 "Couldn't create Blender material from USD material %s",
142 item.key.GetAsString().c_str());
143 continue;
144 }
145
146 const std::string mat_name = make_safe_name(assigned_mat->id.name + 2, true);
147 settings.mat_name_to_mat.add_new(mat_name, assigned_mat);
148
149 if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MAKE_UNIQUE) {
150 /* Record the Blender material we created for the USD material with the given path. */
151 settings.usd_path_to_mat.add_new(item.key, assigned_mat);
152 }
153
154 if (have_import_hook) {
155 /* Defer invoking the hook to convert the material till we can do so from
156 * the main thread. */
157 settings.usd_path_to_mat_for_hook.add_new(item.key, assigned_mat);
158 }
159 }
160
161 if (assigned_mat) {
162 BKE_object_material_assign_single_obdata(bmain, ob, assigned_mat, item.value);
163 }
164 else {
165 /* This shouldn't happen. */
166 CLOG_WARN(&LOG, "Couldn't assign material %s", item.key.GetAsString().c_str());
167 }
168 }
169 if (ob->totcol > 0) {
170 ob->actcol = 1;
171 }
172}
173
174} // namespace utils
175
177{
178 Mesh *mesh = BKE_mesh_add(bmain, name_.c_str());
179
181 object_->data = mesh;
182}
183
184void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime)
185{
186 Mesh *mesh = (Mesh *)object_->data;
187
188 is_initial_load_ = true;
189 const USDMeshReadParams params = create_mesh_read_params(motionSampleTime,
190 import_params_.mesh_read_flag);
191
192 Mesh *read_mesh = this->read_mesh(mesh, params, nullptr);
193
194 is_initial_load_ = false;
195 if (read_mesh != mesh) {
196 BKE_mesh_nomain_to_mesh(read_mesh, mesh, object_);
197 }
198
199 readFaceSetsSample(bmain, mesh, motionSampleTime);
200
201 if (mesh_prim_.GetPointsAttr().ValueMightBeTimeVarying() ||
202 mesh_prim_.GetNormalsAttr().ValueMightBeTimeVarying() ||
203 mesh_prim_.GetVelocitiesAttr().ValueMightBeTimeVarying() ||
204 mesh_prim_.GetCreaseSharpnessesAttr().ValueMightBeTimeVarying() ||
205 mesh_prim_.GetCreaseLengthsAttr().ValueMightBeTimeVarying() ||
206 mesh_prim_.GetCreaseIndicesAttr().ValueMightBeTimeVarying() ||
207 mesh_prim_.GetCornerSharpnessesAttr().ValueMightBeTimeVarying() ||
208 mesh_prim_.GetCornerIndicesAttr().ValueMightBeTimeVarying())
209 {
210 is_time_varying_ = true;
211 }
212
213 if (is_time_varying_) {
215 }
216
217 if (import_params_.import_subdiv) {
218 pxr::TfToken subdivScheme;
219 mesh_prim_.GetSubdivisionSchemeAttr().Get(&subdivScheme, motionSampleTime);
220
221 if (subdivScheme == pxr::UsdGeomTokens->catmullClark) {
223 read_subdiv();
224 }
225 }
226
227 if (import_params_.import_blendshapes) {
229 }
230
231 if (import_params_.import_skeletons) {
233 }
234
235 USDXformReader::read_object_data(bmain, motionSampleTime);
236}
237
238bool USDMeshReader::topology_changed(const Mesh *existing_mesh, const double motionSampleTime)
239{
240 /* TODO(makowalski): Is it the best strategy to cache the mesh
241 * geometry in this function? This needs to be revisited. */
242
243 mesh_prim_.GetFaceVertexIndicesAttr().Get(&face_indices_, motionSampleTime);
244 mesh_prim_.GetFaceVertexCountsAttr().Get(&face_counts_, motionSampleTime);
245 mesh_prim_.GetPointsAttr().Get(&positions_, motionSampleTime);
246
247 const pxr::UsdGeomPrimvarsAPI primvarsAPI(mesh_prim_);
248
249 /* TODO(makowalski): Reading normals probably doesn't belong in this function,
250 * as this is not required to determine if the topology has changed. */
251
252 /* If 'normals' and 'primvars:normals' are both specified, the latter has precedence. */
253 const pxr::UsdGeomPrimvar primvar = primvarsAPI.GetPrimvar(usdtokens::normalsPrimvar);
254 if (primvar.HasValue()) {
255 primvar.ComputeFlattened(&normals_, motionSampleTime);
256 normal_interpolation_ = primvar.GetInterpolation();
257 }
258 else {
259 mesh_prim_.GetNormalsAttr().Get(&normals_, motionSampleTime);
260 normal_interpolation_ = mesh_prim_.GetNormalsInterpolation();
261 }
262
263 return positions_.size() != existing_mesh->verts_num ||
264 face_counts_.size() != existing_mesh->faces_num ||
265 face_indices_.size() != existing_mesh->corners_num;
266}
267
268bool USDMeshReader::read_faces(Mesh *mesh) const
269{
270 MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
271 MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
272
273 int loop_index = 0;
274
275 for (int i = 0; i < face_counts_.size(); i++) {
276 const int face_size = face_counts_[i];
277
278 face_offsets[i] = loop_index;
279
280 /* Polygons are always assumed to be smooth-shaded. If the mesh should be flat-shaded,
281 * this is encoded in custom loop normals. */
282
283 if (is_left_handed_) {
284 int loop_end_index = loop_index + (face_size - 1);
285 for (int f = 0; f < face_size; ++f, ++loop_index) {
286 corner_verts[loop_index] = face_indices_[loop_end_index - f];
287 }
288 }
289 else {
290 for (int f = 0; f < face_size; ++f, ++loop_index) {
291 corner_verts[loop_index] = face_indices_[loop_index];
292 }
293 }
294 }
295
296 /* Check for faces with duplicate vertex indices. These will require a mesh validate to fix. */
297 const OffsetIndices<int> faces = mesh->faces();
298 const bool all_faces_ok = threading::parallel_reduce(
299 faces.index_range(),
300 1024,
301 true,
302 [&](const IndexRange part, const bool ok_so_far) {
303 bool current_faces_ok = ok_so_far;
304 if (ok_so_far) {
305 for (const int i : part) {
306 const IndexRange face_range = faces[i];
307 const Set<int, 32> used_verts(corner_verts.slice(face_range));
308 current_faces_ok = current_faces_ok && used_verts.size() == face_range.size();
309 }
310 }
311 return current_faces_ok;
312 },
313 std::logical_and<>());
314
315 /* If we detect bad faces it would be unsafe to continue beyond this point without first
316 * performing a destructive validate. Any operation requiring mesh connectivity information can
317 * assert or crash if the problem isn't addressed. Performing the check here, before most of the
318 * data has been loaded, unfortunately means any remaining data will be lost. */
319 if (!all_faces_ok) {
320 if (is_initial_load_) {
321 const char *message = N_(
322 "Invalid face data detected for mesh '%s'. Automatic correction will be used, but some "
323 "data will most likely be lost");
324 const std::string prim_path = this->prim_path().GetAsString();
325 BKE_reportf(this->reports(), RPT_WARNING, message, prim_path.c_str());
326 CLOG_WARN(&LOG, message, prim_path.c_str());
327 }
328 BKE_mesh_validate(mesh, false, false);
329 }
330
331 bke::mesh_calc_edges(*mesh, false, false);
332
333 /* It's possible that the number of faces, indices, and verts remain the same but the topology
334 * itself is different. Until finer-grained topology detection can be implemented, always tag the
335 * mesh as needing updated topology mappings. Without this, a time varying mesh could trigger
336 * undefined behavior. */
337 mesh->tag_topology_changed();
338
339 return all_faces_ok;
340}
341
342void USDMeshReader::read_uv_data_primvar(Mesh *mesh,
343 const pxr::UsdGeomPrimvar &primvar,
344 const double motionSampleTime)
345{
346 const StringRef primvar_name(
347 pxr::UsdGeomPrimvar::StripPrimvarsName(primvar.GetName()).GetString());
348
349 const pxr::VtVec2fArray usd_uvs = get_primvar_array<pxr::GfVec2f>(primvar, motionSampleTime);
350 if (usd_uvs.empty()) {
351 return;
352 }
353
354 const pxr::TfToken varying_type = primvar.GetInterpolation();
355 BLI_assert(ELEM(varying_type,
356 pxr::UsdGeomTokens->vertex,
357 pxr::UsdGeomTokens->faceVarying,
358 pxr::UsdGeomTokens->varying));
359
360 if ((varying_type == pxr::UsdGeomTokens->faceVarying && usd_uvs.size() != mesh->corners_num) ||
361 (varying_type == pxr::UsdGeomTokens->vertex && usd_uvs.size() != mesh->verts_num) ||
362 (varying_type == pxr::UsdGeomTokens->varying && usd_uvs.size() != mesh->verts_num))
363 {
366 "USD Import: UV attribute value '%s' count inconsistent with interpolation type",
367 primvar.GetName().GetText());
368 return;
369 }
370
371 bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
372 bke::SpanAttributeWriter<float2> uv_data = attributes.lookup_or_add_for_write_only_span<float2>(
373 primvar_name, bke::AttrDomain::Corner);
374
375 if (!uv_data) {
378 "USD Import: couldn't add UV attribute '%s'",
379 primvar.GetBaseName().GetText());
380 return;
381 }
382
383 if (varying_type == pxr::UsdGeomTokens->faceVarying) {
384 if (is_left_handed_) {
385 /* Reverse the index order. */
386 const OffsetIndices faces = mesh->faces();
387 for (const int i : faces.index_range()) {
388 const IndexRange face = faces[i];
389 for (int j : face.index_range()) {
390 const int rev_index = face.last(j);
391 uv_data.span[face.start() + j] = float2(usd_uvs[rev_index][0], usd_uvs[rev_index][1]);
392 }
393 }
394 }
395 else {
396 for (int i = 0; i < uv_data.span.size(); ++i) {
397 uv_data.span[i] = float2(usd_uvs[i][0], usd_uvs[i][1]);
398 }
399 }
400 }
401 else {
402 /* Handle vertex interpolation. */
403 const Span<int> corner_verts = mesh->corner_verts();
404 BLI_assert(mesh->verts_num == usd_uvs.size());
405 for (int i = 0; i < uv_data.span.size(); ++i) {
406 /* Get the vertex index for this corner. */
407 int vi = corner_verts[i];
408 uv_data.span[i] = float2(usd_uvs[vi][0], usd_uvs[vi][1]);
409 }
410 }
411
412 uv_data.finish();
413}
414
415void USDMeshReader::read_subdiv()
416{
417 ModifierData *md = (ModifierData *)(object_->modifiers.last);
418 SubsurfModifierData *subdiv_data = reinterpret_cast<SubsurfModifierData *>(md);
419
420 pxr::TfToken uv_smooth;
421 mesh_prim_.GetFaceVaryingLinearInterpolationAttr().Get(&uv_smooth);
422
423 if (uv_smooth == pxr::UsdGeomTokens->all) {
424 subdiv_data->uv_smooth = SUBSURF_UV_SMOOTH_NONE;
425 }
426 else if (uv_smooth == pxr::UsdGeomTokens->cornersOnly) {
428 }
429 else if (uv_smooth == pxr::UsdGeomTokens->cornersPlus1) {
431 }
432 else if (uv_smooth == pxr::UsdGeomTokens->cornersPlus2) {
434 }
435 else if (uv_smooth == pxr::UsdGeomTokens->boundaries) {
437 }
438 else if (uv_smooth == pxr::UsdGeomTokens->none) {
439 subdiv_data->uv_smooth = SUBSURF_UV_SMOOTH_ALL;
440 }
441
442 pxr::TfToken boundary_smooth;
443 mesh_prim_.GetInterpolateBoundaryAttr().Get(&boundary_smooth);
444
445 if (boundary_smooth == pxr::UsdGeomTokens->edgeOnly) {
447 }
448 else if (boundary_smooth == pxr::UsdGeomTokens->edgeAndCorner) {
450 }
451}
452
453void USDMeshReader::read_vertex_creases(Mesh *mesh, const double motionSampleTime)
454{
455 pxr::VtIntArray usd_corner_indices;
456 if (!mesh_prim_.GetCornerIndicesAttr().Get(&usd_corner_indices, motionSampleTime)) {
457 return;
458 }
459
460 pxr::VtFloatArray usd_corner_sharpnesses;
461 if (!mesh_prim_.GetCornerSharpnessesAttr().Get(&usd_corner_sharpnesses, motionSampleTime)) {
462 return;
463 }
464
465 /* Prevent the creation of the `crease_vert` attribute if we have no data. */
466 if (usd_corner_indices.empty() || usd_corner_sharpnesses.empty()) {
467 return;
468 }
469
470 /* It is fine to have fewer indices than vertices, but never the other way other. */
471 if (usd_corner_indices.size() > mesh->verts_num) {
472 CLOG_WARN(
473 &LOG, "Too many vertex creases for mesh %s", this->prim_path().GetAsString().c_str());
474 return;
475 }
476
477 if (usd_corner_indices.size() != usd_corner_sharpnesses.size()) {
478 CLOG_WARN(&LOG,
479 "Vertex crease and sharpness count mismatch for mesh %s",
480 this->prim_path().GetAsString().c_str());
481 return;
482 }
483
484 bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
485 bke::SpanAttributeWriter creases = attributes.lookup_or_add_for_write_only_span<float>(
486 "crease_vert", bke::AttrDomain::Point);
487 creases.span.fill(0.0f);
488
489 Span<int> corner_indices = Span(usd_corner_indices.cdata(), usd_corner_indices.size());
490 Span<float> corner_sharpnesses = Span(usd_corner_sharpnesses.cdata(),
491 usd_corner_sharpnesses.size());
492
493 for (size_t i = 0; i < corner_indices.size(); i++) {
494 const float crease = settings_->blender_stage_version_prior_44 ?
495 corner_sharpnesses[i] :
496 bke::subdiv::sharpness_to_crease(corner_sharpnesses[i]);
497 creases.span[corner_indices[i]] = std::clamp(crease, 0.0f, 1.0f);
498 }
499 creases.finish();
500}
501
502void USDMeshReader::read_edge_creases(Mesh *mesh, const double motionSampleTime)
503{
504 pxr::VtArray<int> usd_crease_lengths;
505 pxr::VtArray<int> usd_crease_indices;
506 pxr::VtArray<float> usd_crease_sharpness;
507 mesh_prim_.GetCreaseLengthsAttr().Get(&usd_crease_lengths, motionSampleTime);
508 mesh_prim_.GetCreaseIndicesAttr().Get(&usd_crease_indices, motionSampleTime);
509 mesh_prim_.GetCreaseSharpnessesAttr().Get(&usd_crease_sharpness, motionSampleTime);
510
511 /* Prevent the creation of the `crease_edge` attribute if we have no data. */
512 if (usd_crease_lengths.empty() || usd_crease_indices.empty() || usd_crease_sharpness.empty()) {
513 return;
514 }
515
516 /* There should be as many sharpness values as lengths. */
517 if (usd_crease_lengths.size() != usd_crease_sharpness.size()) {
518 CLOG_WARN(&LOG,
519 "Edge crease and sharpness count mismatch for mesh %s",
520 this->prim_path().GetAsString().c_str());
521 return;
522 }
523
524 /* Build mapping from vert pairs to edge index. */
525 using EdgeMap = VectorSet<OrderedEdge,
526 16,
528 DefaultHash<OrderedEdge>,
529 DefaultEquality<OrderedEdge>,
530 SimpleVectorSetSlot<OrderedEdge, int>,
531 GuardedAllocator>;
532 Span<int2> edges = mesh->edges();
533 EdgeMap edge_map;
534 edge_map.reserve(edges.size());
535
536 for (const int i : edges.index_range()) {
537 edge_map.add(edges[i]);
538 }
539
540 bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
541 bke::SpanAttributeWriter creases = attributes.lookup_or_add_for_write_only_span<float>(
542 "crease_edge", bke::AttrDomain::Edge);
543 creases.span.fill(0.0f);
544
545 Span<int> crease_lengths = Span(usd_crease_lengths.cdata(), usd_crease_lengths.size());
546 Span<int> crease_indices = Span(usd_crease_indices.cdata(), usd_crease_indices.size());
547 Span<float> crease_sharpness = Span(usd_crease_sharpness.cdata(), usd_crease_sharpness.size());
548
549 size_t index_start = 0;
550 for (size_t i = 0; i < crease_lengths.size(); i++) {
551 const int length = crease_lengths[i];
552 if (length < 2) {
553 /* Since each crease must be at least one edge long, each element of this array must be at
554 * least two. If this is not the case it would not be safe to continue. */
555 CLOG_WARN(&LOG,
556 "Edge crease length %d is invalid for mesh %s",
557 length,
558 this->prim_path().GetAsString().c_str());
559 break;
560 }
561
562 if (index_start + length > crease_indices.size()) {
563 CLOG_WARN(&LOG,
564 "Edge crease lengths are out of bounds for mesh %s",
565 this->prim_path().GetAsString().c_str());
566 break;
567 }
568
569 float crease = settings_->blender_stage_version_prior_44 ?
570 crease_sharpness[i] :
571 bke::subdiv::sharpness_to_crease(crease_sharpness[i]);
572 crease = std::clamp(crease, 0.0f, 1.0f);
573 for (size_t j = 0; j < length - 1; j++) {
574 const int v1 = crease_indices[index_start + j];
575 const int v2 = crease_indices[index_start + j + 1];
576 const int edge_i = edge_map.index_of_try({v1, v2});
577 if (edge_i < 0) {
578 continue;
579 }
580
581 creases.span[edge_i] = crease;
582 }
583
584 index_start += length;
585 }
586
587 creases.finish();
588}
589
590void USDMeshReader::read_velocities(Mesh *mesh, const double motionSampleTime)
591{
592 pxr::VtVec3fArray velocities;
593 mesh_prim_.GetVelocitiesAttr().Get(&velocities, motionSampleTime);
594
595 if (!velocities.empty()) {
596 bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
597 bke::SpanAttributeWriter<float3> velocity =
598 attributes.lookup_or_add_for_write_only_span<float3>("velocity", bke::AttrDomain::Point);
599
600 Span<pxr::GfVec3f> usd_data(velocities.cdata(), velocities.size());
601 velocity.span.copy_from(usd_data.cast<float3>());
602 velocity.finish();
603 }
604}
605
606void USDMeshReader::process_normals_vertex_varying(Mesh *mesh)
607{
608 if (normals_.empty()) {
609 return;
610 }
611
612 if (normals_.size() != mesh->verts_num) {
613 CLOG_WARN(&LOG,
614 "Vertex varying normals count mismatch for mesh '%s'",
615 this->prim_path().GetAsString().c_str());
616 return;
617 }
618
619 BLI_STATIC_ASSERT(sizeof(normals_[0]) == sizeof(float3), "Expected float3 normals size");
621 *mesh, {reinterpret_cast<float3 *>(normals_.data()), int64_t(normals_.size())});
622}
623
624void USDMeshReader::process_normals_face_varying(Mesh *mesh) const
625{
626 if (normals_.empty()) {
627 return;
628 }
629
630 /* Check for normals count mismatches to prevent crashes. */
631 if (normals_.size() != mesh->corners_num) {
632 CLOG_WARN(
633 &LOG, "Loop normal count mismatch for mesh '%s'", this->prim_path().GetAsString().c_str());
634 return;
635 }
636
637 Array<float3> corner_normals(mesh->corners_num);
638
639 const OffsetIndices faces = mesh->faces();
640 for (const int i : faces.index_range()) {
641 const IndexRange face = faces[i];
642 for (int j : face.index_range()) {
643 const int corner = face.start() + j;
644
645 int usd_index = face.start();
646 if (is_left_handed_) {
647 usd_index += face.size() - 1 - j;
648 }
649 else {
650 usd_index += j;
651 }
652
653 corner_normals[corner] = detail::convert_value<pxr::GfVec3f, float3>(normals_[usd_index]);
654 }
655 }
656
657 bke::mesh_set_custom_normals(*mesh, corner_normals);
658}
659
660void USDMeshReader::process_normals_uniform(Mesh *mesh) const
661{
662 if (normals_.empty()) {
663 return;
664 }
665
666 /* Check for normals count mismatches to prevent crashes. */
667 if (normals_.size() != mesh->faces_num) {
668 CLOG_WARN(&LOG,
669 "Uniform normal count mismatch for mesh '%s'",
670 this->prim_path().GetAsString().c_str());
671 return;
672 }
673
674 Array<float3> corner_normals(mesh->corners_num);
675
676 const OffsetIndices faces = mesh->faces();
677 for (const int i : faces.index_range()) {
678 for (const int corner : faces[i]) {
679 corner_normals[corner] = detail::convert_value<pxr::GfVec3f, float3>(normals_[i]);
680 }
681 }
682
683 bke::mesh_set_custom_normals(*mesh, corner_normals);
684}
685
686void USDMeshReader::read_mesh_sample(ImportSettings *settings,
687 Mesh *mesh,
688 const double motionSampleTime,
689 const bool new_mesh)
690{
691 /* Note that for new meshes we always want to read verts and faces,
692 * regardless of the value of the read_flag, to avoid a crash downstream
693 * in code that expect this data to be there. */
694
695 if (new_mesh || (settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) {
696 MutableSpan<float3> vert_positions = mesh->vert_positions_for_write();
697 vert_positions.copy_from(Span(positions_.cdata(), positions_.size()).cast<float3>());
698 mesh->tag_positions_changed();
699
700 read_vertex_creases(mesh, motionSampleTime);
701 }
702
703 if (new_mesh || (settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) {
704 if (!read_faces(mesh)) {
705 return;
706 }
707 read_edge_creases(mesh, motionSampleTime);
708
709 if (normal_interpolation_ == pxr::UsdGeomTokens->faceVarying) {
710 process_normals_face_varying(mesh);
711 }
712 else if (normal_interpolation_ == pxr::UsdGeomTokens->uniform) {
713 process_normals_uniform(mesh);
714 }
715 }
716
717 /* Process point normals after reading faces. */
718 if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0 &&
719 normal_interpolation_ == pxr::UsdGeomTokens->vertex)
720 {
721 process_normals_vertex_varying(mesh);
722 }
723
724 /* Custom Data layers. */
725 if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) ||
726 (settings->read_flag & MOD_MESHSEQ_READ_COLOR) ||
727 (settings->read_flag & MOD_MESHSEQ_READ_ATTRIBUTES))
728 {
729 read_velocities(mesh, motionSampleTime);
730 read_custom_data(settings, mesh, motionSampleTime, new_mesh);
731 }
732}
733
734void USDMeshReader::read_custom_data(const ImportSettings *settings,
735 Mesh *mesh,
736 const double motionSampleTime,
737 const bool new_mesh)
738{
739 if (!(mesh && mesh->corners_num > 0)) {
740 return;
741 }
742
743 pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(mesh_prim_);
744 std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
745
746 pxr::TfToken active_color_name;
747 pxr::TfToken active_uv_set_name;
748
749 /* Convert primvars to custom layer data. */
750 for (const pxr::UsdGeomPrimvar &pv : primvars) {
751 const pxr::SdfValueTypeName type = pv.GetTypeName();
752 if (!type.IsArray()) {
753 continue; /* Skip non-array primvar attributes. */
754 }
755
756 const pxr::TfToken varying_type = pv.GetInterpolation();
757 const pxr::TfToken name = pxr::UsdGeomPrimvar::StripPrimvarsName(pv.GetPrimvarName());
758
759 /* To avoid unnecessarily reloading static primvars during animation,
760 * early out if not first load and this primvar isn't animated. */
761 if (!new_mesh && primvar_varying_map_.contains(name) && !primvar_varying_map_.lookup(name)) {
762 continue;
763 }
764
765 /* We handle the non-standard primvar:velocity elsewhere. */
766 if (ELEM(name, "velocity")) {
767 continue;
768 }
769
770 if (ELEM(type,
771 pxr::SdfValueTypeNames->StringArray,
772 pxr::SdfValueTypeNames->QuatdArray,
773 pxr::SdfValueTypeNames->QuathArray))
774 {
775 /* Skip creating known unsupported types, and avoid noisy error prints. */
776 continue;
777 }
778
779 /* Read Color primvars. */
781 if ((settings->read_flag & MOD_MESHSEQ_READ_COLOR) != 0) {
782 /* Set the active color name to 'displayColor', if a color primvar
783 * with this name exists. Otherwise, use the name of the first
784 * color primvar we find for the active color. */
785 if (active_color_name.IsEmpty() || name == usdtokens::displayColor) {
786 active_color_name = name;
787 }
788
789 read_generic_mesh_primvar(mesh, pv, motionSampleTime, is_left_handed_);
790 }
791 }
792
793 /* Read UV primvars. */
794 else if (ELEM(varying_type,
795 pxr::UsdGeomTokens->vertex,
796 pxr::UsdGeomTokens->faceVarying,
797 pxr::UsdGeomTokens->varying) &&
799 {
800 if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) {
801 /* Set the active uv set name to 'st', if a uv set primvar
802 * with this name exists. Otherwise, use the name of the first
803 * uv set primvar we find for the active uv set. */
804 if (active_uv_set_name.IsEmpty() || name == usdtokens::st) {
805 active_uv_set_name = name;
806 }
807 this->read_uv_data_primvar(mesh, pv, motionSampleTime);
808 }
809 }
810
811 /* Read all other primvars. */
812 else {
813 if ((settings->read_flag & MOD_MESHSEQ_READ_ATTRIBUTES) != 0) {
814 read_generic_mesh_primvar(mesh, pv, motionSampleTime, is_left_handed_);
815 }
816 }
817
818 /* Record whether the primvar attribute might be time varying. */
819 if (!primvar_varying_map_.contains(name)) {
820 bool might_be_time_varying = pv.ValueMightBeTimeVarying();
821 primvar_varying_map_.add(name, might_be_time_varying);
822 if (might_be_time_varying) {
823 is_time_varying_ = true;
824 }
825 }
826 } /* End primvar attribute loop. */
827
828 if (!active_color_name.IsEmpty()) {
829 BKE_id_attributes_default_color_set(&mesh->id, active_color_name.GetText());
830 BKE_id_attributes_active_color_set(&mesh->id, active_color_name.GetText());
831 }
832
833 if (!active_uv_set_name.IsEmpty()) {
834 int layer_index = CustomData_get_named_layer_index(
835 &mesh->corner_data, CD_PROP_FLOAT2, active_uv_set_name.GetText());
836 if (layer_index > -1) {
839 }
840 }
841}
842
843void USDMeshReader::assign_facesets_to_material_indices(double motionSampleTime,
844 MutableSpan<int> material_indices,
845 blender::Map<pxr::SdfPath, int> *r_mat_map)
846{
847 if (r_mat_map == nullptr) {
848 return;
849 }
850
851 /* Find the geom subsets that have bound materials.
852 * We don't call #pxr::UsdShadeMaterialBindingAPI::GetMaterialBindSubsets()
853 * because this function returns only those subsets that are in the 'materialBind'
854 * family, but, in practice, applications (like Houdini) might export subsets
855 * in different families that are bound to materials.
856 * TODO(makowalski): Reassess if the above is the best approach. */
857 const std::vector<pxr::UsdGeomSubset> subsets = pxr::UsdGeomSubset::GetAllGeomSubsets(
858 mesh_prim_);
859
860 int current_mat = 0;
861 if (!subsets.empty()) {
862 for (const pxr::UsdGeomSubset &subset : subsets) {
863 pxr::UsdPrim subset_prim = subset.GetPrim();
864 pxr::UsdShadeMaterial subset_mtl = utils::compute_bound_material(subset_prim,
865 import_params_.mtl_purpose);
866 if (!subset_mtl) {
867 continue;
868 }
869
870 pxr::SdfPath subset_mtl_path = subset_mtl.GetPath();
871 if (subset_mtl_path.IsEmpty()) {
872 continue;
873 }
874
875 pxr::TfToken element_type;
876 subset.GetElementTypeAttr().Get(&element_type, motionSampleTime);
877 if (element_type != pxr::UsdGeomTokens->face) {
878 CLOG_WARN(&LOG,
879 "UsdGeomSubset '%s' uses unsupported elementType: %s",
880 subset_prim.GetName().GetText(),
881 element_type.GetText());
882 continue;
883 }
884
885 const int mat_idx = r_mat_map->lookup_or_add(subset_mtl_path, 1 + current_mat++);
886 const int max_element_idx = std::max(0, int(material_indices.size() - 1));
887
888 pxr::VtIntArray indices;
889 subset.GetIndicesAttr().Get(&indices, motionSampleTime);
890
891 int bad_element_count = 0;
892 for (const int element_idx : indices.AsConst()) {
893 const int safe_element_idx = std::clamp(element_idx, 0, max_element_idx);
894 bad_element_count += (safe_element_idx != element_idx) ? 1 : 0;
895 material_indices[safe_element_idx] = mat_idx - 1;
896 }
897
898 if (bad_element_count > 0) {
899 CLOG_WARN(&LOG,
900 "UsdGeomSubset '%s' contains invalid indices; material assignment may be "
901 "incorrect (%d were out of range)",
902 subset_prim.GetName().GetText(),
903 bad_element_count);
904 }
905 }
906 }
907
908 if (r_mat_map->is_empty()) {
909 pxr::UsdShadeMaterial mtl = utils::compute_bound_material(prim_, import_params_.mtl_purpose);
910 if (mtl) {
911 pxr::SdfPath mtl_path = mtl.GetPath();
912
913 if (!mtl_path.IsEmpty()) {
914 r_mat_map->add(mtl.GetPath(), 1);
915 }
916 }
917 }
918}
919
920void USDMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const double motionSampleTime)
921{
922 if (!import_params_.import_materials) {
923 return;
924 }
925
926 blender::Map<pxr::SdfPath, int> mat_map;
927
928 bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
929 bke::SpanAttributeWriter<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
930 "material_index", bke::AttrDomain::Face);
931 this->assign_facesets_to_material_indices(motionSampleTime, material_indices.span, &mat_map);
932 material_indices.finish();
933 /* Build material name map if it's not built yet. */
934 if (this->settings_->mat_name_to_mat.is_empty()) {
935 build_material_map(bmain, this->settings_->mat_name_to_mat);
936 }
937 utils::assign_materials(
938 bmain, object_, mat_map, this->import_params_, this->prim_.GetStage(), *this->settings_);
939}
940
941Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
942 const USDMeshReadParams params,
943 const char ** /*r_err_str*/)
944{
945 mesh_prim_.GetOrientationAttr().Get(&orientation_);
946 if (orientation_ == pxr::UsdGeomTokens->leftHanded) {
947 is_left_handed_ = true;
948 }
949
950 Mesh *active_mesh = existing_mesh;
951 bool new_mesh = false;
952
953 /* TODO(makowalski): implement the optimization of only updating the mesh points when
954 * the topology is consistent, as in the Alembic importer. */
955
956 ImportSettings settings;
957 settings.read_flag |= params.read_flags;
958
959 if (topology_changed(existing_mesh, params.motion_sample_time)) {
960 new_mesh = true;
962 existing_mesh, positions_.size(), 0, face_counts_.size(), face_indices_.size());
963 }
964
965 read_mesh_sample(
966 &settings, active_mesh, params.motion_sample_time, new_mesh || is_initial_load_);
967
968 if (new_mesh) {
969 /* Here we assume that the number of materials doesn't change, i.e. that
970 * the material slots that were created when the object was loaded from
971 * USD are still valid now. */
972 if (active_mesh->faces_num != 0 && import_params_.import_materials) {
973 blender::Map<pxr::SdfPath, int> mat_map;
974 bke::MutableAttributeAccessor attributes = active_mesh->attributes_for_write();
975 bke::SpanAttributeWriter<int> material_indices =
976 attributes.lookup_or_add_for_write_span<int>("material_index", bke::AttrDomain::Face);
977 assign_facesets_to_material_indices(
978 params.motion_sample_time, material_indices.span, &mat_map);
979 material_indices.finish();
980 }
981 }
982
983 if (import_params_.validate_meshes) {
984 if (BKE_mesh_validate(active_mesh, false, false)) {
985 BKE_reportf(reports(), RPT_INFO, "Fixed mesh for prim: %s", mesh_prim_.GetPath().GetText());
986 }
987 }
988
989 return active_mesh;
990}
991
994 const char **r_err_str)
995{
996 Mesh *existing_mesh = geometry_set.get_mesh_for_write();
997 Mesh *new_mesh = read_mesh(existing_mesh, params, r_err_str);
998
999 if (new_mesh != existing_mesh) {
1000 geometry_set.replace_mesh(new_mesh);
1001 }
1002}
1003
1005{
1006 /* Make sure we can apply UsdSkelBindingAPI to the prim.
1007 * Attempting to apply the API to instance proxies generates
1008 * a USD error. */
1009 if (!prim_ || prim_.IsInstanceProxy()) {
1010 return {};
1011 }
1012
1013 pxr::UsdSkelBindingAPI skel_api(prim_);
1014
1015 if (pxr::UsdSkelSkeleton skel = skel_api.GetInheritedSkeleton()) {
1016 return skel.GetPath();
1017 }
1018
1019 return {};
1020}
1021
1022std::optional<XformResult> USDMeshReader::get_local_usd_xform(const float time) const
1023{
1024 if (!import_params_.import_skeletons || prim_.IsInstanceProxy()) {
1025 /* Use the standard transform computation, since we are ignoring
1026 * skinning data. Note that applying the UsdSkelBinding API to an
1027 * instance proxy generates a USD error. */
1029 }
1030
1031 pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI(prim_);
1032 if (pxr::UsdAttribute xf_attr = skel_api.GetGeomBindTransformAttr()) {
1033 if (xf_attr.HasAuthoredValue()) {
1034 pxr::GfMatrix4d bind_xf;
1035 if (skel_api.GetGeomBindTransformAttr().Get(&bind_xf)) {
1036 /* The USD bind transform is a matrix of doubles,
1037 * but we cast it to GfMatrix4f because Blender expects
1038 * a matrix of floats. Also, we assume the transform
1039 * is constant over time. */
1040 return XformResult(pxr::GfMatrix4f(bind_xf), true);
1041 }
1042
1045 "%s: Couldn't compute geom bind transform for %s",
1046 __func__,
1047 prim_.GetPath().GetAsString().c_str());
1048 }
1049 }
1050
1051 return USDXformReader::get_local_usd_xform(time);
1052}
1053
1054} // namespace blender::io::usd
void BKE_id_attributes_default_color_set(struct ID *id, std::optional< blender::StringRef > name)
void BKE_id_attributes_active_color_set(struct ID *id, std::optional< blender::StringRef > name)
Definition attribute.cc:986
CustomData interface, see also DNA_customdata_types.h.
void CustomData_set_layer_render_index(CustomData *data, eCustomDataType type, int n)
int CustomData_get_named_layer_index(const CustomData *data, eCustomDataType type, blender::StringRef name)
void CustomData_set_layer_active_index(CustomData *data, eCustomDataType type, int n)
General operations, lookup, etc. for materials.
void BKE_object_material_assign_single_obdata(Main *bmain, Object *ob, Material *ma, short act)
Mesh * BKE_mesh_new_nomain_from_template(const Mesh *me_src, int verts_num, int edges_num, int faces_num, int corners_num)
bool BKE_mesh_validate(Mesh *mesh, bool do_verbose, bool cddata_check_mask)
void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, Mesh *mesh_dst, Object *ob)
Mesh * BKE_mesh_add(Main *bmain, const char *name)
General operations, lookup, etc. for blender objects.
Object * BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR_RETURNS_NONNULL
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
#define BLI_STATIC_ASSERT(a, msg)
Definition BLI_assert.h:83
#define BLI_assert(a)
Definition BLI_assert.h:46
#define ELEM(...)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
@ CD_PROP_COLOR
@ CD_PROP_FLOAT2
#define MAXMAT
struct Mesh Mesh
@ SUBSURF_BOUNDARY_SMOOTH_ALL
@ SUBSURF_BOUNDARY_SMOOTH_PRESERVE_CORNERS
struct ModifierData ModifierData
@ MOD_MESHSEQ_READ_COLOR
@ MOD_MESHSEQ_READ_VERT
@ MOD_MESHSEQ_READ_ATTRIBUTES
@ MOD_MESHSEQ_READ_UV
@ MOD_MESHSEQ_READ_POLY
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
Object is a sort of wrapper for general info.
@ OB_MESH
ReportList * reports
Definition WM_types.hh:1025
ATTR_WARN_UNUSED_RESULT const BMVert * v2
long long int int64_t
SIMD_FORCE_INLINE btScalar length() const
Return the length of the vector.
Definition btVector3.h:257
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr int64_t start() const
constexpr IndexRange index_range() const
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:265
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
int64_t size() const
Definition BLI_map.hh:976
bool is_empty() const
Definition BLI_map.hh:986
ItemIterator items() const &
Definition BLI_map.hh:902
Value & lookup_or_add(const Key &key, const Value &value)
Definition BLI_map.hh:588
bool contains(const Key &key) const
Definition BLI_set.hh:310
Material * add_material(const pxr::UsdShadeMaterial &usd_material, bool read_usd_preview=true) const
void read_object_data(Main *bmain, double motionSampleTime) override
pxr::SdfPath get_skeleton_path() const
void read_geometry(bke::GeometrySet &geometry_set, USDMeshReadParams params, const char **r_err_str) override
void create_object(Main *bmain) override
bool topology_changed(const Mesh *existing_mesh, double motionSampleTime) override
const USDImportParams & import_params_
void read_object_data(Main *bmain, double motionSampleTime) override
virtual std::optional< XformResult > get_local_usd_xform(float time) const
static ushort indices[]
#define this
bool all(VecOp< bool, D >) RET
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
#define LOG(severity)
Definition log.h:32
static char faces[256]
VectorSet< OrderedEdge, 16, DefaultProbingStrategy, DefaultHash< OrderedEdge >, DefaultEquality< OrderedEdge >, SimpleVectorSetSlot< OrderedEdge, int >, GuardedAllocator > EdgeMap
BLI_INLINE float sharpness_to_crease(float sharpness)
void mesh_calc_edges(Mesh &mesh, bool keep_existing_edges, bool select_new_edges)
void mesh_set_custom_normals_from_verts(Mesh &mesh, MutableSpan< float3 > vert_normals)
void mesh_set_custom_normals(Mesh &mesh, MutableSpan< float3 > corner_normals)
void read_custom_data(const std::string &iobject_full_name, const ICompoundProperty &prop, const CDStreamConfig &config, const Alembic::Abc::ISampleSelector &iss)
static pxr::UsdShadeMaterial compute_bound_material(const pxr::UsdPrim &prim, eUSDMtlPurpose mtl_purpose)
static void assign_materials(Main *bmain, Object *ob, const blender::Map< pxr::SdfPath, int > &mat_index_map, const USDImportParams &params, pxr::UsdStageRefPtr stage, const ImportSettings &settings)
void import_mesh_skel_bindings(Object *mesh_obj, const pxr::UsdPrim &prim, ReportList *reports)
void build_material_map(const Main *bmain, blender::Map< std::string, Material * > &r_mat_map)
Material * find_existing_material(const pxr::SdfPath &usd_mat_path, const USDImportParams &params, const blender::Map< std::string, Material * > &mat_map, const blender::Map< pxr::SdfPath, Material * > &usd_path_to_mat)
USDMeshReadParams create_mesh_read_params(const double motion_sample_time, const int read_flags)
@ USD_MTL_PURPOSE_FULL
Definition usd.hh:46
@ USD_MTL_PURPOSE_ALL
Definition usd.hh:44
@ USD_MTL_PURPOSE_PREVIEW
Definition usd.hh:45
std::string make_safe_name(const StringRef name, bool allow_unicode)
Definition usd_utils.cc:18
void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, ReportList *reports, const bool import_anim)
std::optional< eCustomDataType > convert_usd_type_to_blender(const pxr::SdfValueTypeName usd_type)
void read_generic_mesh_primvar(Mesh *mesh, const pxr::UsdGeomPrimvar &primvar, const double motionSampleTime, const bool is_left_handed)
@ USD_MTL_NAME_COLLISION_MAKE_UNIQUE
Definition usd.hh:36
Value parallel_reduce(IndexRange range, int64_t grain_size, const Value &identity, const Function &function, const Reduction &reduction)
Definition BLI_task.hh:151
VecBase< float, 2 > float2
PythonProbingStrategy<> DefaultProbingStrategy
VecBase< float, 3 > float3
static const pxr::TfToken st("st", pxr::TfToken::Immortal)
static const pxr::TfToken normalsPrimvar("normals", pxr::TfToken::Immortal)
const pxr::TfToken displayColor("displayColor", pxr::TfToken::Immortal)
char name[66]
Definition DNA_ID.h:415
int corners_num
CustomData corner_data
int faces_num
int verts_num
void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
blender::Map< std::string, Material * > mat_name_to_mat
blender::Set< pxr::SdfPath > mat_import_hook_sources
blender::Map< pxr::SdfPath, Material * > usd_path_to_mat_for_hook
blender::Map< pxr::SdfPath, Material * > usd_path_to_mat
i
Definition text_draw.cc:230
#define N_(msgid)