diff --git a/src/libslic3r/BuildVolume.cpp b/src/libslic3r/BuildVolume.cpp index ecbf719c2a..513af79abf 100644 --- a/src/libslic3r/BuildVolume.cpp +++ b/src/libslic3r/BuildVolume.cpp @@ -21,6 +21,8 @@ #include "libslic3r/Geometry/Circle.hpp" #include "libslic3r/Polygon.hpp" +#include "MultipleBeds.hpp" + namespace Slic3r { BuildVolume::BuildVolume(const std::vector &bed_shape, const double max_print_height) : m_bed_shape(bed_shape), m_max_print_height(max_print_height) @@ -284,8 +286,19 @@ BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, con return inside ? (outside ? BuildVolume::ObjectState::Colliding : BuildVolume::ObjectState::Inside) : BuildVolume::ObjectState::Outside; } -BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& its, const Transform3f& trafo, bool may_be_below_bed, bool ignore_bottom) const +BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& its, const Transform3f& trafo_orig, bool may_be_below_bed, bool ignore_bottom, int* bed_idx) const { + ObjectState out = ObjectState::Outside; + if (bed_idx) + *bed_idx = -1; + + for (int bed_id = 0; bed_id <= std::min(s_multiple_beds.get_number_of_beds(), s_multiple_beds.get_max_beds() - 1); ++bed_id) { + + + + Transform3f trafo = trafo_orig; + trafo.pretranslate(-s_multiple_beds.get_bed_translation(bed_id).cast()); + switch (m_type) { case Type::Rectangle: { @@ -298,28 +311,48 @@ BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& i // The following test correctly interprets intersection of a non-convex object with a rectangular build volume. //return rectangle_test(its, trafo, to_2d(build_volume.min), to_2d(build_volume.max), build_volume.max.z()); //FIXME This test does NOT correctly interprets intersection of a non-convex object with a rectangular build volume. - return object_state_templ(its, trafo, may_be_below_bed, [build_volumef](const Vec3f &pt) { return build_volumef.contains(pt); }); + out = object_state_templ(its, trafo, may_be_below_bed, [build_volumef](const Vec3f& pt) { return build_volumef.contains(pt); }); + break; } case Type::Circle: { Geometry::Circlef circle { unscaled(m_circle.center), unscaled(m_circle.radius + SceneEpsilon) }; - return m_max_print_height == 0.0 ? - object_state_templ(its, trafo, may_be_below_bed, [circle](const Vec3f &pt) { return circle.contains(to_2d(pt)); }) : - object_state_templ(its, trafo, may_be_below_bed, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && circle.contains(to_2d(pt)); }); + out = m_max_print_height == 0.0 ? + object_state_templ(its, trafo, may_be_below_bed, [circle](const Vec3f& pt) { return circle.contains(to_2d(pt)); }) : + object_state_templ(its, trafo, may_be_below_bed, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f& pt) { return pt.z() < z && circle.contains(to_2d(pt)); }); + break; } case Type::Convex: //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. case Type::Custom: - return m_max_print_height == 0.0 ? - object_state_templ(its, trafo, may_be_below_bed, [this](const Vec3f &pt) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast()); }) : - object_state_templ(its, trafo, may_be_below_bed, [this, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast()); }); + out = m_max_print_height == 0.0 ? + object_state_templ(its, trafo, may_be_below_bed, [this](const Vec3f& pt) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast()); }) : + object_state_templ(its, trafo, may_be_below_bed, [this, z = m_max_print_height + SceneEpsilon](const Vec3f& pt) { return pt.z() < z && Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast()); }); + break; case Type::Invalid: default: - return ObjectState::Inside; + out = ObjectState::Inside; + break; } + + if (out != ObjectState::Outside) { + if (bed_id == s_multiple_beds.get_number_of_beds()) { + // The object is on the next bed to be added. + s_multiple_beds.request_next_bed(true); + } + if (bed_idx) + *bed_idx = bed_id; + break; + } + + + + } + + return out; } -BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom) const +BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3 volume_bbox_orig, bool ignore_bottom) const { assert(m_type == Type::Rectangle); BoundingBox3Base build_volume = this->bounding_volume().inflated(SceneEpsilon); @@ -327,9 +360,25 @@ BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& vol build_volume.max.z() = std::numeric_limits::max(); if (ignore_bottom) build_volume.min.z() = -std::numeric_limits::max(); - return build_volume.max.z() <= - SceneEpsilon ? ObjectState::Below : - build_volume.contains(volume_bbox) ? ObjectState::Inside : - build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside; + + ObjectState state = ObjectState::Outside; + int bed_idx = 0; + for (bed_idx = 0; bed_idx <= std::min(s_multiple_beds.get_number_of_beds(), s_multiple_beds.get_max_beds() - 1); ++bed_idx) { + BoundingBoxf3 volume_bbox = volume_bbox_orig; + volume_bbox.translate(-s_multiple_beds.get_bed_translation(bed_idx)); + + state = build_volume.max.z() <= -SceneEpsilon ? ObjectState::Below : + build_volume.contains(volume_bbox) ? ObjectState::Inside : + build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside; + if (state != ObjectState::Outside) + break; + } + + if (bed_idx == s_multiple_beds.get_number_of_beds()) { + // The object is on the next bed to be added. + s_multiple_beds.request_next_bed(true); + } + return state; } bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom) const diff --git a/src/libslic3r/BuildVolume.hpp b/src/libslic3r/BuildVolume.hpp index f99683a3a4..c8af4c2f74 100644 --- a/src/libslic3r/BuildVolume.hpp +++ b/src/libslic3r/BuildVolume.hpp @@ -89,10 +89,10 @@ public: // Called by Plater to update Inside / Colliding / Outside state of ModelObjects before slicing. // Called from Model::update_print_volume_state() -> ModelObject::update_instances_print_volume_state() // Using SceneEpsilon - ObjectState object_state(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed, bool ignore_bottom = true) const; + ObjectState object_state(const indexed_triangle_set &its, const Transform3f& trafo, bool may_be_below_bed, bool ignore_bottom = true, int* bed_idx = nullptr) const; // Called by GLVolumeCollection::check_outside_state() after an object is manipulated with gizmos for example. // Called for a rectangular bed: - ObjectState volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom = true) const; + ObjectState volume_state_bbox(BoundingBoxf3 volume_bbox, bool ignore_bottom = true) const; // 2) Test called on G-code paths. // Using BedEpsilon for all tests. diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index de31aaba16..6dfe164e0e 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -445,6 +445,8 @@ set(SLIC3R_SOURCES miniz_extension.hpp miniz_extension.cpp MarchingSquares.hpp + MultipleBeds.cpp + MultipleBeds.hpp Execution/Execution.hpp Execution/ExecutionSeq.hpp Execution/ExecutionTBB.hpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index a889cd3412..04c14cace6 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -21,6 +21,7 @@ #include "MTUtils.hpp" #include "TriangleMeshSlicer.hpp" #include "TriangleSelector.hpp" +#include "MultipleBeds.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" @@ -374,8 +375,10 @@ double Model::max_z() const unsigned int Model::update_print_volume_state(const BuildVolume &build_volume) { unsigned int num_printable = 0; + s_multiple_beds.clear_inst_map(); for (ModelObject* model_object : this->objects) num_printable += model_object->update_instances_print_volume_state(build_volume); + s_multiple_beds.inst_map_updated(); return num_printable; } @@ -1594,11 +1597,12 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume OUTSIDE = 2 }; for (ModelInstance* model_instance : this->instances) { + int bed_idx = -1; unsigned int inside_outside = 0; for (const ModelVolume* vol : this->volumes) if (vol->is_model_part()) { const Transform3d matrix = model_instance->get_matrix() * vol->get_matrix(); - BuildVolume::ObjectState state = build_volume.object_state(vol->mesh().its, matrix.cast(), true /* may be below print bed */); + BuildVolume::ObjectState state = build_volume.object_state(vol->mesh().its, matrix.cast(), true /* may be below print bed */, true /*ignore_bottom*/, &bed_idx); if (state == BuildVolume::ObjectState::Inside) // Volume is completely inside. inside_outside |= INSIDE; @@ -1617,6 +1621,7 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume inside_outside == INSIDE ? ModelInstancePVS_Inside : ModelInstancePVS_Fully_Outside; if (inside_outside == INSIDE) ++num_printable; + s_multiple_beds.set_instance_bed(model_instance->id(), bed_idx); } return num_printable; } diff --git a/src/libslic3r/MultipleBeds.cpp b/src/libslic3r/MultipleBeds.cpp new file mode 100644 index 0000000000..d889a4b85a --- /dev/null +++ b/src/libslic3r/MultipleBeds.cpp @@ -0,0 +1,117 @@ +#include "MultipleBeds.hpp" + +#include "BuildVolume.hpp" +#include "Model.hpp" + +#include + +namespace Slic3r { + +MultipleBeds s_multiple_beds; + + +Vec3d MultipleBeds::get_bed_translation(int id) const +{ + // TODO: Arrange defines this in LogicalBedGap in SceneBuilder.cpp + // TODO: It should be defined as multiple of bed size. + + if (id == 0) + return Vec3d::Zero(); + int x = 0; + int y = 0; +#if 0 + // Linear layout + x = id; +#else + // Grid layout. + ++id; + int a = 1; + while ((a+1)*(a+1) < id) + ++a; + id = id - a*a; + x=a; + y=a; + if (id <= a) + y = id-1; + else + x=id-a-1; +#endif + return 300. * Vec3d(x, y, 0.); +} + + + + + +void MultipleBeds::clear_inst_map() +{ + m_inst_to_bed.clear(); +} + +void MultipleBeds::set_instance_bed(ObjectID id, int bed_idx) +{ + assert(bed_idx < get_max_beds()); + m_inst_to_bed[id] = bed_idx; +} + +void MultipleBeds::inst_map_updated() +{ + int max_bed_idx = 0; + for (const auto& [obj_id, bed_idx] : m_inst_to_bed) + max_bed_idx = std::max(max_bed_idx, bed_idx); + + if (m_number_of_beds != max_bed_idx + 1) { + m_number_of_beds = max_bed_idx + 1; + m_active_bed = m_number_of_beds - 1; + request_next_bed(false); + } + if (m_active_bed >= m_number_of_beds) + m_active_bed = m_number_of_beds - 1; +} + +void MultipleBeds::request_next_bed(bool show) +{ + m_show_next_bed = (get_number_of_beds() < get_max_beds() ? show : false); +} + +void MultipleBeds::set_active_bed(int i) +{ + assert(i < get_max_beds()); + if (i> old_state; + size_t i = 0; + assert(! to_or_from || old_state.empty()); + + for (ModelObject* mo : model.objects) { + for (ModelInstance* mi : mo->instances) { + if (to_or_from) { + old_state.resize(i+1); + old_state[i] = std::make_pair(mi->get_offset(), mi->printable); + if (this->is_instance_on_active_bed(mi->id())) + mi->set_offset(mi->get_offset() - get_bed_translation(get_active_bed())); + else + mi->printable = false; + } else { + mi->set_offset(old_state[i].first); + mi->printable = old_state[i].second; + } + ++i; + } + } + if (! to_or_from) + old_state.clear(); +} + + +bool MultipleBeds::is_instance_on_active_bed(ObjectID id) const +{ + auto it = m_inst_to_bed.find(id); + return (it != m_inst_to_bed.end() && it->second == m_active_bed); +} + +} diff --git a/src/libslic3r/MultipleBeds.hpp b/src/libslic3r/MultipleBeds.hpp new file mode 100644 index 0000000000..4c747fe6f2 --- /dev/null +++ b/src/libslic3r/MultipleBeds.hpp @@ -0,0 +1,56 @@ +#ifndef libslic3r_MultipleBeds_hpp_ +#define libslic3r_MultipleBeds_hpp_ + +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/BoundingBox.hpp" + +#include + +namespace Slic3r { + +class Model; +class BuildVolume; + + +class MultipleBeds { +public: + MultipleBeds() = default; + + static constexpr int get_max_beds() { return MAX_NUMBER_OF_BEDS; }; + Vec3d get_bed_translation(int id) const; + + void clear_inst_map(); + void set_instance_bed(ObjectID id, int bed_idx); + void inst_map_updated(); + + int get_number_of_beds() const { return m_number_of_beds; } + bool should_show_next_bed() const { return m_show_next_bed; } + void request_next_bed(bool show); + int get_active_bed() const { return m_active_bed; } + + void set_active_bed(int i); + void move_active_to_first_bed(Model& model, const BuildVolume& build_volume, bool to_or_from) const; + + + +private: + bool is_instance_on_active_bed(ObjectID id) const; + + + + int m_number_of_beds = 1; + int m_active_bed = 0; + bool m_show_next_bed = false; + std::map m_inst_to_bed; + +}; + +extern MultipleBeds s_multiple_beds; + + + + +} // namespace Slic3r + +#endif // libslic3r_MultipleBeds_hpp_ diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 291e1686ec..b3f08fe40e 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -115,6 +115,8 @@ using deque = template inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); } +constexpr size_t MAX_NUMBER_OF_BEDS = 9; + enum Axis { X=0, Y, diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 6d435177af..26f067cd6c 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -13,6 +13,7 @@ #include "libslic3r/Geometry/Circle.hpp" #include "libslic3r/Tesselate.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/MultipleBeds.hpp" #include "GUI_App.hpp" #include "GLCanvas3D.hpp" @@ -105,22 +106,29 @@ bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, c void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_texture) { - render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, show_texture, false); + for (size_t i = 0; i < s_multiple_beds.get_number_of_beds() + int(s_multiple_beds.should_show_next_bed()); ++i) { + Transform3d mat = view_matrix; + mat.translate(s_multiple_beds.get_bed_translation(i)); + bool is_active = (i == s_multiple_beds.get_active_bed() && s_multiple_beds.get_number_of_beds() != 1); + render_internal(canvas, mat, projection_matrix, bottom, scale_factor, show_texture, false, is_active); + } } void Bed3D::render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor) { - render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, false, true); + render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, false, true, false); } void Bed3D::render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, - bool show_texture, bool picking) + bool show_texture, bool picking, bool active) { m_scale_factor = scale_factor; glsafe(::glEnable(GL_DEPTH_TEST)); m_model.model.set_color(picking ? PICKING_MODEL_COLOR : DEFAULT_MODEL_COLOR); + if (!picking && ! active) + m_model.model.set_color(ColorRGBA(.6f, .6f, 0.6f, 0.5f)); switch (m_type) { diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 1fea354cda..08f65eca35 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -86,7 +86,7 @@ private: void init_contourlines(); static std::tuple detect_type(const Pointfs& shape); void render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, - bool show_texture, bool picking); + bool show_texture, bool picking, bool active); void render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture); void render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix); void render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8a77950221..ddca7796da 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -21,6 +21,7 @@ #include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Layer.hpp" +#include "libslic3r/MultipleBeds.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Technologies.hpp" #include "libslic3r/Tesselate.hpp" @@ -1457,6 +1458,7 @@ bool GLCanvas3D::check_volumes_outside_state(GLVolumeCollection& volumes, ModelI bool contained_min_one = false; const Slic3r::BuildVolume& build_volume = m_bed.build_volume(); + s_multiple_beds.request_next_bed(false); const std::vector volumes_idxs = volumes_to_process_idxs(); for (unsigned int vol_idx : volumes_idxs) { @@ -1828,6 +1830,26 @@ void GLCanvas3D::render() if (show_imgui_demo_window) ImGui::ShowDemoWindow(); #endif // SHOW_IMGUI_DEMO_WINDOW + + { + if (s_multiple_beds.get_number_of_beds() != 1) { + ImGui::SetNextWindowPos(ImVec2(10,10)); + ImGui::SetNextWindowSize(ImVec2(120., 150.)); + ImGui::Begin("Bed selector", 0, ImGuiWindowFlags_NoResize); + for (int i = 0; i < s_multiple_beds.get_number_of_beds(); ++i) { + bool inactive = i != s_multiple_beds.get_active_bed(); + if (inactive) + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0., 0., 0., .5)); + if (ImGui::Button((std::string("Bed number ") + std::to_string(i + 1)).c_str())) + s_multiple_beds.set_active_bed(i); + if (inactive) + ImGui::PopStyleColor(); + } + ImGui::End(); + } + } + + const bool is_looking_downward = camera.is_looking_downward(); // draw scene @@ -5341,8 +5363,19 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by)); } - const BoundingBoxf3 bed_bb = include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume(); + + + const BoundingBoxf3 first_bed_bb = include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume(); + BoundingBoxf3 bed_bb = first_bed_bb; + + for (int i = 0; i < s_multiple_beds.get_number_of_beds() + int(s_multiple_beds.should_show_next_bed()); ++i) { + BoundingBoxf3 this_bed = first_bed_bb; + this_bed.translate(s_multiple_beds.get_bed_translation(i)); + bed_bb.merge(this_bed); + } bb.merge(bed_bb); + + if (!m_main_toolbar.is_enabled()) bb.merge(m_gcode_viewer.get_max_bounding_box()); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 525214cc29..511203470f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -73,6 +73,7 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/miniz_extension.hpp" +#include "libslic3r/MultipleBeds.hpp" // For stl export #include "libslic3r/CSGMesh/ModelToCSGMesh.hpp" @@ -2142,10 +2143,17 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool background_process_timer.Stop(); // Update the "out of print bed" state of ModelInstances. update_print_volume_state(); + + // Move all instances according to their active bed: + s_multiple_beds.move_active_to_first_bed(q->model(), q->build_volume(), true); + // Apply new config to the possibly running background task. bool was_running = background_process.running(); Print::ApplyStatus invalidated = background_process.apply(q->model(), full_config); + // Move all instances back to their respective beds. + s_multiple_beds.move_active_to_first_bed(q->model(), q->build_volume(), false); + // Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile. if (view3D->is_layers_editing_enabled()) view3D->get_wxglcanvas()->Refresh();