Blender V5.0
usd_hierarchy_iterator.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2019 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4#include "usd.hh"
5
8#include "usd_hash_types.hh"
10#include "usd_skel_convert.hh"
12#include "usd_utils.hh"
15#include "usd_writer_camera.hh"
16#include "usd_writer_curves.hh"
17#include "usd_writer_hair.hh"
18#include "usd_writer_light.hh"
19#include "usd_writer_mesh.hh"
22#include "usd_writer_points.hh"
23#include "usd_writer_text.hh"
25#include "usd_writer_volume.hh"
26
27#include <string>
28
29#include "BKE_lib_id.hh"
30#include "BKE_main.hh"
31#include "BKE_report.hh"
32
33#include "BLI_assert.h"
34
35#include "DNA_layer_types.h"
36#include "DNA_object_types.h"
37
38namespace blender::io::usd {
39
41 Depsgraph *depsgraph,
42 pxr::UsdStageRefPtr stage,
44 : AbstractHierarchyIterator(bmain, depsgraph), stage_(stage), params_(params)
45{
46}
47
49{
50 if (params_.selected_objects_only && (object->base_flag & BASE_SELECTED) == 0) {
51 return true;
52 }
53
54 switch (object->type) {
55 case OB_EMPTY:
56 /* Always assume empties are being exported intentionally. */
57 return false;
58 case OB_MESH:
59 case OB_MBALL:
60 case OB_FONT:
61 return !params_.export_meshes;
62 case OB_CAMERA:
63 return !params_.export_cameras;
64 case OB_LAMP:
65 return !params_.export_lights;
67 case OB_CURVES:
68 return !params_.export_curves;
69 case OB_VOLUME:
70 return !params_.export_volumes;
71 case OB_ARMATURE:
72 return !params_.export_armatures;
73 case OB_POINTCLOUD:
74 return !params_.export_points;
75
76 default:
77 /* Assume weak for all other types. */
78 return true;
79 }
80}
81
83{
84 delete static_cast<USDAbstractWriter *>(writer);
85}
86
87std::string USDHierarchyIterator::make_valid_name(const std::string &name) const
88{
89 return make_safe_name(name, params_.allow_unicode);
90}
91
93{
94 skel_export_chaser(stage_,
95 armature_export_map_,
96 skinned_mesh_export_map_,
97 shape_key_mesh_export_map_,
99
100 create_skel_roots(stage_, params_);
101}
102
104{
105 /* The USD stage is already set up to have FPS time-codes per frame. */
106 export_time_ = pxr::UsdTimeCode(frame_nr);
107}
108
109USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context)
110{
111 pxr::SdfPath path;
112 if (!params_.root_prim_path.empty()) {
113 path = pxr::SdfPath(params_.root_prim_path + context->export_path);
114 }
115 else {
116 path = pxr::SdfPath(context->export_path);
117 }
118
119 if (params_.merge_parent_xform && context->is_object_data_context && !context->is_parent) {
120 bool can_merge_with_xform = true;
121 if (params_.export_shapekeys && is_mesh_with_shape_keys(context->object)) {
122 can_merge_with_xform = false;
123 }
124
125 if (params_.use_instancing && (context->is_prototype() || context->is_instance())) {
126 can_merge_with_xform = false;
127 }
128
129 if (can_merge_with_xform) {
130 path = path.GetParentPath();
131 }
132 }
133
134 /* Returns the same path that was passed to `stage_` object during it's creation (via
135 * `pxr::UsdStage::CreateNew` function). */
136 const pxr::SdfLayerHandle root_layer = stage_->GetRootLayer();
137 const std::string export_file_path = root_layer->GetRealPath();
138 auto get_time_code = [this]() { return this->export_time_; };
139
140 USDExporterContext exporter_context = USDExporterContext{
141 bmain_, depsgraph_, stage_, path, get_time_code, params_, export_file_path, nullptr};
142
143 /* Provides optional skel mapping hook. Now it's been used in USDPointInstancerWriter for write
144 * base layer. */
145 exporter_context.add_skel_mapping_fn = [this](const Object *obj, const pxr::SdfPath &usd_path) {
146 this->add_usd_skel_export_mapping(obj, usd_path);
147 };
148
149 return exporter_context;
150}
151
153{
154 if (!context) {
155 return true;
156 }
157
158 if (context->object->type == OB_ARMATURE) {
159 return true;
160 }
161
162 bool is_referencing_self = false;
163 if (context->is_point_instancer()) {
164 /* Mark the point instancer's children as a point instance. */
165 USDExporterContext usd_export_context = create_usd_export_context(context);
166 const ExportChildren *children = graph_children(context);
167
168 pxr::SdfPath instancer_path;
169 if (!params_.root_prim_path.empty()) {
170 instancer_path = pxr::SdfPath(params_.root_prim_path + context->export_path);
171 }
172 else {
173 instancer_path = pxr::SdfPath(context->export_path);
174 }
175
176 if (children != nullptr) {
177 for (HierarchyContext *child_context : *children) {
178 if (!child_context->original_export_path.empty()) {
179 const pxr::SdfPath parent_export_path(context->export_path);
180 const pxr::SdfPath children_original_export_path(child_context->original_export_path);
181
182 /* Detect if the parent is referencing itself via a prototype. */
183 if (parent_export_path.HasPrefix(children_original_export_path)) {
184 is_referencing_self = true;
185 break;
186 }
187 }
188
189 pxr::SdfPath prototype_path;
190 if (child_context->is_instance() && child_context->duplicator != nullptr) {
191 /* When the current child context is point instancer's instance, use reference path
192 * (original_export_path) as the prototype path. */
193 if (!params_.root_prim_path.empty()) {
194 prototype_path = pxr::SdfPath(params_.root_prim_path +
195 child_context->original_export_path);
196 }
197 else {
198 prototype_path = pxr::SdfPath(child_context->original_export_path);
199 }
200
201 prototype_paths_.lookup_or_add(instancer_path, {})
202 .add(std::make_pair(prototype_path, child_context->object));
203 child_context->is_point_instance = true;
204 }
205 else {
206 /* When the current child context is point instancer's prototype, use its own export path
207 * (export_path) as the prototype path. */
208 if (!params_.root_prim_path.empty()) {
209 prototype_path = pxr::SdfPath(params_.root_prim_path + child_context->export_path);
210 }
211 else {
212 prototype_path = pxr::SdfPath(child_context->export_path);
213 }
214
215 prototype_paths_.lookup_or_add(instancer_path, {})
216 .add(std::make_pair(prototype_path, child_context->object));
217 child_context->is_point_proto = true;
218 }
219 }
220 }
221
222 /* MARK: If the "Instance on Points" node uses an Object as a prototype,
223 * but the "Object Info" node has not enabled the "As Instance" option,
224 * then the generated reference path is incorrect and refers to itself. */
225 if (is_referencing_self) {
227 params_.worker_status->reports,
229 "One or more objects used as prototypes in 'Instance on Points' nodes either do not "
230 "have 'As Instance' enabled in their 'Object Info' nodes, or the prototype is the "
231 "base geometry input itself. Both cases prevent valid point instancer export. If it's "
232 "the former, enable 'As Instance' to avoid incorrect self-referencing.");
233
234 /* Clear any paths which had already been accumulated. */
235 Set<std::pair<pxr::SdfPath, Object *>> *paths = prototype_paths_.lookup_ptr(instancer_path);
236 if (paths) {
237 paths->clear();
238 }
239 for (HierarchyContext *child_context : *children) {
240 child_context->is_point_instance = false;
241 child_context->is_point_proto = false;
242 }
243 }
244 }
245
246 return !is_referencing_self;
247}
248
250 const HierarchyContext *context)
251{
252 /* The transform writer is always called before data writers,
253 * so determine if the #Xform's children is a point instancer before writing data. */
254 if (params_.use_instancing) {
255 if (!determine_point_instancers(context)) {
256 /* If we could not determine that our point instancing setup is safe, we should not continue
257 * writing. Continuing would result in enormous amounts of USD warnings about cyclic
258 * references. */
259 return nullptr;
260 }
261 }
262
263 return new USDTransformWriter(create_usd_export_context(context));
264}
265
267{
268 USDExporterContext usd_export_context = create_usd_export_context(context);
269 USDAbstractWriter *data_writer = nullptr;
270 const Set<std::pair<pxr::SdfPath, Object *>> *proto_paths = prototype_paths_.lookup_ptr(
271 usd_export_context.usd_path.GetParentPath());
272 const bool use_point_instancing = context->is_point_instancer() &&
273 (proto_paths && !proto_paths->is_empty());
274
275 switch (context->object->type) {
276 case OB_MESH:
277 if (usd_export_context.export_params.export_meshes) {
278 if (params_.use_instancing && use_point_instancing) {
279 USDExporterContext mesh_context = create_point_instancer_context(context,
280 usd_export_context);
281 std::unique_ptr<USDMeshWriter> mesh_writer = std::make_unique<USDMeshWriter>(
282 mesh_context);
283
284 data_writer = new USDPointInstancerWriter(
285 usd_export_context, *proto_paths, std::move(mesh_writer));
286 }
287 else {
288 data_writer = new USDMeshWriter(usd_export_context);
289 }
290 }
291 else {
292 return nullptr;
293 }
294 break;
295 case OB_CAMERA:
296 if (usd_export_context.export_params.export_cameras) {
297 data_writer = new USDCameraWriter(usd_export_context);
298 }
299 else {
300 return nullptr;
301 }
302 break;
303 case OB_LAMP:
304 if (usd_export_context.export_params.export_lights) {
305 data_writer = new USDLightWriter(usd_export_context);
306 }
307 else {
308 return nullptr;
309 }
310 break;
311 case OB_MBALL:
312 data_writer = new USDMetaballWriter(usd_export_context);
313 break;
314 case OB_FONT:
315 data_writer = new USDTextWriter(usd_export_context);
316 break;
317 case OB_CURVES_LEGACY:
318 case OB_CURVES:
319 if (usd_export_context.export_params.export_curves) {
320 if (params_.use_instancing && use_point_instancing) {
321 USDExporterContext curves_context = create_point_instancer_context(context,
322 usd_export_context);
323 std::unique_ptr<USDCurvesWriter> curves_writer = std::make_unique<USDCurvesWriter>(
324 curves_context);
325
326 data_writer = new USDPointInstancerWriter(
327 usd_export_context, *proto_paths, std::move(curves_writer));
328 }
329 else {
330 data_writer = new USDCurvesWriter(usd_export_context);
331 }
332 }
333 else {
334 return nullptr;
335 }
336 break;
337 case OB_VOLUME:
338 if (usd_export_context.export_params.export_volumes) {
339 data_writer = new USDVolumeWriter(usd_export_context);
340 }
341 else {
342 return nullptr;
343 }
344 break;
345 case OB_ARMATURE:
346 if (usd_export_context.export_params.export_armatures) {
347 data_writer = new USDArmatureWriter(usd_export_context);
348 }
349 else {
350 return nullptr;
351 }
352 break;
353 case OB_POINTCLOUD:
354 if (usd_export_context.export_params.export_points) {
355 if (params_.use_instancing && use_point_instancing) {
356 USDExporterContext point_cloud_context = create_point_instancer_context(
357 context, usd_export_context);
358 std::unique_ptr<USDPointsWriter> point_cloud_writer = std::make_unique<USDPointsWriter>(
359 point_cloud_context);
360
361 data_writer = new USDPointInstancerWriter(
362 usd_export_context, *proto_paths, std::move(point_cloud_writer));
363 }
364 else {
365 data_writer = new USDPointsWriter(usd_export_context);
366 }
367 }
368 else {
369 return nullptr;
370 }
371 break;
372
373 case OB_EMPTY:
374 case OB_SURF:
375 case OB_SPEAKER:
376 case OB_LIGHTPROBE:
377 case OB_LATTICE:
378 case OB_GREASE_PENCIL:
379 return nullptr;
380
381 case OB_TYPE_MAX:
382 BLI_assert_msg(0, "OB_TYPE_MAX should not be used");
383 return nullptr;
384 default:
386 return nullptr;
387 }
388
389 if (data_writer && !data_writer->is_supported(context)) {
390 delete data_writer;
391 return nullptr;
392 }
393
394 if (data_writer && (params_.export_armatures || params_.export_shapekeys)) {
395 add_usd_skel_export_mapping(context->object, data_writer->usd_path());
396 }
397
398 return data_writer;
399}
400
402{
403 if (!params_.export_hair) {
404 return nullptr;
405 }
406 return new USDHairWriter(create_usd_export_context(context));
407}
408
410 const HierarchyContext * /*context*/)
411{
412 return nullptr;
413}
414
416{
417 /* Don't generate data writers for instances. */
418
419 return !(params_.use_instancing && context->is_instance());
420}
421
423{
424 /* Don't generate writers for children of instances. */
425
426 return !(params_.use_instancing && context->is_instance());
427}
428
429void USDHierarchyIterator::add_usd_skel_export_mapping(const Object *obj, const pxr::SdfPath &path)
430{
432 shape_key_mesh_export_map_.add(obj, path);
433 }
434
435 if (params_.export_armatures && obj->type == OB_ARMATURE) {
436 armature_export_map_.add(obj, path);
437 }
438
439 if (params_.export_armatures && obj->type == OB_MESH &&
441 {
442 skinned_mesh_export_map_.add(obj, path);
443 }
444}
445
446USDExporterContext USDHierarchyIterator::create_point_instancer_context(
447 const HierarchyContext *context, const USDExporterContext &export_context) const
448{
449 BLI_assert(context && context->object);
450 std::string base_name = std::string(BKE_id_name(context->object->id)).append("_base");
451 std::string safe_name = make_safe_name(base_name, export_context.export_params.allow_unicode);
452
453 pxr::SdfPath base_path = export_context.usd_path.GetParentPath().AppendChild(
454 pxr::TfToken(safe_name));
455
456 return {export_context.bmain,
457 export_context.depsgraph,
458 export_context.stage,
459 base_path,
460 export_context.get_time_code,
461 export_context.export_params,
462 export_context.export_file_path,
463 export_context.export_image_fn,
464 export_context.add_skel_mapping_fn};
465}
466
467} // namespace blender::io::usd
const char * BKE_id_name(const ID &id)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_WARNING
Definition BKE_report.hh:38
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
Object is a sort of wrapper for general info.
@ OB_SPEAKER
@ OB_LATTICE
@ OB_MBALL
@ OB_EMPTY
@ OB_SURF
@ OB_CAMERA
@ OB_FONT
@ OB_GREASE_PENCIL
@ OB_TYPE_MAX
@ OB_ARMATURE
@ OB_LAMP
@ OB_MESH
@ OB_POINTCLOUD
@ OB_VOLUME
@ OB_CURVES_LEGACY
@ OB_CURVES
@ OB_LIGHTPROBE
struct Object Object
#define BASE_SELECTED(v3d, base)
BPy_StructRNA * depsgraph
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
bool is_empty() const
Definition BLI_set.hh:595
void clear()
Definition BLI_set.hh:551
ExportChildren * graph_children(const HierarchyContext *context)
AbstractHierarchyIterator(Main *bmain, Depsgraph *depsgraph)
blender::Set< HierarchyContext * > ExportChildren
const pxr::SdfPath & usd_path() const
virtual bool is_supported(const HierarchyContext *context) const
bool mark_as_weak_export(const Object *object) const override
AbstractHierarchyWriter * create_data_writer(const HierarchyContext *context) override
void release_writer(AbstractHierarchyWriter *writer) override
bool determine_point_instancers(const HierarchyContext *context)
AbstractHierarchyWriter * create_particle_writer(const HierarchyContext *context) override
USDHierarchyIterator(Main *bmain, Depsgraph *depsgraph, pxr::UsdStageRefPtr stage, const USDExportParams &params)
AbstractHierarchyWriter * create_hair_writer(const HierarchyContext *context) override
bool include_child_writers(const HierarchyContext *context) const override
bool include_data_writers(const HierarchyContext *context) const override
std::string make_valid_name(const std::string &name) const override
AbstractHierarchyWriter * create_transform_writer(const HierarchyContext *context) override
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
static void add(blender::Map< std::string, std::string > &messages, Message &msg)
Definition msgfmt.cc:222
int context(const bContext *C, const char *member, bContextDataResult *result)
void skel_export_chaser(pxr::UsdStageRefPtr stage, const ObjExportMap &armature_export_map, const ObjExportMap &skinned_mesh_export_map, const ObjExportMap &shape_key_mesh_export_map, const Depsgraph *depsgraph)
std::string make_safe_name(const StringRef name, bool allow_unicode)
Definition usd_utils.cc:18
void create_skel_roots(pxr::UsdStageRefPtr stage, const USDExportParams &params)
bool can_export_skinned_mesh(const Object &obj, const Depsgraph *depsgraph)
bool is_mesh_with_shape_keys(const Object *obj)
const char * name
short base_flag
std::function< void(const Object *, const pxr::SdfPath &)> add_skel_mapping_fn