Multiple beds (part 1):

- create new bed when a volume is dragged into it
- limit maximum number of beds
- keep track of which ModelInstances are on which bed
- show list of beds and allow selecting an active one
- move objects from active bed to the first bed when passing Model to Print::apply (and back)
This commit is contained in:
Lukas Matena 2024-10-01 13:00:02 +02:00
parent 9302cb10ae
commit accc4ce266
11 changed files with 301 additions and 21 deletions

View File

@ -21,6 +21,8 @@
#include "libslic3r/Geometry/Circle.hpp"
#include "libslic3r/Polygon.hpp"
#include "MultipleBeds.hpp"
namespace Slic3r {
BuildVolume::BuildVolume(const std::vector<Vec2d> &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<float>());
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<float>(m_circle.center), unscaled<float>(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<double>()); }) :
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<double>()); });
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<double>()); }) :
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<double>()); });
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<Vec3d> 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<double>::max();
if (ignore_bottom)
build_volume.min.z() = -std::numeric_limits<double>::max();
return build_volume.max.z() <= - SceneEpsilon ? ObjectState::Below :
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

View File

@ -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.

View File

@ -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

View File

@ -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<float>(), true /* may be below print bed */);
BuildVolume::ObjectState state = build_volume.object_state(vol->mesh().its, matrix.cast<float>(), 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;
}

View File

@ -0,0 +1,117 @@
#include "MultipleBeds.hpp"
#include "BuildVolume.hpp"
#include "Model.hpp"
#include <cassert>
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<m_number_of_beds)
m_active_bed = i;
}
void MultipleBeds::move_active_to_first_bed(Model& model, const BuildVolume& build_volume, bool to_or_from) const
{
static std::vector<std::pair<Vec3d, bool>> 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);
}
}

View File

@ -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 <map>
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<ObjectID, int> m_inst_to_bed;
};
extern MultipleBeds s_multiple_beds;
} // namespace Slic3r
#endif // libslic3r_MultipleBeds_hpp_

View File

@ -115,6 +115,8 @@ using deque =
template<typename T, typename Q>
inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); }
constexpr size_t MAX_NUMBER_OF_BEDS = 9;
enum Axis {
X=0,
Y,

View File

@ -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)
{

View File

@ -86,7 +86,7 @@ private:
void init_contourlines();
static std::tuple<Type, std::string, std::string> 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);

View File

@ -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<unsigned int> 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,9 +5363,20 @@ 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());

View File

@ -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();