12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118 |
- #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)));
- }
- // TODO:
- //TEST_CASE("Testing fit-into-bed rotation search", "[arrange2][integration]")
- //{
- // using namespace Slic3r;
- // Model model;
- // ModelObject* new_object = model.add_object();
- // new_object->name = "big_cube";
- // new_object->add_instance();
- // TriangleMesh mesh = make_cube(205., 220., 10.);
- // mesh.rotate_z(15 * PI / 180);
- // ModelVolume* new_volume = new_object->add_volume(mesh);
- // new_volume->name = new_object->name;
- // store_3mf("rotfail.3mf", &model, nullptr, false);
- // arr2::RectangleBed bed{scaled(250.), scaled(210.)};
- // arr2::Scene scene{
- // arr2::SceneBuilder{}
- // .set_bed(bed)
- // .set_model(model)
- // .set_arrange_settings(arr2::ArrangeSettings{}
- // .set_distance_from_objects(0.)
- // .set_rotation_enabled(true)
- // )
- // };
- // auto task = arr2::ArrangeTask<arr2::ArrangeItem>::create(scene);
- // auto result = task->process_native(arr2::DummyCtl{});
- // REQUIRE(result->items.size() == 1);
- // REQUIRE(arr2::get_rotation(result->items.front()) > 0.);
- // REQUIRE(arr2::is_arranged(result->items.front()));
- //}
|