Arrange cache in ModeInstance and logical bed remembered.
This commit is contained in:
parent
df7bb94daf
commit
1b0e192046
@ -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);
|
||||
// });
|
||||
// }
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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; }
|
||||
|
Loading…
Reference in New Issue
Block a user