Merge branch 'tm_fill_with_instances'
This commit is contained in:
commit
20995c7b7a
11 changed files with 391 additions and 82 deletions
|
@ -7,6 +7,7 @@
|
||||||
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||||
#include <libnest2d/placers/nfpplacer.hpp>
|
#include <libnest2d/placers/nfpplacer.hpp>
|
||||||
#include <libnest2d/selections/firstfit.hpp>
|
#include <libnest2d/selections/firstfit.hpp>
|
||||||
|
#include <libnest2d/utils/rotcalipers.hpp>
|
||||||
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <ClipperUtils.hpp>
|
#include <ClipperUtils.hpp>
|
||||||
|
@ -83,7 +84,7 @@ const double BIG_ITEM_TRESHOLD = 0.02;
|
||||||
// Fill in the placer algorithm configuration with values carefully chosen for
|
// Fill in the placer algorithm configuration with values carefully chosen for
|
||||||
// Slic3r.
|
// Slic3r.
|
||||||
template<class PConf>
|
template<class PConf>
|
||||||
void fill_config(PConf& pcfg) {
|
void fill_config(PConf& pcfg, const ArrangeParams ¶ms) {
|
||||||
|
|
||||||
// Align the arranged pile into the center of the bin
|
// Align the arranged pile into the center of the bin
|
||||||
pcfg.alignment = PConf::Alignment::CENTER;
|
pcfg.alignment = PConf::Alignment::CENTER;
|
||||||
|
@ -93,14 +94,17 @@ void fill_config(PConf& pcfg) {
|
||||||
|
|
||||||
// TODO cannot use rotations until multiple objects of same geometry can
|
// TODO cannot use rotations until multiple objects of same geometry can
|
||||||
// handle different rotations.
|
// handle different rotations.
|
||||||
pcfg.rotations = { 0.0 };
|
if (params.allow_rotations)
|
||||||
|
pcfg.rotations = {0., PI / 2., PI, 3. * PI / 2. };
|
||||||
|
else
|
||||||
|
pcfg.rotations = {0.};
|
||||||
|
|
||||||
// The accuracy of optimization.
|
// The accuracy of optimization.
|
||||||
// Goes from 0.0 to 1.0 and scales performance as well
|
// Goes from 0.0 to 1.0 and scales performance as well
|
||||||
pcfg.accuracy = 0.65f;
|
pcfg.accuracy = params.accuracy;
|
||||||
|
|
||||||
// Allow parallel execution.
|
// Allow parallel execution.
|
||||||
pcfg.parallel = true;
|
pcfg.parallel = params.parallel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply penalty to object function result. This is used only when alignment
|
// Apply penalty to object function result. This is used only when alignment
|
||||||
|
@ -304,15 +308,15 @@ protected:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AutoArranger(const TBin & bin,
|
AutoArranger(const TBin & bin,
|
||||||
Distance dist,
|
const ArrangeParams ¶ms,
|
||||||
std::function<void(unsigned)> progressind,
|
std::function<void(unsigned)> progressind,
|
||||||
std::function<bool(void)> stopcond)
|
std::function<bool(void)> stopcond)
|
||||||
: m_pck(bin, dist)
|
: m_pck(bin, params.min_obj_distance)
|
||||||
, m_bin(bin)
|
, m_bin(bin)
|
||||||
, m_bin_area(sl::area(bin))
|
, m_bin_area(sl::area(bin))
|
||||||
, m_norm(std::sqrt(m_bin_area))
|
, m_norm(std::sqrt(m_bin_area))
|
||||||
{
|
{
|
||||||
fill_config(m_pconf);
|
fill_config(m_pconf, params);
|
||||||
|
|
||||||
// Set up a callback that is called just before arranging starts
|
// Set up a callback that is called just before arranging starts
|
||||||
// This functionality is provided by the Nester class (m_pack).
|
// This functionality is provided by the Nester class (m_pack).
|
||||||
|
@ -349,12 +353,6 @@ public:
|
||||||
|
|
||||||
m_pck.configure(m_pconf);
|
m_pck.configure(m_pconf);
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoArranger(const TBin & bin,
|
|
||||||
std::function<void(unsigned)> progressind,
|
|
||||||
std::function<bool(void)> stopcond)
|
|
||||||
: AutoArranger{bin, 0 /* no min distance */, progressind, stopcond}
|
|
||||||
{}
|
|
||||||
|
|
||||||
template<class It> inline void operator()(It from, It to) {
|
template<class It> inline void operator()(It from, It to) {
|
||||||
m_rtree.clear();
|
m_rtree.clear();
|
||||||
|
@ -452,12 +450,18 @@ template<class Bin> void remove_large_items(std::vector<Item> &items, Bin &&bin)
|
||||||
++it : it = items.erase(it);
|
++it : it = items.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class S> Radians min_area_boundingbox_rotation(const S &sh)
|
||||||
|
{
|
||||||
|
return minAreaBoundingBox<S, TCompute<S>, boost::rational<LargeInt>>(sh)
|
||||||
|
.angleToX();
|
||||||
|
}
|
||||||
|
|
||||||
template<class BinT> // Arrange for arbitrary bin type
|
template<class BinT> // Arrange for arbitrary bin type
|
||||||
void _arrange(
|
void _arrange(
|
||||||
std::vector<Item> & shapes,
|
std::vector<Item> & shapes,
|
||||||
std::vector<Item> & excludes,
|
std::vector<Item> & excludes,
|
||||||
const BinT & bin,
|
const BinT & bin,
|
||||||
const ArrangeParams & params,
|
const ArrangeParams ¶ms,
|
||||||
std::function<void(unsigned)> progressfn,
|
std::function<void(unsigned)> progressfn,
|
||||||
std::function<bool()> stopfn)
|
std::function<bool()> stopfn)
|
||||||
{
|
{
|
||||||
|
@ -467,11 +471,10 @@ void _arrange(
|
||||||
|
|
||||||
auto corrected_bin = bin;
|
auto corrected_bin = bin;
|
||||||
sl::offset(corrected_bin, md);
|
sl::offset(corrected_bin, md);
|
||||||
|
ArrangeParams mod_params = params;
|
||||||
AutoArranger<BinT> arranger{corrected_bin, progressfn, stopfn};
|
mod_params.min_obj_distance = 0;
|
||||||
|
|
||||||
arranger.config().accuracy = params.accuracy;
|
AutoArranger<BinT> arranger{corrected_bin, mod_params, progressfn, stopfn};
|
||||||
arranger.config().parallel = params.parallel;
|
|
||||||
|
|
||||||
auto infl = coord_t(std::ceil(params.min_obj_distance / 2.0));
|
auto infl = coord_t(std::ceil(params.min_obj_distance / 2.0));
|
||||||
for (Item& itm : shapes) itm.inflate(infl);
|
for (Item& itm : shapes) itm.inflate(infl);
|
||||||
|
@ -487,6 +490,13 @@ void _arrange(
|
||||||
for (auto &itm : shapes ) inp.emplace_back(itm);
|
for (auto &itm : shapes ) inp.emplace_back(itm);
|
||||||
for (auto &itm : excludes) inp.emplace_back(itm);
|
for (auto &itm : excludes) inp.emplace_back(itm);
|
||||||
|
|
||||||
|
// Use the minimum bounding box rotation as a starting point.
|
||||||
|
// TODO: This only works for convex hull. If we ever switch to concave
|
||||||
|
// polygon nesting, a convex hull needs to be calculated.
|
||||||
|
if (params.allow_rotations)
|
||||||
|
for (auto &itm : shapes)
|
||||||
|
itm.rotation(min_area_boundingbox_rotation(itm.rawShape()));
|
||||||
|
|
||||||
arranger(inp.begin(), inp.end());
|
arranger(inp.begin(), inp.end());
|
||||||
for (Item &itm : inp) itm.inflate(-infl);
|
for (Item &itm : inp) itm.inflate(-infl);
|
||||||
}
|
}
|
||||||
|
@ -556,28 +566,35 @@ static void process_arrangeable(const ArrangePolygon &arrpoly,
|
||||||
outp.back().priority(arrpoly.priority);
|
outp.back().priority(arrpoly.priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
|
||||||
|
{
|
||||||
|
if (bed.empty())
|
||||||
|
return fn(InfiniteBed{});
|
||||||
|
else if (bed.size() == 1)
|
||||||
|
return fn(InfiniteBed{bed.front()});
|
||||||
|
else {
|
||||||
|
auto bb = BoundingBox(bed);
|
||||||
|
CircleBed circ = to_circle(bb.center(), bed);
|
||||||
|
auto parea = poly_area(bed);
|
||||||
|
|
||||||
|
if ((1.0 - parea / area(bb)) < 1e-3)
|
||||||
|
return fn(bb);
|
||||||
|
else if (!std::isnan(circ.radius()))
|
||||||
|
return fn(circ);
|
||||||
|
else
|
||||||
|
return fn(Polygon(bed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
void arrange(ArrangePolygons & items,
|
void arrange(ArrangePolygons & items,
|
||||||
const ArrangePolygons &excludes,
|
const ArrangePolygons &excludes,
|
||||||
const Points & bed,
|
const Points & bed,
|
||||||
const ArrangeParams & params)
|
const ArrangeParams & params)
|
||||||
{
|
{
|
||||||
if (bed.empty())
|
call_with_bed(bed, [&](const auto &bin) {
|
||||||
arrange(items, excludes, InfiniteBed{}, params);
|
arrange(items, excludes, bin, params);
|
||||||
else if (bed.size() == 1)
|
});
|
||||||
arrange(items, excludes, InfiniteBed{bed.front()}, params);
|
|
||||||
else {
|
|
||||||
auto bb = BoundingBox(bed);
|
|
||||||
CircleBed circ = to_circle(bb.center(), bed);
|
|
||||||
auto parea = poly_area(bed);
|
|
||||||
|
|
||||||
if ((1.0 - parea / area(bb)) < 1e-3)
|
|
||||||
arrange(items, excludes, bb, params);
|
|
||||||
else if (!std::isnan(circ.radius()))
|
|
||||||
arrange(items, excludes, circ, params);
|
|
||||||
else
|
|
||||||
arrange(items, excludes, Polygon(bed), params);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class BedT>
|
template<class BedT>
|
||||||
|
|
|
@ -78,6 +78,8 @@ struct ArrangeParams {
|
||||||
|
|
||||||
/// Allow parallel execution.
|
/// Allow parallel execution.
|
||||||
bool parallel = true;
|
bool parallel = true;
|
||||||
|
|
||||||
|
bool allow_rotations = false;
|
||||||
|
|
||||||
/// Progress indicator callback called when an object gets packed.
|
/// Progress indicator callback called when an object gets packed.
|
||||||
/// The unsigned argument is the number of items remaining to pack.
|
/// The unsigned argument is the number of items remaining to pack.
|
||||||
|
|
|
@ -162,6 +162,8 @@ set(SLIC3R_GUI_SOURCES
|
||||||
GUI/Jobs/ArrangeJob.cpp
|
GUI/Jobs/ArrangeJob.cpp
|
||||||
GUI/Jobs/RotoptimizeJob.hpp
|
GUI/Jobs/RotoptimizeJob.hpp
|
||||||
GUI/Jobs/RotoptimizeJob.cpp
|
GUI/Jobs/RotoptimizeJob.cpp
|
||||||
|
GUI/Jobs/FillBedJob.hpp
|
||||||
|
GUI/Jobs/FillBedJob.cpp
|
||||||
GUI/Jobs/SLAImportJob.hpp
|
GUI/Jobs/SLAImportJob.hpp
|
||||||
GUI/Jobs/SLAImportJob.cpp
|
GUI/Jobs/SLAImportJob.cpp
|
||||||
GUI/Jobs/ProgressIndicator.hpp
|
GUI/Jobs/ProgressIndicator.hpp
|
||||||
|
|
|
@ -170,7 +170,7 @@ void GLCanvas3D::LayersEditing::init()
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config)
|
void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config)
|
||||||
{
|
{
|
||||||
m_config = config;
|
m_config = config;
|
||||||
delete m_slicing_parameters;
|
delete m_slicing_parameters;
|
||||||
m_slicing_parameters = nullptr;
|
m_slicing_parameters = nullptr;
|
||||||
|
@ -1091,6 +1091,25 @@ wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent);
|
||||||
|
|
||||||
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
|
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
|
||||||
|
|
||||||
|
GLCanvas3D::ArrangeSettings load_arrange_settings()
|
||||||
|
{
|
||||||
|
GLCanvas3D::ArrangeSettings settings;
|
||||||
|
|
||||||
|
std::string dist_str =
|
||||||
|
wxGetApp().app_config->get("arrange", "min_object_distance");
|
||||||
|
|
||||||
|
std::string en_rot_str =
|
||||||
|
wxGetApp().app_config->get("arrange", "enable_rotation");
|
||||||
|
|
||||||
|
if (!dist_str.empty())
|
||||||
|
settings.distance = std::stof(dist_str);
|
||||||
|
|
||||||
|
if (!en_rot_str.empty())
|
||||||
|
settings.enable_rotation = (en_rot_str == "1" || en_rot_str == "yes");
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
|
GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
|
||||||
: m_canvas(canvas)
|
: m_canvas(canvas)
|
||||||
, m_context(nullptr)
|
, m_context(nullptr)
|
||||||
|
@ -1133,6 +1152,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
|
||||||
#endif // ENABLE_RETINA_GL
|
#endif // ENABLE_RETINA_GL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_arrange_settings = load_arrange_settings();
|
||||||
|
|
||||||
m_selection.set_volumes(&m_volumes.volumes);
|
m_selection.set_volumes(&m_volumes.volumes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3847,6 +3868,31 @@ bool GLCanvas3D::_render_search_list(float pos_x) const
|
||||||
return action_taken;
|
return action_taken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GLCanvas3D:: _render_arrange_popup()
|
||||||
|
{
|
||||||
|
ImGuiWrapper *imgui = wxGetApp().imgui();
|
||||||
|
|
||||||
|
float x = 0.5f * (float)get_canvas_size().get_width();
|
||||||
|
imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
|
||||||
|
|
||||||
|
imgui->begin(_(L("Arrange options")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
|
||||||
|
ArrangeSettings settings = m_arrange_settings;
|
||||||
|
|
||||||
|
auto &appcfg = wxGetApp().app_config;
|
||||||
|
|
||||||
|
if (imgui->slider_float(_(L("Gap size")), &settings.distance, 0.f, 100.f)) {
|
||||||
|
m_arrange_settings.distance = settings.distance;
|
||||||
|
appcfg->set("arrange", "min_object_distance", std::to_string(settings.distance));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imgui->checkbox(_(L("Enable rotations")), settings.enable_rotation)) {
|
||||||
|
m_arrange_settings.enable_rotation = settings.enable_rotation;
|
||||||
|
appcfg->set("arrange", "enable_rotation", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
imgui->end();
|
||||||
|
}
|
||||||
|
|
||||||
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
|
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
|
||||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||||
static void debug_output_thumbnail(const ThumbnailData& thumbnail_data)
|
static void debug_output_thumbnail(const ThumbnailData& thumbnail_data)
|
||||||
|
@ -4263,9 +4309,19 @@ bool GLCanvas3D::_init_main_toolbar()
|
||||||
item.sprite_id = 3;
|
item.sprite_id = 3;
|
||||||
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); };
|
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); };
|
||||||
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
|
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
|
||||||
|
item.right.toggable = true;
|
||||||
|
item.right.render_callback = [this](float left, float right, float, float) {
|
||||||
|
if (m_canvas != nullptr)
|
||||||
|
{
|
||||||
|
_render_arrange_popup();
|
||||||
|
}
|
||||||
|
};
|
||||||
if (!m_main_toolbar.add_item(item))
|
if (!m_main_toolbar.add_item(item))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
item.right.toggable = false;
|
||||||
|
item.right.render_callback = GLToolbarItem::Default_Render_Callback;
|
||||||
|
|
||||||
if (!m_main_toolbar.add_separator())
|
if (!m_main_toolbar.add_separator())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
|
@ -381,6 +381,13 @@ public:
|
||||||
Cross
|
Cross
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ArrangeSettings
|
||||||
|
{
|
||||||
|
float distance = 6.;
|
||||||
|
float accuracy = 0.65f;
|
||||||
|
bool enable_rotation = false;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
wxGLCanvas* m_canvas;
|
wxGLCanvas* m_canvas;
|
||||||
wxGLContext* m_context;
|
wxGLContext* m_context;
|
||||||
|
@ -452,6 +459,8 @@ private:
|
||||||
mutable bool m_tooltip_enabled{ true };
|
mutable bool m_tooltip_enabled{ true };
|
||||||
Slope m_slope;
|
Slope m_slope;
|
||||||
|
|
||||||
|
ArrangeSettings m_arrange_settings;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GLCanvas3D(wxGLCanvas* canvas);
|
explicit GLCanvas3D(wxGLCanvas* canvas);
|
||||||
~GLCanvas3D();
|
~GLCanvas3D();
|
||||||
|
@ -671,6 +680,8 @@ public:
|
||||||
void use_slope(bool use) { m_slope.use(use); }
|
void use_slope(bool use) { m_slope.use(use); }
|
||||||
void set_slope_normal_angle(float angle_in_deg) { m_slope.set_normal_angle(angle_in_deg); }
|
void set_slope_normal_angle(float angle_in_deg) { m_slope.set_normal_angle(angle_in_deg); }
|
||||||
|
|
||||||
|
const ArrangeSettings& get_arrange_settings() const { return m_arrange_settings; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _is_shown_on_screen() const;
|
bool _is_shown_on_screen() const;
|
||||||
|
|
||||||
|
@ -717,6 +728,7 @@ private:
|
||||||
void _render_selection_sidebar_hints() const;
|
void _render_selection_sidebar_hints() const;
|
||||||
bool _render_undo_redo_stack(const bool is_undo, float pos_x) const;
|
bool _render_undo_redo_stack(const bool is_undo, float pos_x) const;
|
||||||
bool _render_search_list(float pos_x) const;
|
bool _render_search_list(float pos_x) const;
|
||||||
|
void _render_arrange_popup();
|
||||||
void _render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
|
void _render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
|
||||||
// render thumbnail using an off-screen framebuffer
|
// render thumbnail using an off-screen framebuffer
|
||||||
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
|
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
|
||||||
|
|
|
@ -46,7 +46,7 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static WipeTower get_wipe_tower(Plater &plater)
|
static WipeTower get_wipe_tower(const Plater &plater)
|
||||||
{
|
{
|
||||||
return WipeTower{plater.canvas3D()->get_wipe_tower_info()};
|
return WipeTower{plater.canvas3D()->get_wipe_tower_info()};
|
||||||
}
|
}
|
||||||
|
@ -68,18 +68,13 @@ void ArrangeJob::clear_input()
|
||||||
m_unprintable.reserve(cunprint /* for optional wti */);
|
m_unprintable.reserve(cunprint /* for optional wti */);
|
||||||
}
|
}
|
||||||
|
|
||||||
double ArrangeJob::bed_stride() const {
|
|
||||||
double bedwidth = m_plater->bed_shape_bb().size().x();
|
|
||||||
return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArrangeJob::prepare_all() {
|
void ArrangeJob::prepare_all() {
|
||||||
clear_input();
|
clear_input();
|
||||||
|
|
||||||
for (ModelObject *obj: m_plater->model().objects)
|
for (ModelObject *obj: m_plater->model().objects)
|
||||||
for (ModelInstance *mi : obj->instances) {
|
for (ModelInstance *mi : obj->instances) {
|
||||||
ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
|
ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
|
||||||
cont.emplace_back(get_arrange_poly(mi));
|
cont.emplace_back(get_arrange_poly(mi, m_plater));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto wti = get_wipe_tower(*m_plater))
|
if (auto wti = get_wipe_tower(*m_plater))
|
||||||
|
@ -90,7 +85,7 @@ void ArrangeJob::prepare_selected() {
|
||||||
clear_input();
|
clear_input();
|
||||||
|
|
||||||
Model &model = m_plater->model();
|
Model &model = m_plater->model();
|
||||||
double stride = bed_stride();
|
double stride = bed_stride(m_plater);
|
||||||
|
|
||||||
std::vector<const Selection::InstanceIdxsList *>
|
std::vector<const Selection::InstanceIdxsList *>
|
||||||
obj_sel(model.objects.size(), nullptr);
|
obj_sel(model.objects.size(), nullptr);
|
||||||
|
@ -111,7 +106,7 @@ void ArrangeJob::prepare_selected() {
|
||||||
inst_sel[size_t(inst_id)] = true;
|
inst_sel[size_t(inst_id)] = true;
|
||||||
|
|
||||||
for (size_t i = 0; i < inst_sel.size(); ++i) {
|
for (size_t i = 0; i < inst_sel.size(); ++i) {
|
||||||
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
|
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i], m_plater);
|
||||||
|
|
||||||
ArrangePolygons &cont = mo->instances[i]->printable ?
|
ArrangePolygons &cont = mo->instances[i]->printable ?
|
||||||
(inst_sel[i] ? m_selected :
|
(inst_sel[i] ? m_selected :
|
||||||
|
@ -123,7 +118,7 @@ void ArrangeJob::prepare_selected() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto wti = get_wipe_tower(*m_plater)) {
|
if (auto wti = get_wipe_tower(*m_plater)) {
|
||||||
ArrangePolygon &&ap = get_arrange_poly(&wti);
|
ArrangePolygon &&ap = get_arrange_poly(&wti, m_plater);
|
||||||
|
|
||||||
m_plater->get_selection().is_wipe_tower() ?
|
m_plater->get_selection().is_wipe_tower() ?
|
||||||
m_selected.emplace_back(std::move(ap)) :
|
m_selected.emplace_back(std::move(ap)) :
|
||||||
|
@ -147,11 +142,13 @@ void ArrangeJob::prepare()
|
||||||
void ArrangeJob::process()
|
void ArrangeJob::process()
|
||||||
{
|
{
|
||||||
static const auto arrangestr = _(L("Arranging"));
|
static const auto arrangestr = _(L("Arranging"));
|
||||||
|
|
||||||
double dist = min_object_distance(*m_plater->config());
|
GLCanvas3D::ArrangeSettings settings =
|
||||||
|
m_plater->canvas3D()->get_arrange_settings();
|
||||||
|
|
||||||
arrangement::ArrangeParams params;
|
arrangement::ArrangeParams params;
|
||||||
params.min_obj_distance = scaled(dist);
|
params.min_obj_distance = scaled(settings.distance);
|
||||||
|
params.allow_rotations = settings.enable_rotation;
|
||||||
|
|
||||||
auto count = unsigned(m_selected.size() + m_unprintable.size());
|
auto count = unsigned(m_selected.size() + m_unprintable.size());
|
||||||
Points bedpts = get_bed_shape(*m_plater->config());
|
Points bedpts = get_bed_shape(*m_plater->config());
|
||||||
|
@ -211,14 +208,25 @@ void ArrangeJob::finalize() {
|
||||||
Job::finalize();
|
Job::finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &plater)
|
std::optional<arrangement::ArrangePolygon>
|
||||||
|
get_wipe_tower_arrangepoly(const Plater &plater)
|
||||||
{
|
{
|
||||||
return WipeTower{plater.canvas3D()->get_wipe_tower_info()}.get_arrange_polygon();
|
if (auto wti = get_wipe_tower(plater))
|
||||||
|
return wti.get_arrange_polygon();
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap)
|
void apply_wipe_tower_arrangepoly(Plater & plater,
|
||||||
|
const arrangement::ArrangePolygon &ap)
|
||||||
{
|
{
|
||||||
WipeTower{plater.canvas3D()->get_wipe_tower_info()}.apply_arrange_result(ap.translation.cast<double>(), ap.rotation);
|
WipeTower{plater.canvas3D()->get_wipe_tower_info()}
|
||||||
|
.apply_arrange_result(ap.translation.cast<double>(), ap.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
double bed_stride(const Plater *plater) {
|
||||||
|
double bedwidth = plater->bed_shape_bb().size().x();
|
||||||
|
return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
}} // namespace Slic3r::GUI
|
}} // namespace Slic3r::GUI
|
||||||
|
|
|
@ -14,35 +14,12 @@ class ArrangeJob : public Job
|
||||||
|
|
||||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||||
using ArrangePolygons = arrangement::ArrangePolygons;
|
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||||
|
|
||||||
// The gap between logical beds in the x axis expressed in ratio of
|
|
||||||
// the current bed width.
|
|
||||||
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
|
|
||||||
|
|
||||||
ArrangePolygons m_selected, m_unselected, m_unprintable;
|
ArrangePolygons m_selected, m_unselected, m_unprintable;
|
||||||
|
|
||||||
// clear m_selected and m_unselected, reserve space for next usage
|
// clear m_selected and m_unselected, reserve space for next usage
|
||||||
void clear_input();
|
void clear_input();
|
||||||
|
|
||||||
// Stride between logical beds
|
|
||||||
double bed_stride() const;
|
|
||||||
|
|
||||||
// Set up arrange polygon for a ModelInstance and Wipe tower
|
|
||||||
template<class T> ArrangePolygon get_arrange_poly(T *obj) const
|
|
||||||
{
|
|
||||||
ArrangePolygon ap = obj->get_arrange_polygon();
|
|
||||||
ap.priority = 0;
|
|
||||||
ap.bed_idx = ap.translation.x() / bed_stride();
|
|
||||||
ap.setter = [obj, this](const ArrangePolygon &p) {
|
|
||||||
if (p.is_arranged()) {
|
|
||||||
Vec2d t = p.translation.cast<double>();
|
|
||||||
t.x() += p.bed_idx * bed_stride();
|
|
||||||
obj->apply_arrange_result(t, p.rotation);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return ap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare all objects on the bed regardless of the selection
|
// Prepare all objects on the bed regardless of the selection
|
||||||
void prepare_all();
|
void prepare_all();
|
||||||
|
|
||||||
|
@ -69,9 +46,37 @@ public:
|
||||||
void finalize() override;
|
void finalize() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &);
|
std::optional<arrangement::ArrangePolygon> get_wipe_tower_arrangepoly(const Plater &);
|
||||||
void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap);
|
void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap);
|
||||||
|
|
||||||
|
// The gap between logical beds in the x axis expressed in ratio of
|
||||||
|
// the current bed width.
|
||||||
|
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
|
||||||
|
|
||||||
|
// Stride between logical beds
|
||||||
|
double bed_stride(const Plater *plater);
|
||||||
|
|
||||||
|
// Set up arrange polygon for a ModelInstance and Wipe tower
|
||||||
|
template<class T>
|
||||||
|
arrangement::ArrangePolygon get_arrange_poly(T *obj, const Plater *plater)
|
||||||
|
{
|
||||||
|
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||||
|
|
||||||
|
ArrangePolygon ap = obj->get_arrange_polygon();
|
||||||
|
ap.priority = 0;
|
||||||
|
ap.bed_idx = ap.translation.x() / bed_stride(plater);
|
||||||
|
ap.setter = [obj, plater](const ArrangePolygon &p) {
|
||||||
|
if (p.is_arranged()) {
|
||||||
|
Vec2d t = p.translation.cast<double>();
|
||||||
|
t.x() += p.bed_idx * bed_stride(plater);
|
||||||
|
obj->apply_arrange_result(t, p.rotation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return ap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}} // namespace Slic3r::GUI
|
}} // namespace Slic3r::GUI
|
||||||
|
|
||||||
#endif // ARRANGEJOB_HPP
|
#endif // ARRANGEJOB_HPP
|
||||||
|
|
139
src/slic3r/GUI/Jobs/FillBedJob.cpp
Normal file
139
src/slic3r/GUI/Jobs/FillBedJob.cpp
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
#include "FillBedJob.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
|
|
||||||
|
#include "slic3r/GUI/Plater.hpp"
|
||||||
|
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||||
|
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
void FillBedJob::prepare()
|
||||||
|
{
|
||||||
|
m_selected.clear();
|
||||||
|
m_unselected.clear();
|
||||||
|
m_bedpts.clear();
|
||||||
|
|
||||||
|
m_object_idx = m_plater->get_selected_object_idx();
|
||||||
|
if (m_object_idx == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ModelObject *model_object = m_plater->model().objects[m_object_idx];
|
||||||
|
if (model_object->instances.empty()) return;
|
||||||
|
|
||||||
|
m_selected.reserve(model_object->instances.size());
|
||||||
|
for (ModelInstance *inst : model_object->instances)
|
||||||
|
if (inst->printable) {
|
||||||
|
ArrangePolygon ap = get_arrange_poly(inst, m_plater);
|
||||||
|
++ap.priority; // need to be included in the result
|
||||||
|
m_selected.emplace_back(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_selected.empty()) return;
|
||||||
|
|
||||||
|
m_bedpts = get_bed_shape(*m_plater->config());
|
||||||
|
|
||||||
|
auto &objects = m_plater->model().objects;
|
||||||
|
for (size_t idx = 0; idx < objects.size(); ++idx)
|
||||||
|
if (int(idx) != m_object_idx)
|
||||||
|
for (const ModelInstance *mi : objects[idx]->instances) {
|
||||||
|
m_unselected.emplace_back(mi->get_arrange_polygon());
|
||||||
|
m_unselected.back().bed_idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto wt = get_wipe_tower_arrangepoly(*m_plater))
|
||||||
|
m_unselected.emplace_back(std::move(*wt));
|
||||||
|
|
||||||
|
double sc = scaled<double>(1.) * scaled(1.);
|
||||||
|
|
||||||
|
ExPolygon poly = m_selected.front().poly;
|
||||||
|
double poly_area = poly.area() / sc;
|
||||||
|
double unsel_area = std::accumulate(m_unselected.begin(),
|
||||||
|
m_unselected.end(), 0.,
|
||||||
|
[](double s, const auto &ap) {
|
||||||
|
return s + ap.poly.area();
|
||||||
|
}) / sc;
|
||||||
|
|
||||||
|
double fixed_area = unsel_area + m_selected.size() * poly_area;
|
||||||
|
|
||||||
|
// This is the maximum range, the real number will always be close but less.
|
||||||
|
double bed_area = Polygon{m_bedpts}.area() / sc;
|
||||||
|
|
||||||
|
m_status_range = (bed_area - fixed_area) / poly_area;
|
||||||
|
|
||||||
|
ModelInstance *mi = model_object->instances[0];
|
||||||
|
for (int i = 0; i < m_status_range; ++i) {
|
||||||
|
ArrangePolygon ap;
|
||||||
|
ap.poly = m_selected.front().poly;
|
||||||
|
ap.bed_idx = arrangement::UNARRANGED;
|
||||||
|
ap.setter = [this, mi](const ArrangePolygon &p) {
|
||||||
|
ModelObject *mo = m_plater->model().objects[m_object_idx];
|
||||||
|
ModelInstance *inst = mo->add_instance(*mi);
|
||||||
|
inst->apply_arrange_result(p.translation.cast<double>(), p.rotation);
|
||||||
|
};
|
||||||
|
m_selected.emplace_back(ap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillBedJob::process()
|
||||||
|
{
|
||||||
|
if (m_object_idx == -1 || m_selected.empty()) return;
|
||||||
|
|
||||||
|
GLCanvas3D::ArrangeSettings settings =
|
||||||
|
m_plater->canvas3D()->get_arrange_settings();
|
||||||
|
|
||||||
|
arrangement::ArrangeParams params;
|
||||||
|
params.min_obj_distance = scaled(settings.distance);
|
||||||
|
params.allow_rotations = settings.enable_rotation;
|
||||||
|
|
||||||
|
params.stopcondition = [this]() { return was_canceled(); };
|
||||||
|
|
||||||
|
params.progressind = [this](unsigned st) {
|
||||||
|
if (st > 0)
|
||||||
|
update_status(int(m_status_range - st), _(L("Filling bed")));
|
||||||
|
};
|
||||||
|
|
||||||
|
arrangement::arrange(m_selected, m_unselected, m_bedpts, params);
|
||||||
|
|
||||||
|
// finalize just here.
|
||||||
|
update_status(m_status_range, was_canceled() ?
|
||||||
|
_(L("Bed filling canceled.")) :
|
||||||
|
_(L("Bed filling done.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillBedJob::finalize()
|
||||||
|
{
|
||||||
|
if (m_object_idx == -1) return;
|
||||||
|
|
||||||
|
ModelObject *model_object = m_plater->model().objects[m_object_idx];
|
||||||
|
if (model_object->instances.empty()) return;
|
||||||
|
|
||||||
|
size_t inst_cnt = model_object->instances.size();
|
||||||
|
|
||||||
|
for (ArrangePolygon &ap : m_selected) {
|
||||||
|
if (ap.priority != 0 || !(ap.bed_idx == arrangement::UNARRANGED || ap.bed_idx > 0))
|
||||||
|
ap.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
model_object->ensure_on_bed();
|
||||||
|
|
||||||
|
m_plater->update();
|
||||||
|
|
||||||
|
int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0,
|
||||||
|
[](int s, auto &ap) {
|
||||||
|
return s + int(ap.priority == 0 && ap.bed_idx == 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// FIXME: somebody explain why this is needed for increase_object_instances
|
||||||
|
if (inst_cnt == 1) added_cnt++;
|
||||||
|
|
||||||
|
if (added_cnt > 0)
|
||||||
|
m_plater->sidebar()
|
||||||
|
.obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt));
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GUI
|
46
src/slic3r/GUI/Jobs/FillBedJob.hpp
Normal file
46
src/slic3r/GUI/Jobs/FillBedJob.hpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#ifndef FILLBEDJOB_HPP
|
||||||
|
#define FILLBEDJOB_HPP
|
||||||
|
|
||||||
|
#include "ArrangeJob.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
|
class Plater;
|
||||||
|
|
||||||
|
class FillBedJob : public Job
|
||||||
|
{
|
||||||
|
Plater *m_plater;
|
||||||
|
int m_object_idx = -1;
|
||||||
|
|
||||||
|
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||||
|
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||||
|
|
||||||
|
ArrangePolygons m_selected;
|
||||||
|
ArrangePolygons m_unselected;
|
||||||
|
|
||||||
|
Points m_bedpts;
|
||||||
|
|
||||||
|
int m_status_range = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void prepare() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FillBedJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||||
|
: Job{std::move(pri)}, m_plater{plater}
|
||||||
|
{}
|
||||||
|
|
||||||
|
int status_range() const override
|
||||||
|
{
|
||||||
|
return m_status_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
void process() override;
|
||||||
|
|
||||||
|
void finalize() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GUI
|
||||||
|
|
||||||
|
#endif // FILLBEDJOB_HPP
|
|
@ -63,6 +63,7 @@
|
||||||
#include "Mouse3DController.hpp"
|
#include "Mouse3DController.hpp"
|
||||||
#include "Tab.hpp"
|
#include "Tab.hpp"
|
||||||
#include "Jobs/ArrangeJob.hpp"
|
#include "Jobs/ArrangeJob.hpp"
|
||||||
|
#include "Jobs/FillBedJob.hpp"
|
||||||
#include "Jobs/RotoptimizeJob.hpp"
|
#include "Jobs/RotoptimizeJob.hpp"
|
||||||
#include "Jobs/SLAImportJob.hpp"
|
#include "Jobs/SLAImportJob.hpp"
|
||||||
#include "BackgroundSlicingProcess.hpp"
|
#include "BackgroundSlicingProcess.hpp"
|
||||||
|
@ -1573,7 +1574,7 @@ struct Plater::priv
|
||||||
class Jobs: public ExclusiveJobGroup
|
class Jobs: public ExclusiveJobGroup
|
||||||
{
|
{
|
||||||
priv *m;
|
priv *m;
|
||||||
size_t m_arrange_id, m_rotoptimize_id, m_sla_import_id;
|
size_t m_arrange_id, m_fill_bed_id, m_rotoptimize_id, m_sla_import_id;
|
||||||
|
|
||||||
void before_start() override { m->background_process.stop(); }
|
void before_start() override { m->background_process.stop(); }
|
||||||
|
|
||||||
|
@ -1581,6 +1582,7 @@ struct Plater::priv
|
||||||
Jobs(priv *_m) : m(_m)
|
Jobs(priv *_m) : m(_m)
|
||||||
{
|
{
|
||||||
m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q));
|
m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q));
|
||||||
|
m_fill_bed_id = add_job(std::make_unique<FillBedJob>(m->statusbar(), m->q));
|
||||||
m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q));
|
m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q));
|
||||||
m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->statusbar(), m->q));
|
m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->statusbar(), m->q));
|
||||||
}
|
}
|
||||||
|
@ -1590,6 +1592,12 @@ struct Plater::priv
|
||||||
m->take_snapshot(_(L("Arrange")));
|
m->take_snapshot(_(L("Arrange")));
|
||||||
start(m_arrange_id);
|
start(m_arrange_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fill_bed()
|
||||||
|
{
|
||||||
|
m->take_snapshot(_(L("Fill bed")));
|
||||||
|
start(m_fill_bed_id);
|
||||||
|
}
|
||||||
|
|
||||||
void optimize_rotation()
|
void optimize_rotation()
|
||||||
{
|
{
|
||||||
|
@ -2731,8 +2739,8 @@ void Plater::find_new_position(const ModelInstancePtrs &instances,
|
||||||
movable.emplace_back(std::move(arrpoly));
|
movable.emplace_back(std::move(arrpoly));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p->view3D->get_canvas3d()->get_wipe_tower_info())
|
if (auto wt = get_wipe_tower_arrangepoly(*this))
|
||||||
fixed.emplace_back(get_wipe_tower_arrangepoly(*this));
|
fixed.emplace_back(*wt);
|
||||||
|
|
||||||
arrangement::arrange(movable, fixed, get_bed_shape(*config()),
|
arrangement::arrange(movable, fixed, get_bed_shape(*config()),
|
||||||
arrangement::ArrangeParams{min_d});
|
arrangement::ArrangeParams{min_d});
|
||||||
|
@ -3860,6 +3868,8 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
|
||||||
[this](wxCommandEvent&) { q->decrease_instances(); }, "remove_copies", nullptr, [this]() { return can_decrease_instances(); }, q);
|
[this](wxCommandEvent&) { q->decrease_instances(); }, "remove_copies", nullptr, [this]() { return can_decrease_instances(); }, q);
|
||||||
wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _L("Set number of instances") + dots, _L("Change the number of instances of the selected object"),
|
wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _L("Set number of instances") + dots, _L("Change the number of instances of the selected object"),
|
||||||
[this](wxCommandEvent&) { q->set_number_of_copies(); }, "number_of_copies", nullptr, [this]() { return can_increase_instances(); }, q);
|
[this](wxCommandEvent&) { q->set_number_of_copies(); }, "number_of_copies", nullptr, [this]() { return can_increase_instances(); }, q);
|
||||||
|
append_menu_item(menu, wxID_ANY, _L("Fill bed with instances") + dots, _L("Fill the remaining area of bed with instances of the selected object"),
|
||||||
|
[this](wxCommandEvent&) { q->fill_bed_with_instances(); }, "", nullptr, [this]() { return can_increase_instances(); }, q);
|
||||||
|
|
||||||
|
|
||||||
items_increase.push_back(item_increase);
|
items_increase.push_back(item_increase);
|
||||||
|
@ -4864,6 +4874,11 @@ void Plater::set_number_of_copies(/*size_t num*/)
|
||||||
decrease_instances(-diff);
|
decrease_instances(-diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Plater::fill_bed_with_instances()
|
||||||
|
{
|
||||||
|
p->m_ui_jobs.fill_bed();
|
||||||
|
}
|
||||||
|
|
||||||
bool Plater::is_selection_empty() const
|
bool Plater::is_selection_empty() const
|
||||||
{
|
{
|
||||||
return p->get_selection().is_empty() || p->get_selection().is_wipe_tower();
|
return p->get_selection().is_empty() || p->get_selection().is_wipe_tower();
|
||||||
|
@ -5648,6 +5663,11 @@ GLCanvas3D* Plater::canvas3D()
|
||||||
return p->view3D->get_canvas3d();
|
return p->view3D->get_canvas3d();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GLCanvas3D* Plater::canvas3D() const
|
||||||
|
{
|
||||||
|
return p->view3D->get_canvas3d();
|
||||||
|
}
|
||||||
|
|
||||||
GLCanvas3D* Plater::get_current_canvas3D()
|
GLCanvas3D* Plater::get_current_canvas3D()
|
||||||
{
|
{
|
||||||
return p->get_current_canvas3D();
|
return p->get_current_canvas3D();
|
||||||
|
|
|
@ -181,6 +181,7 @@ public:
|
||||||
void increase_instances(size_t num = 1);
|
void increase_instances(size_t num = 1);
|
||||||
void decrease_instances(size_t num = 1);
|
void decrease_instances(size_t num = 1);
|
||||||
void set_number_of_copies(/*size_t num*/);
|
void set_number_of_copies(/*size_t num*/);
|
||||||
|
void fill_bed_with_instances();
|
||||||
bool is_selection_empty() const;
|
bool is_selection_empty() const;
|
||||||
void scale_selection_to_fit_print_volume();
|
void scale_selection_to_fit_print_volume();
|
||||||
void convert_unit(bool from_imperial_unit);
|
void convert_unit(bool from_imperial_unit);
|
||||||
|
@ -245,6 +246,7 @@ public:
|
||||||
int get_selected_object_idx();
|
int get_selected_object_idx();
|
||||||
bool is_single_full_object_selection() const;
|
bool is_single_full_object_selection() const;
|
||||||
GLCanvas3D* canvas3D();
|
GLCanvas3D* canvas3D();
|
||||||
|
const GLCanvas3D * canvas3D() const;
|
||||||
GLCanvas3D* get_current_canvas3D();
|
GLCanvas3D* get_current_canvas3D();
|
||||||
BoundingBoxf bed_shape_bb() const;
|
BoundingBoxf bed_shape_bb() const;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue