Blender V4.3
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.h"
22#include "BKE_mesh.hh"
23#include "BKE_object.hh"
24#include "BKE_report.hh"
25
26#include "BLI_array.hh"
27#include "BLI_map.hh"
29#include "BLI_span.hh"
30
32#include "DNA_material_types.h"
33#include "DNA_modifier_types.h"
34#include "DNA_object_types.h"
36
37#include <pxr/base/gf/matrix4f.h>
38#include <pxr/base/vt/array.h>
39#include <pxr/base/vt/types.h>
40#include <pxr/usd/sdf/types.h>
41#include <pxr/usd/usdGeom/primvarsAPI.h>
42#include <pxr/usd/usdGeom/subset.h>
43#include <pxr/usd/usdShade/materialBindingAPI.h>
44#include <pxr/usd/usdShade/tokens.h>
45#include <pxr/usd/usdSkel/bindingAPI.h>
46
47#include <algorithm>
48
49#include "CLG_log.h"
50static CLG_LogRef LOG = {"io.usd"};
51
52namespace usdtokens {
53/* Materials */
54static const pxr::TfToken st("st", pxr::TfToken::Immortal);
55static const pxr::TfToken UVMap("UVMap", pxr::TfToken::Immortal);
56static const pxr::TfToken normalsPrimvar("normals", pxr::TfToken::Immortal);
57} // namespace usdtokens
58
59namespace utils {
60using namespace blender::io::usd;
61static pxr::UsdShadeMaterial compute_bound_material(const pxr::UsdPrim &prim,
62 eUSDMtlPurpose mtl_purpose)
63{
64 const pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(prim);
65
66 /* See the following documentation for material resolution behavior:
67 * https://openusd.org/release/api/class_usd_shade_material_binding_a_p_i.html#UsdShadeMaterialBindingAPI_MaterialResolution
68 */
69
70 pxr::UsdShadeMaterial mtl;
71 switch (mtl_purpose) {
73 mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->full);
74 if (!mtl) {
75 /* Add an additional Blender-specific fallback to help with oddly authored USD files. */
76 mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview);
77 }
78 break;
80 mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview);
81 break;
83 mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->allPurpose);
84 break;
85 }
86
87 return mtl;
88}
89
90static void assign_materials(Main *bmain,
91 Object *ob,
92 const blender::Map<pxr::SdfPath, int> &mat_index_map,
94 pxr::UsdStageRefPtr stage,
96 blender::Map<std::string, std::string> &usd_path_to_mat_name)
97{
98 using namespace blender::io::usd;
99 if (!(stage && bmain && ob)) {
100 return;
101 }
102
103 if (mat_index_map.size() > MAXMAT) {
104 return;
105 }
106
107 USDMaterialReader mat_reader(params, bmain);
108
109 for (const auto item : mat_index_map.items()) {
111 item.key, params, mat_name_to_mat, usd_path_to_mat_name);
112 if (!assigned_mat) {
113 /* Blender material doesn't exist, so create it now. */
114
115 /* Look up the USD material. */
116 pxr::UsdPrim prim = stage->GetPrimAtPath(item.key);
117 pxr::UsdShadeMaterial usd_mat(prim);
118
119 if (!usd_mat) {
120 CLOG_WARN(
121 &LOG, "Couldn't construct USD material from prim %s", item.key.GetAsString().c_str());
122 continue;
123 }
124
125 /* Add the Blender material. */
126 assigned_mat = mat_reader.add_material(usd_mat);
127
128 if (!assigned_mat) {
129 CLOG_WARN(&LOG,
130 "Couldn't create Blender material from USD material %s",
131 item.key.GetAsString().c_str());
132 continue;
133 }
134
135 const std::string mat_name = make_safe_name(assigned_mat->id.name + 2, true);
136 mat_name_to_mat.lookup_or_add_default(mat_name) = assigned_mat;
137
138 if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MAKE_UNIQUE) {
139 /* Record the name of the Blender material we created for the USD material
140 * with the given path. */
141 usd_path_to_mat_name.lookup_or_add_default(item.key.GetAsString()) = mat_name;
142 }
143 }
144
145 if (assigned_mat) {
146 BKE_object_material_assign_single_obdata(bmain, ob, assigned_mat, item.value);
147 }
148 else {
149 /* This shouldn't happen. */
150 CLOG_WARN(&LOG, "Couldn't assign material %s", item.key.GetAsString().c_str());
151 }
152 }
153 if (ob->totcol > 0) {
154 ob->actcol = 1;
155 }
156}
157
158} // namespace utils
159
160namespace blender::io::usd {
161
162USDMeshReader::USDMeshReader(const pxr::UsdPrim &prim,
163 const USDImportParams &import_params,
164 const ImportSettings &settings)
165 : USDGeomReader(prim, import_params, settings),
166 mesh_prim_(prim),
167 is_left_handed_(false),
168 is_time_varying_(false),
169 is_initial_load_(false)
170{
171}
172
173void USDMeshReader::create_object(Main *bmain, const double /*motionSampleTime*/)
174{
175 Mesh *mesh = BKE_mesh_add(bmain, name_.c_str());
176
178 object_->data = mesh;
179}
180
181void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime)
182{
183 Mesh *mesh = (Mesh *)object_->data;
184
185 is_initial_load_ = true;
186 const USDMeshReadParams params = create_mesh_read_params(motionSampleTime,
188
189 Mesh *read_mesh = this->read_mesh(mesh, params, nullptr);
190
191 is_initial_load_ = false;
192 if (read_mesh != mesh) {
193 BKE_mesh_nomain_to_mesh(read_mesh, mesh, object_);
194 }
195
196 readFaceSetsSample(bmain, mesh, motionSampleTime);
197
198 if (mesh_prim_.GetPointsAttr().ValueMightBeTimeVarying() ||
199 mesh_prim_.GetVelocitiesAttr().ValueMightBeTimeVarying())
200 {
201 is_time_varying_ = true;
202 }
203
204 if (is_time_varying_) {
206 }
207
209 pxr::TfToken subdivScheme;
210 mesh_prim_.GetSubdivisionSchemeAttr().Get(&subdivScheme, motionSampleTime);
211
212 if (subdivScheme == pxr::UsdGeomTokens->catmullClark) {
214 }
215 }
216
219 }
220
223 }
224
225 USDXformReader::read_object_data(bmain, motionSampleTime);
226}
227
229{
230 return bool(mesh_prim_);
231}
232
233bool USDMeshReader::topology_changed(const Mesh *existing_mesh, const double motionSampleTime)
234{
235 /* TODO(makowalski): Is it the best strategy to cache the mesh
236 * geometry in this function? This needs to be revisited. */
237
238 mesh_prim_.GetFaceVertexIndicesAttr().Get(&face_indices_, motionSampleTime);
239 mesh_prim_.GetFaceVertexCountsAttr().Get(&face_counts_, motionSampleTime);
240 mesh_prim_.GetPointsAttr().Get(&positions_, motionSampleTime);
241
242 pxr::UsdGeomPrimvarsAPI primvarsAPI(mesh_prim_);
243
244 /* TODO(makowalski): Reading normals probably doesn't belong in this function,
245 * as this is not required to determine if the topology has changed. */
246
247 /* If 'normals' and 'primvars:normals' are both specified, the latter has precedence. */
248 pxr::UsdGeomPrimvar primvar = primvarsAPI.GetPrimvar(usdtokens::normalsPrimvar);
249 if (primvar.HasValue()) {
250 primvar.ComputeFlattened(&normals_, motionSampleTime);
251 normal_interpolation_ = primvar.GetInterpolation();
252 }
253 else {
254 mesh_prim_.GetNormalsAttr().Get(&normals_, motionSampleTime);
255 normal_interpolation_ = mesh_prim_.GetNormalsInterpolation();
256 }
257
258 return positions_.size() != existing_mesh->verts_num ||
259 face_counts_.size() != existing_mesh->faces_num ||
260 face_indices_.size() != existing_mesh->corners_num;
261}
262
263void USDMeshReader::read_mpolys(Mesh *mesh) const
264{
265 MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
266 MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
267
268 int loop_index = 0;
269
270 for (int i = 0; i < face_counts_.size(); i++) {
271 const int face_size = face_counts_[i];
272
273 face_offsets[i] = loop_index;
274
275 /* Polygons are always assumed to be smooth-shaded. If the mesh should be flat-shaded,
276 * this is encoded in custom loop normals. */
277
278 if (is_left_handed_) {
279 int loop_end_index = loop_index + (face_size - 1);
280 for (int f = 0; f < face_size; ++f, ++loop_index) {
281 corner_verts[loop_index] = face_indices_[loop_end_index - f];
282 }
283 }
284 else {
285 for (int f = 0; f < face_size; ++f, ++loop_index) {
286 corner_verts[loop_index] = face_indices_[loop_index];
287 }
288 }
289 }
290
291 bke::mesh_calc_edges(*mesh, false, false);
292}
293
294void USDMeshReader::read_uv_data_primvar(Mesh *mesh,
295 const pxr::UsdGeomPrimvar &primvar,
296 const double motionSampleTime)
297{
298 const StringRef primvar_name(
299 pxr::UsdGeomPrimvar::StripPrimvarsName(primvar.GetName()).GetString());
300
301 pxr::VtArray<pxr::GfVec2f> usd_uvs = get_primvar_array<pxr::GfVec2f>(primvar, motionSampleTime);
302 if (usd_uvs.empty()) {
303 return;
304 }
305
306 const pxr::TfToken varying_type = primvar.GetInterpolation();
307 BLI_assert(ELEM(varying_type,
308 pxr::UsdGeomTokens->vertex,
309 pxr::UsdGeomTokens->faceVarying,
310 pxr::UsdGeomTokens->varying));
311
312 if ((varying_type == pxr::UsdGeomTokens->faceVarying && usd_uvs.size() != mesh->corners_num) ||
313 (varying_type == pxr::UsdGeomTokens->vertex && usd_uvs.size() != mesh->verts_num) ||
314 (varying_type == pxr::UsdGeomTokens->varying && usd_uvs.size() != mesh->verts_num))
315 {
318 "USD Import: UV attribute value '%s' count inconsistent with interpolation type",
319 primvar.GetName().GetText());
320 return;
321 }
322
323 bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
324 bke::SpanAttributeWriter<float2> uv_data = attributes.lookup_or_add_for_write_only_span<float2>(
325 primvar_name, bke::AttrDomain::Corner);
326
327 if (!uv_data) {
330 "USD Import: couldn't add UV attribute '%s'",
331 primvar.GetBaseName().GetText());
332 return;
333 }
334
335 if (varying_type == pxr::UsdGeomTokens->faceVarying) {
336 if (is_left_handed_) {
337 /* Reverse the index order. */
338 const OffsetIndices faces = mesh->faces();
339 for (const int i : faces.index_range()) {
340 const IndexRange face = faces[i];
341 for (int j : face.index_range()) {
342 const int rev_index = face.last(j);
343 uv_data.span[face.start() + j] = float2(usd_uvs[rev_index][0], usd_uvs[rev_index][1]);
344 }
345 }
346 }
347 else {
348 for (int i = 0; i < uv_data.span.size(); ++i) {
349 uv_data.span[i] = float2(usd_uvs[i][0], usd_uvs[i][1]);
350 }
351 }
352 }
353 else {
354 /* Handle vertex interpolation. */
355 const Span<int> corner_verts = mesh->corner_verts();
356 BLI_assert(mesh->verts_num == usd_uvs.size());
357 for (int i = 0; i < uv_data.span.size(); ++i) {
358 /* Get the vertex index for this corner. */
359 int vi = corner_verts[i];
360 uv_data.span[i] = float2(usd_uvs[vi][0], usd_uvs[vi][1]);
361 }
362 }
363
364 uv_data.finish();
365}
366
367void USDMeshReader::read_vertex_creases(Mesh *mesh, const double motionSampleTime)
368{
369 pxr::VtIntArray corner_indices;
370 if (!mesh_prim_.GetCornerIndicesAttr().Get(&corner_indices, motionSampleTime)) {
371 return;
372 }
373
374 pxr::VtIntArray corner_sharpnesses;
375 if (!mesh_prim_.GetCornerSharpnessesAttr().Get(&corner_sharpnesses, motionSampleTime)) {
376 return;
377 }
378
379 /* It is fine to have fewer indices than vertices, but never the other way other. */
380 if (corner_indices.size() > mesh->verts_num) {
381 CLOG_WARN(&LOG, "Too many vertex creases for mesh %s", prim_path_.c_str());
382 return;
383 }
384
385 if (corner_indices.size() != corner_sharpnesses.size()) {
386 CLOG_WARN(
387 &LOG, "Vertex crease and sharpnesses count mismatch for mesh %s", prim_path_.c_str());
388 return;
389 }
390
391 bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
392 bke::SpanAttributeWriter creases = attributes.lookup_or_add_for_write_only_span<float>(
393 "crease_vert", bke::AttrDomain::Point);
394
395 for (size_t i = 0; i < corner_indices.size(); i++) {
396 creases.span[corner_indices[i]] = corner_sharpnesses[i];
397 }
398 creases.finish();
399}
400
401void USDMeshReader::read_velocities(Mesh *mesh, const double motionSampleTime)
402{
403 pxr::VtVec3fArray velocities;
404 mesh_prim_.GetVelocitiesAttr().Get(&velocities, motionSampleTime);
405
406 if (!velocities.empty()) {
407 bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
409 attributes.lookup_or_add_for_write_only_span<float3>("velocity", bke::AttrDomain::Point);
410
411 Span<pxr::GfVec3f> usd_data(velocities.data(), velocities.size());
412 velocity.span.copy_from(usd_data.cast<float3>());
413 velocity.finish();
414 }
415}
416
417void USDMeshReader::process_normals_vertex_varying(Mesh *mesh)
418{
419 if (normals_.empty()) {
420 return;
421 }
422
423 if (normals_.size() != mesh->verts_num) {
424 CLOG_WARN(&LOG, "Vertex varying normals count mismatch for mesh '%s'", prim_path_.c_str());
425 return;
426 }
427
428 BLI_STATIC_ASSERT(sizeof(normals_[0]) == sizeof(float3), "Expected float3 normals size");
429 BKE_mesh_set_custom_normals_from_verts(mesh, reinterpret_cast<float(*)[3]>(normals_.data()));
430}
431
432void USDMeshReader::process_normals_face_varying(Mesh *mesh) const
433{
434 if (normals_.empty()) {
435 return;
436 }
437
438 /* Check for normals count mismatches to prevent crashes. */
439 if (normals_.size() != mesh->corners_num) {
440 CLOG_WARN(&LOG, "Loop normal count mismatch for mesh '%s'", prim_path_.c_str());
441 return;
442 }
443
444 Array<float3> corner_normals(mesh->corners_num);
445
446 const OffsetIndices faces = mesh->faces();
447 for (const int i : faces.index_range()) {
448 const IndexRange face = faces[i];
449 for (int j : face.index_range()) {
450 const int corner = face.start() + j;
451
452 int usd_index = face.start();
453 if (is_left_handed_) {
454 usd_index += face.size() - 1 - j;
455 }
456 else {
457 usd_index += j;
458 }
459
460 corner_normals[corner] = detail::convert_value<pxr::GfVec3f, float3>(normals_[usd_index]);
461 }
462 }
463
464 BKE_mesh_set_custom_normals(mesh, reinterpret_cast<float(*)[3]>(corner_normals.data()));
465}
466
467void USDMeshReader::process_normals_uniform(Mesh *mesh) const
468{
469 if (normals_.empty()) {
470 return;
471 }
472
473 /* Check for normals count mismatches to prevent crashes. */
474 if (normals_.size() != mesh->faces_num) {
475 CLOG_WARN(&LOG, "Uniform normal count mismatch for mesh '%s'", prim_path_.c_str());
476 return;
477 }
478
479 Array<float3> corner_normals(mesh->corners_num);
480
481 const OffsetIndices faces = mesh->faces();
482 for (const int i : faces.index_range()) {
483 for (const int corner : faces[i]) {
484 corner_normals[corner] = detail::convert_value<pxr::GfVec3f, float3>(normals_[i]);
485 }
486 }
487
488 BKE_mesh_set_custom_normals(mesh, reinterpret_cast<float(*)[3]>(corner_normals.data()));
489}
490
491void USDMeshReader::read_mesh_sample(ImportSettings *settings,
492 Mesh *mesh,
493 const double motionSampleTime,
494 const bool new_mesh)
495{
496 /* Note that for new meshes we always want to read verts and faces,
497 * regardless of the value of the read_flag, to avoid a crash downstream
498 * in code that expect this data to be there. */
499
500 if (new_mesh || (settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) {
501 MutableSpan<float3> vert_positions = mesh->vert_positions_for_write();
502 vert_positions.copy_from(Span(positions_.data(), positions_.size()).cast<float3>());
503 mesh->tag_positions_changed();
504
505 read_vertex_creases(mesh, motionSampleTime);
506 }
507
508 if (new_mesh || (settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) {
509 read_mpolys(mesh);
510 if (normal_interpolation_ == pxr::UsdGeomTokens->faceVarying) {
511 process_normals_face_varying(mesh);
512 }
513 else if (normal_interpolation_ == pxr::UsdGeomTokens->uniform) {
514 process_normals_uniform(mesh);
515 }
516 }
517
518 /* Process point normals after reading faces. */
519 if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0 &&
520 normal_interpolation_ == pxr::UsdGeomTokens->vertex)
521 {
522 process_normals_vertex_varying(mesh);
523 }
524
525 /* Custom Data layers. */
526 if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) ||
527 (settings->read_flag & MOD_MESHSEQ_READ_COLOR) ||
528 (settings->read_flag & MOD_MESHSEQ_READ_ATTRIBUTES))
529 {
530 read_velocities(mesh, motionSampleTime);
531 read_custom_data(settings, mesh, motionSampleTime, new_mesh);
532 }
533}
534
535void USDMeshReader::read_custom_data(const ImportSettings *settings,
536 Mesh *mesh,
537 const double motionSampleTime,
538 const bool new_mesh)
539{
540 if (!(mesh && mesh_prim_ && mesh->corners_num > 0)) {
541 return;
542 }
543
544 pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(mesh_prim_);
545 std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
546
547 pxr::TfToken active_color_name;
548 pxr::TfToken active_uv_set_name;
549
550 /* Convert primvars to custom layer data. */
551 for (pxr::UsdGeomPrimvar &pv : primvars) {
552 if (!pv.HasValue()) {
555 "Skipping primvar %s, mesh %s -- no value",
556 pv.GetName().GetText(),
557 &mesh->id.name[2]);
558 continue;
559 }
560
561 if (!pv.GetAttr().GetTypeName().IsArray()) {
562 /* Non-array attributes are technically improper USD. */
563 continue;
564 }
565
566 const pxr::SdfValueTypeName type = pv.GetTypeName();
567 const pxr::TfToken varying_type = pv.GetInterpolation();
568 const pxr::TfToken name = pxr::UsdGeomPrimvar::StripPrimvarsName(pv.GetPrimvarName());
569
570 /* To avoid unnecessarily reloading static primvars during animation,
571 * early out if not first load and this primvar isn't animated. */
572 if (!new_mesh && primvar_varying_map_.contains(name) && !primvar_varying_map_.lookup(name)) {
573 continue;
574 }
575
576 /* We handle the non-standard primvar:velocity elsewhere. */
577 if (ELEM(name, "velocity")) {
578 continue;
579 }
580
581 if (ELEM(type,
582 pxr::SdfValueTypeNames->StringArray,
583 pxr::SdfValueTypeNames->QuatfArray,
584 pxr::SdfValueTypeNames->QuatdArray,
585 pxr::SdfValueTypeNames->QuathArray))
586 {
587 /* Skip creating known unsupported types, and avoid noisy error prints. */
588 continue;
589 }
590
591 /* Read Color primvars. */
593 if ((settings->read_flag & MOD_MESHSEQ_READ_COLOR) != 0) {
594 /* Set the active color name to 'displayColor', if a color primvar
595 * with this name exists. Otherwise, use the name of the first
596 * color primvar we find for the active color. */
597 if (active_color_name.IsEmpty() || name == usdtokens::displayColor) {
598 active_color_name = name;
599 }
600
601 read_generic_mesh_primvar(mesh, pv, motionSampleTime, is_left_handed_);
602 }
603 }
604
605 /* Read UV primvars. */
606 else if (ELEM(varying_type,
607 pxr::UsdGeomTokens->vertex,
608 pxr::UsdGeomTokens->faceVarying,
609 pxr::UsdGeomTokens->varying) &&
611 {
612 if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) {
613 /* Set the active uv set name to 'st', if a uv set primvar
614 * with this name exists. Otherwise, use the name of the first
615 * uv set primvar we find for the active uv set. */
616 if (active_uv_set_name.IsEmpty() || name == usdtokens::st) {
617 active_uv_set_name = name;
618 }
619 this->read_uv_data_primvar(mesh, pv, motionSampleTime);
620 }
621 }
622
623 /* Read all other primvars. */
624 else {
625 if ((settings->read_flag & MOD_MESHSEQ_READ_ATTRIBUTES) != 0) {
626 read_generic_mesh_primvar(mesh, pv, motionSampleTime, is_left_handed_);
627 }
628 }
629
630 /* Record whether the primvar attribute might be time varying. */
631 if (!primvar_varying_map_.contains(name)) {
632 bool might_be_time_varying = pv.ValueMightBeTimeVarying();
633 primvar_varying_map_.add(name, might_be_time_varying);
634 if (might_be_time_varying) {
635 is_time_varying_ = true;
636 }
637 }
638 } /* End primvar attribute loop. */
639
640 if (!active_color_name.IsEmpty()) {
641 BKE_id_attributes_default_color_set(&mesh->id, active_color_name.GetText());
642 BKE_id_attributes_active_color_set(&mesh->id, active_color_name.GetText());
643 }
644
645 if (!active_uv_set_name.IsEmpty()) {
646 int layer_index = CustomData_get_named_layer_index(
647 &mesh->corner_data, CD_PROP_FLOAT2, active_uv_set_name.GetText());
648 if (layer_index > -1) {
649 CustomData_set_layer_active_index(&mesh->corner_data, CD_PROP_FLOAT2, layer_index);
650 CustomData_set_layer_render_index(&mesh->corner_data, CD_PROP_FLOAT2, layer_index);
651 }
652 }
653}
654
655void USDMeshReader::assign_facesets_to_material_indices(double motionSampleTime,
656 MutableSpan<int> material_indices,
658{
659 if (r_mat_map == nullptr) {
660 return;
661 }
662
663 /* Find the geom subsets that have bound materials.
664 * We don't call #pxr::UsdShadeMaterialBindingAPI::GetMaterialBindSubsets()
665 * because this function returns only those subsets that are in the 'materialBind'
666 * family, but, in practice, applications (like Houdini) might export subsets
667 * in different families that are bound to materials.
668 * TODO(makowalski): Reassess if the above is the best approach. */
669 const std::vector<pxr::UsdGeomSubset> subsets = pxr::UsdGeomSubset::GetAllGeomSubsets(
670 mesh_prim_);
671
672 int current_mat = 0;
673 if (!subsets.empty()) {
674 for (const pxr::UsdGeomSubset &subset : subsets) {
675 pxr::UsdPrim subset_prim = subset.GetPrim();
676 pxr::UsdShadeMaterial subset_mtl = utils::compute_bound_material(subset_prim,
678 if (!subset_mtl) {
679 continue;
680 }
681
682 pxr::SdfPath subset_mtl_path = subset_mtl.GetPath();
683 if (subset_mtl_path.IsEmpty()) {
684 continue;
685 }
686
687 pxr::TfToken element_type;
688 subset.GetElementTypeAttr().Get(&element_type, motionSampleTime);
689 if (element_type != pxr::UsdGeomTokens->face) {
690 CLOG_WARN(&LOG,
691 "UsdGeomSubset '%s' uses unsupported elementType: %s",
692 subset_prim.GetName().GetText(),
693 element_type.GetText());
694 continue;
695 }
696
697 const int mat_idx = r_mat_map->lookup_or_add(subset_mtl_path, 1 + current_mat++);
698 const int max_element_idx = std::max(0, int(material_indices.size() - 1));
699
700 pxr::VtIntArray indices;
701 subset.GetIndicesAttr().Get(&indices, motionSampleTime);
702
703 int bad_element_count = 0;
704 for (const int element_idx : indices) {
705 const int safe_element_idx = std::clamp(element_idx, 0, max_element_idx);
706 bad_element_count += (safe_element_idx != element_idx) ? 1 : 0;
707 material_indices[safe_element_idx] = mat_idx - 1;
708 }
709
710 if (bad_element_count > 0) {
711 CLOG_WARN(&LOG,
712 "UsdGeomSubset '%s' contains invalid indices; material assignment may be "
713 "incorrect (%d were out of range)",
714 subset_prim.GetName().GetText(),
715 bad_element_count);
716 }
717 }
718 }
719
720 if (r_mat_map->is_empty()) {
722 if (mtl) {
723 pxr::SdfPath mtl_path = mtl.GetPath();
724
725 if (!mtl_path.IsEmpty()) {
726 r_mat_map->add(mtl.GetPath(), 1);
727 }
728 }
729 }
730}
731
732void USDMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const double motionSampleTime)
733{
735 return;
736 }
737
739
740 bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
741 bke::SpanAttributeWriter<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
742 "material_index", bke::AttrDomain::Face);
743 this->assign_facesets_to_material_indices(motionSampleTime, material_indices.span, &mat_map);
744 material_indices.finish();
745 /* Build material name map if it's not built yet. */
746 if (this->settings_->mat_name_to_mat.is_empty()) {
748 }
750 object_,
751 mat_map,
752 this->import_params_,
753 this->prim_.GetStage(),
754 this->settings_->mat_name_to_mat,
755 this->settings_->usd_path_to_mat_name);
756}
757
758Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
760 const char ** /*r_err_str*/)
761{
762 if (!mesh_prim_) {
763 return existing_mesh;
764 }
765
766 mesh_prim_.GetOrientationAttr().Get(&orientation_);
767 if (orientation_ == pxr::UsdGeomTokens->leftHanded) {
768 is_left_handed_ = true;
769 }
770
771 Mesh *active_mesh = existing_mesh;
772 bool new_mesh = false;
773
774 /* TODO(makowalski): implement the optimization of only updating the mesh points when
775 * the topology is consistent, as in the Alembic importer. */
776
777 ImportSettings settings;
778 if (settings_) {
779 settings.validate_meshes = settings_->validate_meshes;
780 }
781 settings.read_flag |= params.read_flags;
782
783 if (topology_changed(existing_mesh, params.motion_sample_time)) {
784 new_mesh = true;
786 existing_mesh, positions_.size(), 0, face_counts_.size(), face_indices_.size());
787 }
788
789 read_mesh_sample(
790 &settings, active_mesh, params.motion_sample_time, new_mesh || is_initial_load_);
791
792 if (new_mesh) {
793 /* Here we assume that the number of materials doesn't change, i.e. that
794 * the material slots that were created when the object was loaded from
795 * USD are still valid now. */
796 if (active_mesh->faces_num != 0 && import_params_.import_materials) {
798 bke::MutableAttributeAccessor attributes = active_mesh->attributes_for_write();
799 bke::SpanAttributeWriter<int> material_indices =
800 attributes.lookup_or_add_for_write_span<int>("material_index", bke::AttrDomain::Face);
801 assign_facesets_to_material_indices(
802 params.motion_sample_time, material_indices.span, &mat_map);
803 material_indices.finish();
804 }
805 }
806
807 if (settings.validate_meshes) {
808 if (BKE_mesh_validate(active_mesh, false, false)) {
809 BKE_reportf(reports(), RPT_INFO, "Fixed mesh for prim: %s", mesh_prim_.GetPath().GetText());
810 }
811 }
812
813 return active_mesh;
814}
815
818 const char **r_err_str)
819{
820 Mesh *existing_mesh = geometry_set.get_mesh_for_write();
821 Mesh *new_mesh = read_mesh(existing_mesh, params, r_err_str);
822
823 if (new_mesh != existing_mesh) {
824 geometry_set.replace_mesh(new_mesh);
825 }
826}
827
829{
830 /* Make sure we can apply UsdSkelBindingAPI to the prim.
831 * Attempting to apply the API to instance proxies generates
832 * a USD error. */
833 if (!prim_ || prim_.IsInstanceProxy()) {
834 return "";
835 }
836
837 pxr::UsdSkelBindingAPI skel_api(prim_);
838
839 if (pxr::UsdSkelSkeleton skel = skel_api.GetInheritedSkeleton()) {
840 return skel.GetPath().GetAsString();
841 }
842
843 return "";
844}
845
846std::optional<XformResult> USDMeshReader::get_local_usd_xform(const float time) const
847{
848 if (!import_params_.import_skeletons || prim_.IsInstanceProxy()) {
849 /* Use the standard transform computation, since we are ignoring
850 * skinning data. Note that applying the UsdSkelBinding API to an
851 * instance proxy generates a USD error. */
853 }
854
855 pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI(prim_);
856 if (pxr::UsdAttribute xf_attr = skel_api.GetGeomBindTransformAttr()) {
857 if (xf_attr.HasAuthoredValue()) {
858 pxr::GfMatrix4d bind_xf;
859 if (skel_api.GetGeomBindTransformAttr().Get(&bind_xf)) {
860 /* The USD bind transform is a matrix of doubles,
861 * but we cast it to GfMatrix4f because Blender expects
862 * a matrix of floats. Also, we assume the transform
863 * is constant over time. */
864 return XformResult(pxr::GfMatrix4f(bind_xf), true);
865 }
866 else {
869 "%s: Couldn't compute geom bind transform for %s",
870 __func__,
871 prim_.GetPath().GetAsString().c_str());
872 }
873 }
874 }
875
877}
878
879} // namespace blender::io::usd
void BKE_id_attributes_default_color_set(struct ID *id, const char *name)
Definition attribute.cc:994
void BKE_id_attributes_active_color_set(struct ID *id, const char *name)
Definition attribute.cc:965
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(struct Main *bmain, struct Object *ob, struct Material *ma, short act)
void BKE_mesh_set_custom_normals(Mesh *mesh, float(*r_custom_loop_normals)[3])
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)
void BKE_mesh_set_custom_normals_from_verts(Mesh *mesh, float(*r_custom_vert_normals)[3])
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:87
#define BLI_assert(a)
Definition BLI_assert.h:50
#define ELEM(...)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
@ CD_PROP_COLOR
@ CD_PROP_FLOAT2
#define MAXMAT
@ MOD_MESHSEQ_READ_COLOR
@ MOD_MESHSEQ_READ_VERT
@ MOD_MESHSEQ_READ_ATTRIBUTES
@ MOD_MESHSEQ_READ_UV
@ MOD_MESHSEQ_READ_POLY
Object is a sort of wrapper for general info.
@ OB_MESH
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t start() const
Value & lookup_or_add_default(const Key &key)
Definition BLI_map.hh:601
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:271
const Value & lookup(const Key &key) const
Definition BLI_map.hh:506
int64_t size() const
Definition BLI_map.hh:927
ItemIterator items() const
Definition BLI_map.hh:864
bool is_empty() const
Definition BLI_map.hh:937
bool contains(const Key &key) const
Definition BLI_map.hh:329
Value & lookup_or_add(const Key &key, const Value &value)
Definition BLI_map.hh:551
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:726
Span< NewT > constexpr cast() const
Definition BLI_span.hh:419
Material * add_material(const pxr::UsdShadeMaterial &usd_material) const
void read_object_data(Main *bmain, double motionSampleTime) override
void create_object(Main *bmain, double motionSampleTime) override
USDMeshReader(const pxr::UsdPrim &prim, const USDImportParams &import_params, const ImportSettings &settings)
std::string get_skeleton_path() const
void read_geometry(bke::GeometrySet &geometry_set, USDMeshReadParams params, const char **r_err_str) override
bool topology_changed(const Mesh *existing_mesh, double motionSampleTime) override
const ImportSettings * settings_
const USDImportParams & import_params_
const std::string & name() const
void read_object_data(Main *bmain, double motionSampleTime) override
virtual std::optional< XformResult > get_local_usd_xform(float time) const
EvaluationStage stage
Definition deg_eval.cc:83
static ushort indices[]
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
#define LOG(severity)
Definition log.h:33
void mesh_calc_edges(Mesh &mesh, bool keep_existing_edges, bool select_new_edges)
To convert_value(const From value)
void build_material_map(const Main *bmain, blender::Map< std::string, Material * > *r_mat_map)
pxr::VtArray< T > get_primvar_array(const pxr::UsdGeomPrimvar &primvar, const pxr::UsdTimeCode timecode)
std::string make_safe_name(const std::string &name, bool allow_unicode)
Definition usd_utils.cc:16
Material * find_existing_material(const pxr::SdfPath &usd_mat_path, const USDImportParams &params, const blender::Map< std::string, Material * > &mat_map, const blender::Map< std::string, std::string > &usd_path_to_mat_name)
USDMeshReadParams create_mesh_read_params(const double motion_sample_time, const int read_flags)
std::tuple< pxr::GfMatrix4f, bool > XformResult
@ USD_MTL_PURPOSE_FULL
Definition usd.hh:46
@ USD_MTL_PURPOSE_ALL
Definition usd.hh:44
@ USD_MTL_PURPOSE_PREVIEW
Definition usd.hh:45
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
void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, ReportList *reports)
VecBase< float, 2 > float2
static const pxr::TfToken st("st", pxr::TfToken::Immortal)
static const pxr::TfToken UVMap("UVMap", pxr::TfToken::Immortal)
static const pxr::TfToken normalsPrimvar("normals", pxr::TfToken::Immortal)
const pxr::TfToken displayColor("displayColor", pxr::TfToken::Immortal)
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 blender::io::usd::USDImportParams &params, pxr::UsdStageRefPtr stage, blender::Map< std::string, Material * > &mat_name_to_mat, blender::Map< std::string, std::string > &usd_path_to_mat_name)
char name[66]
Definition DNA_ID.h:425
int corners_num
int faces_num
int verts_num
void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
blender::Map< std::string, Material * > mat_name_to_mat
static CLG_LogRef LOG