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