Blender V5.0
abstract_hierarchy_iterator_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2019 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
5
7
8#include "BKE_scene.hh"
9#include "BLI_map.hh"
10#include "BLI_path_utils.hh"
11#include "BLI_set.hh"
12#include "BLO_readfile.hh"
13#include "DEG_depsgraph.hh"
15#include "DNA_object_types.h"
16
17namespace blender::io {
18
19namespace {
20
21/* Mapping from ID.name to set of export hierarchy path. Duplicated objects can be exported
22 * multiple times with different export paths, hence the set. */
23using used_writers = blender::Map<std::string, blender::Set<std::string>>;
24
25class TestHierarchyWriter : public AbstractHierarchyWriter {
26 public:
27 std::string writer_type;
28 used_writers &writers_map;
29
30 TestHierarchyWriter(const std::string &writer_type, used_writers &writers_map)
31 : writer_type(writer_type), writers_map(writers_map)
32 {
33 }
34
35 void write(HierarchyContext &context) override
36 {
37 const char *id_name = context.object->id.name;
38 Set<std::string> &writers = writers_map.lookup_or_add(id_name, {});
39
40 if (writers.contains(context.export_path)) {
41 ADD_FAILURE() << "Unexpectedly found another " << writer_type << " writer for " << id_name
42 << " to export to " << context.export_path;
43 }
44 writers.add_new(context.export_path);
45 }
46};
47
48} // namespace
49
51 public: /* Public so that the test cases can directly inspect the created writers. */
52 used_writers transform_writers;
53 used_writers data_writers;
54 used_writers hair_writers;
55 used_writers particle_writers;
56
57 explicit TestingHierarchyIterator(Main *bmain, Depsgraph *depsgraph)
59 {
60 }
62 {
64 }
65
66 protected:
68 {
69 return new TestHierarchyWriter("transform", transform_writers);
70 }
72 {
73 return new TestHierarchyWriter("data", data_writers);
74 }
76 {
77 return new TestHierarchyWriter("hair", hair_writers);
78 }
80 {
81 return new TestHierarchyWriter("particle", particle_writers);
82 }
83
85 {
86 delete writer;
87 }
88};
89
91 protected:
93
94 void SetUp() override
95 {
96 BlendfileLoadingBaseTest::SetUp();
97 iterator = nullptr;
98 }
99
100 void TearDown() override
101 {
104 }
105
106 /* Create a test iterator. */
108 {
110 }
111 /* Free the test iterator if it is not nullptr. */
113 {
114 if (iterator == nullptr) {
115 return;
116 }
117 delete iterator;
118 iterator = nullptr;
119 }
120};
121
123{
124 /* Load the test blend file. */
125 if (!blendfile_load("usd" SEP_STR "usd_hierarchy_export_test.blend")) {
126 return;
127 }
128 depsgraph_create(DAG_EVAL_RENDER);
129 iterator_create();
130
131 iterator->iterate_and_write();
132
133 /* Mapping from object name to set of export paths. */
134 used_writers expected_transforms = {
135 {"OBCamera", {"/Camera"}},
136 {"OBDupli1", {"/Dupli1"}},
137 {"OBDupli2", {"/ParentOfDupli2/Dupli2"}},
138 {"OBGEO_Ear_L",
139 {"/Dupli1/GEO_Head-0/GEO_Ear_L-1",
140 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L",
141 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1"}},
142 {"OBGEO_Ear_R",
143 {"/Dupli1/GEO_Head-0/GEO_Ear_R-2",
144 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R",
145 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2"}},
146 {"OBGEO_Head",
147 {"/Dupli1/GEO_Head-0",
148 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head",
149 "/ParentOfDupli2/Dupli2/GEO_Head-0"}},
150 {"OBGEO_Nose",
151 {"/Dupli1/GEO_Head-0/GEO_Nose-3",
152 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose",
153 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3"}},
154 {"OBGround plane", {"/Ground plane"}},
155 {"OBOutsideDupliGrandParent", {"/Ground plane/OutsideDupliGrandParent"}},
156 {"OBOutsideDupliParent", {"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent"}},
157 {"OBParentOfDupli2", {"/ParentOfDupli2"}}};
158 EXPECT_EQ(expected_transforms, iterator->transform_writers);
159
160 used_writers expected_data = {
161 {"OBCamera", {"/Camera/Camera"}},
162 {"OBGEO_Ear_L",
163 {"/Dupli1/GEO_Head-0/GEO_Ear_L-1/Ear",
164 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L/Ear",
165 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1/Ear"}},
166 {"OBGEO_Ear_R",
167 {"/Dupli1/GEO_Head-0/GEO_Ear_R-2/Ear",
168 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R/Ear",
169 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2/Ear"}},
170 {"OBGEO_Head",
171 {"/Dupli1/GEO_Head-0/Face",
172 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/Face",
173 "/ParentOfDupli2/Dupli2/GEO_Head-0/Face"}},
174 {"OBGEO_Nose",
175 {"/Dupli1/GEO_Head-0/GEO_Nose-3/Nose",
176 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose/Nose",
177 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3/Nose"}},
178 {"OBGround plane", {"/Ground plane/Plane"}},
179 {"OBParentOfDupli2", {"/ParentOfDupli2/Icosphere"}},
180 };
181
182 EXPECT_EQ(expected_data, iterator->data_writers);
183
184 /* The scene has no hair or particle systems. */
185 EXPECT_EQ(0, iterator->hair_writers.size());
186 EXPECT_EQ(0, iterator->particle_writers.size());
187
188 /* On the second iteration, everything should be written as well.
189 * This tests the default value of iterator->export_subset_. */
190 iterator->transform_writers.clear();
191 iterator->data_writers.clear();
192 iterator->iterate_and_write();
193 EXPECT_EQ(expected_transforms, iterator->transform_writers);
194 EXPECT_EQ(expected_data, iterator->data_writers);
195}
196
198{
199 /* The scene has no hair or particle systems, and this is already covered by ExportHierarchyTest,
200 * so not included here. Update this test when hair & particle systems are included. */
201
202 /* Load the test blend file. */
203 if (!blendfile_load("usd" SEP_STR "usd_hierarchy_export_test.blend")) {
204 return;
205 }
206 depsgraph_create(DAG_EVAL_RENDER);
207 iterator_create();
208
209 /* Mapping from object name to set of export paths. */
210 used_writers expected_transforms = {
211 {"OBCamera", {"/Camera"}},
212 {"OBDupli1", {"/Dupli1"}},
213 {"OBDupli2", {"/ParentOfDupli2/Dupli2"}},
214 {"OBGEO_Ear_L",
215 {"/Dupli1/GEO_Head-0/GEO_Ear_L-1",
216 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L",
217 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1"}},
218 {"OBGEO_Ear_R",
219 {"/Dupli1/GEO_Head-0/GEO_Ear_R-2",
220 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R",
221 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2"}},
222 {"OBGEO_Head",
223 {"/Dupli1/GEO_Head-0",
224 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head",
225 "/ParentOfDupli2/Dupli2/GEO_Head-0"}},
226 {"OBGEO_Nose",
227 {"/Dupli1/GEO_Head-0/GEO_Nose-3",
228 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose",
229 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3"}},
230 {"OBGround plane", {"/Ground plane"}},
231 {"OBOutsideDupliGrandParent", {"/Ground plane/OutsideDupliGrandParent"}},
232 {"OBOutsideDupliParent", {"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent"}},
233 {"OBParentOfDupli2", {"/ParentOfDupli2"}}};
234
235 used_writers expected_data = {
236 {"OBCamera", {"/Camera/Camera"}},
237 {"OBGEO_Ear_L",
238 {"/Dupli1/GEO_Head-0/GEO_Ear_L-1/Ear",
239 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L/Ear",
240 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1/Ear"}},
241 {"OBGEO_Ear_R",
242 {"/Dupli1/GEO_Head-0/GEO_Ear_R-2/Ear",
243 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R/Ear",
244 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2/Ear"}},
245 {"OBGEO_Head",
246 {"/Dupli1/GEO_Head-0/Face",
247 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/Face",
248 "/ParentOfDupli2/Dupli2/GEO_Head-0/Face"}},
249 {"OBGEO_Nose",
250 {"/Dupli1/GEO_Head-0/GEO_Nose-3/Nose",
251 "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose/Nose",
252 "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3/Nose"}},
253 {"OBGround plane", {"/Ground plane/Plane"}},
254 {"OBParentOfDupli2", {"/ParentOfDupli2/Icosphere"}},
255 };
256
257 /* Even when only asking an export of transforms, on the first frame everything should be
258 * exported. */
259 {
260 ExportSubset export_subset = {false};
261 export_subset.transforms = true;
262 export_subset.shapes = false;
263 iterator->set_export_subset(export_subset);
264 }
265 iterator->iterate_and_write();
266 EXPECT_EQ(expected_transforms, iterator->transform_writers);
267 EXPECT_EQ(expected_data, iterator->data_writers);
268
269 /* Clear data to prepare for the next iteration. */
270 iterator->transform_writers.clear();
271 iterator->data_writers.clear();
272
273 /* Second iteration, should only write transforms now. */
274 iterator->iterate_and_write();
275 EXPECT_EQ(expected_transforms, iterator->transform_writers);
276 EXPECT_EQ(0, iterator->data_writers.size());
277
278 /* Clear data to prepare for the next iteration. */
279 iterator->transform_writers.clear();
280 iterator->data_writers.clear();
281
282 /* Third iteration, should only write data now. */
283 {
284 ExportSubset export_subset = {false};
285 export_subset.transforms = false;
286 export_subset.shapes = true;
287 iterator->set_export_subset(export_subset);
288 }
289 iterator->iterate_and_write();
290 EXPECT_EQ(0, iterator->transform_writers.size());
291 EXPECT_EQ(expected_data, iterator->data_writers);
292
293 /* Clear data to prepare for the next iteration. */
294 iterator->transform_writers.clear();
295 iterator->data_writers.clear();
296
297 /* Fourth iteration, should export everything now. */
298 {
299 ExportSubset export_subset = {false};
300 export_subset.transforms = true;
301 export_subset.shapes = true;
302 iterator->set_export_subset(export_subset);
303 }
304 iterator->iterate_and_write();
305 EXPECT_EQ(expected_transforms, iterator->transform_writers);
306 EXPECT_EQ(expected_data, iterator->data_writers);
307}
308
309/* Test class that constructs a depsgraph in such a way that it includes invisible objects. */
311 protected:
312 void depsgraph_create(eEvaluationMode depsgraph_evaluation_mode) override
313 {
315 bfile->main, bfile->curscene, bfile->cur_view_layer, depsgraph_evaluation_mode);
318 }
319};
320
322{
323 if (!blendfile_load("alembic" SEP_STR "visibility.blend")) {
324 return;
325 }
326 depsgraph_create(DAG_EVAL_RENDER);
327 iterator_create();
328
329 iterator->iterate_and_write();
330
331 /* Mapping from object name to set of export paths. */
332 used_writers expected_transforms = {{"OBInvisibleAnimatedCube", {"/InvisibleAnimatedCube"}},
333 {"OBInvisibleCube", {"/InvisibleCube"}},
334 {"OBVisibleCube", {"/VisibleCube"}}};
335 EXPECT_EQ(expected_transforms, iterator->transform_writers);
336
337 used_writers expected_data = {{"OBInvisibleAnimatedCube", {"/InvisibleAnimatedCube/Cube"}},
338 {"OBInvisibleCube", {"/InvisibleCube/Cube"}},
339 {"OBVisibleCube", {"/VisibleCube/Cube"}}};
340
341 EXPECT_EQ(expected_data, iterator->data_writers);
342
343 /* The scene has no hair or particle systems. */
344 EXPECT_EQ(0, iterator->hair_writers.size());
345 EXPECT_EQ(0, iterator->particle_writers.size());
346}
347
348} // namespace blender::io
void BKE_scene_graph_update_tagged(Depsgraph *depsgraph, Main *bmain)
Definition scene.cc:2621
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
external readfile function prototypes.
eEvaluationMode
@ DAG_EVAL_RENDER
Depsgraph * DEG_graph_new(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode)
Definition depsgraph.cc:278
void DEG_graph_build_for_all_objects(Depsgraph *graph)
Object is a sort of wrapper for general info.
BPy_StructRNA * depsgraph
bool contains(const Key &key) const
Definition BLI_set.hh:310
void add_new(const Key &key)
Definition BLI_set.hh:233
void depsgraph_create(eEvaluationMode depsgraph_evaluation_mode) override
AbstractHierarchyIterator(Main *bmain, Depsgraph *depsgraph)
void release_writer(AbstractHierarchyWriter *writer) override
AbstractHierarchyWriter * create_transform_writer(const HierarchyContext *) override
AbstractHierarchyWriter * create_hair_writer(const HierarchyContext *) override
AbstractHierarchyWriter * create_data_writer(const HierarchyContext *) override
TestingHierarchyIterator(Main *bmain, Depsgraph *depsgraph)
AbstractHierarchyWriter * create_particle_writer(const HierarchyContext *) override
int context(const bContext *C, const char *member, bContextDataResult *result)
TEST_F(AbstractHierarchyIteratorTest, ExportHierarchyTest)
#define SEP_STR
Definition unit.cc:39