Blender  V2.93
abstract_hierarchy_iterator_test.cc
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2019 Blender Foundation.
17  * All rights reserved.
18  */
20 
22 
23 #include "BKE_scene.h"
24 #include "BLI_math.h"
25 #include "BLO_readfile.h"
26 #include "DEG_depsgraph.h"
27 #include "DEG_depsgraph_build.h"
28 #include "DNA_object_types.h"
29 
30 #include <map>
31 #include <set>
32 
33 namespace blender::io {
34 
35 namespace {
36 
37 /* Mapping from ID.name to set of export hierarchy path. Duplicated objects can be exported
38  * multiple times with different export paths, hence the set. */
39 using used_writers = std::map<std::string, std::set<std::string>>;
40 
41 class TestHierarchyWriter : public AbstractHierarchyWriter {
42  public:
43  std::string writer_type;
44  used_writers &writers_map;
45 
46  TestHierarchyWriter(const std::string &writer_type, used_writers &writers_map)
48  {
49  }
50 
51  void write(HierarchyContext &context) override
52  {
53  const char *id_name = context.object->id.name;
54  used_writers::mapped_type &writers = writers_map[id_name];
55 
56  if (writers.find(context.export_path) != writers.end()) {
57  ADD_FAILURE() << "Unexpectedly found another " << writer_type << " writer for " << id_name
58  << " to export to " << context.export_path;
59  }
60  writers.insert(context.export_path);
61  }
62 };
63 
64 } // namespace
65 
67  public: /* Public so that the test cases can directly inspect the created writers. */
68  used_writers transform_writers;
69  used_writers data_writers;
70  used_writers hair_writers;
71  used_writers particle_writers;
72 
74  {
75  }
77  {
79  }
80 
81  protected:
83  {
84  return new TestHierarchyWriter("transform", transform_writers);
85  }
87  {
88  return new TestHierarchyWriter("data", data_writers);
89  }
91  {
92  return new TestHierarchyWriter("hair", hair_writers);
93  }
95  {
96  return new TestHierarchyWriter("particle", particle_writers);
97  }
98 
99  void release_writer(AbstractHierarchyWriter *writer) override
100  {
101  delete writer;
102  }
103 };
104 
106  protected:
108 
109  void SetUp() override
110  {
111  BlendfileLoadingBaseTest::SetUp();
112  iterator = nullptr;
113  }
114 
115  void TearDown() override
116  {
117  iterator_free();
119  }
120 
121  /* Create a test iterator. */
123  {
125  }
126  /* Free the test iterator if it is not nullptr. */
128  {
129  if (iterator == nullptr) {
130  return;
131  }
132  delete iterator;
133  iterator = nullptr;
134  }
135 };
136 
138 {
139  /* Load the test blend file. */
140  if (!blendfile_load("usd/usd_hierarchy_export_test.blend")) {
141  return;
142  }
143  depsgraph_create(DAG_EVAL_RENDER);
144  iterator_create();
145 
146  iterator->iterate_and_write();
147 
148  /* Mapping from object name to set of export paths. */
149  used_writers expected_transforms = {
150  {"OBCamera", {"/Camera"}},
151  {"OBDupli1", {"/Dupli1"}},
152  {"OBDupli2", {"/ParentOfDupli2/Dupli2"}},
153  {"OBGEO_Ear_L",
154  {"/Dupli1/GEO_Head-0/GEO_Ear_L-1",
155  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L",
156  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1"}},
157  {"OBGEO_Ear_R",
158  {"/Dupli1/GEO_Head-0/GEO_Ear_R-2",
159  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R",
160  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2"}},
161  {"OBGEO_Head",
162  {"/Dupli1/GEO_Head-0",
163  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head",
164  "/ParentOfDupli2/Dupli2/GEO_Head-0"}},
165  {"OBGEO_Nose",
166  {"/Dupli1/GEO_Head-0/GEO_Nose-3",
167  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose",
168  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3"}},
169  {"OBGround plane", {"/Ground plane"}},
170  {"OBOutsideDupliGrandParent", {"/Ground plane/OutsideDupliGrandParent"}},
171  {"OBOutsideDupliParent", {"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent"}},
172  {"OBParentOfDupli2", {"/ParentOfDupli2"}}};
173  EXPECT_EQ(expected_transforms, iterator->transform_writers);
174 
175  used_writers expected_data = {
176  {"OBCamera", {"/Camera/Camera"}},
177  {"OBGEO_Ear_L",
178  {"/Dupli1/GEO_Head-0/GEO_Ear_L-1/Ear",
179  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L/Ear",
180  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1/Ear"}},
181  {"OBGEO_Ear_R",
182  {"/Dupli1/GEO_Head-0/GEO_Ear_R-2/Ear",
183  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R/Ear",
184  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2/Ear"}},
185  {"OBGEO_Head",
186  {"/Dupli1/GEO_Head-0/Face",
187  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/Face",
188  "/ParentOfDupli2/Dupli2/GEO_Head-0/Face"}},
189  {"OBGEO_Nose",
190  {"/Dupli1/GEO_Head-0/GEO_Nose-3/Nose",
191  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose/Nose",
192  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3/Nose"}},
193  {"OBGround plane", {"/Ground plane/Plane"}},
194  {"OBParentOfDupli2", {"/ParentOfDupli2/Icosphere"}},
195  };
196 
197  EXPECT_EQ(expected_data, iterator->data_writers);
198 
199  /* The scene has no hair or particle systems. */
200  EXPECT_EQ(0, iterator->hair_writers.size());
201  EXPECT_EQ(0, iterator->particle_writers.size());
202 
203  /* On the second iteration, everything should be written as well.
204  * This tests the default value of iterator->export_subset_. */
205  iterator->transform_writers.clear();
206  iterator->data_writers.clear();
207  iterator->iterate_and_write();
208  EXPECT_EQ(expected_transforms, iterator->transform_writers);
209  EXPECT_EQ(expected_data, iterator->data_writers);
210 }
211 
213 {
214  /* The scene has no hair or particle systems, and this is already covered by ExportHierarchyTest,
215  * so not included here. Update this test when hair & particle systems are included. */
216 
217  /* Load the test blend file. */
218  if (!blendfile_load("usd/usd_hierarchy_export_test.blend")) {
219  return;
220  }
221  depsgraph_create(DAG_EVAL_RENDER);
222  iterator_create();
223 
224  /* Mapping from object name to set of export paths. */
225  used_writers expected_transforms = {
226  {"OBCamera", {"/Camera"}},
227  {"OBDupli1", {"/Dupli1"}},
228  {"OBDupli2", {"/ParentOfDupli2/Dupli2"}},
229  {"OBGEO_Ear_L",
230  {"/Dupli1/GEO_Head-0/GEO_Ear_L-1",
231  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L",
232  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1"}},
233  {"OBGEO_Ear_R",
234  {"/Dupli1/GEO_Head-0/GEO_Ear_R-2",
235  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R",
236  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2"}},
237  {"OBGEO_Head",
238  {"/Dupli1/GEO_Head-0",
239  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head",
240  "/ParentOfDupli2/Dupli2/GEO_Head-0"}},
241  {"OBGEO_Nose",
242  {"/Dupli1/GEO_Head-0/GEO_Nose-3",
243  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose",
244  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3"}},
245  {"OBGround plane", {"/Ground plane"}},
246  {"OBOutsideDupliGrandParent", {"/Ground plane/OutsideDupliGrandParent"}},
247  {"OBOutsideDupliParent", {"/Ground plane/OutsideDupliGrandParent/OutsideDupliParent"}},
248  {"OBParentOfDupli2", {"/ParentOfDupli2"}}};
249 
250  used_writers expected_data = {
251  {"OBCamera", {"/Camera/Camera"}},
252  {"OBGEO_Ear_L",
253  {"/Dupli1/GEO_Head-0/GEO_Ear_L-1/Ear",
254  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_L/Ear",
255  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_L-1/Ear"}},
256  {"OBGEO_Ear_R",
257  {"/Dupli1/GEO_Head-0/GEO_Ear_R-2/Ear",
258  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Ear_R/Ear",
259  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Ear_R-2/Ear"}},
260  {"OBGEO_Head",
261  {"/Dupli1/GEO_Head-0/Face",
262  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/Face",
263  "/ParentOfDupli2/Dupli2/GEO_Head-0/Face"}},
264  {"OBGEO_Nose",
265  {"/Dupli1/GEO_Head-0/GEO_Nose-3/Nose",
266  "/Ground plane/OutsideDupliGrandParent/OutsideDupliParent/GEO_Head/GEO_Nose/Nose",
267  "/ParentOfDupli2/Dupli2/GEO_Head-0/GEO_Nose-3/Nose"}},
268  {"OBGround plane", {"/Ground plane/Plane"}},
269  {"OBParentOfDupli2", {"/ParentOfDupli2/Icosphere"}},
270  };
271 
272  /* Even when only asking an export of transforms, on the first frame everything should be
273  * exported. */
274  {
275  ExportSubset export_subset = {false};
276  export_subset.transforms = true;
277  export_subset.shapes = false;
278  iterator->set_export_subset(export_subset);
279  }
280  iterator->iterate_and_write();
281  EXPECT_EQ(expected_transforms, iterator->transform_writers);
282  EXPECT_EQ(expected_data, iterator->data_writers);
283 
284  /* Clear data to prepare for the next iteration. */
285  iterator->transform_writers.clear();
286  iterator->data_writers.clear();
287 
288  /* Second iteration, should only write transforms now. */
289  iterator->iterate_and_write();
290  EXPECT_EQ(expected_transforms, iterator->transform_writers);
291  EXPECT_EQ(0, iterator->data_writers.size());
292 
293  /* Clear data to prepare for the next iteration. */
294  iterator->transform_writers.clear();
295  iterator->data_writers.clear();
296 
297  /* Third iteration, should only write data now. */
298  {
299  ExportSubset export_subset = {false};
300  export_subset.transforms = false;
301  export_subset.shapes = true;
302  iterator->set_export_subset(export_subset);
303  }
304  iterator->iterate_and_write();
305  EXPECT_EQ(0, iterator->transform_writers.size());
306  EXPECT_EQ(expected_data, iterator->data_writers);
307 
308  /* Clear data to prepare for the next iteration. */
309  iterator->transform_writers.clear();
310  iterator->data_writers.clear();
311 
312  /* Fourth iteration, should export everything now. */
313  {
314  ExportSubset export_subset = {false};
315  export_subset.transforms = true;
316  export_subset.shapes = true;
317  iterator->set_export_subset(export_subset);
318  }
319  iterator->iterate_and_write();
320  EXPECT_EQ(expected_transforms, iterator->transform_writers);
321  EXPECT_EQ(expected_data, iterator->data_writers);
322 }
323 
324 /* Test class that constructs a depsgraph in such a way that it includes invisible objects. */
326  protected:
327  void depsgraph_create(eEvaluationMode depsgraph_evaluation_mode) override
328  {
330  bfile->main, bfile->curscene, bfile->cur_view_layer, depsgraph_evaluation_mode);
333  }
334 };
335 
337 {
338  if (!blendfile_load("alembic/visibility.blend")) {
339  return;
340  }
341  depsgraph_create(DAG_EVAL_RENDER);
342  iterator_create();
343 
344  iterator->iterate_and_write();
345 
346  /* Mapping from object name to set of export paths. */
347  used_writers expected_transforms = {{"OBInvisibleAnimatedCube", {"/InvisibleAnimatedCube"}},
348  {"OBInvisibleCube", {"/InvisibleCube"}},
349  {"OBVisibleCube", {"/VisibleCube"}}};
350  EXPECT_EQ(expected_transforms, iterator->transform_writers);
351 
352  used_writers expected_data = {{"OBInvisibleAnimatedCube", {"/InvisibleAnimatedCube/Cube"}},
353  {"OBInvisibleCube", {"/InvisibleCube/Cube"}},
354  {"OBVisibleCube", {"/VisibleCube/Cube"}}};
355 
356  EXPECT_EQ(expected_data, iterator->data_writers);
357 
358  /* The scene has no hair or particle systems. */
359  EXPECT_EQ(0, iterator->hair_writers.size());
360  EXPECT_EQ(0, iterator->particle_writers.size());
361 }
362 
363 } // namespace blender::io
void BKE_scene_graph_update_tagged(struct Depsgraph *depsgraph, struct Main *bmain)
Definition: scene.c:2713
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
external readfile function prototypes.
Depsgraph * DEG_graph_new(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, eEvaluationMode mode)
Definition: depsgraph.cc:281
struct Depsgraph Depsgraph
Definition: DEG_depsgraph.h:51
eEvaluationMode
Definition: DEG_depsgraph.h:60
@ DAG_EVAL_RENDER
Definition: DEG_depsgraph.h:62
void DEG_graph_build_for_all_objects(struct 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
AbstractHierarchyWriter * create_hair_writer(const HierarchyContext *) override
void release_writer(AbstractHierarchyWriter *writer) override
AbstractHierarchyWriter * create_particle_writer(const HierarchyContext *) override
AbstractHierarchyWriter * create_data_writer(const HierarchyContext *) override
AbstractHierarchyWriter * create_transform_writer(const HierarchyContext *) override
std::string id_name(void *id)
const Depsgraph * depsgraph
TEST_F(AbstractHierarchyIteratorTest, ExportHierarchyTest)
struct SELECTID_Context context
Definition: select_engine.c:47
struct Scene * curscene
Definition: BLO_readfile.h:78
struct ViewLayer * cur_view_layer
Definition: BLO_readfile.h:79
struct Main * main
Definition: BLO_readfile.h:70