slic3r_arrangejob_tests.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #include "catch2/catch.hpp"
  2. #include "test_utils.hpp"
  3. #include <random>
  4. #include "slic3r/GUI/Jobs/UIThreadWorker.hpp"
  5. #include "slic3r/GUI/Jobs/BoostThreadWorker.hpp"
  6. #include "slic3r/GUI/Jobs/ArrangeJob2.hpp"
  7. #include "libslic3r/Model.hpp"
  8. #include "libslic3r/SLAPrint.hpp"
  9. #include "libslic3r/Format/3mf.hpp"
  10. class RandomArrangeSettings: public Slic3r::arr2::ArrangeSettingsView {
  11. Slic3r::arr2::ArrangeSettingsDb::Values m_v;
  12. std::mt19937 m_rng;
  13. public:
  14. explicit RandomArrangeSettings(int seed) : m_rng(seed)
  15. {
  16. std::uniform_real_distribution<float> fdist(0., 100.f);
  17. std::uniform_int_distribution<> bdist(0, 1);
  18. std::uniform_int_distribution<> dist;
  19. m_v.d_obj = fdist(m_rng);
  20. m_v.d_bed = fdist(m_rng);
  21. m_v.rotations = bdist(m_rng);
  22. m_v.geom_handling = static_cast<GeometryHandling>(dist(m_rng) % ghCount);
  23. m_v.arr_strategy = static_cast<ArrangeStrategy>(dist(m_rng) % asCount);
  24. m_v.xl_align = static_cast<XLPivots>(dist(m_rng) % xlpCount);
  25. }
  26. explicit RandomArrangeSettings() : m_rng(std::random_device{} ()) {}
  27. float get_distance_from_objects() const override { return m_v.d_obj; }
  28. float get_distance_from_bed() const override { return m_v.d_bed; }
  29. bool is_rotation_enabled() const override { return m_v.rotations; }
  30. XLPivots get_xl_alignment() const override { return m_v.xl_align; }
  31. GeometryHandling get_geometry_handling() const override { return m_v.geom_handling; }
  32. ArrangeStrategy get_arrange_strategy() const override { return m_v.arr_strategy; }
  33. };
  34. TEMPLATE_TEST_CASE("Arranging empty bed should do nothing",
  35. "[arrangejob][fillbedjob]",
  36. Slic3r::GUI::ArrangeJob2,
  37. Slic3r::GUI::FillBedJob2)
  38. {
  39. using namespace Slic3r;
  40. using namespace Slic3r::GUI;
  41. using JobType = TestType;
  42. Model m;
  43. UIThreadWorker w;
  44. RandomArrangeSettings settings;
  45. w.push(std::make_unique<JobType>(arr2::Scene{
  46. arr2::SceneBuilder{}.set_model(m).set_arrange_settings(&settings)}));
  47. w.process_events();
  48. REQUIRE(m.objects.empty());
  49. }
  50. static void center_first_instance(Slic3r::ModelObject *mo,
  51. const Slic3r::BoundingBox &bedbb)
  52. {
  53. using namespace Slic3r;
  54. Vec2d d = unscaled(bedbb).center() -
  55. to_2d(mo->instance_bounding_box(0).center());
  56. auto tr = mo->instances.front()->get_transformation().get_matrix();
  57. tr.translate(to_3d(d, 0.));
  58. mo->instances.front()->set_transformation(Geometry::Transformation(tr));
  59. }
  60. TEST_CASE("Basic arrange with cube", "[arrangejob]") {
  61. using namespace Slic3r;
  62. using namespace Slic3r::GUI;
  63. std::string basepath = TEST_DATA_DIR PATH_SEPARATOR;
  64. DynamicPrintConfig cfg;
  65. cfg.load_from_ini(basepath + "default_fff.ini",
  66. ForwardCompatibilitySubstitutionRule::Enable);
  67. Model m = Model::read_from_file(basepath + "20mm_cube.obj", &cfg);
  68. UIThreadWorker w;
  69. arr2::ArrangeSettings settings;
  70. Points bedpts = get_bed_shape(cfg);
  71. arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts);
  72. SECTION("Single cube needs to be centered") {
  73. w.push(std::make_unique<ArrangeJob2>(arr2::Scene{
  74. arr2::SceneBuilder{}
  75. .set_model(m)
  76. .set_arrange_settings(&settings)
  77. .set_bed(cfg)}));
  78. w.process_events();
  79. REQUIRE(m.objects.size() == 1);
  80. REQUIRE(m.objects.front()->instances.size() == 1);
  81. Vec3d c3 = m.objects.front()->bounding_box_exact().center();
  82. Point c{scaled(c3.x()), scaled(c3.y())};
  83. REQUIRE(c == bounding_box(bed).center());
  84. }
  85. SECTION("Selected cube needs to go beside existing") {
  86. REQUIRE(m.objects.size() == 1);
  87. ModelObject *mo = m.objects.front();
  88. // Center the first instance within the bed
  89. center_first_instance(mo, bounding_box(bed));
  90. m.objects.front()->add_instance();
  91. REQUIRE(m.objects.front()->instances.size() == 2);
  92. arr2::FixedSelection sel({ {false, true} });
  93. arr2::Scene scene{arr2::SceneBuilder{}
  94. .set_model(m)
  95. .set_arrange_settings(&settings)
  96. .set_bed(cfg)
  97. .set_selection(&sel)};
  98. w.push(std::make_unique<ArrangeJob2>(std::move(scene)));
  99. w.process_events();
  100. auto bb0 = m.objects.front()->instance_bounding_box(0);
  101. auto bb1 = m.objects.front()->instance_bounding_box(1);
  102. REQUIRE(!bb0.contains(bb1));
  103. bb0.merge(bb1);
  104. Vec2d sz = to_2d(bb0.size());
  105. if (sz.x() > sz.y())
  106. std::swap(sz.x(), sz.y());
  107. double d_obj = settings.get_distance_from_objects();
  108. REQUIRE(sz.y() == Approx(2. * bb1.size().y() + d_obj));
  109. }
  110. SECTION("Selected cube (different object), needs to go beside existing") {
  111. REQUIRE(m.objects.size() == 1);
  112. ModelObject *mo = m.objects.front();
  113. // Center the first instance within the bed
  114. center_first_instance(mo, bounding_box(bed));
  115. ModelObject *mosel = m.add_object(*m.objects.front());
  116. arr2::FixedSelection sel({ {false}, {true} });
  117. arr2::Scene scene{arr2::SceneBuilder{}
  118. .set_model(m)
  119. .set_arrange_settings(&settings)
  120. .set_bed(cfg)
  121. .set_selection(&sel)};
  122. w.push(std::make_unique<ArrangeJob2>(std::move(scene)));
  123. w.process_events();
  124. auto bb0 = mo->instance_bounding_box(0);
  125. auto bb1 = mosel->instance_bounding_box(0);
  126. REQUIRE(!bb0.contains(bb1));
  127. bb0.merge(bb1);
  128. Vec2d sz = to_2d(bb0.size());
  129. if (sz.x() > sz.y())
  130. std::swap(sz.x(), sz.y());
  131. double d_obj = settings.get_distance_from_objects();
  132. REQUIRE(sz.y() == Approx(2. * bb1.size().y() + d_obj));
  133. }
  134. SECTION("Four cubes needs to touch each other after arrange") {
  135. ModelObject *mo = m.objects.front();
  136. mo->add_instance();
  137. mo->add_instance();
  138. mo->add_instance();
  139. auto bedbb = unscaled<double>(bounding_box(bed));
  140. ModelInstance *mi = mo->instances[0];
  141. Vec2d d = bedbb.min - to_2d(mo->instance_bounding_box(0).center());
  142. auto tr = mi->get_transformation().get_matrix();
  143. tr.translate(to_3d(d, 0.));
  144. mi->set_transformation(Geometry::Transformation(tr));
  145. mi = mo->instances[1];
  146. d = Vec2d(bedbb.min.x(), bedbb.max.y()) -
  147. to_2d(mo->instance_bounding_box(1).center());
  148. tr = mi->get_transformation().get_matrix();
  149. tr.translate(to_3d(d, 0.));
  150. mi->set_transformation(Geometry::Transformation(tr));
  151. mi = mo->instances[2];
  152. d = bedbb.max - to_2d(mo->instance_bounding_box(2).center());
  153. tr = mi->get_transformation().get_matrix();
  154. tr.translate(to_3d(d, 0.));
  155. mi->set_transformation(Geometry::Transformation(tr));
  156. mi = mo->instances[3];
  157. d = Vec2d(bedbb.max.x(), bedbb.min.y()) -
  158. to_2d(mo->instance_bounding_box(3).center());
  159. tr = mi->get_transformation().get_matrix();
  160. tr.translate(to_3d(d, 0.));
  161. mi->set_transformation(Geometry::Transformation(tr));
  162. arr2::Scene scene{arr2::SceneBuilder{}
  163. .set_model(m)
  164. .set_arrange_settings(&settings)
  165. .set_bed(cfg)};
  166. w.push(std::make_unique<ArrangeJob2>(std::move(scene)));
  167. w.process_events();
  168. auto pilebb = m.objects.front()->bounding_box_exact();
  169. Vec3d c3 = pilebb.center();
  170. Point c{scaled(c3.x()), scaled(c3.y())};
  171. REQUIRE(c == bounding_box(bed).center());
  172. float d_obj = settings.get_distance_from_objects();
  173. REQUIRE(pilebb.size().x() == Approx(2. * 20. + d_obj));
  174. REQUIRE(pilebb.size().y() == Approx(2. * 20. + d_obj));
  175. }
  176. }
  177. struct DummyProgress: Slic3r::ProgressIndicator {
  178. int range = 100;
  179. int pr = 0;
  180. std::string statustxt;
  181. void set_range(int r) override { range = r; }
  182. void set_cancel_callback(CancelFn = CancelFn()) override {}
  183. void set_progress(int p) override { pr = p; }
  184. void set_status_text(const char *txt) override { statustxt = txt; }
  185. int get_range() const override { return range; }
  186. };
  187. TEST_CASE("Test for modifying model during arrangement", "[arrangejob][fillbedjob]")
  188. {
  189. using namespace Slic3r;
  190. using namespace Slic3r::GUI;
  191. std::string basepath = TEST_DATA_DIR PATH_SEPARATOR;
  192. DynamicPrintConfig cfg;
  193. cfg.load_from_ini(basepath + "default_fff.ini",
  194. ForwardCompatibilitySubstitutionRule::Enable);
  195. Model m;
  196. ModelObject* new_object = m.add_object();
  197. new_object->name = "20mm_cyl";
  198. new_object->add_instance();
  199. TriangleMesh mesh = make_cylinder(10., 10.);
  200. ModelVolume* new_volume = new_object->add_volume(mesh);
  201. new_volume->name = new_object->name;
  202. Points bedpts = get_bed_shape(cfg);
  203. arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts);
  204. BoostThreadWorker w(std::make_unique<DummyProgress>());
  205. RandomArrangeSettings settings;
  206. SECTION("Remove 10 cylinder instances during arrange") {
  207. for (size_t i = 1; i < 10; ++i)
  208. new_object->add_instance();
  209. arr2::Scene scene{arr2::SceneBuilder{}
  210. .set_model(m)
  211. .set_arrange_settings(&settings)
  212. .set_bed(cfg)};
  213. ArrangeJob2::Callbacks cbs;
  214. cbs.on_prepared = [&m] (auto &) {
  215. m.clear_objects();
  216. };
  217. w.push(std::make_unique<ArrangeJob2>(std::move(scene), cbs));
  218. w.wait_for_current_job();
  219. REQUIRE(m.objects.empty());
  220. }
  221. }
  222. //TEST_CASE("Logical bed needs to be used when physical bed is full",
  223. // "[arrangejob][fillbedjob]")
  224. //{
  225. // using namespace Slic3r;
  226. // using namespace Slic3r::GUI;
  227. // std::string basepath = TEST_DATA_DIR PATH_SEPARATOR;
  228. // DynamicPrintConfig cfg;
  229. // cfg.load_from_ini(basepath + "default_fff.ini",
  230. // ForwardCompatibilitySubstitutionRule::Enable);
  231. // Model m;
  232. // ModelObject* new_object = m.add_object();
  233. // new_object->name = "bigbox";
  234. // new_object->add_instance();
  235. // TriangleMesh mesh = make_cube(200., 200., 10.);
  236. // ModelVolume* new_volume = new_object->add_volume(mesh);
  237. // new_volume->name = new_object->name;
  238. // Points bedpts = get_bed_shape(cfg);
  239. // arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts);
  240. // auto bedbb = bounding_box(bed);
  241. // center_first_instance(new_object, bedbb);
  242. // new_object = m.add_object();
  243. // new_object->name = "40x20mm_box";
  244. // new_object->add_instance();
  245. // mesh = make_cube(50., 50., 50.);
  246. // new_volume = new_object->add_volume(mesh);
  247. // new_volume->name = new_object->name;
  248. // UIThreadWorker w(std::make_unique<DummyProgress>());
  249. // arr2::ArrangeSettings settings;
  250. // SECTION("Single cube needs to be on first logical bed") {
  251. // {
  252. // arr2::Scene scene{&m, &settings, &cfg};
  253. // w.push(std::make_unique<ArrangeJob2>(std::move(scene)));
  254. // w.process_events();
  255. // }
  256. // store_3mf("logicalbed_10mm.3mf", &m, &cfg, false);
  257. // REQUIRE(m.objects.size() == 2);
  258. // Vec3d c3 = m.objects[1]->bounding_box_exact().center();
  259. // Point result_center{scaled(c3.x()), scaled(c3.y())};
  260. // auto bedidx_ojb1 = scene.virtual_bed_handler().get_bed_index(m.objects[1]->instances[0]);
  261. // REQUIRE(bedidx_ojb1 == 1);
  262. // }
  263. //}