Blender V5.0
usd_reader_stage.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021 Tangent Animation and. NVIDIA Corporation. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "usd_reader_stage.hh"
6
7#include "usd_hook.hh"
9#include "usd_reader_curve.hh"
11#include "usd_reader_light.hh"
13#include "usd_reader_mesh.hh"
14#include "usd_reader_nurbs.hh"
16#include "usd_reader_points.hh"
17#include "usd_reader_prim.hh"
18#include "usd_reader_shape.hh"
20#include "usd_reader_volume.hh"
21#include "usd_reader_xform.hh"
22#include "usd_utils.hh"
23
24#include <pxr/usd/usd/primRange.h>
25#include <pxr/usd/usdGeom/camera.h>
26#include <pxr/usd/usdGeom/capsule.h>
27#include <pxr/usd/usdGeom/capsule_1.h>
28#include <pxr/usd/usdGeom/cone.h>
29#include <pxr/usd/usdGeom/cube.h>
30#include <pxr/usd/usdGeom/cylinder.h>
31#include <pxr/usd/usdGeom/cylinder_1.h>
32#include <pxr/usd/usdGeom/mesh.h>
33#include <pxr/usd/usdGeom/metrics.h>
34#include <pxr/usd/usdGeom/nurbsCurves.h>
35#include <pxr/usd/usdGeom/plane.h>
36#include <pxr/usd/usdGeom/pointInstancer.h>
37#include <pxr/usd/usdGeom/points.h>
38#include <pxr/usd/usdGeom/scope.h>
39#include <pxr/usd/usdGeom/sphere.h>
40#include <pxr/usd/usdGeom/tokens.h>
41#include <pxr/usd/usdGeom/xform.h>
42#include <pxr/usd/usdLux/boundableLightBase.h>
43#include <pxr/usd/usdLux/domeLight.h>
44#include <pxr/usd/usdLux/domeLight_1.h>
45#include <pxr/usd/usdLux/nonboundableLightBase.h>
46#include <pxr/usd/usdShade/material.h>
47
48#include "BLI_map.hh"
49#include "BLI_math_base.h"
50#include "BLI_math_matrix.h"
51#include "BLI_math_rotation.h"
52#include "BLI_sort.hh"
53#include "BLI_string.h"
54
55#include "BKE_collection.hh"
56#include "BKE_lib_id.hh"
57#include "BKE_modifier.hh"
58#include "BKE_report.hh"
59
60#include "CLG_log.h"
61
63#include "DNA_material_types.h"
64
65#include <fmt/core.h>
66
67static CLG_LogRef LOG = {"io.usd"};
68
69namespace blender::io::usd {
70
71static void decref(USDPrimReader *reader)
72{
73 if (!reader) {
74 return;
75 }
76
77 reader->decref();
78
79 if (reader->refcount() == 0) {
80 delete reader;
81 }
82}
83
87static Collection *create_collection(Main *bmain, Collection *parent, const char *name)
88{
89 if (!bmain) {
90 return nullptr;
91 }
92
93 return BKE_collection_add(bmain, parent, name);
94}
95
102 USDInstanceReader *instance_reader,
103 const blender::Map<pxr::SdfPath, Collection *> &proto_collection_map)
104{
105 if (!instance_reader) {
106 return;
107 }
108
109 pxr::SdfPath proto_path = instance_reader->proto_path();
110
111 Collection *collection = proto_collection_map.lookup_default(proto_path, nullptr);
112 if (collection != nullptr) {
113 instance_reader->set_instance_collection(collection);
114 }
115 else {
116 CLOG_WARN(&LOG,
117 "Couldn't find prototype collection for %s",
118 instance_reader->prim_path().GetAsString().c_str());
119 }
120}
121
122/* Update the given import settings with the global rotation matrix to orient
123 * imported objects with Z-up, if necessary */
124static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings &settings)
125{
126 if (!stage || pxr::UsdGeomGetStageUpAxis(stage) == pxr::UsdGeomTokens->z) {
127 return;
128 }
129
130 settings.do_convert_mat = true;
131
132 /* Rotate 90 degrees about the X-axis. */
133 float rmat[3][3];
134 float axis[3] = {1.0f, 0.0f, 0.0f};
136
137 unit_m4(settings.conversion_mat);
138 copy_m4_m3(settings.conversion_mat, rmat);
139}
140
145static void find_prefix_to_skip(pxr::UsdStageRefPtr stage, ImportSettings &settings)
146{
147 if (!stage) {
148 return;
149 }
150
151 pxr::TfToken generated_key("Blender:generated");
152 pxr::SdfPath path("/");
153 auto prim = stage->GetPseudoRoot();
154 while (true) {
155
156 uint32_t child_count = 0;
157 for (auto child : prim.GetChildren()) {
158 if (child_count == 0) {
159 prim = child.GetPrim();
160 }
161 ++child_count;
162 }
163
164 if (child_count != 1) {
165 /* Our blender write out only supports a single root chain,
166 * so whenever we encounter more than one child, we should
167 * early exit */
168 break;
169 }
170
171 /* We only care about prims that have the key and the value doesn't matter */
172 if (!prim.HasCustomDataKey(generated_key)) {
173 break;
174 }
175 path = path.AppendChild(prim.GetName());
176 }
177
178 /* Treat the root as empty */
179 if (path == pxr::SdfPath("/")) {
180 path = pxr::SdfPath();
181 }
182
183 settings.skip_prefix = path;
184}
185
189static void determine_blender_compat(pxr::UsdStageRefPtr stage, ImportSettings &settings)
190{
191 const std::string doc = stage->GetRootLayer()->GetDocumentation();
192
193 /* Was the incoming Stage written by Blender? If so, set some broad compatibility flags. */
194 if (doc.find("Blender v", 0) == 0) {
195 /* Set flag if the Blender Stage was from before version 4.4. */
196 settings.blender_stage_version_prior_44 = doc < "Blender v4.4";
197 }
198}
199
201 const USDImportParams &params,
202 const std::function<CacheFile *()> &get_cache_file_fn)
204{
208 settings_.get_cache_file = get_cache_file_fn;
209 settings_.stage_meters_per_unit = pxr::UsdGeomGetStageMetersPerUnit(stage);
210 settings_.scene_scale = params.scale;
211 if (params.apply_unit_conversion_scale) {
212 settings_.scene_scale *= settings_.stage_meters_per_unit;
213 }
214}
215
220
222{
223 return stage_;
224}
225
226bool USDStageReader::is_primitive_prim(const pxr::UsdPrim &prim) const
227{
228 return (prim.IsA<pxr::UsdGeomCapsule>() || prim.IsA<pxr::UsdGeomCapsule_1>() ||
229 prim.IsA<pxr::UsdGeomCylinder>() || prim.IsA<pxr::UsdGeomCylinder_1>() ||
230 prim.IsA<pxr::UsdGeomCone>() || prim.IsA<pxr::UsdGeomCube>() ||
231 prim.IsA<pxr::UsdGeomSphere>() || prim.IsA<pxr::UsdGeomPlane>());
232}
233
235{
236 if (params_.support_scene_instancing && prim.IsInstance()) {
237 return new USDInstanceReader(prim, params_, settings_);
238 }
239 if (params_.import_shapes && is_primitive_prim(prim)) {
240 return new USDShapeReader(prim, params_, settings_);
241 }
242 if (prim.IsA<pxr::UsdGeomPointInstancer>()) {
243 return new USDPointInstancerReader(prim, params_, settings_);
244 }
245 if (params_.import_cameras && prim.IsA<pxr::UsdGeomCamera>()) {
246 return new USDCameraReader(prim, params_, settings_);
247 }
248 if (params_.import_curves && prim.IsA<pxr::UsdGeomBasisCurves>()) {
249 return new USDBasisCurvesReader(prim, params_, settings_);
250 }
251 if (params_.import_curves && prim.IsA<pxr::UsdGeomNurbsCurves>()) {
252 return new USDNurbsReader(prim, params_, settings_);
253 }
254 if (params_.import_meshes && prim.IsA<pxr::UsdGeomMesh>()) {
255 return new USDMeshReader(prim, params_, settings_);
256 }
257 if (params_.import_lights &&
258 (prim.IsA<pxr::UsdLuxDomeLight>() || prim.IsA<pxr::UsdLuxDomeLight_1>()))
259 {
260 /* Dome lights are handled elsewhere. */
261 return nullptr;
262 }
263 if (params_.import_lights &&
264 (prim.IsA<pxr::UsdLuxBoundableLightBase>() || prim.IsA<pxr::UsdLuxNonboundableLightBase>()))
265 {
266 return new USDLightReader(prim, params_, settings_);
267 }
268 if (params_.import_volumes && prim.IsA<pxr::UsdVolVolume>()) {
269 return new USDVolumeReader(prim, params_, settings_);
270 }
271 if (params_.import_skeletons && prim.IsA<pxr::UsdSkelSkeleton>()) {
272 return new USDSkeletonReader(prim, params_, settings_);
273 }
274 if (params_.import_points && prim.IsA<pxr::UsdGeomPoints>()) {
275 return new USDPointsReader(prim, params_, settings_);
276 }
277 if (prim.IsA<pxr::UsdGeomImageable>()) {
278 return new USDXformReader(prim, params_, settings_);
279 }
280
281 return nullptr;
282}
283
285{
286 if (params_.support_scene_instancing && prim.IsInstance()) {
287 return new USDInstanceReader(prim, params_, settings_);
288 }
289 if (is_primitive_prim(prim)) {
290 return new USDShapeReader(prim, params_, settings_);
291 }
292 if (prim.IsA<pxr::UsdGeomCamera>()) {
293 return new USDCameraReader(prim, params_, settings_);
294 }
295 if (prim.IsA<pxr::UsdGeomBasisCurves>()) {
296 return new USDBasisCurvesReader(prim, params_, settings_);
297 }
298 if (prim.IsA<pxr::UsdGeomNurbsCurves>()) {
299 return new USDNurbsReader(prim, params_, settings_);
300 }
301 if (prim.IsA<pxr::UsdGeomMesh>()) {
302 return new USDMeshReader(prim, params_, settings_);
303 }
304 if (prim.IsA<pxr::UsdLuxDomeLight>() || prim.IsA<pxr::UsdLuxDomeLight_1>()) {
305 /* We don't handle dome lights. */
306 return nullptr;
307 }
308 if (prim.IsA<pxr::UsdLuxBoundableLightBase>() || prim.IsA<pxr::UsdLuxNonboundableLightBase>()) {
309 return new USDLightReader(prim, params_, settings_);
310 }
311 if (prim.IsA<pxr::UsdVolVolume>()) {
312 return new USDVolumeReader(prim, params_, settings_);
313 }
314 if (prim.IsA<pxr::UsdSkelSkeleton>()) {
315 return new USDSkeletonReader(prim, params_, settings_);
316 }
317 if (prim.IsA<pxr::UsdGeomPoints>()) {
318 return new USDPointsReader(prim, params_, settings_);
319 }
320 if (prim.IsA<pxr::UsdGeomPointInstancer>()) {
321 return new USDPointInstancerReader(prim, params_, settings_);
322 }
323 if (prim.IsA<pxr::UsdGeomImageable>()) {
324 return new USDXformReader(prim, params_, settings_);
325 }
326 return nullptr;
327}
328
329bool USDStageReader::include_by_visibility(const pxr::UsdGeomImageable &imageable) const
330{
332 /* Invisible prims are allowed. */
333 return true;
334 }
335
336 pxr::UsdAttribute visibility_attr = imageable.GetVisibilityAttr();
337
338 if (!visibility_attr) {
339 /* No visibility attribute, so allow. */
340 return true;
341 }
342
343 /* Include if the prim has an animating visibility attribute or is not invisible. */
344
345 if (visibility_attr.ValueMightBeTimeVarying()) {
346 return true;
347 }
348
349 pxr::TfToken visibility;
350 visibility_attr.Get(&visibility);
351 return visibility != pxr::UsdGeomTokens->invisible;
352}
353
354bool USDStageReader::include_by_purpose(const pxr::UsdGeomImageable &imageable) const
355{
356 if (params_.import_skeletons && imageable.GetPrim().IsA<pxr::UsdSkelSkeleton>()) {
357 /* Always include skeletons, if requested by the user, regardless of purpose. */
358 return true;
359 }
360
361 if (params_.import_guide && params_.import_proxy && params_.import_render) {
362 /* The options allow any purpose, so we trivially include the prim. */
363 return true;
364 }
365
366 pxr::UsdAttribute purpose_attr = imageable.GetPurposeAttr();
367
368 if (!purpose_attr) {
369 /* No purpose attribute, so trivially include the prim. */
370 return true;
371 }
372
373 pxr::TfToken purpose;
374 purpose_attr.Get(&purpose);
375
376 if (purpose == pxr::UsdGeomTokens->guide) {
377 return params_.import_guide;
378 }
379 if (purpose == pxr::UsdGeomTokens->proxy) {
380 return params_.import_proxy;
381 }
382 if (purpose == pxr::UsdGeomTokens->render) {
383 return params_.import_render;
384 }
385
386 return true;
387}
388
389bool USDStageReader::merge_with_parent(USDPrimReader *reader) const
390{
391 /* Don't merge if the param is set to false */
392 if (!params_.merge_parent_xform) {
393 return false;
394 }
395
396 USDXformReader *xform_reader = dynamic_cast<USDXformReader *>(reader);
397
398 if (!xform_reader) {
399 return false;
400 }
401
402 /* Check if the Xform reader is already merged. */
403 if (xform_reader->use_parent_xform()) {
404 return false;
405 }
406
407 /* Only merge if the parent is an Xform. */
408 if (!xform_reader->prim().GetParent().IsA<pxr::UsdGeomXform>()) {
409 return false;
410 }
411
412 /* Don't merge Xform and Scope prims. */
413 if (xform_reader->prim().IsA<pxr::UsdGeomXform>() ||
414 xform_reader->prim().IsA<pxr::UsdGeomScope>())
415 {
416 return false;
417 }
418
419 /* Don't merge if the prim has authored transform ops. */
420 if (xform_reader->prim_has_xform_ops()) {
421 return false;
422 }
423
424 /* Flag the Xform reader as merged. */
425 xform_reader->set_use_parent_xform(true);
426
427 return true;
428}
429
430USDPrimReader *USDStageReader::collect_readers(const pxr::UsdPrim &prim,
431 const UsdPathSet &pruned_prims,
432 const bool defined_prims_only,
433 blender::Vector<USDPrimReader *> &r_readers)
434{
435 if (prim.IsA<pxr::UsdGeomImageable>()) {
436 pxr::UsdGeomImageable imageable(prim);
437
438 if (!include_by_purpose(imageable)) {
439 return nullptr;
440 }
441
442 if (!include_by_visibility(imageable)) {
443 return nullptr;
444 }
445 }
446
447 if (prim.IsA<pxr::UsdLuxDomeLight>() || prim.IsA<pxr::UsdLuxDomeLight_1>()) {
448 USDDomeLightReader *reader = new USDDomeLightReader(prim, params_, settings_);
449 reader->incref();
450 dome_light_readers_.append(reader);
451 }
452
453 pxr::Usd_PrimFlagsConjunction filter_flags = pxr::UsdPrimIsActive && pxr::UsdPrimIsLoaded &&
454 !pxr::UsdPrimIsAbstract;
455
456 if (defined_prims_only) {
457 filter_flags &= pxr::UsdPrimIsDefined;
458 }
459
460 pxr::Usd_PrimFlagsPredicate filter_predicate(filter_flags);
461 if (!params_.support_scene_instancing) {
462 filter_predicate = pxr::UsdTraverseInstanceProxies(filter_predicate);
463 }
464
465 blender::Vector<USDPrimReader *> child_readers;
466
467 pxr::UsdPrimSiblingRange children = prim.GetFilteredChildren(filter_predicate);
468
469 for (const auto &child_prim : children) {
470 if (pruned_prims.contains(child_prim.GetPath())) {
471 continue;
472 }
473 if (USDPrimReader *child_reader = collect_readers(
474 child_prim, pruned_prims, defined_prims_only, r_readers))
475 {
476 child_readers.append(child_reader);
477 }
478 }
479
480 if (prim.IsPseudoRoot()) {
481 return nullptr;
482 }
483
484 /* If we find prims that have been auto generated by Blender, we skip them on import
485 * so that the imported scene can closely match the exported scene */
486 if (!settings_.skip_prefix.IsEmpty()) {
487 if (settings_.skip_prefix.HasPrefix(prim.GetPath())) {
488 return nullptr;
489 }
490 }
491
492 /* Check if we can merge an Xform with its child prim. */
493 if (child_readers.size() == 1) {
494
495 USDPrimReader *child_reader = child_readers.first();
496
497 if (merge_with_parent(child_reader)) {
498 return child_reader;
499 }
500 }
501
502 if (prim.IsA<pxr::UsdShadeMaterial>()) {
503 /* Record material path for later processing, if needed,
504 * e.g., when importing all materials. */
505 material_paths_.append(prim.GetPath());
506
507 /* We don't create readers for materials, so return early. */
508 return nullptr;
509 }
510
511 USDPrimReader *reader = create_reader_if_allowed(prim);
512
513 if (!reader) {
514 return nullptr;
515 }
516 if (!reader->valid()) {
517 return nullptr;
518 }
519
520 r_readers.append(reader);
521 reader->incref();
522
523 /* Set each child reader's parent. */
524 for (USDPrimReader *child_reader : child_readers) {
525 child_reader->parent(reader);
526 }
527
528 return reader;
529}
530
532{
533 if (!valid()) {
534 return;
535 }
536
538
539 /* Identify paths to point instancer prototypes, as these will be converted
540 * in a separate pass over the stage. */
541 UsdPathSet instancer_proto_paths = collect_point_instancer_proto_paths();
542
543 /* Iterate through the stage. */
544 pxr::UsdPrim root = stage_->GetPseudoRoot();
545
546 stage_->SetInterpolationType(pxr::UsdInterpolationType::UsdInterpolationTypeHeld);
547
548 /* Create readers, skipping over prototype prims in this pass. */
549 collect_readers(root, instancer_proto_paths, params_.import_defined_only, readers_);
550
551 if (params_.support_scene_instancing) {
552 /* Collect the scene-graph instance prototypes. */
553 std::vector<pxr::UsdPrim> protos = stage_->GetPrototypes();
554
555 for (const pxr::UsdPrim &proto_prim : protos) {
557 collect_readers(proto_prim, instancer_proto_paths, true, proto_readers);
558 proto_readers_.add(proto_prim.GetPath(), proto_readers);
559
560 for (USDPrimReader *reader : proto_readers) {
561 readers_.append(reader);
562 reader->incref();
563 }
564 }
565 }
566
567 if (!instancer_proto_paths.is_empty()) {
568 create_point_instancer_proto_readers(instancer_proto_paths);
569 }
570}
571
573{
574 /* Iterate over the skeleton readers to create the
575 * armature object map, which maps a USD skeleton prim
576 * path to the corresponding armature object. */
577 blender::Map<pxr::SdfPath, Object *> usd_path_to_armature;
578 for (const USDPrimReader *reader : readers_) {
579 if (dynamic_cast<const USDSkeletonReader *>(reader) && reader->object()) {
580 usd_path_to_armature.add(reader->prim_path(), reader->object());
581 }
582 }
583
584 /* Iterate over the mesh readers and set armature objects on armature modifiers. */
585 for (const USDPrimReader *reader : readers_) {
586 if (!reader->object()) {
587 continue;
588 }
589 const USDMeshReader *mesh_reader = dynamic_cast<const USDMeshReader *>(reader);
590 if (!mesh_reader) {
591 continue;
592 }
593 /* Check if the mesh object has an armature modifier. */
595 if (!md) {
596 continue;
597 }
598
599 ArmatureModifierData *amd = reinterpret_cast<ArmatureModifierData *>(md);
600
601 /* Assign the armature based on the bound USD skeleton path of the skinned mesh. */
602 pxr::SdfPath skel_path = mesh_reader->get_skeleton_path();
603 Object *object = usd_path_to_armature.lookup_default(skel_path, nullptr);
604 if (object == nullptr) {
607 "%s: Couldn't find armature object corresponding to USD skeleton %s",
608 __func__,
609 skel_path.GetAsString().c_str());
610 }
611 amd->object = object;
612 }
613}
614
616{
617 BLI_assert(valid());
618
619 /* Build the material name map if it's not built yet. */
620 if (settings_.mat_name_to_mat.is_empty()) {
621 build_material_map(bmain, settings_.mat_name_to_mat);
622 }
623
624 USDMaterialReader mtl_reader(params_, *bmain);
625 for (const pxr::SdfPath &mtl_path : material_paths_) {
626 pxr::UsdPrim prim = stage_->GetPrimAtPath(mtl_path);
627
628 pxr::UsdShadeMaterial usd_mtl(prim);
629 if (!usd_mtl) {
630 continue;
631 }
632
634 prim.GetPath(), params_, settings_.mat_name_to_mat, settings_.usd_path_to_mat))
635 {
636 /* The material already exists. */
637 continue;
638 }
639
640 /* Can the material be handled by an import hook? */
641 const bool have_import_hook = settings_.mat_import_hook_sources.contains(mtl_path);
642
643 /* Add the Blender material. If we have an import hook which can handle this material
644 * we don't import USD Preview Surface shaders. */
645 Material *new_mtl = mtl_reader.add_material(usd_mtl, !have_import_hook);
646 BLI_assert_msg(new_mtl, "Failed to create material");
647
648 settings_.mat_name_to_mat.add_new(new_mtl->id.name + 2, new_mtl);
649
650 if (params_.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MAKE_UNIQUE) {
651 /* Record the Blender material we created for the USD material with the given path.
652 * This is to prevent importing the material again when assigning materials to objects
653 * elsewhere in the code. */
654 settings_.usd_path_to_mat.add_new(mtl_path, new_mtl);
655 }
656
657 if (have_import_hook) {
658 /* Defer invoking the hook to convert the material till we can do so from
659 * the main thread. */
660 settings_.usd_path_to_mat_for_hook.add_new(mtl_path, new_mtl);
661 }
662 }
663}
664
666{
667 /* Iterate over the imported materials and set a fake user for any unused
668 * materials. */
669 for (Material *mat : settings_.usd_path_to_mat.values()) {
670 if (mat->id.us == 0) {
671 id_fake_user_set(&mat->id);
672 }
673 }
674}
675
677{
678 pxr::UsdPrimRange range = stage_->Traverse();
679 for (pxr::UsdPrim prim : range) {
680 if (prim.IsA<pxr::UsdShadeMaterial>()) {
681 pxr::UsdShadeMaterial usd_mat(prim);
683 settings_.mat_import_hook_sources.add(prim.GetPath());
684 }
685 }
686 }
687}
688
690{
691 if (settings_.usd_path_to_mat_for_hook.is_empty()) {
692 /* No materials can be converted by a hook. */
693 return;
694 }
695
696 for (const auto item : settings_.usd_path_to_mat_for_hook.items()) {
697 pxr::UsdPrim prim = stage_->GetPrimAtPath(item.key);
698
699 pxr::UsdShadeMaterial usd_mtl(prim);
700 if (!usd_mtl) {
701 continue;
702 }
703
705 stage_, item.value, usd_mtl, params_, reports());
706
707 if (!success) {
708 /* None of the hooks succeeded, so fall back on importing USD Preview Surface if possible. */
709 CLOG_WARN(&LOG,
710 "USD hook 'on_material_import' for material %s failed, attempting to convert USD "
711 "Preview Surface material",
712 usd_mtl.GetPath().GetAsString().c_str());
713
714 USDMaterialReader mat_reader(this->params_, *bmain);
715 mat_reader.import_usd_preview(item.value, usd_mtl);
716 }
717 }
718}
719
721{
722 for (USDPrimReader *reader : readers_) {
723 decref(reader);
724 }
725 readers_.clear();
726
727 for (const auto item : proto_readers_.items()) {
728 for (USDPrimReader *reader : item.value) {
729 decref(reader);
730 }
731 }
732 proto_readers_.clear();
733
734 for (const auto item : instancer_proto_readers_.items()) {
735 for (USDPrimReader *reader : item.value) {
736 decref(reader);
737 }
738 }
740
742 decref(reader);
743 }
744 dome_light_readers_.clear();
745}
746
748{
750 readers_.begin(), readers_.end(), [](const USDPrimReader *a, const USDPrimReader *b) {
751 const char *na = a ? a->name().c_str() : "";
752 const char *nb = b ? b->name().c_str() : "";
753 return BLI_strcasecmp(na, nb) < 0;
754 });
755}
756
758{
759 if (proto_readers_.is_empty() && instancer_proto_readers_.is_empty()) {
760 return;
761 }
762
763 Collection *all_protos_collection = create_collection(bmain, parent_collection, "prototypes");
764
765 if (all_protos_collection) {
766 all_protos_collection->flag |= COLLECTION_HIDE_VIEWPORT;
767 all_protos_collection->flag |= COLLECTION_HIDE_RENDER;
768 if (parent_collection) {
769 DEG_id_tag_update(&parent_collection->id, ID_RECALC_HIERARCHY);
770 }
771 }
772
773 blender::Map<pxr::SdfPath, Collection *> proto_collection_map;
774
775 for (const pxr::SdfPath &path : proto_readers_.keys()) {
776 Collection *proto_collection = create_collection(bmain, all_protos_collection, "proto");
777
778 proto_collection_map.add(path, proto_collection);
779 }
780
781 /* Set the instance collections on the readers, including the prototype
782 * readers (which are included in readers_), as instancing may be nested. */
783
784 for (USDPrimReader *reader : readers_) {
785 if (USDInstanceReader *instance_reader = dynamic_cast<USDInstanceReader *>(reader)) {
786 set_instance_collection(instance_reader, proto_collection_map);
787 }
788 }
789
790 /* Add the prototype objects to the collections. */
791 for (const auto &item : proto_readers_.items()) {
792 Collection *collection = proto_collection_map.lookup_default(item.key, nullptr);
793 if (collection == nullptr) {
794 CLOG_WARN(&LOG,
795 "Couldn't find collection when adding objects for prototype %s",
796 item.key.GetAsString().c_str());
797 continue;
798 }
799
800 for (const USDPrimReader *reader : item.value) {
801 Object *ob = reader->object();
802
803 if (!ob) {
804 continue;
805 }
806
807 BKE_collection_object_add(bmain, collection, ob);
808 }
809 }
810
811 /* Create collections for the point instancer prototypes. */
812
813 /* For every point instancer reader, create a "prototypes" collection and set it
814 * on the Collection Info node referenced by the geometry nodes modifier created by
815 * the reader. We also create collections containing prototype geometry as children
816 * of the "prototypes" collection. These child collections will be indexed for
817 * instancing by the Instance on Points geometry node.
818 *
819 * Note that the prototype collections will be ordered alphabetically by the Collection
820 * Info node. We must therefore take care to generate collection names that will maintain
821 * the original prototype order, so that the prototype indices will remain valid. We use
822 * the naming convention proto_<index>, where the index suffix may be zero padded (e.g.,
823 * "proto_00", "proto_01", "proto_02", etc.).
824 */
825
826 for (USDPrimReader *reader : readers_) {
827 USDPointInstancerReader *instancer_reader = dynamic_cast<USDPointInstancerReader *>(reader);
828 if (!instancer_reader) {
829 continue;
830 }
831
832 pxr::SdfPathVector proto_paths = instancer_reader->proto_paths();
833 const pxr::SdfPath &instancer_path = reader->prim().GetPath();
834 Collection *instancer_protos_coll = create_collection(
835 bmain, all_protos_collection, instancer_path.GetName().c_str());
836
837 /* Determine the max number of digits we will need for the possibly zero-padded
838 * string representing the prototype index. */
839 const int max_index_digits = integer_digits_i(proto_paths.size());
840
841 int proto_index = 0;
842
843 for (const pxr::SdfPath &proto_path : proto_paths) {
844 BLI_assert(max_index_digits > 0);
845
846 /* Format the collection name to follow the proto_<index> pattern. */
847 std::string coll_name = fmt::format("proto_{0:0{1}}", proto_index, max_index_digits);
848
849 /* Create the collection and populate it with the prototype objects. */
850 Collection *proto_coll = create_collection(bmain, instancer_protos_coll, coll_name.c_str());
851 blender::Vector<USDPrimReader *> proto_readers = instancer_proto_readers_.lookup_default(
852 proto_path, {});
853 for (const USDPrimReader *proto : proto_readers) {
854 Object *ob = proto->object();
855 if (!ob) {
856 continue;
857 }
858 BKE_collection_object_add(bmain, proto_coll, ob);
859 }
860 ++proto_index;
861 }
862
863 instancer_reader->set_collection(bmain, *instancer_protos_coll);
864 }
865}
866
867void USDStageReader::create_point_instancer_proto_readers(const UsdPathSet &proto_paths)
868{
869 if (proto_paths.is_empty()) {
870 return;
871 }
872
873 for (const pxr::SdfPath &path : proto_paths) {
874
875 pxr::UsdPrim proto_prim = stage_->GetPrimAtPath(path);
876
877 if (!proto_prim) {
878 continue;
879 }
880
881 Vector<USDPrimReader *> proto_readers;
882
883 /* Note that point instancer prototypes may be defined as overs, so
884 * we must call collect readers with argument defined_prims_only = false. */
885 collect_readers(proto_prim, proto_paths, false /* include undefined prims */, proto_readers);
886
887 instancer_proto_readers_.add(path, proto_readers);
888
889 for (USDPrimReader *reader : proto_readers) {
890 reader->set_is_in_instancer_proto(true);
891 readers_.append(reader);
892 reader->incref();
893 }
894 }
895}
896
897void USDStageReader::collect_point_instancer_proto_paths(const pxr::UsdPrim &prim,
898 UsdPathSet &r_paths) const
899{
900 /* Note that we use custom filter flags to allow traversing undefined prims,
901 * because prototype prims may be defined as overs which are skipped by the
902 * default predicate. */
903 pxr::Usd_PrimFlagsConjunction filter_flags = pxr::UsdPrimIsActive && pxr::UsdPrimIsLoaded &&
904 !pxr::UsdPrimIsAbstract;
905
906 pxr::UsdPrimSiblingRange children = prim.GetFilteredChildren(filter_flags);
907
908 for (const auto &child_prim : children) {
909
910 /* Note we allow undefined prims in case prototypes are defined as overs.
911 * If the prim is defined, we apply additional checks for inclusion. */
912 if (child_prim.IsDefined()) {
913 const pxr::UsdGeomImageable imageable = pxr::UsdGeomImageable(child_prim);
914 if (!imageable) {
915 continue;
916 }
917
918 /* We should only traverse through a hierarchy, and any potential instancers, if they would
919 * be included by our purpose and visibility checks, matching what is inside
920 * #collect_readers. */
921 if (!include_by_purpose(imageable)) {
922 continue;
923 }
924
925 if (!include_by_visibility(imageable)) {
926 continue;
927 }
928 }
929
930 /* We should only consider potential point instancers if they would be included by the scene
931 * instancing flags. */
932 if (!params_.support_scene_instancing && child_prim.IsInPrototype()) {
933 continue;
934 }
935
936 if (pxr::UsdGeomPointInstancer instancer = pxr::UsdGeomPointInstancer(child_prim)) {
937 pxr::SdfPathVector paths;
938 instancer.GetPrototypesRel().GetTargets(&paths);
939 for (const pxr::SdfPath &path : paths) {
940 r_paths.add(path);
941 }
942 }
943
944 collect_point_instancer_proto_paths(child_prim, r_paths);
945 }
946}
947
948UsdPathSet USDStageReader::collect_point_instancer_proto_paths() const
949{
951
952 if (!stage_) {
953 return result;
954 }
955
956 collect_point_instancer_proto_paths(stage_->GetPseudoRoot(), result);
957
958 std::vector<pxr::UsdPrim> protos = stage_->GetPrototypes();
959
960 for (const pxr::UsdPrim &proto_prim : protos) {
961 collect_point_instancer_proto_paths(proto_prim, result);
962 }
963
964 return result;
965}
966
967} // namespace blender::io::usd
Collection * BKE_collection_add(Main *bmain, Collection *collection_parent, const char *name_custom)
bool BKE_collection_object_add(Main *bmain, Collection *collection, Object *ob)
void id_fake_user_set(ID *id)
Definition lib_id.cc:396
ModifierData * BKE_modifiers_findby_type(const Object *ob, ModifierType type)
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
MINLINE int integer_digits_i(int i)
#define M_PI_2
void copy_m4_m3(float m1[4][4], const float m2[3][3])
void unit_m4(float m[4][4])
void axis_angle_normalized_to_mat3(float R[3][3], const float axis[3], float angle)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:189
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_HIERARCHY
Definition DNA_ID.h:1158
Object groups, one object can be in many groups at once.
@ COLLECTION_HIDE_RENDER
@ COLLECTION_HIDE_VIEWPORT
@ eModifierType_Armature
SIMD_FORCE_INLINE const btScalar & z() const
Return the z value.
Definition btQuadWord.h:117
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:570
bool is_empty() const
Definition BLI_set.hh:595
int64_t size() const
void append(const T &value)
const T & first() const
void set_instance_collection(Collection *coll)
Material * add_material(const pxr::UsdShadeMaterial &usd_material, bool read_usd_preview=true) const
void import_usd_preview(Material *mtl, const pxr::UsdShadeMaterial &usd_material) const
pxr::SdfPath get_skeleton_path() const
void set_collection(Main *bmain, Collection &coll)
USDPrimReader * create_reader(const pxr::UsdPrim &prim)
void call_material_import_hooks(struct Main *bmain) const
const USDImportParams & params() const
USDPrimReader * create_reader_if_allowed(const pxr::UsdPrim &prim)
blender::Vector< USDPrimReader * > readers_
blender::Vector< pxr::SdfPath > material_paths_
USDStageReader(pxr::UsdStageRefPtr stage, const USDImportParams &params, const std::function< CacheFile *()> &get_cache_file_fn={})
blender::Vector< USDDomeLightReader * > dome_light_readers_
void create_proto_collections(Main *bmain, Collection *parent_collection)
void import_all_materials(struct Main *bmain)
#define LOG(level)
Definition log.h:97
bool have_material_import_hook(pxr::UsdStageRefPtr stage, const pxr::UsdShadeMaterial &usd_material, const USDImportParams &import_params, ReportList *reports)
Definition usd_hook.cc:653
blender::Set< pxr::SdfPath > UsdPathSet
static void decref(USDPrimReader *reader)
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)
static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings &settings)
static Collection * create_collection(Main *bmain, Collection *parent, const char *name)
static void set_instance_collection(USDInstanceReader *instance_reader, const blender::Map< pxr::SdfPath, Collection * > &proto_collection_map)
bool call_material_import_hooks(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, const USDImportParams &import_params, ReportList *reports)
Definition usd_hook.cc:668
static void find_prefix_to_skip(pxr::UsdStageRefPtr stage, ImportSettings &settings)
@ USD_MTL_NAME_COLLISION_MAKE_UNIQUE
Definition usd.hh:36
static void determine_blender_compat(pxr::UsdStageRefPtr stage, ImportSettings &settings)
void parallel_sort(RandomAccessIterator begin, RandomAccessIterator end)
Definition BLI_sort.hh:23
const char * name
char name[258]
Definition DNA_ID.h:432