Arrange cache in ModeInstance and logical bed remembered.
This commit is contained in:
parent
df7bb94daf
commit
1b0e192046
9 changed files with 488 additions and 412 deletions
|
@ -128,25 +128,6 @@ public:
|
||||||
THolesContainer<RawShape>&& holes):
|
THolesContainer<RawShape>&& holes):
|
||||||
sh_(sl::create<RawShape>(std::move(contour), std::move(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 bool isFixed() const noexcept { return fixed_; }
|
||||||
inline void markAsFixed(bool fixed = true) { fixed_ = fixed; }
|
inline void markAsFixed(bool fixed = true) { fixed_ = fixed; }
|
||||||
inline void binId(int idx) { binid_ = idx; }
|
inline void binId(int idx) { binid_ = idx; }
|
||||||
|
@ -881,34 +862,6 @@ public:
|
||||||
{
|
{
|
||||||
return selector_.getResult();
|
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);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,13 @@ public:
|
||||||
|
|
||||||
std::for_each(first, last, [this](Item& itm) {
|
std::for_each(first, last, [this](Item& itm) {
|
||||||
if(itm.isFixed()) {
|
if(itm.isFixed()) {
|
||||||
if(packed_bins_.empty()) packed_bins_.emplace_back();
|
if (itm.binId() < 0) itm.binId(0);
|
||||||
packed_bins_.front().emplace_back(itm);
|
auto binidx = size_t(itm.binId());
|
||||||
|
|
||||||
|
while(packed_bins_.size() <= binidx)
|
||||||
|
packed_bins_.emplace_back();
|
||||||
|
|
||||||
|
packed_bins_[binidx].emplace_back(itm);
|
||||||
} else {
|
} else {
|
||||||
store_.emplace_back(itm);
|
store_.emplace_back(itm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -434,9 +434,7 @@ inline Circle to_lnCircle(const CircleBed& circ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the type of bed geometry from a simple vector of points.
|
// Get the type of bed geometry from a simple vector of points.
|
||||||
BedShapeHint bedShape(const Polyline &bed) {
|
BedShapeHint::BedShapeHint(const Polyline &bed) {
|
||||||
BedShapeHint ret;
|
|
||||||
|
|
||||||
auto x = [](const Point& p) { return p(X); };
|
auto x = [](const Point& p) { return p(X); };
|
||||||
auto y = [](const Point& p) { return p(Y); };
|
auto y = [](const Point& p) { return p(Y); };
|
||||||
|
|
||||||
|
@ -497,19 +495,16 @@ BedShapeHint bedShape(const Polyline &bed) {
|
||||||
auto parea = poly_area(bed);
|
auto parea = poly_area(bed);
|
||||||
|
|
||||||
if( (1.0 - parea/area(bb)) < 1e-3 ) {
|
if( (1.0 - parea/area(bb)) < 1e-3 ) {
|
||||||
ret.type = BedShapeType::BOX;
|
m_type = BedShapes::bsBox;
|
||||||
ret.shape.box = bb;
|
m_bed.box = bb;
|
||||||
}
|
}
|
||||||
else if(auto c = isCircle(bed)) {
|
else if(auto c = isCircle(bed)) {
|
||||||
ret.type = BedShapeType::CIRCLE;
|
m_type = BedShapes::bsCircle;
|
||||||
ret.shape.circ = c;
|
m_bed.circ = c;
|
||||||
} else {
|
} else {
|
||||||
ret.type = BedShapeType::IRREGULAR;
|
m_type = BedShapes::bsIrregular;
|
||||||
ret.shape.polygon = bed;
|
m_bed.polygon = bed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the bed shape by hand
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class BinT> // Arrange for arbitrary bin type
|
template<class BinT> // Arrange for arbitrary bin type
|
||||||
|
@ -588,6 +583,7 @@ void arrange(ArrangePolygons & arrangables,
|
||||||
outp.emplace_back(std::move(clpath));
|
outp.emplace_back(std::move(clpath));
|
||||||
outp.back().rotation(rotation);
|
outp.back().rotation(rotation);
|
||||||
outp.back().translation({offs.x(), offs.y()});
|
outp.back().translation({offs.x(), offs.y()});
|
||||||
|
outp.back().binId(arrpoly.bed_idx);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (ArrangePolygon &arrangeable : arrangables)
|
for (ArrangePolygon &arrangeable : arrangables)
|
||||||
|
@ -596,6 +592,8 @@ void arrange(ArrangePolygons & arrangables,
|
||||||
for (const ArrangePolygon &fixed: excludes)
|
for (const ArrangePolygon &fixed: excludes)
|
||||||
process_arrangeable(fixed, fixeditems);
|
process_arrangeable(fixed, fixeditems);
|
||||||
|
|
||||||
|
for (Item &itm : fixeditems) itm.inflate(-2 * SCALED_EPSILON);
|
||||||
|
|
||||||
// Integer ceiling the min distance from the bed perimeters
|
// Integer ceiling the min distance from the bed perimeters
|
||||||
coord_t md = min_obj_dist - SCALED_EPSILON;
|
coord_t md = min_obj_dist - SCALED_EPSILON;
|
||||||
md = (md % 2) ? md / 2 + 1 : md / 2;
|
md = (md % 2) ? md / 2 + 1 : md / 2;
|
||||||
|
@ -603,39 +601,38 @@ void arrange(ArrangePolygons & arrangables,
|
||||||
auto &cfn = stopcondition;
|
auto &cfn = stopcondition;
|
||||||
auto &pri = progressind;
|
auto &pri = progressind;
|
||||||
|
|
||||||
switch (bedhint.type) {
|
switch (bedhint.get_type()) {
|
||||||
case BedShapeType::BOX: {
|
case bsBox: {
|
||||||
// Create the arranger for the box shaped bed
|
// 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};
|
bbb.min -= Point{md, md}, bbb.max += Point{md, md};
|
||||||
Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}};
|
Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}};
|
||||||
|
|
||||||
_arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn);
|
_arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BedShapeType::CIRCLE: {
|
case bsCircle: {
|
||||||
auto c = bedhint.shape.circ;
|
auto cc = to_lnCircle(bedhint.get_circle());
|
||||||
auto cc = to_lnCircle(c);
|
|
||||||
|
|
||||||
_arrange(items, fixeditems, cc, min_obj_dist, pri, cfn);
|
_arrange(items, fixeditems, cc, min_obj_dist, pri, cfn);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BedShapeType::IRREGULAR: {
|
case bsIrregular: {
|
||||||
auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon);
|
auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular());
|
||||||
auto irrbed = sl::create<clppr::Polygon>(std::move(ctour));
|
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);
|
_arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BedShapeType::INFINITE: {
|
case bsInfinite: {
|
||||||
const InfiniteBed& nobin = bedhint.shape.infinite;
|
const InfiniteBed& nobin = bedhint.get_infinite();
|
||||||
auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()});
|
auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()});
|
||||||
|
|
||||||
_arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn);
|
_arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BedShapeType::UNKNOWN: {
|
case bsUnknown: {
|
||||||
// We know nothing about the bed, let it be infinite and zero centered
|
// We know nothing about the bed, let it be infinite and zero centered
|
||||||
_arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn);
|
_arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -22,97 +22,152 @@ public:
|
||||||
inline operator bool() { return !std::isnan(radius_); }
|
inline operator bool() { return !std::isnan(radius_); }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Representing an unbounded bin
|
/// Representing an unbounded bed.
|
||||||
struct InfiniteBed { Point center; };
|
struct InfiniteBed { Point center; };
|
||||||
|
|
||||||
/// Types of print bed shapes.
|
/// Types of print bed shapes.
|
||||||
enum class BedShapeType {
|
enum BedShapes {
|
||||||
BOX,
|
bsBox,
|
||||||
CIRCLE,
|
bsCircle,
|
||||||
IRREGULAR,
|
bsIrregular,
|
||||||
INFINITE,
|
bsInfinite,
|
||||||
UNKNOWN
|
bsUnknown
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Info about the print bed for the arrange() function.
|
/// Info about the print bed for the arrange() function. This is a variant
|
||||||
struct BedShapeHint {
|
/// holding one of the four shapes a bed can be.
|
||||||
BedShapeType type = BedShapeType::INFINITE;
|
class BedShapeHint {
|
||||||
union BedShape_u { // I know but who cares... TODO: use variant from cpp17?
|
BedShapes m_type = BedShapes::bsInfinite;
|
||||||
|
|
||||||
|
union BedShape_u { // TODO: use variant from cpp17?
|
||||||
CircleBed circ;
|
CircleBed circ;
|
||||||
BoundingBox box;
|
BoundingBox box;
|
||||||
Polyline polygon;
|
Polyline polygon;
|
||||||
InfiniteBed infinite{};
|
InfiniteBed infbed{};
|
||||||
~BedShape_u() {}
|
~BedShape_u() {}
|
||||||
BedShape_u() {};
|
BedShape_u() {};
|
||||||
} shape;
|
} m_bed;
|
||||||
|
|
||||||
BedShapeHint() {};
|
public:
|
||||||
|
|
||||||
~BedShapeHint() {
|
BedShapeHint(){};
|
||||||
if (type == BedShapeType::IRREGULAR)
|
|
||||||
shape.polygon.Slic3r::Polyline::~Polyline();
|
|
||||||
};
|
|
||||||
|
|
||||||
BedShapeHint(const BedShapeHint &cpy) {
|
/// Get a bed shape hint for arrange() from a naked Polyline.
|
||||||
*this = cpy;
|
explicit BedShapeHint(const Polyline &polyl);
|
||||||
|
explicit BedShapeHint(const BoundingBox &bb)
|
||||||
|
{
|
||||||
|
m_type = bsBox; m_bed.box = bb;
|
||||||
}
|
}
|
||||||
|
|
||||||
BedShapeHint& operator=(const BedShapeHint &cpy) {
|
explicit BedShapeHint(const CircleBed &c)
|
||||||
type = cpy.type;
|
{
|
||||||
switch(type) {
|
m_type = bsCircle; m_bed.circ = c;
|
||||||
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;
|
explicit BedShapeHint(const InfiniteBed &ibed)
|
||||||
case BedShapeType::INFINITE: shape.infinite = cpy.shape.infinite; break;
|
{
|
||||||
case BedShapeType::UNKNOWN: break;
|
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;
|
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.
|
/// A logical bed representing an object not being arranged. Either the arrange
|
||||||
BedShapeHint bedShape(const Polyline& bed);
|
/// 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 long UNARRANGED = -1;
|
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 {
|
struct ArrangePolygon {
|
||||||
const ExPolygon poly;
|
const ExPolygon poly; /// The 2D silhouette to be arranged
|
||||||
Vec2crd translation{0, 0};
|
Vec2crd translation{0, 0}; /// The translation of the poly
|
||||||
double rotation{0.0};
|
double rotation{0.0}; /// The rotation of the poly in radians
|
||||||
long bed_idx{UNARRANGED};
|
int bed_idx{UNARRANGED}; /// To which logical bed does poly belong...
|
||||||
|
|
||||||
ArrangePolygon(const ExPolygon &p, const Vec2crd &tr = {}, double rot = 0.0)
|
ArrangePolygon(ExPolygon p, const Vec2crd &tr = {}, double rot = 0.0)
|
||||||
: poly{p}, translation{tr}, rotation{rot}
|
: poly{std::move(p)}, translation{tr}, rotation{rot}
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
using ArrangePolygons = std::vector<ArrangePolygon>;
|
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
|
* WARNING: Currently, only convex polygons are supported by the libnest2d
|
||||||
* all the items provided in the model argument. If the items don't fit on
|
* library which is used to do the arrangement. This might change in the future
|
||||||
* one print bed, the remaining will be placed onto newly created print
|
* this is why the interface contains a general polygon capable to have holes.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* \param items Input which are object pointers implementing the
|
* \param items Input vector of ArrangePolygons. The transformation, rotation
|
||||||
* Arrangeable interface.
|
* 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
|
* \param min_obj_distance The minimum distance which is allowed for any
|
||||||
* pair of items on the print bed in any direction.
|
* pair of items on the print bed in any direction.
|
||||||
*
|
*
|
||||||
* \param bedhint Info about the shape and type of the
|
* \param bedhint Info about the shape and type of the bed.
|
||||||
* 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 progressind Progress indicator callback called when
|
* \param progressind Progress indicator callback called when
|
||||||
* an object gets packed. The unsigned argument is the number of items
|
* 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);
|
std::function<bool(void)> stopcondition = nullptr);
|
||||||
|
|
||||||
/// Same as the previous, only that it takes unmovable items as an
|
/// 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,
|
void arrange(ArrangePolygons & items,
|
||||||
const ArrangePolygons & excludes,
|
const ArrangePolygons & excludes,
|
||||||
coord_t min_obj_distance,
|
coord_t min_obj_distance,
|
||||||
|
|
|
@ -373,34 +373,6 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb
|
||||||
but altering their instance positions */
|
but altering their instance positions */
|
||||||
bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
|
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;
|
size_t count = 0;
|
||||||
for (auto obj : objects) count += obj->instances.size();
|
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);
|
instances.emplace_back(minst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
arrangement::BedShapeHint bedhint;
|
arrangement::BedShapeHint bedhint;
|
||||||
|
|
||||||
if (bb) {
|
if (bb)
|
||||||
bedhint.type = arrangement::BedShapeType::BOX;
|
bedhint = arrangement::BedShapeHint(
|
||||||
bedhint.shape.box = BoundingBox(scaled(bb->min), scaled(bb->max));
|
BoundingBox(scaled(bb->min), scaled(bb->max)));
|
||||||
}
|
|
||||||
|
|
||||||
arrangement::arrange(input, scaled(dist), bedhint);
|
arrangement::arrange(input, scaled(dist), bedhint);
|
||||||
|
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
|
|
||||||
for(size_t i = 0; i < input.size(); ++i) {
|
for(size_t i = 0; i < input.size(); ++i) {
|
||||||
auto inst = instances[i];
|
if (input[i].bed_idx == 0) { // no logical beds are allowed
|
||||||
inst->set_rotation(Z, input[i].rotation);
|
instances[i]->apply_arrange_result(input[i].translation,
|
||||||
auto tr = unscaled<double>(input[i].translation);
|
input[i].rotation);
|
||||||
inst->set_offset(X, tr.x());
|
} else ret = false;
|
||||||
inst->set_offset(Y, tr.y());
|
|
||||||
|
|
||||||
if (input[i].bed_idx != 0) ret = false; // no logical beds are allowed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1843,6 +1809,7 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
|
||||||
{
|
{
|
||||||
static const double SIMPLIFY_TOLERANCE_MM = 0.1;
|
static const double SIMPLIFY_TOLERANCE_MM = 0.1;
|
||||||
|
|
||||||
|
if (!m_arrange_cache.valid) {
|
||||||
Vec3d rotation = get_rotation();
|
Vec3d rotation = get_rotation();
|
||||||
rotation.z() = 0.;
|
rotation.z() = 0.;
|
||||||
Transform3d trafo_instance =
|
Transform3d trafo_instance =
|
||||||
|
@ -1860,10 +1827,18 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
|
||||||
Polygons pp{p};
|
Polygons pp{p};
|
||||||
pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM));
|
pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM));
|
||||||
if (!pp.empty()) p = pp.front();
|
if (!pp.empty()) p = pp.front();
|
||||||
|
m_arrange_cache.poly.contour = std::move(p);
|
||||||
|
m_arrange_cache.valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
ExPolygon ep; ep.contour = std::move(p);
|
arrangement::ArrangePolygon ret{m_arrange_cache.poly,
|
||||||
|
Vec2crd{scaled(get_offset(X)),
|
||||||
|
scaled(get_offset(Y))},
|
||||||
|
get_rotation(Z)};
|
||||||
|
|
||||||
return {ep, 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
|
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
|
||||||
|
|
|
@ -512,7 +512,7 @@ public:
|
||||||
ModelObject* get_object() const { return this->object; }
|
ModelObject* get_object() const { return this->object; }
|
||||||
|
|
||||||
const Geometry::Transformation& get_transformation() const { return m_transformation; }
|
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(); }
|
const Vec3d& get_offset() const { return m_transformation.get_offset(); }
|
||||||
double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
|
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(); }
|
const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
|
||||||
double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
|
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(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); }
|
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(); }
|
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); }
|
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(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); }
|
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(); }
|
const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
|
||||||
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
|
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
|
||||||
bool is_left_handed() const { return m_transformation.is_left_handed(); }
|
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(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); }
|
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
|
// To be called on an external mesh
|
||||||
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
|
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; }
|
bool is_printable() const { return print_volume_state == PVS_Inside; }
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
// Implement arrangement::Arrangeable interface
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Getting the input polygon for arrange
|
// Getting the input polygon for arrange
|
||||||
arrangement::ArrangePolygon get_arrange_polygon() const;
|
arrangement::ArrangePolygon get_arrange_polygon() const;
|
||||||
|
|
||||||
// Apply the arrange result on the ModelInstance
|
// 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
|
// write the transformation data into the model instance
|
||||||
set_rotation(Z, rot_rads);
|
set_rotation(Z, rot_rads);
|
||||||
set_offset(X, unscale<double>(offs(X)));
|
set_offset(X, unscale<double>(offs(X)));
|
||||||
set_offset(Y, unscale<double>(offs(Y)));
|
set_offset(Y, unscale<double>(offs(Y)));
|
||||||
|
m_arrange_cache.bed_idx = bed_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -583,15 +580,28 @@ private:
|
||||||
ModelObject* object;
|
ModelObject* object;
|
||||||
|
|
||||||
// Constructor, which assigns a new unique ID.
|
// 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.
|
// Constructor, which assigns a new unique ID.
|
||||||
explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
|
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;
|
ModelInstance() = delete;
|
||||||
explicit ModelInstance(ModelInstance &&rhs) = delete;
|
explicit ModelInstance(ModelInstance &&rhs) = delete;
|
||||||
ModelInstance& operator=(const ModelInstance &rhs) = delete;
|
ModelInstance& operator=(const ModelInstance &rhs) = delete;
|
||||||
ModelInstance& operator=(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.
|
// The print bed content.
|
||||||
|
|
|
@ -5739,8 +5739,9 @@ const SLAPrint* GLCanvas3D::sla_print() const
|
||||||
return (m_process == nullptr) ? nullptr : m_process->sla_print();
|
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)
|
||||||
{
|
{
|
||||||
|
Vec2d offset = unscaled(off);
|
||||||
m_pos = offset;
|
m_pos = offset;
|
||||||
m_rotation = rotation_rads;
|
m_rotation = rotation_rads;
|
||||||
DynamicPrintConfig cfg;
|
DynamicPrintConfig cfg;
|
||||||
|
|
|
@ -624,7 +624,7 @@ public:
|
||||||
return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y());
|
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
|
arrangement::ArrangePolygon get_arrange_polygon() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -1272,59 +1272,64 @@ struct Plater::priv
|
||||||
// objects would be frozen for the user. In case of arrange, an animation
|
// 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 shown, or with the optimize orientations, partial results
|
||||||
// could be displayed.
|
// could be displayed.
|
||||||
class Job: public wxEvtHandler {
|
class Job : public wxEvtHandler
|
||||||
|
{
|
||||||
int m_range = 100;
|
int m_range = 100;
|
||||||
std::future<void> m_ftr;
|
std::future<void> m_ftr;
|
||||||
priv *m_plater = nullptr;
|
priv * m_plater = nullptr;
|
||||||
std::atomic<bool> m_running {false}, m_canceled {false};
|
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||||
bool m_finalized = false;
|
bool m_finalized = false;
|
||||||
|
|
||||||
void run() {
|
void run()
|
||||||
m_running.store(true); process(); m_running.store(false);
|
{
|
||||||
|
m_running.store(true);
|
||||||
|
process();
|
||||||
|
m_running.store(false);
|
||||||
|
|
||||||
// ensure to call the last status to finalize the job
|
// ensure to call the last status to finalize the job
|
||||||
update_status(status_range(), "");
|
update_status(status_range(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
// status range for a particular job
|
// status range for a particular job
|
||||||
virtual int status_range() const { return 100; }
|
virtual int status_range() const { return 100; }
|
||||||
|
|
||||||
// status update, to be used from the work thread (process() method)
|
// status update, to be used from the work thread (process() method)
|
||||||
void update_status(int st, const wxString& msg = "") {
|
void update_status(int st, const wxString &msg = "")
|
||||||
auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg);
|
{
|
||||||
|
auto evt = new wxThreadEvent();
|
||||||
|
evt->SetInt(st);
|
||||||
|
evt->SetString(msg);
|
||||||
wxQueueEvent(this, evt);
|
wxQueueEvent(this, evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
priv& plater() { return *m_plater; }
|
priv &plater() { return *m_plater; }
|
||||||
bool was_canceled() const { return m_canceled.load(); }
|
bool was_canceled() const { return m_canceled.load(); }
|
||||||
|
|
||||||
// Launched just before start(), a job can use it to prepare internals
|
// Launched just before start(), a job can use it to prepare internals
|
||||||
virtual void prepare() {}
|
virtual void prepare() {}
|
||||||
|
|
||||||
// Launched when the job is finished. It refreshes the 3dscene by def.
|
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||||
virtual void finalize() {
|
virtual void finalize()
|
||||||
|
{
|
||||||
// Do a full refresh of scene tree, including regenerating
|
// Do a full refresh of scene tree, including regenerating
|
||||||
// all the GLVolumes. FIXME The update function shall just
|
// all the GLVolumes. FIXME The update function shall just
|
||||||
// reload the modified matrices.
|
// reload the modified matrices.
|
||||||
if(! was_canceled())
|
if (!was_canceled()) plater().update(true);
|
||||||
plater().update(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
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();
|
auto msg = evt.GetString();
|
||||||
if(! msg.empty()) plater().statusbar()->set_status_text(msg);
|
if (!msg.empty())
|
||||||
|
plater().statusbar()->set_status_text(msg);
|
||||||
|
|
||||||
if(m_finalized) return;
|
if (m_finalized) return;
|
||||||
|
|
||||||
plater().statusbar()->set_progress(evt.GetInt());
|
plater().statusbar()->set_progress(evt.GetInt());
|
||||||
if(evt.GetInt() == status_range()) {
|
if (evt.GetInt() == status_range()) {
|
||||||
|
|
||||||
// set back the original range and cancel callback
|
// set back the original range and cancel callback
|
||||||
plater().statusbar()->set_range(m_range);
|
plater().statusbar()->set_range(m_range);
|
||||||
plater().statusbar()->set_cancel_callback();
|
plater().statusbar()->set_cancel_callback();
|
||||||
|
@ -1338,28 +1343,16 @@ struct Plater::priv
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use this when we all migrated to VS2019
|
Job(const Job &) = delete;
|
||||||
// Job(const Job&) = delete;
|
Job(Job &&) = default;
|
||||||
// Job(Job&&) = default;
|
Job &operator=(const Job &) = delete;
|
||||||
// Job& operator=(const Job&) = delete;
|
Job &operator=(Job &&) = default;
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void process() = 0;
|
virtual void process() = 0;
|
||||||
|
|
||||||
void start() { // Start the job. No effect if the job is already running
|
void start()
|
||||||
if(! m_running.load()) {
|
{ // Start the job. No effect if the job is already running
|
||||||
|
if (!m_running.load()) {
|
||||||
prepare();
|
prepare();
|
||||||
|
|
||||||
// Save the current status indicatior range and push the new one
|
// Save the current status indicatior range and push the new one
|
||||||
|
@ -1368,9 +1361,8 @@ struct Plater::priv
|
||||||
|
|
||||||
// init cancellation flag and set the cancel callback
|
// init cancellation flag and set the cancel callback
|
||||||
m_canceled.store(false);
|
m_canceled.store(false);
|
||||||
plater().statusbar()->set_cancel_callback( [this](){
|
plater().statusbar()->set_cancel_callback(
|
||||||
m_canceled.store(true);
|
[this]() { m_canceled.store(true); });
|
||||||
});
|
|
||||||
|
|
||||||
m_finalized = false;
|
m_finalized = false;
|
||||||
|
|
||||||
|
@ -1379,9 +1371,10 @@ struct Plater::priv
|
||||||
|
|
||||||
try { // Execute the job
|
try { // Execute the job
|
||||||
m_ftr = std::async(std::launch::async, &Job::run, this);
|
m_ftr = std::async(std::launch::async, &Job::run, this);
|
||||||
} catch(std::exception& ) {
|
} catch (std::exception &) {
|
||||||
update_status(status_range(),
|
update_status(status_range(),
|
||||||
_(L("ERROR: not enough resources to execute a new job.")));
|
_(L("ERROR: not enough resources to "
|
||||||
|
"execute a new job.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The state changes will be undone when the process hits the
|
// The state changes will be undone when the process hits the
|
||||||
|
@ -1389,16 +1382,18 @@ struct Plater::priv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// To wait for the running job and join the threads. False is returned
|
// To wait for the running job and join the threads. False is
|
||||||
// if the timeout has been reached and the job is still running. Call
|
// returned if the timeout has been reached and the job is still
|
||||||
// cancel() before this fn if you want to explicitly end the job.
|
// running. Call cancel() before this fn if you want to explicitly
|
||||||
bool join(int timeout_ms = 0) const {
|
// end the job.
|
||||||
if(!m_ftr.valid()) return true;
|
bool join(int timeout_ms = 0) const
|
||||||
|
{
|
||||||
|
if (!m_ftr.valid()) return true;
|
||||||
|
|
||||||
if(timeout_ms <= 0)
|
if (timeout_ms <= 0)
|
||||||
m_ftr.wait();
|
m_ftr.wait();
|
||||||
else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) ==
|
else if (m_ftr.wait_for(std::chrono::milliseconds(
|
||||||
std::future_status::timeout)
|
timeout_ms)) == std::future_status::timeout)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1413,6 +1408,154 @@ struct Plater::priv
|
||||||
Rotoptimize
|
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
|
// 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
|
// run at a time. Also, the background process will be stopped if a job is
|
||||||
// started.
|
// started.
|
||||||
|
@ -1422,84 +1565,8 @@ struct Plater::priv
|
||||||
|
|
||||||
priv * m_plater;
|
priv * m_plater;
|
||||||
|
|
||||||
class ArrangeJob : public Job
|
ArrangeJob arrange_job{m_plater};
|
||||||
{
|
RotoptimizeJob rotoptimize_job{m_plater};
|
||||||
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};
|
|
||||||
|
|
||||||
// To create a new job, just define a new subclass of Job, implement
|
// To create a new job, just define a new subclass of Job, implement
|
||||||
// the process and the optional prepare() and finalize() methods
|
// 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 Plater::priv::get_bed_shape_hint() const {
|
||||||
arrangement::BedShapeHint bedshape;
|
|
||||||
|
|
||||||
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
|
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
|
||||||
assert(bed_shape_opt);
|
assert(bed_shape_opt);
|
||||||
|
|
||||||
if (bed_shape_opt) {
|
if (!bed_shape_opt) return {};
|
||||||
|
|
||||||
auto &bedpoints = bed_shape_opt->values;
|
auto &bedpoints = bed_shape_opt->values;
|
||||||
Polyline bedpoly; bedpoly.points.reserve(bedpoints.size());
|
Polyline bedpoly; bedpoly.points.reserve(bedpoints.size());
|
||||||
for (auto &v : bedpoints) bedpoly.append(scaled(v));
|
for (auto &v : bedpoints) bedpoly.append(scaled(v));
|
||||||
bedshape = arrangement::bedShape(bedpoly);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bedshape;
|
return arrangement::BedShapeHint(bedpoly);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() {
|
void Plater::priv::ArrangeJob::process() {
|
||||||
auto count = unsigned(m_selected.size());
|
static const auto arrangestr = _(L("Arranging"));
|
||||||
plater().model.arrange_objects(6.f, nullptr);
|
|
||||||
// static const auto arrangestr = _(L("Arranging"));
|
|
||||||
|
|
||||||
// // FIXME: I don't know how to obtain the minimum distance, it depends
|
// 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.
|
// on printer technology. I guess the following should work but it crashes.
|
||||||
// double dist = 6; // PrintConfig::min_object_distance(config);
|
double dist = 6; // PrintConfig::min_object_distance(config);
|
||||||
// if (plater().printer_technology == ptFFF) {
|
if (plater().printer_technology == ptFFF) {
|
||||||
// dist = PrintConfig::min_object_distance(plater().config);
|
dist = PrintConfig::min_object_distance(plater().config);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// coord_t min_obj_distance = scaled(dist);
|
coord_t min_obj_distance = scaled(dist);
|
||||||
// auto count = unsigned(m_selected.size());
|
auto count = unsigned(m_selected.polys.size());
|
||||||
// arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint();
|
arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint();
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
// arrangement::arrange(m_selected, m_unselected, min_obj_distance,
|
arrangement::arrange(m_selected.polys, m_unselected.polys,
|
||||||
// bedshape,
|
min_obj_distance,
|
||||||
// [this, count](unsigned st) {
|
bedshape,
|
||||||
// if (st > 0) // will not finalize after last one
|
[this, count](unsigned st) {
|
||||||
// update_status(count - st, arrangestr);
|
if (st > 0) // will not finalize after last one
|
||||||
// },
|
update_status(count - st, arrangestr);
|
||||||
// [this]() { return was_canceled(); });
|
},
|
||||||
// } catch (std::exception & /*e*/) {
|
[this]() { return was_canceled(); });
|
||||||
// GUI::show_error(plater().q,
|
} catch (std::exception & /*e*/) {
|
||||||
// _(L("Could not arrange model objects! "
|
GUI::show_error(plater().q,
|
||||||
// "Some geometries may be invalid.")));
|
_(L("Could not arrange model objects! "
|
||||||
// }
|
"Some geometries may be invalid.")));
|
||||||
|
}
|
||||||
|
|
||||||
// finalize just here.
|
// finalize just here.
|
||||||
update_status(int(count),
|
update_status(int(count),
|
||||||
|
@ -2503,11 +2567,27 @@ void find_new_position(const Model & model,
|
||||||
coord_t min_d,
|
coord_t min_d,
|
||||||
const arrangement::BedShapeHint &bedhint)
|
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();
|
int obj_idx = plater().get_selected_object_idx();
|
||||||
if (obj_idx < 0) { return; }
|
if (obj_idx < 0) { return; }
|
||||||
|
|
Loading…
Reference in a new issue