123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080 |
- #include <catch2/catch.hpp>
- #include "test_utils.hpp"
- #include <libslic3r/Arrange/Arrange.hpp>
- #include <libslic3r/Arrange/Items/ArrangeItem.hpp>
- #include <libslic3r/Arrange/Tasks/ArrangeTask.hpp>
- #include <libslic3r/Arrange/SceneBuilder.hpp>
- #include "libslic3r/Model.hpp"
- #include "libslic3r/Geometry/ConvexHull.hpp"
- #include "libslic3r/Format/3mf.hpp"
- #include "libslic3r/ModelArrange.hpp"
- static Slic3r::Model get_example_model_with_20mm_cube()
- {
- using namespace Slic3r;
- Model model;
- ModelObject* new_object = model.add_object();
- new_object->name = "20mm_cube";
- new_object->add_instance();
- TriangleMesh mesh = make_cube(20., 20., 20.);
- mesh.translate(Vec3f{-10.f, -10.f, 0.});
- ModelVolume* new_volume = new_object->add_volume(mesh);
- new_volume->name = new_object->name;
- return model;
- }
- [[maybe_unused]]
- static Slic3r::Model get_example_model_with_random_cube_objects(size_t N = 0)
- {
- using namespace Slic3r;
- Model model;
- auto cube_count = N == 0 ? random_value(size_t(1), size_t(100)) : N;
- INFO("Cube count " << cube_count);
- ModelObject* new_object = model.add_object();
- new_object->name = "20mm_cube";
- TriangleMesh mesh = make_cube(20., 20., 20.);
- ModelVolume* new_volume = new_object->add_volume(mesh);
- new_volume->name = new_object->name;
- for (size_t i = 0; i < cube_count; ++i) {
- ModelInstance *inst = new_object->add_instance();
- arr2::transform_instance(*inst,
- Vec2d{random_value(-arr2::UnscaledCoordLimit / 10., arr2::UnscaledCoordLimit / 10.),
- random_value(-arr2::UnscaledCoordLimit / 10., arr2::UnscaledCoordLimit / 10.)},
- random_value(0., 2 * PI));
- }
- return model;
- }
- static Slic3r::Model get_example_model_with_arranged_primitives()
- {
- using namespace Slic3r;
- Model model;
- ModelObject* new_object = model.add_object();
- new_object->name = "20mm_cube";
- ModelInstance *cube_inst = new_object->add_instance();
- TriangleMesh mesh = make_cube(20., 20., 20.);
- mesh.translate(Vec3f{-10.f, -10.f, 0.});
- ModelVolume* new_volume = new_object->add_volume(mesh);
- new_volume->name = new_object->name;
- ModelInstance *inst = new_object->add_instance(*cube_inst);
- auto tr = inst->get_matrix();
- tr.translate(Vec3d{25., 0., 0.});
- inst->set_transformation(Geometry::Transformation{tr});
- new_object = model.add_object();
- new_object->name = "20mm_cyl";
- new_object->add_instance();
- mesh = make_cylinder(10., 20.);
- mesh.translate(Vec3f{0., -25.f, 0.});
- new_volume = new_object->add_volume(mesh);
- new_volume->name = new_object->name;
- new_object = model.add_object();
- new_object->name = "20mm_sphere";
- new_object->add_instance();
- mesh = make_sphere(10.);
- mesh.translate(Vec3f{25., -25.f, 0.});
- new_volume = new_object->add_volume(mesh);
- new_volume->name = new_object->name;
- return model;
- }
- class RandomArrangeSettings: public Slic3r::arr2::ArrangeSettingsView {
- Slic3r::arr2::ArrangeSettingsDb::Values m_v;
- std::mt19937 m_rng;
- public:
- explicit RandomArrangeSettings(int seed) : m_rng(seed)
- {
- std::uniform_real_distribution<float> fdist(0., 100.f);
- std::uniform_int_distribution<> bdist(0, 1);
- std::uniform_int_distribution<> dist;
- m_v.d_obj = fdist(m_rng);
- m_v.d_bed = fdist(m_rng);
- m_v.rotations = bdist(m_rng);
- m_v.geom_handling = static_cast<GeometryHandling>(dist(m_rng) % ghCount);
- m_v.arr_strategy = static_cast<ArrangeStrategy>(dist(m_rng) % asCount);
- m_v.xl_align = static_cast<XLPivots>(dist(m_rng) % xlpCount);
- }
- explicit RandomArrangeSettings() : m_rng(std::random_device{} ()) {}
- float get_distance_from_objects() const override { return m_v.d_obj; }
- float get_distance_from_bed() const override { return m_v.d_bed; }
- bool is_rotation_enabled() const override { return m_v.rotations; }
- XLPivots get_xl_alignment() const override { return m_v.xl_align; }
- GeometryHandling get_geometry_handling() const override { return m_v.geom_handling; }
- ArrangeStrategy get_arrange_strategy() const override { return m_v.arr_strategy; }
- };
- TEST_CASE("ModelInstance should be retrievable when imbued into ArrangeItem",
- "[arrange2][integration]")
- {
- using namespace Slic3r;
- Model model = get_example_model_with_20mm_cube();
- auto mi = model.objects.front()->instances.front();
- arr2::ArrangeItem itm;
- arr2::PhysicalOnlyVBedHandler vbedh;
- auto vbedh_ptr = static_cast<arr2::VirtualBedHandler *>(&vbedh);
- auto arrbl = arr2::ArrangeableModelInstance{mi, vbedh_ptr, nullptr, {0, 0}};
- arr2::imbue_id(itm, arrbl.id());
- std::optional<ObjectID> id_returned = arr2::retrieve_id(itm);
- REQUIRE((id_returned && *id_returned == mi->id()));
- }
- struct PhysicalBed
- {
- Slic3r::arr2::InfiniteBed bed;
- Slic3r::arr2::PhysicalOnlyVBedHandler vbedh;
- int bed_idx_min = 0, bed_idx_max = 0;
- };
- struct XStriderBed
- {
- Slic3r::arr2::RectangleBed bed;
- Slic3r::arr2::XStriderVBedHandler vbedh;
- int bed_idx_min = 0, bed_idx_max = 100;
- XStriderBed() :
- bed{Slic3r::scaled(250.), Slic3r::scaled(210.)},
- vbedh{bounding_box(bed), bounding_box(bed).size().x() / 10} {}
- };
- TEMPLATE_TEST_CASE("Writing arrange transformations into ModelInstance should be correct",
- "[arrange2][integration]",
- PhysicalBed,
- XStriderBed)
- {
- auto [tx, ty, rot] = GENERATE(map(
- [](int i) {
- return std::make_tuple(-Slic3r::arr2::UnscaledCoordLimit / 2. + i * Slic3r::arr2::UnscaledCoordLimit / 100.,
- -Slic3r::arr2::UnscaledCoordLimit / 2. + i * Slic3r::arr2::UnscaledCoordLimit / 100.,
- -PI + i * (2 * PI / 100.));
- },
- range(0, 100)));
- using namespace Slic3r;
- Model model = get_example_model_with_20mm_cube();
- auto transl = scaled(Vec2d(tx, ty));
- INFO("Translation = : " << transl.transpose());
- INFO("Rotation is: " << rot * 180 / PI);
- auto mi = model.objects.front()->instances.front();
- BoundingBox bb_before = scaled(to_2d(arr2::instance_bounding_box(*mi)));
- TestType bed_case;
- auto bed_index = random_value<int>(bed_case.bed_idx_min, bed_case.bed_idx_max);
- bed_case.vbedh.assign_bed(arr2::VBedPlaceableMI{*mi}, bed_index);
- INFO("bed_index = " << bed_index);
- auto builder = arr2::SceneBuilder{}
- .set_bed(bed_case.bed)
- .set_model(model)
- .set_arrange_settings(arr2::ArrangeSettings{}.set_distance_from_objects(0.))
- .set_virtual_bed_handler(&bed_case.vbedh);
- arr2::Scene scene{std::move(builder)};
- using ArrItem = arr2::ArrangeItem;
- auto cvt = arr2::ArrangeableToItemConverter<ArrItem>::create(scene);
- ArrItem itm;
- scene.model().visit_arrangeable(model.objects.front()->instances.front()->id(),
- [&cvt, &itm](const arr2::Arrangeable &arrbl){
- itm = cvt->convert(arrbl);
- });
- BoundingBox bb_itm_before = arr2::fixed_bounding_box(itm);
- REQUIRE((bb_itm_before.min - bb_before.min).norm() < SCALED_EPSILON);
- REQUIRE((bb_itm_before.max - bb_before.max).norm() < SCALED_EPSILON);
- arr2::rotate(itm, rot);
- arr2::translate(itm, transl);
- arr2::set_bed_index(itm, arr2::PhysicalBedId);
- if (auto id = retrieve_id(itm)) {
- scene.model().visit_arrangeable(*id, [&itm](arr2::Arrangeable &arrbl) {
- arrbl.transform(unscaled(get_translation(itm)), get_rotation(itm));
- });
- }
- auto phys_tr = bed_case.vbedh.get_physical_bed_trafo(bed_index);
- auto outline = arr2::extract_convex_outline(*mi, phys_tr);
- BoundingBox bb_after = get_extents(outline);
- BoundingBox bb_itm_after = arr2::fixed_bounding_box(itm);
- REQUIRE((bb_itm_after.min - bb_after.min).norm() < 2 * SCALED_EPSILON);
- REQUIRE((bb_itm_after.max - bb_after.max).norm() < 2 * SCALED_EPSILON);
- }
- struct OutlineExtractorConvex {
- auto operator() (const Slic3r::ModelInstance *mi)
- {
- return Slic3r::arr2::extract_convex_outline(*mi);
- }
- };
- struct OutlineExtractorFull {
- auto operator() (const Slic3r::ModelInstance *mi)
- {
- return Slic3r::arr2::extract_full_outline(*mi);
- }
- };
- TEMPLATE_TEST_CASE("Outline extraction from ModelInstance",
- "[arrange2][integration]",
- OutlineExtractorConvex,
- OutlineExtractorFull)
- {
- using namespace Slic3r;
- using OutlineExtractor = TestType;
- Model model = get_example_model_with_20mm_cube();
- ModelInstance *mi = model.objects.front()->instances.front();
- auto matrix = mi->get_matrix();
- matrix.scale(Vec3d{random_value(0.1, 5.),
- random_value(0.1, 5.),
- random_value(0.1, 5.)});
- matrix.rotate(Eigen::AngleAxisd(random_value(-PI, PI), Vec3d::UnitZ()));
- matrix.translate(Vec3d{random_value(-100., 100.),
- random_value(-100., 100.),
- random_value(0., 100.)});
- mi->set_transformation(Geometry::Transformation{matrix});
- GIVEN("An empty ModelInstance without mesh")
- {
- const ModelInstance *mi = model.add_object()->add_instance();
- WHEN("the outline is generated") {
- auto outline = OutlineExtractor{}(mi);
- THEN ("the outline is empty") {
- REQUIRE(outline.empty());
- }
- }
- }
- GIVEN("A simple cube as outline") {
- const ModelInstance *mi = model.objects.front()->instances.front();
- WHEN("the outline is generated") {
- auto outline = OutlineExtractor{}(mi);
- THEN("the 2D ortho projection of the model bounding box is the "
- "same as the outline's bb")
- {
- auto bb = unscaled(get_extents(outline));
- auto modelbb = to_2d(model.bounding_box_exact());
- REQUIRE((bb.min - modelbb.min).norm() < EPSILON);
- REQUIRE((bb.max - modelbb.max).norm() < EPSILON);
- }
- }
- }
- }
- template<class VBH>
- auto create_vbed_handler(const Slic3r::BoundingBox &bedbb, coord_t gap)
- {
- return VBH{};
- }
- template<>
- auto create_vbed_handler<Slic3r::arr2::PhysicalOnlyVBedHandler>(const Slic3r::BoundingBox &bedbb, coord_t gap)
- {
- return Slic3r::arr2::PhysicalOnlyVBedHandler{};
- }
- template<>
- auto create_vbed_handler<Slic3r::arr2::XStriderVBedHandler>(const Slic3r::BoundingBox &bedbb, coord_t gap)
- {
- return Slic3r::arr2::XStriderVBedHandler{bedbb, gap};
- }
- template<>
- auto create_vbed_handler<Slic3r::arr2::YStriderVBedHandler>(const Slic3r::BoundingBox &bedbb, coord_t gap)
- {
- return Slic3r::arr2::YStriderVBedHandler{bedbb, gap};
- }
- template<>
- auto create_vbed_handler<Slic3r::arr2::GridStriderVBedHandler>(const Slic3r::BoundingBox &bedbb, coord_t gap)
- {
- return Slic3r::arr2::GridStriderVBedHandler{bedbb, gap};
- }
- TEMPLATE_TEST_CASE("Common virtual bed handlers",
- "[arrange2][integration][vbeds]",
- Slic3r::arr2::PhysicalOnlyVBedHandler,
- Slic3r::arr2::XStriderVBedHandler,
- Slic3r::arr2::YStriderVBedHandler,
- Slic3r::arr2::GridStriderVBedHandler)
- {
- using namespace Slic3r;
- using VBP = arr2::VBedPlaceableMI;
- Model model = get_example_model_with_20mm_cube();
- const auto bedsize = Vec2d{random_value(21., 500.), random_value(21., 500.)};
- const Vec2crd bed_displace = {random_value(scaled(-100.), scaled(100.)),
- random_value(scaled(-100.), scaled(100.))};
- const BoundingBox bedbb{bed_displace, scaled(bedsize) + bed_displace};
- INFO("Bed boundaries bedbb = { {" << unscaled(bedbb.min).transpose() << "}, {"
- << unscaled(bedbb.max).transpose() << "} }" );
- auto modelbb = model.bounding_box_exact();
- // Center the single instance within the model
- arr2::transform_instance(*model.objects.front()->instances.front(),
- unscaled(bedbb.center()) - to_2d(modelbb.center()),
- 0.);
- const auto vbed_gap = GENERATE(0, random_value(1, scaled(100.)));
- INFO("vbed_gap = " << unscaled(vbed_gap));
- std::unique_ptr<arr2::VirtualBedHandler> vbedh = std::make_unique<TestType>(
- create_vbed_handler<TestType>(bedbb, vbed_gap));
- GIVEN("A ModelInstance on the physical bed")
- {
- ModelInstance *mi = model.objects.front()->instances.front();
- WHEN ("trying to move the item to an invalid bed index")
- {
- auto &mi_to_move = *model.objects.front()->add_instance(*mi);
- Transform3d mi_trafo_before = mi_to_move.get_matrix();
- bool was_accepted = vbedh->assign_bed(VBP{mi_to_move}, arr2::Unarranged);
- Transform3d mi_trafo_after = mi_to_move.get_matrix();
- THEN("the model instance should be unchanged") {
- REQUIRE(!was_accepted);
- REQUIRE(mi_trafo_before.isApprox(mi_trafo_after));
- }
- }
- }
- GIVEN("A ModelInstance being assigned to a virtual bed")
- {
- ModelInstance *mi = model.objects.front()->instances.front();
- auto bedidx_to = GENERATE(random_value(-1000, -1), 0, random_value(1, 1000));
- INFO("bed index = " << bedidx_to);
- auto &mi_to_move = *model.objects.front()->add_instance(*mi);
- // Move model instance to the given virtual bed
- bool was_accepted = vbedh->assign_bed(VBP{mi_to_move}, bedidx_to);
- WHEN ("querying the virtual bed index of this item")
- {
- int bedidx_on = vbedh->get_bed_index(VBP{mi_to_move});
- THEN("should actually be on that bed, or the assign should be discarded") {
- REQUIRE(((!was_accepted) || (bedidx_to == bedidx_on)));
- }
- THEN("assigning the same bed index again should produce the same result")
- {
- auto &mi_to_move_cpy = *model.objects.front()->add_instance(mi_to_move);
- bool was_accepted_rep = vbedh->assign_bed(VBP{mi_to_move_cpy}, bedidx_to);
- int bedidx_on_rep = vbedh->get_bed_index(VBP{mi_to_move_cpy});
- REQUIRE(was_accepted_rep == was_accepted);
- REQUIRE(((!was_accepted_rep) || (bedidx_to == bedidx_on_rep)));
- }
- }
- WHEN ("moving back to the physical bed")
- {
- auto &mi_back_to_phys = *model.objects.front()->add_instance(mi_to_move);
- bool moved_back_to_physical = vbedh->assign_bed(VBP{mi_back_to_phys}, arr2::PhysicalBedId);
- THEN("model instance should actually move back to the physical bed")
- {
- REQUIRE(moved_back_to_physical);
- int bedidx_mi2 = vbedh->get_bed_index(VBP{mi_back_to_phys});
- REQUIRE(bedidx_mi2 == 0);
- }
- THEN("the bounding box should be inside bed")
- {
- auto bbf = arr2::instance_bounding_box(mi_back_to_phys);
- auto bb = BoundingBox{scaled(to_2d(bbf))};
- INFO("bb = { {" << unscaled(bb.min).transpose() << "}, {"
- << unscaled(bb.max).transpose() << "} }" );
- REQUIRE(bedbb.contains(bb));
- }
- }
- WHEN("extracting transformed model instance bounding box using the "
- "physical bed trafo")
- {
- int from_bed_idx = vbedh->get_bed_index(VBP{mi_to_move});
- auto physical_bed_trafo = vbedh->get_physical_bed_trafo(from_bed_idx);
- auto &mi_back_to_phys = *model.objects.front()->add_instance(mi_to_move);
- mi_back_to_phys.set_transformation(Geometry::Transformation{
- physical_bed_trafo * mi_back_to_phys.get_matrix()});
- auto bbf = arr2::instance_bounding_box(mi_back_to_phys);
- auto bb = BoundingBox{scaled(to_2d(bbf))};
- THEN("the bounding box should be inside bed")
- {
- INFO("bb = { {" << unscaled(bb.min).transpose() << "}, {"
- << unscaled(bb.max).transpose() << "} }" );
- REQUIRE(bedbb.contains(bb));
- }
- THEN("the outline should be inside the physical bed")
- {
- auto outline = arr2::extract_convex_outline(mi_to_move,
- physical_bed_trafo);
- auto bb = get_extents(outline);
- INFO("bb = { {" << bb.min.transpose() << "}, {"
- << bb.max.transpose() << "} }" );
- REQUIRE(bedbb.contains(bb));
- }
- }
- }
- }
- TEST_CASE("Virtual bed handlers - StriderVBedHandler", "[arrange2][integration][vbeds]")
- {
- using namespace Slic3r;
- using VBP = arr2::VBedPlaceableMI;
- Model model = get_example_model_with_20mm_cube();
- static const Vec2d bedsize{250., 210.};
- static const BoundingBox bedbb{{0, 0}, scaled(bedsize)};
- static const auto modelbb = model.bounding_box_exact();
- GIVEN("An instance of StriderVBedHandler with a stride of the bed width"
- " and random non-negative gap")
- {
- auto [instance_pos, instance_displace] = GENERATE(table<std::string, Vec2d>({
- {"start", unscaled(bedbb.min) - to_2d(modelbb.min) + Vec2d::Ones() * EPSILON}, // at the min edge of vbed
- {"middle", unscaled(bedbb.center()) - to_2d(modelbb.center())}, // at the center
- {"end", unscaled(bedbb.max) - to_2d(modelbb.max) - Vec2d::Ones() * EPSILON} // at the max edge of vbed
- }));
- // Center the single instance within the model
- arr2::transform_instance(*model.objects.front()->instances.front(),
- instance_displace,
- 0.);
- INFO("Instance pos at " << instance_pos << " of bed");
- coord_t gap = GENERATE(0, random_value(1, scaled(100.)));
- INFO("Gap is " << unscaled(gap));
- arr2::XStriderVBedHandler vbh{bedbb, gap};
- WHEN("a model instance is on the Nth virtual bed (spatially)")
- {
- ModelInstance *mi = model.objects.front()->instances.front();
- auto &mi_to_move = *model.objects.front()->add_instance(*mi);
- auto bed_index = GENERATE(random_value(-1000, -1), 0, random_value(1, 1000));
- INFO("N is " << bed_index);
- double bed_disp = bed_index * unscaled(vbh.stride_scaled());
- arr2::transform_instance(mi_to_move, Vec2d{bed_disp, 0.}, 0.);
- THEN("the bed index of this model instance should be max(0, N)")
- {
- REQUIRE(vbh.get_bed_index(VBP{mi_to_move}) == bed_index);
- }
- THEN("the physical trafo should move the instance back to bed 0")
- {
- auto tr = vbh.get_physical_bed_trafo(bed_index);
- mi_to_move.set_transformation(Geometry::Transformation{tr * mi_to_move.get_matrix()});
- REQUIRE(vbh.get_bed_index(VBP{mi_to_move}) == 0);
- auto instbb = BoundingBox{scaled(to_2d(arr2::instance_bounding_box(mi_to_move)))};
- INFO("bedbb = { {" << bedbb.min.transpose() << "}, {" << bedbb.max.transpose() << "} }" );
- INFO("instbb = { {" << instbb.min.transpose() << "}, {" << instbb.max.transpose() << "} }" );
- REQUIRE(bedbb.contains(instbb));
- }
- }
- WHEN("a model instance is on the physical bed")
- {
- ModelInstance *mi = model.objects.front()->instances.front();
- auto &mi_to_move = *model.objects.front()->add_instance(*mi);
- THEN("assigning the model instance to the Nth bed will move it N*stride in the X axis")
- {
- auto bed_index = GENERATE(random_value(-1000, -1), 0, random_value(1, 1000));
- INFO("N is " << bed_index);
- if (vbh.assign_bed(VBP{mi_to_move}, bed_index))
- REQUIRE(vbh.get_bed_index(VBP{mi_to_move}) == bed_index);
- else
- REQUIRE(bed_index < 0);
- auto tr = vbh.get_physical_bed_trafo(bed_index);
- auto ref_pos = tr * Vec3d::Zero();
- auto displace = bed_index * (unscaled(vbh.stride_scaled()));
- REQUIRE(ref_pos.x() == Approx(-displace));
- auto ref_pos_mi = mi_to_move.get_matrix() * Vec3d::Zero();
- REQUIRE(ref_pos_mi.x() == Approx(instance_displace.x() + (bed_index >= 0) * displace));
- }
- }
- }
- GIVEN("An instance of StriderVBedHandler with a stride of the bed width"
- " and a 100mm gap")
- {
- coord_t gap = scaled(100.);
- arr2::XStriderVBedHandler vbh{bedbb, gap};
- WHEN("a model instance is within the gap on the Nth virtual bed")
- {
- ModelInstance *mi = model.objects.front()->instances.front();
- auto &mi_to_move = *model.objects.front()->add_instance(*mi);
- auto bed_index = GENERATE(random_value(-1000, -1), 0, random_value(1, 1000));
- INFO("N is " << bed_index);
- auto bed_disp = Vec2d{bed_index * unscaled(vbh.stride_scaled()), 0.};
- auto instbb_before = to_2d(arr2::instance_bounding_box(mi_to_move));
- auto transl_to_bed_end = Vec2d{bed_disp + unscaled(bedbb.max)
- - instbb_before.min + Vec2d::Ones() * EPSILON};
- arr2::transform_instance(mi_to_move,
- transl_to_bed_end + Vec2d{unscaled(gap / 2), 0.},
- 0.);
- THEN("the model instance should reside on the Nth logical bed but "
- "outside of the bed boundaries")
- {
- REQUIRE(vbh.get_bed_index(VBP{mi_to_move}) == bed_index);
- auto instbb = BoundingBox{scaled(to_2d(arr2::instance_bounding_box(mi_to_move)))};
- INFO("bedbb = { {" << bedbb.min.transpose() << "}, {" << bedbb.max.transpose() << "} }" );
- INFO("instbb = { {" << instbb.min.transpose() << "}, {" << instbb.max.transpose() << "} }" );
- REQUIRE(! bedbb.contains(instbb));
- }
- }
- }
- }
- TEMPLATE_TEST_CASE("Bed needs to be completely filled with 1cm cubes",
- "[arrange2][integration][bedfilling]",
- Slic3r::arr2::ArrangeItem)
- {
- using namespace Slic3r;
- using ArrItem = TestType;
- std::string basepath = TEST_DATA_DIR PATH_SEPARATOR;
- DynamicPrintConfig cfg;
- cfg.load_from_ini(basepath + "default_fff.ini",
- ForwardCompatibilitySubstitutionRule::Enable);
- cfg.set_key_value("bed_shape",
- new ConfigOptionPoints(
- {{0., 0.}, {100., 0.}, {100., 100.}, {0, 100.}}));
- Model m;
- ModelObject* new_object = m.add_object();
- new_object->name = "10mm_box";
- new_object->add_instance();
- TriangleMesh mesh = make_cube(10., 10., 10.);
- ModelVolume* new_volume = new_object->add_volume(mesh);
- new_volume->name = new_object->name;
- store_3mf("fillbed_10mm.3mf", &m, &cfg, false);
- arr2::ArrangeSettings settings;
- settings.values().d_obj = 0.;
- settings.values().d_bed = 0.;
- arr2::FixedSelection sel({{true}});
- arr2::Scene scene{arr2::SceneBuilder{}
- .set_model(m)
- .set_arrange_settings(settings)
- .set_selection(&sel)
- .set_bed(cfg)};
- auto task = arr2::FillBedTask<ArrItem>::create(scene);
- auto result = task->process_native(arr2::DummyCtl{});
- result->apply_on(scene.model());
- store_3mf("fillbed_10mm_result.3mf", &m, &cfg, false);
- Points bedpts = get_bed_shape(cfg);
- arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts);
- REQUIRE(bed.which() == 1); // Rectangle bed
- auto bedbb = unscaled(bounding_box(bed));
- auto bedbbsz = bedbb.size();
- REQUIRE(m.objects.size() == 1);
- REQUIRE(m.objects.front()->instances.size() ==
- bedbbsz.x() * bedbbsz.y() / 100);
- REQUIRE(task->unselected.empty());
- REQUIRE(result->to_add.size() + result->arranged_items.size() == arr2::model_instance_count(m));
- // All the existing items should be on the physical bed
- REQUIRE(std::all_of(result->arranged_items.begin(),
- result->arranged_items.end(), [](auto &itm) {
- return arr2::get_bed_index(itm) == 0;
- }));
- REQUIRE(
- std::all_of(result->to_add.begin(), result->to_add.end(), [](auto &itm) {
- return arr2::get_bed_index(itm) == 0;
- }));
- }
- template<class It, class Fn>
- static void foreach_combo(const Slic3r::Range<It> &range, const Fn &fn)
- {
- std::vector<bool> pairs(range.size(), false);
- assert(range.size() >= 2);
- pairs[range.size() - 1] = true;
- pairs[range.size() - 2] = true;
- do {
- std::vector<typename std::iterator_traits<It>::value_type> items;
- for (size_t i = 0; i < pairs.size(); i++) {
- if (pairs[i]) {
- auto it = range.begin();
- std::advance(it, i);
- items.emplace_back(*it);
- }
- }
- fn (items[0], items[1]);
- } while (std::next_permutation(pairs.begin(), pairs.end()));
- }
- TEST_CASE("Testing minimum area bounding box rotation on simple cubes", "[arrange2][integration]")
- {
- using namespace Slic3r;
- BoundingBox bb{Point::Zero(), scaled(Vec2d(10., 10.))};
- Polygon sh = arr2::to_rectangle(bb);
- auto prot = random_value(0., 2 * PI);
- sh.translate(Vec2crd{random_value<coord_t>(-scaled(10.), scaled(10.)),
- random_value<coord_t>(-scaled(10.), scaled(10.))});
- sh.rotate(prot);
- INFO("box item is rotated by: " << prot << " rads");
- arr2::ArrangeItem itm{sh};
- arr2::rotate(itm, random_value(0., 2 * PI));
- double rot = arr2::get_min_area_bounding_box_rotation(itm);
- arr2::translate(itm,
- Vec2crd{random_value<coord_t>(-scaled(10.), scaled(10.)),
- random_value<coord_t>(-scaled(10.), scaled(10.))});
- arr2::rotate(itm, rot);
- auto itmbb = arr2::fixed_bounding_box(itm);
- REQUIRE(std::abs(itmbb.size().norm() - bb.size().norm()) <
- SCALED_EPSILON * SCALED_EPSILON);
- }
- template<class It>
- bool is_collision_free(const Slic3r::Range<It> &item_range)
- {
- using namespace Slic3r;
- bool collision_free = true;
- foreach_combo(item_range, [&collision_free](auto &itm1, auto &itm2) {
- auto outline1 = offset(arr2::fixed_outline(itm1), -scaled<float>(EPSILON));
- auto outline2 = offset(arr2::fixed_outline(itm2), -scaled<float>(EPSILON));
- auto inters = intersection(outline1, outline2);
- collision_free = collision_free && inters.empty();
- });
- return collision_free;
- }
- TEST_CASE("Testing a simple arrange on cubes", "[arrange2][integration]")
- {
- using namespace Slic3r;
- Model model = get_example_model_with_random_cube_objects(size_t{10});
- arr2::ArrangeSettings settings;
- settings.set_rotation_enabled(true);
- auto bed = arr2::RectangleBed{scaled(250.), scaled(210.)};
- arr2::Scene scene{arr2::SceneBuilder{}
- .set_model(model)
- .set_arrange_settings(settings)
- .set_bed(bed)};
- auto task = arr2::ArrangeTask<arr2::ArrangeItem>::create(scene);
- REQUIRE(task->printable.selected.size() == arr2::model_instance_count(model));
- auto result = task->process_native(arr2::DummyCtl{});
- REQUIRE(result);
- REQUIRE(result->items.size() == task->printable.selected.size());
- bool applied = result->apply_on(scene.model());
- REQUIRE(applied);
- REQUIRE(std::all_of(result->items.begin(),
- result->items.end(),
- [](auto &item) { return arr2::is_arranged(item); }));
- REQUIRE(std::all_of(task->printable.selected.begin(), task->printable.selected.end(),
- [&bed](auto &item) { return bounding_box(bed).contains(arr2::envelope_bounding_box(item)); }));
- REQUIRE(std::all_of(task->unprintable.selected.begin(), task->unprintable.selected.end(),
- [&bed](auto &item) { return bounding_box(bed).contains(arr2::envelope_bounding_box(item)); }));
- REQUIRE(is_collision_free(range(task->printable.selected)));
- }
- TEST_CASE("Testing arrangement involving virtual beds", "[arrange2][integration]")
- {
- using namespace Slic3r;
- Model model = get_example_model_with_arranged_primitives();
- DynamicPrintConfig cfg;
- cfg.load_from_ini(std::string(TEST_DATA_DIR PATH_SEPARATOR) + "default_fff.ini",
- ForwardCompatibilitySubstitutionRule::Enable);
- auto bed = arr2::to_arrange_bed(get_bed_shape(cfg));
- auto bedbb = bounding_box(bed);
- auto bedsz = unscaled(bedbb.size());
- auto strategy = GENERATE(arr2::ArrangeSettingsView::asAuto,
- arr2::ArrangeSettingsView::asPullToCenter);
- INFO ("Strategy = " << strategy);
- auto settings = arr2::ArrangeSettings{}
- .set_distance_from_objects(0.)
- .set_arrange_strategy(strategy);
- arr2::Scene scene{arr2::SceneBuilder{}
- .set_model(model)
- .set_arrange_settings(settings)
- .set_bed(cfg)};
- auto itm_conv = arr2::ArrangeableToItemConverter<arr2::ArrangeItem>::create(scene);
- auto task = arr2::ArrangeTask<arr2::ArrangeItem>::create(scene, *itm_conv);
- ModelObject* new_object = model.add_object();
- new_object->name = "big_cube";
- ModelInstance *bigcube_inst = new_object->add_instance();
- TriangleMesh mesh = make_cube(bedsz.x() - 5., bedsz.y() - 5., 20.);
- ModelVolume* new_volume = new_object->add_volume(mesh);
- new_volume->name = new_object->name;
- {
- arr2::ArrangeItem bigitm;
- scene.model().visit_arrangeable(bigcube_inst->id(),
- [&bigitm, &itm_conv](
- const arr2::Arrangeable &arrbl) {
- bigitm = itm_conv->convert(arrbl);
- });
- task->printable.selected.emplace_back(std::move(bigitm));
- }
- REQUIRE(task->printable.selected.size() == arr2::model_instance_count(model));
- auto result = task->process_native(arr2::DummyCtl{});
- REQUIRE(result);
- REQUIRE(result->items.size() == task->printable.selected.size());
- REQUIRE(std::all_of(result->items.begin(),
- std::prev(result->items.end()),
- [](auto &item) { return arr2::get_bed_index(item) == 1; }));
- REQUIRE(arr2::get_bed_index(result->items.back()) == arr2::PhysicalBedId);
- bool applied = result->apply_on(scene.model());
- REQUIRE(applied);
- store_3mf("vbed_test_result.3mf", &model, &cfg, false);
- REQUIRE(std::all_of(task->printable.selected.begin(), task->printable.selected.end(),
- [&bed](auto &item) { return bounding_box(bed).contains(arr2::envelope_bounding_box(item)); }));
- REQUIRE(is_collision_free(Range{task->printable.selected.begin(), std::prev(task->printable.selected.end())}));
- }
- bool settings_eq(const Slic3r::arr2::ArrangeSettingsView &v1,
- const Slic3r::arr2::ArrangeSettingsView &v2)
- {
- return v1.is_rotation_enabled() == v2.is_rotation_enabled() &&
- v1.get_arrange_strategy() == v2.get_arrange_strategy() &&
- v1.get_distance_from_bed() == Approx(v2.get_distance_from_bed()) &&
- v1.get_distance_from_objects() == Approx(v2.get_distance_from_objects()) &&
- v1.get_geometry_handling() == v2.get_geometry_handling() &&
- v1.get_xl_alignment() == v2.get_xl_alignment();
- ;
- }
- namespace Slic3r { namespace arr2 {
- class MocWT: public ArrangeableWipeTowerBase {
- public:
- using ArrangeableWipeTowerBase::ArrangeableWipeTowerBase;
- };
- class MocWTH : public WipeTowerHandler {
- std::function<bool()> m_sel_pred;
- ObjectID m_id;
- public:
- MocWTH(const ObjectID &id) : m_id{id} {}
- void visit(std::function<void(Arrangeable &)> fn) override
- {
- MocWT wt{m_id, Polygon{}, m_sel_pred};
- fn(wt);
- }
- void visit(std::function<void(const Arrangeable &)> fn) const override
- {
- MocWT wt{m_id, Polygon{}, m_sel_pred};
- fn(wt);
- }
- void set_selection_predicate(std::function<bool()> pred) override
- {
- m_sel_pred = std::move(pred);
- }
- };
- }} // namespace Slic3r::arr2
- TEST_CASE("Test SceneBuilder", "[arrange2][integration]")
- {
- using namespace Slic3r;
- GIVEN("An empty SceneBuilder")
- {
- arr2::SceneBuilder bld;
- WHEN("building an ArrangeScene from it")
- {
- arr2::Scene scene{std::move(bld)};
- THEN("The scene should still be initialized consistently with empty model")
- {
- // This would segfault if model_wt isn't initialized properly
- REQUIRE(scene.model().arrangeable_count() == 0);
- REQUIRE(settings_eq(scene.settings(), arr2::ArrangeSettings{}));
- REQUIRE(scene.selected_ids().empty());
- }
- THEN("The associated bed should be an instance of InfiniteBed")
- {
- scene.visit_bed([](auto &bed){
- REQUIRE(std::is_convertible_v<decltype(bed), arr2::InfiniteBed>);
- });
- }
- }
- WHEN("pushing random settings into the builder")
- {
- RandomArrangeSettings settings;
- auto bld2 = arr2::SceneBuilder{}.set_arrange_settings(&settings);
- arr2::Scene scene{std::move(bld)};
- THEN("settings of the resulting scene should be equal")
- {
- REQUIRE(settings_eq(scene.settings(), settings));
- }
- }
- }
- GIVEN("An existing instance of the class Model")
- {
- auto N = random_value(1, 20);
- Model model = get_example_model_with_random_cube_objects(N);
- INFO("model object count " << N);
- WHEN("a scene is built from a builder that holds a reference to an existing model")
- {
- arr2::Scene scene{arr2::SceneBuilder{}.set_model(&model)};
- THEN("the model of the constructed scene should have the same number of arrangeables") {
- REQUIRE(scene.model().arrangeable_count() == arr2::model_instance_count(model));
- }
- }
- }
- GIVEN("An instance of DynamicPrintConfig with rectangular bed")
- {
- std::string basepath = TEST_DATA_DIR PATH_SEPARATOR;
- DynamicPrintConfig cfg;
- cfg.load_from_ini(basepath + "default_fff.ini",
- ForwardCompatibilitySubstitutionRule::Enable);
- WHEN("a scene is built with a bed initialized from this DynamicPrintConfig")
- {
- arr2::Scene scene(arr2::SceneBuilder{}.set_bed(cfg));
- auto bedbb = bounding_box(get_bed_shape(cfg));
- THEN("the bed should be a rectangular bed with the same dimensions as the bed points")
- {
- scene.visit_bed([&bedbb, &scene](auto &bed) {
- constexpr bool is_rect = std::is_convertible_v<
- decltype(bed), arr2::RectangleBed>;
- REQUIRE(is_rect);
- if constexpr (is_rect) {
- bedbb.offset(scaled(scene.settings().get_distance_from_objects() / 2.));
- REQUIRE(bedbb.size().x() == bed.width());
- REQUIRE(bedbb.size().y() == bed.height());
- }
- });
- }
- }
- }
- GIVEN("A wipe tower handler that uses the builder's selection mask")
- {
- arr2::SceneBuilder bld;
- Model mdl;
- bld.set_model(mdl);
- bld.set_wipe_tower_handler(std::make_unique<arr2::MocWTH>(mdl.wipe_tower.id()));
- WHEN("the selection mask is initialized as a fallback default in the created scene")
- {
- arr2::Scene scene{std::move(bld)};
- THEN("the wipe tower should use the fallback selmask (created after set_wipe_tower)")
- {
- // Should be the wipe tower
- REQUIRE(scene.model().arrangeable_count() == 1);
- bool wt_selected = false;
- scene.model()
- .visit_arrangeable(mdl.wipe_tower.id(),
- [&wt_selected](
- const arr2::Arrangeable &arrbl) {
- wt_selected = arrbl.is_selected();
- });
- REQUIRE(wt_selected);
- }
- }
- }
- }
- TEST_CASE("Testing duplicate function to really duplicate the whole Model",
- "[arrange2][integration]")
- {
- using namespace Slic3r;
- Model model = get_example_model_with_arranged_primitives();
- store_3mf("dupl_example.3mf", &model, nullptr, false);
- size_t instcnt = arr2::model_instance_count(model);
- size_t copies_num = random_value(1, 10);
- INFO("Copies: " << copies_num);
- auto bed = arr2::InfiniteBed{};
- arr2::ArrangeSettings settings;
- settings.set_arrange_strategy(arr2::ArrangeSettings::asPullToCenter);
- arr2::DuplicableModel dup_model{&model, arr2::VirtualBedHandler::create(bed), bounding_box(bed)};
- arr2::Scene scene{arr2::BasicSceneBuilder{}
- .set_arrangeable_model(&dup_model)
- .set_arrange_settings(&settings)
- .set_bed(bed)};
- auto task = arr2::MultiplySelectionTask<arr2::ArrangeItem>::create(scene, copies_num);
- auto result = task->process_native(arr2::DummyCtl{});
- bool applied = result->apply_on(scene.model());
- if (applied) {
- dup_model.apply_duplicates();
- store_3mf("dupl_example_result.3mf", &model, nullptr, false);
- REQUIRE(applied);
- }
- size_t new_instcnt = arr2::model_instance_count(model);
- REQUIRE(new_instcnt == (copies_num + 1) * instcnt);
- REQUIRE(std::all_of(result->arranged_items.begin(),
- result->arranged_items.end(),
- [](auto &item) { return arr2::is_arranged(item); }));
- REQUIRE(std::all_of(result->to_add.begin(),
- result->to_add.end(),
- [](auto &item) { return arr2::is_arranged(item); }));
- REQUIRE(std::all_of(task->selected.begin(), task->selected.end(),
- [&bed](auto &item) { return bounding_box(bed).contains(arr2::envelope_bounding_box(item)); }));
- REQUIRE(is_collision_free(range(task->selected)));
- }
|