Arrange cache in ModeInstance and logical bed remembered.

This commit is contained in:
tamasmeszaros 2019-07-15 17:30:44 +02:00
parent df7bb94daf
commit 1b0e192046
9 changed files with 488 additions and 412 deletions

View File

@ -127,25 +127,6 @@ public:
inline _Item(TContour<RawShape>&& contour,
THolesContainer<RawShape>&& holes):
sh_(sl::create<RawShape>(std::move(contour), std::move(holes))) {}
// template<class... Args>
// _Item(std::function<void(const _Item&, unsigned)> applyfn, Args &&... args):
// _Item(std::forward<Args>(args)...)
// {
// applyfn_ = std::move(applyfn);
// }
// Call the apply callback set in constructor. Within the callback, the
// original caller can apply the stored transformation to the original
// objects inteded for nesting. It might not be the shape handed over
// to _Item (e.g. arranging 3D shapes based on 2D silhouette or the
// client uses a simplified or processed polygon for nesting)
// This callback, if present, will be called for each item after the nesting
// is finished.
// inline void callApplyFunction(unsigned binidx) const
// {
// if (applyfn_) applyfn_(*this, binidx);
// }
inline bool isFixed() const noexcept { return fixed_; }
inline void markAsFixed(bool fixed = true) { fixed_ = fixed; }
@ -881,34 +862,6 @@ public:
{
return selector_.getResult();
}
private:
// This function will be used only if the iterators are pointing to
// a type compatible with the libnets2d::_Item template.
// This way we can use references to input elements as they will
// have to exist for the lifetime of this call.
// template<class It, class Key>
// inline ConvertibleOnly<It, void> _execute(It from, It to)
// {
// __execute(from, to);
// }
// template<class It> inline void _execute(It from, It to)
// {
// auto infl = static_cast<Coord>(std::ceil(min_obj_distance_/2.0));
// if(infl > 0) std::for_each(from, to, [this](Item& item) {
// item.inflate(infl);
// });
// selector_.template packItems<PlacementStrategy>(
// from, to, bin_, pconfig_);
// if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) {
// item.inflate(-infl);
// });
// }
};
}

View File

@ -42,8 +42,13 @@ public:
std::for_each(first, last, [this](Item& itm) {
if(itm.isFixed()) {
if(packed_bins_.empty()) packed_bins_.emplace_back();
packed_bins_.front().emplace_back(itm);
if (itm.binId() < 0) itm.binId(0);
auto binidx = size_t(itm.binId());
while(packed_bins_.size() <= binidx)
packed_bins_.emplace_back();
packed_bins_[binidx].emplace_back(itm);
} else {
store_.emplace_back(itm);
}

View File

@ -434,9 +434,7 @@ inline Circle to_lnCircle(const CircleBed& circ) {
}
// Get the type of bed geometry from a simple vector of points.
BedShapeHint bedShape(const Polyline &bed) {
BedShapeHint ret;
BedShapeHint::BedShapeHint(const Polyline &bed) {
auto x = [](const Point& p) { return p(X); };
auto y = [](const Point& p) { return p(Y); };
@ -497,19 +495,16 @@ BedShapeHint bedShape(const Polyline &bed) {
auto parea = poly_area(bed);
if( (1.0 - parea/area(bb)) < 1e-3 ) {
ret.type = BedShapeType::BOX;
ret.shape.box = bb;
m_type = BedShapes::bsBox;
m_bed.box = bb;
}
else if(auto c = isCircle(bed)) {
ret.type = BedShapeType::CIRCLE;
ret.shape.circ = c;
m_type = BedShapes::bsCircle;
m_bed.circ = c;
} else {
ret.type = BedShapeType::IRREGULAR;
ret.shape.polygon = bed;
m_type = BedShapes::bsIrregular;
m_bed.polygon = bed;
}
// Determine the bed shape by hand
return ret;
}
template<class BinT> // Arrange for arbitrary bin type
@ -588,6 +583,7 @@ void arrange(ArrangePolygons & arrangables,
outp.emplace_back(std::move(clpath));
outp.back().rotation(rotation);
outp.back().translation({offs.x(), offs.y()});
outp.back().binId(arrpoly.bed_idx);
};
for (ArrangePolygon &arrangeable : arrangables)
@ -596,6 +592,8 @@ void arrange(ArrangePolygons & arrangables,
for (const ArrangePolygon &fixed: excludes)
process_arrangeable(fixed, fixeditems);
for (Item &itm : fixeditems) itm.inflate(-2 * SCALED_EPSILON);
// Integer ceiling the min distance from the bed perimeters
coord_t md = min_obj_dist - SCALED_EPSILON;
md = (md % 2) ? md / 2 + 1 : md / 2;
@ -603,39 +601,38 @@ void arrange(ArrangePolygons & arrangables,
auto &cfn = stopcondition;
auto &pri = progressind;
switch (bedhint.type) {
case BedShapeType::BOX: {
switch (bedhint.get_type()) {
case bsBox: {
// Create the arranger for the box shaped bed
BoundingBox bbb = bedhint.shape.box;
BoundingBox bbb = bedhint.get_box();
bbb.min -= Point{md, md}, bbb.max += Point{md, md};
Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}};
_arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn);
break;
}
case BedShapeType::CIRCLE: {
auto c = bedhint.shape.circ;
auto cc = to_lnCircle(c);
case bsCircle: {
auto cc = to_lnCircle(bedhint.get_circle());
_arrange(items, fixeditems, cc, min_obj_dist, pri, cfn);
break;
}
case BedShapeType::IRREGULAR: {
auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon);
case bsIrregular: {
auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular());
auto irrbed = sl::create<clppr::Polygon>(std::move(ctour));
BoundingBox polybb(bedhint.shape.polygon);
BoundingBox polybb(bedhint.get_irregular());
_arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn);
break;
}
case BedShapeType::INFINITE: {
const InfiniteBed& nobin = bedhint.shape.infinite;
case bsInfinite: {
const InfiniteBed& nobin = bedhint.get_infinite();
auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()});
_arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn);
break;
}
case BedShapeType::UNKNOWN: {
case bsUnknown: {
// We know nothing about the bed, let it be infinite and zero centered
_arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn);
break;

View File

@ -22,97 +22,152 @@ public:
inline operator bool() { return !std::isnan(radius_); }
};
/// Representing an unbounded bin
/// Representing an unbounded bed.
struct InfiniteBed { Point center; };
/// Types of print bed shapes.
enum class BedShapeType {
BOX,
CIRCLE,
IRREGULAR,
INFINITE,
UNKNOWN
enum BedShapes {
bsBox,
bsCircle,
bsIrregular,
bsInfinite,
bsUnknown
};
/// Info about the print bed for the arrange() function.
struct BedShapeHint {
BedShapeType type = BedShapeType::INFINITE;
union BedShape_u { // I know but who cares... TODO: use variant from cpp17?
/// Info about the print bed for the arrange() function. This is a variant
/// holding one of the four shapes a bed can be.
class BedShapeHint {
BedShapes m_type = BedShapes::bsInfinite;
union BedShape_u { // TODO: use variant from cpp17?
CircleBed circ;
BoundingBox box;
Polyline polygon;
InfiniteBed infinite{};
InfiniteBed infbed{};
~BedShape_u() {}
BedShape_u() {};
} shape;
} m_bed;
BedShapeHint() {};
public:
BedShapeHint(){};
~BedShapeHint() {
if (type == BedShapeType::IRREGULAR)
shape.polygon.Slic3r::Polyline::~Polyline();
};
BedShapeHint(const BedShapeHint &cpy) {
*this = cpy;
/// Get a bed shape hint for arrange() from a naked Polyline.
explicit BedShapeHint(const Polyline &polyl);
explicit BedShapeHint(const BoundingBox &bb)
{
m_type = bsBox; m_bed.box = bb;
}
BedShapeHint& operator=(const BedShapeHint &cpy) {
type = cpy.type;
switch(type) {
case BedShapeType::BOX: shape.box = cpy.shape.box; break;
case BedShapeType::CIRCLE: shape.circ = cpy.shape.circ; break;
case BedShapeType::IRREGULAR: shape.polygon = cpy.shape.polygon; break;
case BedShapeType::INFINITE: shape.infinite = cpy.shape.infinite; break;
case BedShapeType::UNKNOWN: break;
explicit BedShapeHint(const CircleBed &c)
{
m_type = bsCircle; m_bed.circ = c;
}
explicit BedShapeHint(const InfiniteBed &ibed)
{
m_type = bsInfinite; m_bed.infbed = ibed;
}
~BedShapeHint()
{
if (m_type == BedShapes::bsIrregular)
m_bed.polygon.Slic3r::Polyline::~Polyline();
};
BedShapeHint(const BedShapeHint &cpy) { *this = cpy; }
BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); }
BedShapeHint &operator=(const BedShapeHint &cpy)
{
m_type = cpy.m_type;
switch(m_type) {
case bsBox: m_bed.box = cpy.m_bed.box; break;
case bsCircle: m_bed.circ = cpy.m_bed.circ; break;
case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break;
case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break;
case bsUnknown: break;
}
return *this;
}
BedShapeHint& operator=(BedShapeHint &&cpy)
{
m_type = cpy.m_type;
switch(m_type) {
case bsBox: m_bed.box = std::move(cpy.m_bed.box); break;
case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break;
case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break;
case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break;
case bsUnknown: break;
}
return *this;
}
BedShapes get_type() const { return m_type; }
const BoundingBox &get_box() const
{
assert(m_type == bsBox); return m_bed.box;
}
const CircleBed &get_circle() const
{
assert(m_type == bsCircle); return m_bed.circ;
}
const Polyline &get_irregular() const
{
assert(m_type == bsIrregular); return m_bed.polygon;
}
const InfiniteBed &get_infinite() const
{
assert(m_type == bsInfinite); return m_bed.infbed;
}
};
/// Get a bed shape hint for arrange() from a naked Polyline.
BedShapeHint bedShape(const Polyline& bed);
static const constexpr long UNARRANGED = -1;
/// A logical bed representing an object not being arranged. Either the arrange
/// has not yet succesfully run on this ArrangePolygon or it could not fit the
/// object due to overly large size or invalid geometry.
static const constexpr int UNARRANGED = -1;
/// Input/Output structure for the arrange() function. The poly field will not
/// be modified during arrangement. Instead, the translation and rotation fields
/// will mark the needed transformation for the polygon to be in the arranged
/// position. These can also be set to an initial offset and rotation.
///
/// The bed_idx field will indicate the logical bed into which the
/// polygon belongs: UNARRANGED means no place for the polygon
/// (also the initial state before arrange), 0..N means the index of the bed.
/// Zero is the physical bed, larger than zero means a virtual bed.
struct ArrangePolygon {
const ExPolygon poly;
Vec2crd translation{0, 0};
double rotation{0.0};
long bed_idx{UNARRANGED};
const ExPolygon poly; /// The 2D silhouette to be arranged
Vec2crd translation{0, 0}; /// The translation of the poly
double rotation{0.0}; /// The rotation of the poly in radians
int bed_idx{UNARRANGED}; /// To which logical bed does poly belong...
ArrangePolygon(const ExPolygon &p, const Vec2crd &tr = {}, double rot = 0.0)
: poly{p}, translation{tr}, rotation{rot}
ArrangePolygon(ExPolygon p, const Vec2crd &tr = {}, double rot = 0.0)
: poly{std::move(p)}, translation{tr}, rotation{rot}
{}
};
using ArrangePolygons = std::vector<ArrangePolygon>;
/**
* \brief Arranges the model objects on the screen.
* \brief Arranges the input polygons.
*
* The arrangement considers multiple bins (aka. print beds) for placing
* all the items provided in the model argument. If the items don't fit on
* one print bed, the remaining will be placed onto newly created print
* beds. The first_bin_only parameter, if set to true, disables this
* behavior and makes sure that only one print bed is filled and the
* remaining items will be untouched. When set to false, the items which
* could not fit onto the print bed will be placed next to the print bed so
* the user should see a pile of items on the print bed and some other
* piles outside the print area that can be dragged later onto the print
* bed as a group.
* WARNING: Currently, only convex polygons are supported by the libnest2d
* library which is used to do the arrangement. This might change in the future
* this is why the interface contains a general polygon capable to have holes.
*
* \param items Input which are object pointers implementing the
* Arrangeable interface.
* \param items Input vector of ArrangePolygons. The transformation, rotation
* and bin_idx fields will be changed after the call finished and can be used
* to apply the result on the input polygon.
*
* \param min_obj_distance The minimum distance which is allowed for any
* pair of items on the print bed in any direction.
*
* \param bedhint Info about the shape and type of the
* bed. remaining items which do not fit onto the print area next to the
* print bed or leave them untouched (let the user arrange them by hand or
* remove them).
* \param bedhint Info about the shape and type of the bed.
*
* \param progressind Progress indicator callback called when
* an object gets packed. The unsigned argument is the number of items
@ -127,7 +182,7 @@ void arrange(ArrangePolygons & items,
std::function<bool(void)> stopcondition = nullptr);
/// Same as the previous, only that it takes unmovable items as an
/// additional argument.
/// additional argument. Those will be considered as already arranged objects.
void arrange(ArrangePolygons & items,
const ArrangePolygons & excludes,
coord_t min_obj_distance,

View File

@ -372,35 +372,7 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb
/* arrange objects preserving their instance count
but altering their instance positions */
bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
{
// get the (transformed) size of each instance so that we take
// into account their different transformations when packing
// Pointfs instance_sizes;
// Pointfs instance_centers;
// for (const ModelObject *o : this->objects)
// for (size_t i = 0; i < o->instances.size(); ++ i) {
// // an accurate snug bounding box around the transformed mesh.
// BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
// instance_sizes.emplace_back(to_2d(bbox.size()));
// instance_centers.emplace_back(to_2d(bbox.center()));
// }
// Pointfs positions;
// if (! _arrange(instance_sizes, dist, bb, positions))
// return false;
// size_t idx = 0;
// for (ModelObject *o : this->objects) {
// for (ModelInstance *i : o->instances) {
// Vec2d offset_xy = positions[idx] - instance_centers[idx];
// i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z)));
// ++idx;
// }
// o->invalidate_bounding_box();
// }
// return true;
{
size_t count = 0;
for (auto obj : objects) count += obj->instances.size();
@ -414,29 +386,23 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
instances.emplace_back(minst);
}
arrangement::BedShapeHint bedhint;
if (bb) {
bedhint.type = arrangement::BedShapeType::BOX;
bedhint.shape.box = BoundingBox(scaled(bb->min), scaled(bb->max));
}
if (bb)
bedhint = arrangement::BedShapeHint(
BoundingBox(scaled(bb->min), scaled(bb->max)));
arrangement::arrange(input, scaled(dist), bedhint);
bool ret = true;
for(size_t i = 0; i < input.size(); ++i) {
auto inst = instances[i];
inst->set_rotation(Z, input[i].rotation);
auto tr = unscaled<double>(input[i].translation);
inst->set_offset(X, tr.x());
inst->set_offset(Y, tr.y());
if (input[i].bed_idx != 0) ret = false; // no logical beds are allowed
if (input[i].bed_idx == 0) { // no logical beds are allowed
instances[i]->apply_arrange_result(input[i].translation,
input[i].rotation);
} else ret = false;
}
return ret;
}
@ -1842,28 +1808,37 @@ void ModelInstance::transform_polygon(Polygon* polygon) const
arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
{
static const double SIMPLIFY_TOLERANCE_MM = 0.1;
Vec3d rotation = get_rotation();
rotation.z() = 0.;
Transform3d trafo_instance =
Geometry::assemble_transform(Vec3d::Zero(), rotation,
get_scaling_factor(), get_mirror());
Polygon p = get_object()->convex_hull_2d(trafo_instance);
assert(!p.points.empty());
// this may happen for malformed models, see:
// https://github.com/prusa3d/PrusaSlicer/issues/2209
if (p.points.empty()) return {{}};
Polygons pp{p};
pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM));
if (!pp.empty()) p = pp.front();
ExPolygon ep; ep.contour = std::move(p);
if (!m_arrange_cache.valid) {
Vec3d rotation = get_rotation();
rotation.z() = 0.;
Transform3d trafo_instance =
Geometry::assemble_transform(Vec3d::Zero(), rotation,
get_scaling_factor(), get_mirror());
return {ep, Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}, get_rotation(Z)};
Polygon p = get_object()->convex_hull_2d(trafo_instance);
assert(!p.points.empty());
// this may happen for malformed models, see:
// https://github.com/prusa3d/PrusaSlicer/issues/2209
if (p.points.empty()) return {{}};
Polygons pp{p};
pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM));
if (!pp.empty()) p = pp.front();
m_arrange_cache.poly.contour = std::move(p);
m_arrange_cache.valid = true;
}
arrangement::ArrangePolygon ret{m_arrange_cache.poly,
Vec2crd{scaled(get_offset(X)),
scaled(get_offset(Y))},
get_rotation(Z)};
ret.bed_idx = m_arrange_cache.bed_idx;
return ret;
}
// Test whether the two models contain the same number of ModelObjects with the same set of IDs

View File

@ -512,7 +512,7 @@ public:
ModelObject* get_object() const { return this->object; }
const Geometry::Transformation& get_transformation() const { return m_transformation; }
void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; }
void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; m_arrange_cache.valid = false; }
const Vec3d& get_offset() const { return m_transformation.get_offset(); }
double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
@ -523,21 +523,21 @@ public:
const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); }
void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); }
void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); m_arrange_cache.valid = false; }
void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); if (axis != Z) m_arrange_cache.valid = false; }
const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); }
void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); }
void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); }
void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); m_arrange_cache.valid = false; }
void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); m_arrange_cache.valid = false; }
const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
bool is_left_handed() const { return m_transformation.is_left_handed(); }
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); m_arrange_cache.valid = false; }
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); m_arrange_cache.valid = false; }
// To be called on an external mesh
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
@ -554,20 +554,17 @@ public:
bool is_printable() const { return print_volume_state == PVS_Inside; }
// /////////////////////////////////////////////////////////////////////////
// Implement arrangement::Arrangeable interface
// /////////////////////////////////////////////////////////////////////////
// Getting the input polygon for arrange
arrangement::ArrangePolygon get_arrange_polygon() const;
// Apply the arrange result on the ModelInstance
void apply_arrange_result(Vec2crd offs, double rot_rads)
void apply_arrange_result(Vec2crd offs, double rot_rads, int bed_idx = 0)
{
// write the transformation data into the model instance
set_rotation(Z, rot_rads);
set_offset(X, unscale<double>(offs(X)));
set_offset(Y, unscale<double>(offs(Y)));
m_arrange_cache.bed_idx = bed_idx;
}
protected:
@ -583,15 +580,28 @@ private:
ModelObject* object;
// Constructor, which assigns a new unique ID.
explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) {}
explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside)
{
get_arrange_polygon(); // initialize the arrange cache
}
// Constructor, which assigns a new unique ID.
explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) {}
m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside)
{
get_arrange_polygon(); // initialize the arrange cache
}
ModelInstance() = delete;
explicit ModelInstance(ModelInstance &&rhs) = delete;
ModelInstance& operator=(const ModelInstance &rhs) = delete;
ModelInstance& operator=(ModelInstance &&rhs) = delete;
// Warning! This object is not guarded against concurrency.
mutable struct ArrangeCache {
bool valid = false;
int bed_idx { arrangement::UNARRANGED };
ExPolygon poly;
} m_arrange_cache;
};
// The print bed content.

View File

@ -5739,10 +5739,11 @@ const SLAPrint* GLCanvas3D::sla_print() const
return (m_process == nullptr) ? nullptr : m_process->sla_print();
}
void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2d offset, double rotation_rads)
void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2crd off, double rotation_rads)
{
m_pos = offset;
m_rotation = rotation_rads;
Vec2d offset = unscaled(off);
m_pos = offset;
m_rotation = rotation_rads;
DynamicPrintConfig cfg;
cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = m_pos(X);
cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = m_pos(Y);

View File

@ -624,7 +624,7 @@ public:
return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y());
}
void apply_arrange_result(Vec2d offset, double rotation_rads);
void apply_arrange_result(Vec2crd offset, double rotation_rads);
arrangement::ArrangePolygon get_arrange_polygon() const
{

View File

@ -1272,147 +1272,290 @@ struct Plater::priv
// objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results
// could be displayed.
class Job: public wxEvtHandler {
int m_range = 100;
class Job : public wxEvtHandler
{
int m_range = 100;
std::future<void> m_ftr;
priv *m_plater = nullptr;
std::atomic<bool> m_running {false}, m_canceled {false};
bool m_finalized = false;
void run() {
m_running.store(true); process(); m_running.store(false);
priv * m_plater = nullptr;
std::atomic<bool> m_running{false}, m_canceled{false};
bool m_finalized = false;
void run()
{
m_running.store(true);
process();
m_running.store(false);
// ensure to call the last status to finalize the job
update_status(status_range(), "");
}
protected:
// status range for a particular job
virtual int status_range() const { return 100; }
// status update, to be used from the work thread (process() method)
void update_status(int st, const wxString& msg = "") {
auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg);
wxQueueEvent(this, evt);
void update_status(int st, const wxString &msg = "")
{
auto evt = new wxThreadEvent();
evt->SetInt(st);
evt->SetString(msg);
wxQueueEvent(this, evt);
}
priv& plater() { return *m_plater; }
bool was_canceled() const { return m_canceled.load(); }
priv &plater() { return *m_plater; }
bool was_canceled() const { return m_canceled.load(); }
// Launched just before start(), a job can use it to prepare internals
virtual void prepare() {}
// Launched when the job is finished. It refreshes the 3dscene by def.
virtual void finalize() {
// Launched when the job is finished. It refreshes the 3Dscene by def.
virtual void finalize()
{
// Do a full refresh of scene tree, including regenerating
// all the GLVolumes. FIXME The update function shall just
// reload the modified matrices.
if(! was_canceled())
plater().update(true);
if (!was_canceled()) plater().update(true);
}
public:
Job(priv *_plater): m_plater(_plater)
Job(priv *_plater) : m_plater(_plater)
{
Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
auto msg = evt.GetString();
if(! msg.empty()) plater().statusbar()->set_status_text(msg);
if(m_finalized) return;
if (!msg.empty())
plater().statusbar()->set_status_text(msg);
if (m_finalized) return;
plater().statusbar()->set_progress(evt.GetInt());
if(evt.GetInt() == status_range()) {
if (evt.GetInt() == status_range()) {
// set back the original range and cancel callback
plater().statusbar()->set_range(m_range);
plater().statusbar()->set_cancel_callback();
wxEndBusyCursor();
finalize();
// dont do finalization again for the same process
m_finalized = true;
}
});
}
// TODO: use this when we all migrated to VS2019
// Job(const Job&) = delete;
// Job(Job&&) = default;
// Job& operator=(const Job&) = delete;
// Job& operator=(Job&&) = default;
Job(const Job&) = delete;
Job& operator=(const Job&) = delete;
Job(Job &&o) :
m_range(o.m_range),
m_ftr(std::move(o.m_ftr)),
m_plater(o.m_plater),
m_finalized(o.m_finalized)
{
m_running.store(o.m_running.load());
m_canceled.store(o.m_canceled.load());
}
Job(const Job &) = delete;
Job(Job &&) = default;
Job &operator=(const Job &) = delete;
Job &operator=(Job &&) = default;
virtual void process() = 0;
void start() { // Start the job. No effect if the job is already running
if(! m_running.load()) {
prepare();
void start()
{ // Start the job. No effect if the job is already running
if (!m_running.load()) {
prepare();
// Save the current status indicatior range and push the new one
m_range = plater().statusbar()->get_range();
plater().statusbar()->set_range(status_range());
// init cancellation flag and set the cancel callback
m_canceled.store(false);
plater().statusbar()->set_cancel_callback( [this](){
m_canceled.store(true);
});
plater().statusbar()->set_cancel_callback(
[this]() { m_canceled.store(true); });
m_finalized = false;
// Changing cursor to busy
wxBeginBusyCursor();
try { // Execute the job
try { // Execute the job
m_ftr = std::async(std::launch::async, &Job::run, this);
} catch(std::exception& ) {
update_status(status_range(),
_(L("ERROR: not enough resources to execute a new job.")));
} catch (std::exception &) {
update_status(status_range(),
_(L("ERROR: not enough resources to "
"execute a new job.")));
}
// The state changes will be undone when the process hits the
// last status value, in the status update handler (see ctor)
}
}
// To wait for the running job and join the threads. False is returned
// if the timeout has been reached and the job is still running. Call
// cancel() before this fn if you want to explicitly end the job.
bool join(int timeout_ms = 0) const {
if(!m_ftr.valid()) return true;
if(timeout_ms <= 0)
// To wait for the running job and join the threads. False is
// returned if the timeout has been reached and the job is still
// running. Call cancel() before this fn if you want to explicitly
// end the job.
bool join(int timeout_ms = 0) const
{
if (!m_ftr.valid()) return true;
if (timeout_ms <= 0)
m_ftr.wait();
else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) ==
std::future_status::timeout)
else if (m_ftr.wait_for(std::chrono::milliseconds(
timeout_ms)) == std::future_status::timeout)
return false;
return true;
}
bool is_running() const { return m_running.load(); }
void cancel() { m_canceled.store(true); }
};
enum class Jobs : size_t {
Arrange,
Rotoptimize
};
class ArrangeJob : public Job
{
// 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.;
// Cache the wti info
GLCanvas3D::WipeTowerInfo m_wti;
// Cache the selected instances needed to write back the arrange
// result. The order of instances is the same as the arrange polys
struct IndexedArrangePolys {
ModelInstancePtrs insts;
arrangement::ArrangePolygons polys;
void reserve(size_t cap) { insts.reserve(cap); polys.reserve(cap); }
void clear() { insts.clear(); polys.clear(); }
void emplace_back(ModelInstance *inst) {
insts.emplace_back(inst);
polys.emplace_back(inst->get_arrange_polygon());
}
void swap(IndexedArrangePolys &pp) {
insts.swap(pp.insts); polys.swap(pp.polys);
}
};
IndexedArrangePolys m_selected, m_unselected;
protected:
void prepare() override
{
m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info();
// Get the selection map
Selection& sel = plater().get_selection();
const Selection::ObjectIdxsToInstanceIdxsMap &selmap =
sel.get_content();
Model &model = plater().model;
size_t count = 0; // To know how much space to reserve
for (auto obj : model.objects) count += obj->instances.size();
m_selected.clear(), m_unselected.clear();
m_selected.reserve(count + 1 /* for optional wti */);
m_unselected.reserve(count + 1 /* for optional wti */);
// Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
auto oit = selmap.find(int(oidx));
if (oit != selmap.end()) { // Object is selected
auto &iids = oit->second;
// Go through instances and check if inside selection
size_t instcnt = model.objects[oidx]->instances.size();
for (size_t iidx = 0; iidx < instcnt; ++iidx) {
auto instit = iids.find(iidx);
ModelInstance *oi = model.objects[oidx]
->instances[iidx];
// Instance is selected
instit != iids.end() ?
m_selected.emplace_back(oi) :
m_unselected.emplace_back(oi);
}
} else // object not selected, all instances are unselected
for (ModelInstance *oi : model.objects[oidx]->instances)
m_unselected.emplace_back(oi);
}
// If the selection is completely empty, consider all items as the
// selection
if (m_selected.insts.empty() && m_selected.polys.empty())
m_selected.swap(m_unselected);
if (m_wti)
sel.is_wipe_tower() ?
m_selected.polys.emplace_back(m_wti.get_arrange_polygon()) :
m_unselected.polys.emplace_back(m_wti.get_arrange_polygon());
// Stride between logical beds
double bedwidth = plater().bed_shape_bb().size().x();
coord_t stride = scaled((1. + LOGICAL_BED_GAP) * bedwidth);
for (arrangement::ArrangePolygon &ap : m_selected.polys)
if (ap.bed_idx > 0) ap.translation.x() -= ap.bed_idx * stride;
for (arrangement::ArrangePolygon &ap : m_unselected.polys)
if (ap.bed_idx > 0) ap.translation.x() -= ap.bed_idx * stride;
}
public:
using Job::Job;
int status_range() const override
{
return int(m_selected.polys.size());
}
void process() override;
void finalize() override {
if (was_canceled()) { // Ignore the arrange result if aborted.
Job::finalize();
return;
}
// Stride between logical beds
double bedwidth = plater().bed_shape_bb().size().x();
coord_t stride = scaled((1. + LOGICAL_BED_GAP) * bedwidth);
for(size_t i = 0; i < m_selected.insts.size(); ++i) {
if (m_selected.polys[i].bed_idx != arrangement::UNARRANGED) {
Vec2crd offs = m_selected.polys[i].translation;
double rot = m_selected.polys[i].rotation;
int bdidx = m_selected.polys[i].bed_idx;
offs.x() += bdidx * stride;
m_selected.insts[i]->apply_arrange_result(offs, rot, bdidx);
}
}
// Handle the wipe tower
const arrangement::ArrangePolygon &wtipoly = m_selected.polys.back();
if (m_wti && wtipoly.bed_idx != arrangement::UNARRANGED) {
Vec2crd o = wtipoly.translation;
double r = wtipoly.rotation;
o.x() += wtipoly.bed_idx * stride;
m_wti.apply_arrange_result(o, r);
}
// Call original finalize (will update the scene)
Job::finalize();
}
};
class RotoptimizeJob : public Job
{
public:
using Job::Job;
void process() override;
};
// Jobs defined inside the group class will be managed so that only one can
// run at a time. Also, the background process will be stopped if a job is
// started.
@ -1422,84 +1565,8 @@ struct Plater::priv
priv * m_plater;
class ArrangeJob : public Job
{
GLCanvas3D::WipeTowerInfo m_wti;
arrangement::ArrangePolygons m_selected, m_unselected;
static std::array<arrangement::ArrangePolygons, 2> collect(
Model &model, const Selection &sel)
{
const Selection::ObjectIdxsToInstanceIdxsMap &selmap =
sel.get_content();
size_t count = 0;
for (auto obj : model.objects) count += obj->instances.size();
arrangement::ArrangePolygons selected, unselected;
selected.reserve(count + 1 /* for optional wti */);
unselected.reserve(count + 1 /* for optional wti */);
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
auto oit = selmap.find(int(oidx));
if (oit != selmap.end()) {
auto &iids = oit->second;
for (size_t iidx = 0;
iidx < model.objects[oidx]->instances.size();
++iidx)
{
auto instit = iids.find(iidx);
ModelInstance *inst = model.objects[oidx]
->instances[iidx];
instit == iids.end() ?
unselected.emplace_back(inst->get_arrange_polygon()) :
selected.emplace_back(inst->get_arrange_polygon());
}
} else // object not selected, all instances are unselected
for (auto inst : model.objects[oidx]->instances)
unselected.emplace_back(inst->get_arrange_polygon());
}
if (selected.empty()) selected.swap(unselected);
return {selected, unselected};
}
protected:
void prepare() override
{
m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info();
const Selection& sel = plater().get_selection();
BoundingBoxf bedbb(plater().bed.get_shape());
auto arrinput = collect(plater().model, sel);
m_selected.swap(arrinput[0]);
m_unselected.swap(arrinput[1]);
if (m_wti)
sel.is_wipe_tower() ?
m_selected.emplace_back(m_wti.get_arrange_polygon()) :
m_unselected.emplace_back(m_wti.get_arrange_polygon());
}
public:
using Job::Job;
int status_range() const override
{
return int(m_selected.size());
}
void process() override;
} arrange_job{m_plater};
class RotoptimizeJob : public Job
{
public:
using Job::Job;
void process() override;
} rotoptimize_job{m_plater};
ArrangeJob arrange_job{m_plater};
RotoptimizeJob rotoptimize_job{m_plater};
// To create a new job, just define a new subclass of Job, implement
// the process and the optional prepare() and finalize() methods
@ -2447,50 +2514,47 @@ void Plater::priv::sla_optimize_rotation() {
}
arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const {
arrangement::BedShapeHint bedshape;
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
assert(bed_shape_opt);
if (bed_shape_opt) {
auto &bedpoints = bed_shape_opt->values;
Polyline bedpoly; bedpoly.points.reserve(bedpoints.size());
for (auto &v : bedpoints) bedpoly.append(scaled(v));
bedshape = arrangement::bedShape(bedpoly);
}
if (!bed_shape_opt) return {};
return bedshape;
auto &bedpoints = bed_shape_opt->values;
Polyline bedpoly; bedpoly.points.reserve(bedpoints.size());
for (auto &v : bedpoints) bedpoly.append(scaled(v));
return arrangement::BedShapeHint(bedpoly);
}
void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() {
auto count = unsigned(m_selected.size());
plater().model.arrange_objects(6.f, nullptr);
// static const auto arrangestr = _(L("Arranging"));
void Plater::priv::ArrangeJob::process() {
static const auto arrangestr = _(L("Arranging"));
// // FIXME: I don't know how to obtain the minimum distance, it depends
// // on printer technology. I guess the following should work but it crashes.
// double dist = 6; // PrintConfig::min_object_distance(config);
// if (plater().printer_technology == ptFFF) {
// dist = PrintConfig::min_object_distance(plater().config);
// }
// FIXME: I don't know how to obtain the minimum distance, it depends
// on printer technology. I guess the following should work but it crashes.
double dist = 6; // PrintConfig::min_object_distance(config);
if (plater().printer_technology == ptFFF) {
dist = PrintConfig::min_object_distance(plater().config);
}
// coord_t min_obj_distance = scaled(dist);
// auto count = unsigned(m_selected.size());
// arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint();
coord_t min_obj_distance = scaled(dist);
auto count = unsigned(m_selected.polys.size());
arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint();
// try {
// arrangement::arrange(m_selected, m_unselected, min_obj_distance,
// bedshape,
// [this, count](unsigned st) {
// if (st > 0) // will not finalize after last one
// update_status(count - st, arrangestr);
// },
// [this]() { return was_canceled(); });
// } catch (std::exception & /*e*/) {
// GUI::show_error(plater().q,
// _(L("Could not arrange model objects! "
// "Some geometries may be invalid.")));
// }
try {
arrangement::arrange(m_selected.polys, m_unselected.polys,
min_obj_distance,
bedshape,
[this, count](unsigned st) {
if (st > 0) // will not finalize after last one
update_status(count - st, arrangestr);
},
[this]() { return was_canceled(); });
} catch (std::exception & /*e*/) {
GUI::show_error(plater().q,
_(L("Could not arrange model objects! "
"Some geometries may be invalid.")));
}
// finalize just here.
update_status(int(count),
@ -2503,11 +2567,27 @@ void find_new_position(const Model & model,
coord_t min_d,
const arrangement::BedShapeHint &bedhint)
{
arrangement::ArrangePolygons movable, fixed;
// TODO
for (const ModelObject *mo : model.objects)
for (const ModelInstance *inst : mo->instances) {
auto it = std::find(instances.begin(), instances.end(), inst);
if (it != instances.end())
fixed.emplace_back(inst->get_arrange_polygon());
}
for (ModelInstance *inst : instances)
movable.emplace_back(inst->get_arrange_polygon());
arrangement::arrange(movable, fixed, min_d, bedhint);
for (size_t i = 0; i < instances.size(); ++i)
if (movable[i].bed_idx == 0)
instances[i]->apply_arrange_result(movable[i].translation,
movable[i].rotation);
}
void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
void Plater::priv::RotoptimizeJob::process()
{
int obj_idx = plater().get_selected_object_idx();
if (obj_idx < 0) { return; }