This commit is contained in:
Enrico Turri 2019-07-22 11:23:52 +02:00
commit 996b227391
26 changed files with 1947 additions and 2056 deletions

View File

@ -19,7 +19,7 @@ AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: false
BinPackArguments: true
BinPackParameters: false
BraceWrapping:
AfterClass: true

View File

@ -30,9 +30,7 @@ using Circle = _Circle<PointImpl>;
using Item = _Item<PolygonImpl>;
using Rectangle = _Rectangle<PolygonImpl>;
using PackGroup = _PackGroup<PolygonImpl>;
using IndexedPackGroup = _IndexedPackGroup<PolygonImpl>;
using FillerSelection = selections::_FillerSelection<PolygonImpl>;
using FirstFitSelection = selections::_FirstFitSelection<PolygonImpl>;
@ -61,20 +59,20 @@ extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute(
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator>
PackGroup nest(Iterator from, Iterator to,
void nest(Iterator from, Iterator to,
const typename Placer::BinType& bin,
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
Nester<Placer, Selector> nester(bin, dist, pconf, sconf);
return nester.execute(from, to);
_Nester<Placer, Selector> nester(bin, dist, pconf, sconf);
nester.execute(from, to);
}
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator>
PackGroup nest(Iterator from, Iterator to,
void nest(Iterator from, Iterator to,
const typename Placer::BinType& bin,
ProgressFunction prg,
StopCondition scond = []() { return false; },
@ -82,10 +80,10 @@ PackGroup nest(Iterator from, Iterator to,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
Nester<Placer, Selector> nester(bin, dist, pconf, sconf);
_Nester<Placer, Selector> nester(bin, dist, pconf, sconf);
if(prg) nester.progressIndicator(prg);
if(scond) nester.stopCondition(scond);
return nester.execute(from, to);
nester.execute(from, to);
}
#ifdef LIBNEST2D_STATIC
@ -93,14 +91,14 @@ PackGroup nest(Iterator from, Iterator to,
extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template PackGroup nest(std::vector<Item>::iterator from,
extern template void nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
extern template PackGroup nest(std::vector<Item>::iterator from,
extern template void nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
@ -114,20 +112,19 @@ extern template PackGroup nest(std::vector<Item>::iterator from,
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
PackGroup nest(Container&& cont,
void nest(Container&& cont,
const typename Placer::BinType& bin,
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
return nest<Placer, Selector>(cont.begin(), cont.end(),
bin, dist, pconf, sconf);
nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, pconf, sconf);
}
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
PackGroup nest(Container&& cont,
void nest(Container&& cont,
const typename Placer::BinType& bin,
ProgressFunction prg,
StopCondition scond = []() { return false; },
@ -135,8 +132,8 @@ PackGroup nest(Container&& cont,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
return nest<Placer, Selector>(cont.begin(), cont.end(),
bin, prg, scond, dist, pconf, sconf);
nest<Placer, Selector>(cont.begin(), cont.end(), bin, prg, scond, dist,
pconf, sconf);
}
}

View File

@ -41,25 +41,25 @@ template<> struct HolesContainer<PolygonImpl> { using Type = ClipperLib::Paths;
namespace pointlike {
// Tell libnest2d how to extract the X coord from a ClipperPoint object
template<> inline TCoord<PointImpl> x(const PointImpl& p)
template<> inline ClipperLib::cInt x(const PointImpl& p)
{
return p.X;
}
// Tell libnest2d how to extract the Y coord from a ClipperPoint object
template<> inline TCoord<PointImpl> y(const PointImpl& p)
template<> inline ClipperLib::cInt y(const PointImpl& p)
{
return p.Y;
}
// Tell libnest2d how to extract the X coord from a ClipperPoint object
template<> inline TCoord<PointImpl>& x(PointImpl& p)
template<> inline ClipperLib::cInt& x(PointImpl& p)
{
return p.X;
}
// Tell libnest2d how to extract the Y coord from a ClipperPoint object
template<> inline TCoord<PointImpl>& y(PointImpl& p)
template<> inline ClipperLib::cInt& y(PointImpl& p)
{
return p.Y;
}
@ -71,7 +71,8 @@ template<> inline TCoord<PointImpl>& y(PointImpl& p)
namespace shapelike {
template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
template<>
inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance, const PolygonTag&)
{
#define DISABLE_BOOST_OFFSET
@ -123,6 +124,14 @@ template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
}
}
template<>
inline void offset(PathImpl& sh, TCoord<PointImpl> distance, const PathTag&)
{
PolygonImpl p(std::move(sh));
offset(p, distance, PolygonTag());
sh = p.Contour;
}
// Tell libnest2d how to make string out of a ClipperPolygon object
template<> inline std::string toString(const PolygonImpl& sh)
{
@ -260,9 +269,11 @@ inline TMultiShape<PolygonImpl> clipper_execute(
assert(!pptr->IsHole());
if(pptr->IsOpen()) {
if(!poly.Contour.empty() ) {
auto front_p = poly.Contour.front();
poly.Contour.emplace_back(front_p);
auto &back_p = poly.Contour.back();
if(front_p.X != back_p.X || front_p.Y != back_p.X)
poly.Contour.emplace_back(front_p);
}
for(auto h : pptr->Childs) { processHole(h, poly); }
@ -275,9 +286,11 @@ inline TMultiShape<PolygonImpl> clipper_execute(
assert(pptr->IsHole());
if(pptr->IsOpen()) {
auto front_p = poly.Holes.back().front();
poly.Holes.back().emplace_back(front_p);
if(!poly.Contour.empty() ) {
auto front_p = poly.Contour.front();
auto &back_p = poly.Contour.back();
if(front_p.X != back_p.X || front_p.Y != back_p.X)
poly.Contour.emplace_back(front_p);
}
for(auto c : pptr->Childs) processPoly(c);

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,8 @@
namespace libnest2d {
static const constexpr int BIN_ID_UNSET = -1;
/**
* \brief An item to be placed on a bin.
*
@ -34,22 +36,22 @@ class _Item {
RawShape sh_;
// Transformation data
Vertex translation_;
Radians rotation_;
Coord offset_distance_;
Vertex translation_{0, 0};
Radians rotation_{0.0};
Coord inflation_{0};
// Info about whether the transformations will have to take place
// This is needed because if floating point is used, it is hard to say
// that a zero angle is not a rotation because of testing for equality.
bool has_rotation_ = false, has_translation_ = false, has_offset_ = false;
bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false;
// For caching the calculations as they can get pretty expensive.
mutable RawShape tr_cache_;
mutable bool tr_cache_valid_ = false;
mutable double area_cache_ = 0;
mutable bool area_cache_valid_ = false;
mutable RawShape offset_cache_;
mutable bool offset_cache_valid_ = false;
mutable RawShape inflate_cache_;
mutable bool inflate_cache_valid_ = false;
enum class Convexity: char {
UNCHECKED,
@ -66,6 +68,9 @@ class _Item {
BBCache(): valid(false) {}
} bb_cache_;
int binid_{BIN_ID_UNSET}, priority_{0};
bool fixed_{false};
public:
/// The type of the shape which was handed over as the template argument.
@ -121,8 +126,16 @@ public:
inline _Item(TContour<RawShape>&& contour,
THolesContainer<RawShape>&& holes):
sh_(sl::create<RawShape>(std::move(contour),
std::move(holes))) {}
sh_(sl::create<RawShape>(std::move(contour), std::move(holes))) {}
inline bool isFixed() const noexcept { return fixed_; }
inline void markAsFixed(bool fixed = true) { fixed_ = fixed; }
inline void binId(int idx) { binid_ = idx; }
inline int binId() const noexcept { return binid_; }
inline void priority(int p) { priority_ = p; }
inline int priority() const noexcept { return priority_; }
/**
* @brief Convert the polygon to string representation. The format depends
@ -200,7 +213,7 @@ public:
double ret ;
if(area_cache_valid_) ret = area_cache_;
else {
ret = sl::area(offsettedShape());
ret = sl::area(infaltedShape());
area_cache_ = ret;
area_cache_valid_ = true;
}
@ -272,16 +285,20 @@ public:
rotation(rotation() + rads);
}
inline void addOffset(Coord distance) BP2D_NOEXCEPT
inline void inflation(Coord distance) BP2D_NOEXCEPT
{
offset_distance_ = distance;
has_offset_ = true;
inflation_ = distance;
has_inflation_ = true;
invalidateCache();
}
inline void removeOffset() BP2D_NOEXCEPT {
has_offset_ = false;
invalidateCache();
inline Coord inflation() const BP2D_NOEXCEPT {
return inflation_;
}
inline void inflate(Coord distance) BP2D_NOEXCEPT
{
inflation(inflation() + distance);
}
inline Radians rotation() const BP2D_NOEXCEPT
@ -315,7 +332,7 @@ public:
{
if(tr_cache_valid_) return tr_cache_;
RawShape cpy = offsettedShape();
RawShape cpy = infaltedShape();
if(has_rotation_) sl::rotate(cpy, rotation_);
if(has_translation_) sl::translate(cpy, translation_);
tr_cache_ = cpy; tr_cache_valid_ = true;
@ -336,17 +353,17 @@ public:
inline void resetTransformation() BP2D_NOEXCEPT
{
has_translation_ = false; has_rotation_ = false; has_offset_ = false;
has_translation_ = false; has_rotation_ = false; has_inflation_ = false;
invalidateCache();
}
inline Box boundingBox() const {
if(!bb_cache_.valid) {
if(!has_rotation_)
bb_cache_.bb = sl::boundingBox(offsettedShape());
bb_cache_.bb = sl::boundingBox(infaltedShape());
else {
// TODO make sure this works
auto rotsh = offsettedShape();
auto rotsh = infaltedShape();
sl::rotate(rotsh, rotation_);
bb_cache_.bb = sl::boundingBox(rotsh);
}
@ -395,14 +412,14 @@ public:
private:
inline const RawShape& offsettedShape() const {
if(has_offset_ ) {
if(offset_cache_valid_) return offset_cache_;
inline const RawShape& infaltedShape() const {
if(has_inflation_ ) {
if(inflate_cache_valid_) return inflate_cache_;
offset_cache_ = sh_;
sl::offset(offset_cache_, offset_distance_);
offset_cache_valid_ = true;
return offset_cache_;
inflate_cache_ = sh_;
sl::offset(inflate_cache_, inflation_);
inflate_cache_valid_ = true;
return inflate_cache_;
}
return sh_;
}
@ -412,7 +429,7 @@ private:
tr_cache_valid_ = false;
lmb_valid_ = false; rmt_valid_ = false;
area_cache_valid_ = false;
offset_cache_valid_ = false;
inflate_cache_valid_ = false;
bb_cache_.valid = false;
convexity_ = Convexity::UNCHECKED;
}
@ -492,24 +509,6 @@ template<class RawShape> using _ItemGroup = std::vector<_ItemRef<RawShape>>;
template<class RawShape>
using _PackGroup = std::vector<std::vector<_ItemRef<RawShape>>>;
/**
* \brief A list of packed (index, item) pair vectors. Each vector represents a
* bin.
*
* The index is points to the position of the item in the original input
* sequence. This way the caller can use the items as a transformation data
* carrier and transform the original objects manually.
*/
template<class RawShape>
using _IndexedPackGroup = std::vector<
std::vector<
std::pair<
unsigned,
_ItemRef<RawShape>
>
>
>;
template<class Iterator>
struct ConstItemRange {
Iterator from;
@ -738,54 +737,45 @@ public:
return impl_.getResult();
}
/**
* @brief Loading a group of already packed bins. It is best to use a result
* from a previous packing. The algorithm will consider this input as if the
* objects are already packed and not move them. If any of these items are
* outside the bin, it is up to the placer algorithm what will happen.
* Packing additional items can fail for the bottom-left and nfp placers.
* @param pckgrp A packgroup which is a vector of item vectors. Each item
* vector corresponds to a packed bin.
*/
inline void preload(const PackGroup& pckgrp) { impl_.preload(pckgrp); }
void clear() { impl_.clear(); }
};
/**
* The Arranger is the front-end class for the libnest2d library. It takes the
* input items and outputs the items with the proper transformations to be
* inside the provided bin.
* The _Nester is the front-end class for the libnest2d library. It takes the
* input items and changes their transformations to be inside the provided bin.
*/
template<class PlacementStrategy, class SelectionStrategy >
class Nester {
class _Nester {
using TSel = SelectionStrategyLike<SelectionStrategy>;
TSel selector_;
public:
using Item = typename PlacementStrategy::Item;
using ShapeType = typename Item::ShapeType;
using ItemRef = std::reference_wrapper<Item>;
using TPlacer = PlacementStrategyLike<PlacementStrategy>;
using BinType = typename TPlacer::BinType;
using PlacementConfig = typename TPlacer::Config;
using SelectionConfig = typename TSel::Config;
using Unit = TCoord<TPoint<typename Item::ShapeType>>;
using IndexedPackGroup = _IndexedPackGroup<typename Item::ShapeType>;
using Coord = TCoord<TPoint<typename Item::ShapeType>>;
using PackGroup = _PackGroup<typename Item::ShapeType>;
using ResultType = PackGroup;
using ResultTypeIndexed = IndexedPackGroup;
private:
BinType bin_;
PlacementConfig pconfig_;
Unit min_obj_distance_;
Coord min_obj_distance_;
using SItem = typename SelectionStrategy::Item;
using TPItem = remove_cvref_t<Item>;
using TSItem = remove_cvref_t<SItem>;
std::vector<TPItem> item_cache_;
StopCondition stopfn_;
template<class It> using TVal = remove_ref_t<typename It::value_type>;
template<class It, class Out>
using ItemIteratorOnly =
enable_if_t<std::is_convertible<TVal<It>&, TPItem&>::value, Out>;
public:
@ -798,10 +788,8 @@ public:
template<class TBinType = BinType,
class PConf = PlacementConfig,
class SConf = SelectionConfig>
Nester( TBinType&& bin,
Unit min_obj_distance = 0,
const PConf& pconfig = PConf(),
const SConf& sconfig = SConf()):
_Nester(TBinType&& bin, Coord min_obj_distance = 0,
const PConf& pconfig = PConf(), const SConf& sconfig = SConf()):
bin_(std::forward<TBinType>(bin)),
pconfig_(pconfig),
min_obj_distance_(min_obj_distance)
@ -814,182 +802,59 @@ public:
void configure(const PlacementConfig& pconf) { pconfig_ = pconf; }
void configure(const SelectionConfig& sconf) { selector_.configure(sconf); }
void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) {
void configure(const PlacementConfig& pconf, const SelectionConfig& sconf)
{
pconfig_ = pconf;
selector_.configure(sconf);
}
void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) {
void configure(const SelectionConfig& sconf, const PlacementConfig& pconf)
{
pconfig_ = pconf;
selector_.configure(sconf);
}
/**
* \brief Arrange an input sequence and return a PackGroup object with
* the packed groups corresponding to the bins.
* \brief Arrange an input sequence of _Item-s.
*
* To get the result, call the translation(), rotation() and binId()
* methods of each item. If only the transformed polygon is needed, call
* transformedShape() to get the properly transformed shapes.
*
* The number of groups in the pack group is the number of bins opened by
* the selection algorithm.
*/
template<class TIterator>
inline PackGroup execute(TIterator from, TIterator to)
template<class It>
inline ItemIteratorOnly<It, void> execute(It from, It to)
{
return _execute(from, to);
}
auto infl = static_cast<Coord>(std::ceil(min_obj_distance_/2.0));
if(infl > 0) std::for_each(from, to, [this, infl](Item& item) {
item.inflate(infl);
});
/**
* A version of the arrange method returning an IndexedPackGroup with
* the item indexes into the original input sequence.
*
* Takes a little longer to collect the indices. Scales linearly with the
* input sequence size.
*/
template<class TIterator>
inline IndexedPackGroup executeIndexed(TIterator from, TIterator to)
{
return _executeIndexed(from, to);
}
selector_.template packItems<PlacementStrategy>(
from, to, bin_, pconfig_);
/// Shorthand to normal arrange method.
template<class TIterator>
inline PackGroup operator() (TIterator from, TIterator to)
{
return _execute(from, to);
if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) {
item.inflate(-infl);
});
}
/// Set a progress indicator function object for the selector.
inline Nester& progressIndicator(ProgressFunction func)
inline _Nester& progressIndicator(ProgressFunction func)
{
selector_.progressIndicator(func); return *this;
}
/// Set a predicate to tell when to abort nesting.
inline Nester& stopCondition(StopCondition fn)
inline _Nester& stopCondition(StopCondition fn)
{
selector_.stopCondition(fn); return *this;
stopfn_ = fn; selector_.stopCondition(fn); return *this;
}
inline const PackGroup& lastResult() const
{
return selector_.getResult();
}
inline void preload(const PackGroup& pgrp)
{
selector_.preload(pgrp);
}
inline void preload(const IndexedPackGroup& ipgrp)
{
PackGroup pgrp; pgrp.reserve(ipgrp.size());
for(auto& ig : ipgrp) {
pgrp.emplace_back(); pgrp.back().reserve(ig.size());
for(auto& r : ig) pgrp.back().emplace_back(r.second);
}
preload(pgrp);
}
private:
template<class TIterator,
class IT = remove_cvref_t<typename TIterator::value_type>,
// 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.
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
>
inline const PackGroup& _execute(TIterator from, TIterator to, bool = false)
{
__execute(from, to);
return lastResult();
}
template<class TIterator,
class IT = remove_cvref_t<typename TIterator::value_type>,
class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
>
inline const PackGroup& _execute(TIterator from, TIterator to, int = false)
{
item_cache_ = {from, to};
__execute(item_cache_.begin(), item_cache_.end());
return lastResult();
}
template<class TIterator,
class IT = remove_cvref_t<typename TIterator::value_type>,
// This function will be used only if the iterators are pointing to
// a type compatible with the libnest2d::_Item template.
// This way we can use references to input elements as they will
// have to exist for the lifetime of this call.
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
>
inline IndexedPackGroup _executeIndexed(TIterator from,
TIterator to,
bool = false)
{
__execute(from, to);
return createIndexedPackGroup(from, to, selector_);
}
template<class TIterator,
class IT = remove_cvref_t<typename TIterator::value_type>,
class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
>
inline IndexedPackGroup _executeIndexed(TIterator from,
TIterator to,
int = false)
{
item_cache_ = {from, to};
__execute(item_cache_.begin(), item_cache_.end());
return createIndexedPackGroup(from, to, selector_);
}
template<class TIterator>
static IndexedPackGroup createIndexedPackGroup(TIterator from,
TIterator to,
TSel& selector)
{
IndexedPackGroup pg;
pg.reserve(selector.getResult().size());
const PackGroup& pckgrp = selector.getResult();
for(size_t i = 0; i < pckgrp.size(); i++) {
auto items = pckgrp[i];
pg.emplace_back();
pg[i].reserve(items.size());
for(Item& itemA : items) {
auto it = from;
unsigned idx = 0;
while(it != to) {
Item& itemB = *it;
if(&itemB == &itemA) break;
it++; idx++;
}
pg[i].emplace_back(idx, itemA);
}
}
return pg;
}
template<class TIter> inline void __execute(TIter from, TIter to)
{
if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) {
item.addOffset(static_cast<Unit>(std::ceil(min_obj_distance_/2.0)));
});
selector_.template packItems<PlacementStrategy>(
from, to, bin_, pconfig_);
if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) {
item.removeOffset();
});
}
};
}

View File

@ -68,11 +68,11 @@ public:
return toWallPoly(item, Dir::DOWN);
}
inline Unit availableSpaceLeft(const Item& item) {
inline Coord availableSpaceLeft(const Item& item) {
return availableSpace(item, Dir::LEFT);
}
inline Unit availableSpaceDown(const Item& item) {
inline Coord availableSpaceDown(const Item& item) {
return availableSpace(item, Dir::DOWN);
}
@ -83,7 +83,7 @@ protected:
// Get initial position for item in the top right corner
setInitialPosition(item);
Unit d = availableSpaceDown(item);
Coord d = availableSpaceDown(item);
auto eps = config_.epsilon;
bool can_move = d > eps;
bool can_be_packed = can_move;
@ -179,7 +179,7 @@ protected:
return ret;
}
Unit availableSpace(const Item& _item, const Dir dir) {
Coord availableSpace(const Item& _item, const Dir dir) {
Item item (_item.transformedShape());
@ -223,7 +223,7 @@ protected:
cmp);
// Get the initial distance in floating point
Unit m = getCoord(*minvertex_it);
Coord m = getCoord(*minvertex_it);
// Check available distance for every vertex of item to the objects
// in the way for the nearest intersection

View File

@ -581,8 +581,12 @@ public:
static inline double overfit(const Box& bb, const Box& bin)
{
auto wdiff = double(bb.width() - bin.width());
auto hdiff = double(bb.height() - bin.height());
auto Bw = bin.width();
auto Bh = bin.height();
auto mBw = -Bw;
auto mBh = -Bh;
auto wdiff = double(bb.width()) + mBw;
auto hdiff = double(bb.height()) + mBh;
double diff = 0;
if(wdiff > 0) diff += wdiff;
if(hdiff > 0) diff += hdiff;
@ -801,7 +805,6 @@ private:
// optimize
config_.object_function = prev_func;
}
}
struct Optimum {
@ -816,29 +819,14 @@ private:
class Optimizer: public opt::TOptimizer<opt::Method::L_SUBPLEX> {
public:
Optimizer() {
Optimizer(float accuracy = 1.f) {
opt::StopCriteria stopcr;
stopcr.max_iterations = 200;
stopcr.max_iterations = unsigned(std::floor(1000 * accuracy));
stopcr.relative_score_difference = 1e-20;
this->stopcr_ = stopcr;
}
};
static Box boundingBox(const Box& pilebb, const Box& ibb ) {
auto& pminc = pilebb.minCorner();
auto& pmaxc = pilebb.maxCorner();
auto& iminc = ibb.minCorner();
auto& imaxc = ibb.maxCorner();
Vertex minc, maxc;
setX(minc, std::min(getX(pminc), getX(iminc)));
setY(minc, std::min(getY(pminc), getY(iminc)));
setX(maxc, std::max(getX(pmaxc), getX(imaxc)));
setY(maxc, std::max(getY(pmaxc), getY(imaxc)));
return Box(minc, maxc);
}
using Edges = EdgeCache<RawShape>;
template<class Range = ConstItemRange<typename Base::DefaultIter>>
@ -935,7 +923,7 @@ private:
_objfunc = [norm, binbb, pbb, ins_check](const Item& item)
{
auto ibb = item.boundingBox();
auto fullbb = boundingBox(pbb, ibb);
auto fullbb = sl::boundingBox(pbb, ibb);
double score = pl::distance(ibb.center(),
binbb.center());
@ -1005,14 +993,15 @@ private:
auto& rofn = rawobjfunc;
auto& nfpoint = getNfpPoint;
float accuracy = config_.accuracy;
__parallel::enumerate(
cache.corners().begin(),
cache.corners().end(),
[&results, &item, &rofn, &nfpoint, ch]
[&results, &item, &rofn, &nfpoint, ch, accuracy]
(double pos, size_t n)
{
Optimizer solver;
Optimizer solver(accuracy);
Item itemcpy = item;
auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy]
@ -1059,10 +1048,10 @@ private:
__parallel::enumerate(cache.corners(hidx).begin(),
cache.corners(hidx).end(),
[&results, &item, &nfpoint,
&rofn, ch, hidx]
&rofn, ch, hidx, accuracy]
(double pos, size_t n)
{
Optimizer solver;
Optimizer solver(accuracy);
Item itmcpy = item;
auto hole_ofn =

View File

@ -18,7 +18,6 @@ public:
using Segment = _Segment<Vertex>;
using BinType = TBin;
using Coord = TCoord<Vertex>;
using Unit = Coord;
using Config = Cfg;
using ItemGroup = _ItemGroup<RawShape>;
using DefaultIter = typename ItemGroup::const_iterator;
@ -131,7 +130,6 @@ using typename Base::Vertex; \
using typename Base::Segment; \
using typename Base::PackResult; \
using typename Base::Coord; \
using typename Base::Unit; \
private:
}

View File

@ -712,6 +712,11 @@ public:
packjob(placers[idx], remaining, idx); idx++;
}
int binid = 0;
for(auto &bin : packed_bins_) {
for(Item& itm : bin) itm.binId(binid);
binid++;
}
}
};

View File

@ -40,6 +40,20 @@ public:
std::vector<Placer> placers;
placers.reserve(last-first);
std::for_each(first, last, [this](Item& itm) {
if(itm.isFixed()) {
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);
}
});
// If the packed_items array is not empty we have to create as many
// placers as there are elements in packed bins and preload each item
// into the appropriate placer
@ -49,10 +63,9 @@ public:
placers.back().preload(ig);
}
std::copy(first, last, std::back_inserter(store_));
auto sortfunc = [](Item& i1, Item& i2) {
return i1.area() > i2.area();
int p1 = i1.priority(), p2 = i2.priority();
return p1 == p2 ? i1.area() > i2.area() : p1 > p2;
};
std::sort(store_.begin(), store_.end(), sortfunc);
@ -76,7 +89,6 @@ public:
}
}
auto it = store_.begin();
while(it != store_.end() && !cancelled()) {
@ -84,8 +96,10 @@ public:
size_t j = 0;
while(!was_packed && !cancelled()) {
for(; j < placers.size() && !was_packed && !cancelled(); j++) {
if((was_packed = placers[j].pack(*it, rem(it, store_) )))
makeProgress(placers[j], j);
if((was_packed = placers[j].pack(*it, rem(it, store_) ))) {
it->get().binId(int(j));
makeProgress(placers[j], j);
}
}
if(!was_packed) {

View File

@ -22,8 +22,6 @@ public:
inline void stopCondition(StopCondition cond) { stopcond_ = cond; }
inline void preload(const PackGroup& pckgrp) { packed_bins_ = pckgrp; }
inline void clear() { packed_bins_.clear(); }
protected:

View File

@ -363,29 +363,43 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight)
{5, 5},
{20, 20} };
Box bin(210, 250, {105, 125});
Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250));
ASSERT_EQ(bin.width(), 210);
ASSERT_EQ(bin.height(), 250);
ASSERT_EQ(getX(bin.center()), 105);
ASSERT_EQ(getY(bin.center()), 125);
auto groups = arrange(rects.begin(), rects.end());
_Nester<BottomLeftPlacer, DJDHeuristic> arrange(bin);
ASSERT_EQ(groups.size(), 1u);
ASSERT_EQ(groups[0].size(), rects.size());
arrange.execute(rects.begin(), rects.end());
auto max_group = std::max_element(rects.begin(), rects.end(),
[](const Item &i1, const Item &i2) {
return i1.binId() < i2.binId();
});
int groups = max_group == rects.end() ? 0 : max_group->binId() + 1;
ASSERT_EQ(groups, 1u);
ASSERT_TRUE(
std::all_of(rects.begin(), rects.end(), [](const Rectangle &itm) {
return itm.binId() != BIN_ID_UNSET;
}));
// check for no intersections, no containment:
for(auto result : groups) {
bool valid = true;
for(Item& r1 : result) {
for(Item& r2 : result) {
if(&r1 != &r2 ) {
valid = !Item::intersects(r1, r2) || Item::touches(r1, r2);
valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
ASSERT_TRUE(valid);
}
bool valid = true;
for(Item& r1 : rects) {
for(Item& r2 : rects) {
if(&r1 != &r2 ) {
valid = !Item::intersects(r1, r2) || Item::touches(r1, r2);
ASSERT_TRUE(valid);
valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
ASSERT_TRUE(valid);
}
}
}
}
TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
@ -415,21 +429,36 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
{5, 5},
{20, 20} };
Box bin(210, 250, {105, 125});
ASSERT_EQ(bin.width(), 210);
ASSERT_EQ(bin.height(), 250);
ASSERT_EQ(getX(bin.center()), 105);
ASSERT_EQ(getY(bin.center()), 125);
Coord min_obj_distance = 5;
Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250),
min_obj_distance);
_Nester<BottomLeftPlacer, DJDHeuristic> arrange(bin, min_obj_distance);
auto groups = arrange(rects.begin(), rects.end());
arrange.execute(rects.begin(), rects.end());
ASSERT_EQ(groups.size(), 1u);
ASSERT_EQ(groups[0].size(), rects.size());
auto max_group = std::max_element(rects.begin(), rects.end(),
[](const Item &i1, const Item &i2) {
return i1.binId() < i2.binId();
});
size_t groups = max_group == rects.end() ? 0 : max_group->binId() + 1;
ASSERT_EQ(groups, 1u);
ASSERT_TRUE(
std::all_of(rects.begin(), rects.end(), [](const Rectangle &itm) {
return itm.binId() != BIN_ID_UNSET;
}));
// check for no intersections, no containment:
auto result = groups[0];
bool valid = true;
for(Item& r1 : result) {
for(Item& r2 : result) {
for(Item& r1 : rects) {
for(Item& r2 : rects) {
if(&r1 != &r2 ) {
valid = !Item::intersects(r1, r2);
valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
@ -542,26 +571,23 @@ TEST(GeometryAlgorithms, convexHull) {
TEST(GeometryAlgorithms, NestTest) {
std::vector<Item> input = prusaParts();
PackGroup result = libnest2d::nest(input,
Box(250000000, 210000000),
[](unsigned cnt) {
std::cout
<< "parts left: " << cnt
<< std::endl;
});
libnest2d::nest(input, Box(250000000, 210000000), [](unsigned cnt) {
std::cout << "parts left: " << cnt << std::endl;
});
ASSERT_LE(result.size(), 2);
auto max_binid_it = std::max_element(input.begin(), input.end(),
[](const Item &i1, const Item &i2) {
return i1.binId() < i2.binId();
});
size_t partsum = std::accumulate(result.begin(),
result.end(),
size_t(0),
[](size_t s,
const decltype(
result)::value_type &bin) {
return s += bin.size();
});
size_t bins = max_binid_it == input.end() ? 0 : max_binid_it->binId() + 1;
ASSERT_EQ(input.size(), partsum);
ASSERT_EQ(bins, 2u);
ASSERT_TRUE(
std::all_of(input.begin(), input.end(), [](const Item &itm) {
return itm.binId() != BIN_ID_UNSET;
}));
}
namespace {

642
src/libslic3r/Arrange.cpp Normal file
View File

@ -0,0 +1,642 @@
#include "Arrange.hpp"
#include "Geometry.hpp"
#include "SVG.hpp"
#include "MTUtils.hpp"
#include <libnest2d/backends/clipper/geometries.hpp>
#include <libnest2d/optimizers/nlopt/subplex.hpp>
#include <libnest2d/placers/nfpplacer.hpp>
#include <libnest2d/selections/firstfit.hpp>
#include <numeric>
#include <ClipperUtils.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <boost/multiprecision/integer.hpp>
#include <boost/rational.hpp>
namespace libnest2d {
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
using LargeInt = __int128;
#else
using LargeInt = boost::multiprecision::int128_t;
template<> struct _NumTag<LargeInt>
{
using Type = ScalarTag;
};
#endif
template<class T> struct _NumTag<boost::rational<T>>
{
using Type = RationalTag;
};
namespace nfp {
template<class S> struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
{
NfpResult<S> operator()(const S &sh, const S &other)
{
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
}
};
} // namespace nfp
} // namespace libnest2d
namespace Slic3r {
template<class Tout = double, class = FloatingOnly<Tout>, int...EigenArgs>
inline SLIC3R_CONSTEXPR Eigen::Matrix<Tout, 2, EigenArgs...> unscaled(
const ClipperLib::IntPoint &v) SLIC3R_NOEXCEPT
{
return Eigen::Matrix<Tout, 2, EigenArgs...>{unscaled<Tout>(v.X),
unscaled<Tout>(v.Y)};
}
namespace arrangement {
using namespace libnest2d;
namespace clppr = ClipperLib;
// Get the libnest2d types for clipper backend
using Item = _Item<clppr::Polygon>;
using Box = _Box<clppr::IntPoint>;
using Circle = _Circle<clppr::IntPoint>;
using Segment = _Segment<clppr::IntPoint>;
using MultiPolygon = TMultiShape<clppr::Polygon>;
// Summon the spatial indexing facilities from boost
namespace bgi = boost::geometry::index;
using SpatElement = std::pair<Box, unsigned>;
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
// A coefficient used in separating bigger items and smaller items.
const double BIG_ITEM_TRESHOLD = 0.02;
// Fill in the placer algorithm configuration with values carefully chosen for
// Slic3r.
template<class PConf>
void fillConfig(PConf& pcfg) {
// Align the arranged pile into the center of the bin
pcfg.alignment = PConf::Alignment::CENTER;
// Start placing the items from the center of the print bed
pcfg.starting_point = PConf::Alignment::CENTER;
// TODO cannot use rotations until multiple objects of same geometry can
// handle different rotations.
pcfg.rotations = { 0.0 };
// The accuracy of optimization.
// Goes from 0.0 to 1.0 and scales performance as well
pcfg.accuracy = 0.65f;
// Allow parallel execution.
pcfg.parallel = true;
}
// Apply penalty to object function result. This is used only when alignment
// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN)
double fixed_overfit(const std::tuple<double, Box>& result, const Box &binbb)
{
double score = std::get<0>(result);
Box pilebb = std::get<1>(result);
Box fullbb = sl::boundingBox(pilebb, binbb);
auto diff = double(fullbb.area()) - binbb.area();
if(diff > 0) score += diff;
return score;
}
// A class encapsulating the libnest2d Nester class and extending it with other
// management and spatial index structures for acceleration.
template<class TBin>
class AutoArranger {
public:
// Useful type shortcuts...
using Placer = typename placers::_NofitPolyPlacer<clppr::Polygon, TBin>;
using Selector = selections::_FirstFitSelection<clppr::Polygon>;
using Packer = _Nester<Placer, Selector>;
using PConfig = typename Packer::PlacementConfig;
using Distance = TCoord<PointImpl>;
protected:
Packer m_pck;
PConfig m_pconf; // Placement configuration
TBin m_bin;
double m_bin_area;
SpatIndex m_rtree; // spatial index for the normal (bigger) objects
SpatIndex m_smallsrtree; // spatial index for only the smaller items
double m_norm; // A coefficient to scale distances
MultiPolygon m_merged_pile; // The already merged pile (vector of items)
Box m_pilebb; // The bounding box of the merged pile.
ItemGroup m_remaining; // Remaining items (m_items at the beginning)
ItemGroup m_items; // The items to be packed
template<class T> ArithmeticOnly<T, double> norm(T val)
{
return double(val) / m_norm;
}
// This is "the" object function which is evaluated many times for each
// vertex (decimated with the accuracy parameter) of each object.
// Therefore it is upmost crucial for this function to be as efficient
// as it possibly can be but at the same time, it has to provide
// reasonable results.
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
objfunc(const Item &item, const clppr::IntPoint &bincenter)
{
const double bin_area = m_bin_area;
const SpatIndex& spatindex = m_rtree;
const SpatIndex& smalls_spatindex = m_smallsrtree;
const ItemGroup& remaining = m_remaining;
// We will treat big items (compared to the print bed) differently
auto isBig = [bin_area](double a) {
return a/bin_area > BIG_ITEM_TRESHOLD ;
};
// Candidate item bounding box
auto ibb = item.boundingBox();
// Calculate the full bounding box of the pile with the candidate item
auto fullbb = sl::boundingBox(m_pilebb, ibb);
// The bounding box of the big items (they will accumulate in the center
// of the pile
Box bigbb;
if(spatindex.empty()) bigbb = fullbb;
else {
auto boostbb = spatindex.bounds();
boost::geometry::convert(boostbb, bigbb);
}
// Will hold the resulting score
double score = 0;
// Density is the pack density: how big is the arranged pile
double density = 0;
// Distinction of cases for the arrangement scene
enum e_cases {
// This branch is for big items in a mixed (big and small) scene
// OR for all items in a small-only scene.
BIG_ITEM,
// This branch is for the last big item in a mixed scene
LAST_BIG_ITEM,
// For small items in a mixed scene.
SMALL_ITEM
} compute_case;
bool bigitems = isBig(item.area()) || spatindex.empty();
if(bigitems && !remaining.empty()) compute_case = BIG_ITEM;
else if (bigitems && remaining.empty()) compute_case = LAST_BIG_ITEM;
else compute_case = SMALL_ITEM;
switch (compute_case) {
case BIG_ITEM: {
const clppr::IntPoint& minc = ibb.minCorner(); // bottom left corner
const clppr::IntPoint& maxc = ibb.maxCorner(); // top right corner
// top left and bottom right corners
clppr::IntPoint top_left{getX(minc), getY(maxc)};
clppr::IntPoint bottom_right{getX(maxc), getY(minc)};
// Now the distance of the gravity center will be calculated to the
// five anchor points and the smallest will be chosen.
std::array<double, 5> dists;
auto cc = fullbb.center(); // The gravity center
dists[0] = pl::distance(minc, cc);
dists[1] = pl::distance(maxc, cc);
dists[2] = pl::distance(ibb.center(), cc);
dists[3] = pl::distance(top_left, cc);
dists[4] = pl::distance(bottom_right, cc);
// The smalles distance from the arranged pile center:
double dist = norm(*(std::min_element(dists.begin(), dists.end())));
double bindist = norm(pl::distance(ibb.center(), bincenter));
dist = 0.8 * dist + 0.2*bindist;
// Prepare a variable for the alignment score.
// This will indicate: how well is the candidate item
// aligned with its neighbors. We will check the alignment
// with all neighbors and return the score for the best
// alignment. So it is enough for the candidate to be
// aligned with only one item.
auto alignment_score = 1.0;
auto query = bgi::intersects(ibb);
auto& index = isBig(item.area()) ? spatindex : smalls_spatindex;
// Query the spatial index for the neighbors
std::vector<SpatElement> result;
result.reserve(index.size());
index.query(query, std::back_inserter(result));
// now get the score for the best alignment
for(auto& e : result) {
auto idx = e.second;
Item& p = m_items[idx];
auto parea = p.area();
if(std::abs(1.0 - parea/item.area()) < 1e-6) {
auto bb = sl::boundingBox(p.boundingBox(), ibb);
auto bbarea = bb.area();
auto ascore = 1.0 - (item.area() + parea)/bbarea;
if(ascore < alignment_score) alignment_score = ascore;
}
}
density = std::sqrt(norm(fullbb.width()) * norm(fullbb.height()));
// The final mix of the score is the balance between the
// distance from the full pile center, the pack density and
// the alignment with the neighbors
if (result.empty())
score = 0.5 * dist + 0.5 * density;
else
score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score;
break;
}
case LAST_BIG_ITEM: {
auto mp = m_merged_pile;
mp.emplace_back(item.transformedShape());
auto chull = sl::convexHull(mp);
placers::EdgeCache<clppr::Polygon> ec(chull);
double circ = norm(ec.circumference());
double bcirc = 2.0 * norm(fullbb.width() + fullbb.height());
score = 0.5 * circ + 0.5 * bcirc;
break;
}
case SMALL_ITEM: {
// Here there are the small items that should be placed around the
// already processed bigger items.
// No need to play around with the anchor points, the center will be
// just fine for small items
score = norm(pl::distance(ibb.center(), bigbb.center()));
break;
}
}
return std::make_tuple(score, fullbb);
}
std::function<double(const Item&)> get_objfn();
public:
AutoArranger(const TBin & bin,
Distance dist,
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond)
: m_pck(bin, dist)
, m_bin(bin)
, m_bin_area(sl::area(bin))
, m_norm(std::sqrt(m_bin_area))
{
fillConfig(m_pconf);
// Set up a callback that is called just before arranging starts
// This functionality is provided by the Nester class (m_pack).
m_pconf.before_packing =
[this](const MultiPolygon& merged_pile, // merged pile
const ItemGroup& items, // packed items
const ItemGroup& remaining) // future items to be packed
{
m_items = items;
m_merged_pile = merged_pile;
m_remaining = remaining;
m_pilebb = sl::boundingBox(merged_pile);
m_rtree.clear();
m_smallsrtree.clear();
// We will treat big items (compared to the print bed) differently
auto isBig = [this](double a) {
return a / m_bin_area > BIG_ITEM_TRESHOLD ;
};
for(unsigned idx = 0; idx < items.size(); ++idx) {
Item& itm = items[idx];
if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx});
m_smallsrtree.insert({itm.boundingBox(), idx});
}
};
m_pconf.object_function = get_objfn();
if (progressind) m_pck.progressIndicator(progressind);
if (stopcond) m_pck.stopCondition(stopcond);
m_pck.configure(m_pconf);
}
template<class...Args> inline void operator()(Args&&...args) {
m_rtree.clear(); /*m_preload_idx.clear();*/
m_pck.execute(std::forward<Args>(args)...);
}
inline void preload(std::vector<Item>& fixeditems) {
m_pconf.alignment = PConfig::Alignment::DONT_ALIGN;
auto bb = sl::boundingBox(m_bin);
auto bbcenter = bb.center();
m_pconf.object_function = [this, bb, bbcenter](const Item &item) {
return fixed_overfit(objfunc(item, bbcenter), bb);
};
// Build the rtree for queries to work
for(unsigned idx = 0; idx < fixeditems.size(); ++idx) {
Item& itm = fixeditems[idx];
itm.markAsFixed();
}
m_pck.configure(m_pconf);
}
};
template<> std::function<double(const Item&)> AutoArranger<Box>::get_objfn()
{
auto bincenter = m_bin.center();
return [this, bincenter](const Item &itm) {
auto result = objfunc(itm, bincenter);
double score = std::get<0>(result);
auto& fullbb = std::get<1>(result);
double miss = Placer::overfit(fullbb, m_bin);
miss = miss > 0? miss : 0;
score += miss*miss;
return score;
};
}
template<> std::function<double(const Item&)> AutoArranger<Circle>::get_objfn()
{
auto bincenter = m_bin.center();
return [this, bincenter](const Item &item) {
auto result = objfunc(item, bincenter);
double score = std::get<0>(result);
auto isBig = [this](const Item& itm) {
return itm.area() / m_bin_area > BIG_ITEM_TRESHOLD ;
};
if(isBig(item)) {
auto mp = m_merged_pile;
mp.push_back(item.transformedShape());
auto chull = sl::convexHull(mp);
double miss = Placer::overfit(chull, m_bin);
if(miss < 0) miss = 0;
score += miss*miss;
}
return score;
};
}
// Specialization for a generalized polygon.
// Warning: this is unfinished business. It may or may not work.
template<>
std::function<double(const Item &)> AutoArranger<clppr::Polygon>::get_objfn()
{
auto bincenter = sl::boundingBox(m_bin).center();
return [this, bincenter](const Item &item) {
return std::get<0>(objfunc(item, bincenter));
};
}
inline Circle to_lnCircle(const CircleBed& circ) {
return Circle({circ.center()(0), circ.center()(1)}, circ.radius());
}
// Get the type of bed geometry from a simple vector of points.
BedShapeHint::BedShapeHint(const Polyline &bed) {
auto x = [](const Point& p) { return p(X); };
auto y = [](const Point& p) { return p(Y); };
auto width = [x](const BoundingBox& box) {
return x(box.max) - x(box.min);
};
auto height = [y](const BoundingBox& box) {
return y(box.max) - y(box.min);
};
auto area = [&width, &height](const BoundingBox& box) {
double w = width(box);
double h = height(box);
return w * h;
};
auto poly_area = [](Polyline p) {
Polygon pp; pp.points.reserve(p.points.size() + 1);
pp.points = std::move(p.points);
pp.points.emplace_back(pp.points.front());
return std::abs(pp.area());
};
auto distance_to = [x, y](const Point& p1, const Point& p2) {
double dx = x(p2) - x(p1);
double dy = y(p2) - y(p1);
return std::sqrt(dx*dx + dy*dy);
};
auto bb = bed.bounding_box();
auto isCircle = [bb, distance_to](const Polyline& polygon) {
auto center = bb.center();
std::vector<double> vertex_distances;
double avg_dist = 0;
for (auto pt: polygon.points)
{
double distance = distance_to(center, pt);
vertex_distances.push_back(distance);
avg_dist += distance;
}
avg_dist /= vertex_distances.size();
CircleBed ret(center, avg_dist);
for(auto el : vertex_distances)
{
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
ret = CircleBed();
break;
}
}
return ret;
};
auto parea = poly_area(bed);
if( (1.0 - parea/area(bb)) < 1e-3 ) {
m_type = BedShapes::bsBox;
m_bed.box = bb;
}
else if(auto c = isCircle(bed)) {
m_type = BedShapes::bsCircle;
m_bed.circ = c;
} else {
m_type = BedShapes::bsIrregular;
m_bed.polygon = bed;
}
}
template<class BinT> // Arrange for arbitrary bin type
void _arrange(
std::vector<Item> & shapes,
std::vector<Item> & excludes,
const BinT & bin,
coord_t minobjd,
std::function<void(unsigned)> prind,
std::function<bool()> stopfn)
{
// Integer ceiling the min distance from the bed perimeters
coord_t md = minobjd - 2 * scaled(0.1 + EPSILON);
md = (md % 2) ? md / 2 + 1 : md / 2;
auto corrected_bin = bin;
sl::offset(corrected_bin, md);
AutoArranger<BinT> arranger{corrected_bin, 0, prind, stopfn};
auto infl = coord_t(std::ceil(minobjd / 2.0));
for (Item& itm : shapes) itm.inflate(infl);
for (Item& itm : excludes) itm.inflate(infl);
auto it = excludes.begin();
while (it != excludes.end())
sl::isInside(it->transformedShape(), corrected_bin) ?
++it : it = excludes.erase(it);
// If there is something on the plate
if (!excludes.empty()) arranger.preload(excludes);
std::vector<std::reference_wrapper<Item>> inp;
inp.reserve(shapes.size() + excludes.size());
for (auto &itm : shapes ) inp.emplace_back(itm);
for (auto &itm : excludes) inp.emplace_back(itm);
arranger(inp.begin(), inp.end());
for (Item &itm : inp) itm.inflate(-infl);
}
// The final client function for arrangement. A progress indicator and
// a stop predicate can be also be passed to control the process.
void arrange(ArrangePolygons & arrangables,
const ArrangePolygons & excludes,
coord_t min_obj_dist,
const BedShapeHint & bedhint,
std::function<void(unsigned)> progressind,
std::function<bool()> stopcondition)
{
namespace clppr = ClipperLib;
std::vector<Item> items, fixeditems;
items.reserve(arrangables.size());
// Create Item from Arrangeable
auto process_arrangeable =
[](const ArrangePolygon &arrpoly, std::vector<Item> &outp)
{
Polygon p = arrpoly.poly.contour;
const Vec2crd & offs = arrpoly.translation;
double rotation = arrpoly.rotation;
if (p.is_counter_clockwise()) p.reverse();
clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
auto firstp = clpath.Contour.front();
clpath.Contour.emplace_back(firstp);
outp.emplace_back(std::move(clpath));
outp.back().rotation(rotation);
outp.back().translation({offs.x(), offs.y()});
outp.back().binId(arrpoly.bed_idx);
outp.back().priority(arrpoly.priority);
};
for (ArrangePolygon &arrangeable : arrangables)
process_arrangeable(arrangeable, items);
for (const ArrangePolygon &fixed: excludes)
process_arrangeable(fixed, fixeditems);
for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON));
auto &cfn = stopcondition;
auto &pri = progressind;
switch (bedhint.get_type()) {
case bsBox: {
// Create the arranger for the box shaped bed
BoundingBox bbb = bedhint.get_box();
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 bsCircle: {
auto cc = to_lnCircle(bedhint.get_circle());
_arrange(items, fixeditems, cc, min_obj_dist, pri, cfn);
break;
}
case bsIrregular: {
auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular());
auto irrbed = sl::create<clppr::Polygon>(std::move(ctour));
BoundingBox polybb(bedhint.get_irregular());
_arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn);
break;
}
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 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;
}
};
for(size_t i = 0; i < items.size(); ++i) {
clppr::IntPoint tr = items[i].translation();
arrangables[i].translation = {coord_t(tr.X), coord_t(tr.Y)};
arrangables[i].rotation = items[i].rotation();
arrangables[i].bed_idx = items[i].binId();
}
}
// Arrange, without the fixed items (excludes)
void arrange(ArrangePolygons & inp,
coord_t min_d,
const BedShapeHint & bedhint,
std::function<void(unsigned)> prfn,
std::function<bool()> stopfn)
{
arrange(inp, {}, min_d, bedhint, prfn, stopfn);
}
} // namespace arr
} // namespace Slic3r

201
src/libslic3r/Arrange.hpp Normal file
View File

@ -0,0 +1,201 @@
#ifndef MODELARRANGE_HPP
#define MODELARRANGE_HPP
#include "ExPolygon.hpp"
#include "BoundingBox.hpp"
namespace Slic3r {
namespace arrangement {
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
class CircleBed {
Point center_;
double radius_;
public:
inline CircleBed(): center_(0, 0), radius_(std::nan("")) {}
inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
inline double radius() const { return radius_; }
inline const Point& center() const { return center_; }
inline operator bool() { return !std::isnan(radius_); }
};
/// Representing an unbounded bed.
struct InfiniteBed { Point center; };
/// Types of print bed shapes.
enum BedShapes {
bsBox,
bsCircle,
bsIrregular,
bsInfinite,
bsUnknown
};
/// 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 infbed{};
~BedShape_u() {}
BedShape_u() {};
} m_bed;
public:
BedShapeHint(){};
/// 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;
}
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;
}
};
/// A logical bed representing an object not being arranged. Either the arrange
/// has not yet successfully 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 {
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...
int priority{0};
/// Optional setter function which can store arbitrary data in its closure
std::function<void(const ArrangePolygon&)> setter = nullptr;
/// Helper function to call the setter with the arrange data arguments
void apply() const { if (setter) setter(*this); }
/// Test if arrange() was called previously and gave a successful result.
bool is_arranged() const { return bed_idx != UNARRANGED; }
};
using ArrangePolygons = std::vector<ArrangePolygon>;
/**
* \brief Arranges the input polygons.
*
* 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 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.
*
* \param progressind Progress indicator callback called when
* an object gets packed. The unsigned argument is the number of items
* remaining to pack.
*
* \param stopcondition A predicate returning true if abort is needed.
*/
void arrange(ArrangePolygons & items,
coord_t min_obj_distance,
const BedShapeHint & bedhint,
std::function<void(unsigned)> progressind = nullptr,
std::function<bool(void)> stopcondition = nullptr);
/// Same as the previous, only that it takes unmovable items as an
/// additional argument. Those will be considered as already arranged objects.
void arrange(ArrangePolygons & items,
const ArrangePolygons & excludes,
coord_t min_obj_distance,
const BedShapeHint & bedhint,
std::function<void(unsigned)> progressind = nullptr,
std::function<bool(void)> stopcondition = nullptr);
} // arr
} // Slic3r
#endif // MODELARRANGE_HPP

View File

@ -106,8 +106,8 @@ add_library(libslic3r STATIC
Line.hpp
Model.cpp
Model.hpp
ModelArrange.hpp
ModelArrange.cpp
Arrange.hpp
Arrange.cpp
MotionPlanner.cpp
MotionPlanner.hpp
MultiPoint.cpp

View File

@ -314,49 +314,48 @@ template<class I> struct is_scaled_coord
};
// Meta predicates for floating, 'scaled coord' and generic arithmetic types
template<class T>
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, T>;
template<class T, class O = T>
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>;
template<class T>
using ScaledCoordOnly = enable_if_t<is_scaled_coord<T>::value, T>;
template<class T, class O = T>
using ScaledCoordOnly = enable_if_t<is_scaled_coord<T>::value, O>;
template<class T>
using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, T>;
// A shorter form for a generic Eigen vector which is widely used in PrusaSlicer
template<class T, int N>
using EigenVec = Eigen::Matrix<T, N, 1, Eigen::DontAlign>;
template<class T, class O = T>
using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, O>;
// Semantics are the following:
// Upscaling (scaled()): only from floating point types (or Vec) to either
// floating point or integer 'scaled coord' coordinates.
// Downscaling (unscaled()): from arithmetic types (or Vec) to either
// floating point only
// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only
// Conversion definition from unscaled to floating point scaled
template<class Tout,
class Tin,
class = FloatingOnly<Tin>,
class = FloatingOnly<Tout>>
inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT
class = FloatingOnly<Tin>>
inline constexpr FloatingOnly<Tout> scaled(const Tin &v) noexcept
{
return static_cast<Tout>(v / static_cast<Tin>(SCALING_FACTOR));
return Tout(v / Tin(SCALING_FACTOR));
}
// Conversion definition from unscaled to integer 'scaled coord'.
// TODO: is the rounding necessary ? Here it is to show that it can be different
// but it does not have to be. Using std::round means loosing noexcept and
// constexpr modifiers
// TODO: is the rounding necessary? Here it is commented out to show that
// it can be different for integers but it does not have to be. Using
// std::round means loosing noexcept and constexpr modifiers
template<class Tout = coord_t, class Tin, class = FloatingOnly<Tin>>
inline SLIC3R_CONSTEXPR ScaledCoordOnly<Tout> scaled(const Tin &v) SLIC3R_NOEXCEPT
inline constexpr ScaledCoordOnly<Tout> scaled(const Tin &v) noexcept
{
//return static_cast<Tout>(std::round(v / SCALING_FACTOR));
return static_cast<Tout>(v / static_cast<Tin>(SCALING_FACTOR));
return Tout(v / Tin(SCALING_FACTOR));
}
// Conversion for Eigen vectors (N dimensional points)
template<class Tout = coord_t, class Tin, int N, class = FloatingOnly<Tin>>
inline EigenVec<ArithmeticOnly<Tout>, N> scaled(const EigenVec<Tin, N> &v)
template<class Tout = coord_t,
class Tin,
int N,
class = FloatingOnly<Tin>,
int...EigenArgs>
inline Eigen::Matrix<ArithmeticOnly<Tout>, N, EigenArgs...>
scaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v)
{
return (v / SCALING_FACTOR).template cast<Tout>();
}
@ -366,9 +365,9 @@ template<class Tout = double,
class Tin,
class = ArithmeticOnly<Tin>,
class = FloatingOnly<Tout>>
inline SLIC3R_CONSTEXPR Tout unscaled(const Tin &v) SLIC3R_NOEXCEPT
inline constexpr Tout unscaled(const Tin &v) noexcept
{
return static_cast<Tout>(v * static_cast<Tout>(SCALING_FACTOR));
return Tout(v * Tout(SCALING_FACTOR));
}
// Unscaling for Eigen vectors. Input base type can be arithmetic, output base
@ -377,9 +376,10 @@ template<class Tout = double,
class Tin,
int N,
class = ArithmeticOnly<Tin>,
class = FloatingOnly<Tout>>
inline SLIC3R_CONSTEXPR EigenVec<Tout, N> unscaled(
const EigenVec<Tin, N> &v) SLIC3R_NOEXCEPT
class = FloatingOnly<Tout>,
int...EigenArgs>
inline constexpr Eigen::Matrix<Tout, N, EigenArgs...>
unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept
{
return v.template cast<Tout>() * SCALING_FACTOR;
}

View File

@ -1,5 +1,6 @@
#include "Model.hpp"
#include "Geometry.hpp"
#include "MTUtils.hpp"
#include "Format/AMF.hpp"
#include "Format/OBJ.hpp"
@ -370,33 +371,43 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb
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()));
size_t count = 0;
for (auto obj : objects) count += obj->instances.size();
arrangement::ArrangePolygons input;
ModelInstancePtrs instances;
input.reserve(count);
instances.reserve(count);
for (ModelObject *mo : objects)
for (ModelInstance *minst : mo->instances) {
input.emplace_back(minst->get_arrange_polygon());
instances.emplace_back(minst);
}
Pointfs positions;
if (! _arrange(instance_sizes, dist, bb, positions))
return false;
arrangement::BedShapeHint bedhint;
coord_t bedwidth = 0;
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();
if (bb) {
bedwidth = scaled(bb->size().x());
bedhint = arrangement::BedShapeHint(
BoundingBox(scaled(bb->min), scaled(bb->max)));
}
return true;
arrangement::arrange(input, scaled(dist), bedhint);
bool ret = true;
coord_t stride = bedwidth + bedwidth / 5;
for(size_t i = 0; i < input.size(); ++i) {
if (input[i].bed_idx != 0) ret = false;
if (input[i].bed_idx >= 0) {
input[i].translation += Vec2crd{input[i].bed_idx * stride, 0};
instances[i]->apply_arrange_result(input[i].translation,
input[i].rotation);
}
}
return ret;
}
// Duplicate the entire model preserving instance relative positions.
@ -1814,6 +1825,36 @@ void ModelInstance::transform_polygon(Polygon* polygon) const
polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin
}
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()) {
Polygons pp{p};
pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM));
if (!pp.empty()) p = pp.front();
}
arrangement::ArrangePolygon ret;
ret.poly.contour = std::move(p);
ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))};
ret.rotation = get_rotation(Z);
return ret;
}
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
// ordered in the same order. In that case it is not necessary to kill the background processing.
bool model_object_list_equal(const Model &model_old, const Model &model_new)

View File

@ -10,6 +10,7 @@
#include "Slicing.hpp"
#include "SLA/SLACommon.hpp"
#include "TriangleMesh.hpp"
#include "Arrange.hpp"
#include <map>
#include <memory>
@ -640,6 +641,18 @@ public:
bool is_printable() const { return print_volume_state == PVS_Inside; }
// Getting the input polygon for arrange
arrangement::ArrangePolygon get_arrange_polygon() const;
// Apply the arrange result on the ModelInstance
void apply_arrange_result(const Vec2crd& offs, double rotation)
{
// write the transformation data into the model instance
set_rotation(Z, rotation);
set_offset(X, unscale<double>(offs(X)));
set_offset(Y, unscale<double>(offs(Y)));
}
protected:
friend class Print;
friend class SLAPrint;
@ -654,10 +667,10 @@ private:
ModelObject* object;
// Constructor, which assigns a new unique ID.
explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) { assert(this->id().valid()); }
explicit ModelInstance(ModelObject *object) : print_volume_state(PVS_Inside), object(object) { assert(this->id().valid()); }
// 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) { assert(this->id().valid() && this->id() != other.id()); }
m_transformation(other.m_transformation), print_volume_state(PVS_Inside), object(object) { assert(this->id().valid() && this->id() != other.id()); }
explicit ModelInstance(ModelInstance &&rhs) = delete;
ModelInstance& operator=(const ModelInstance &rhs) = delete;

File diff suppressed because it is too large Load Diff

View File

@ -1,95 +0,0 @@
#ifndef MODELARRANGE_HPP
#define MODELARRANGE_HPP
#include "Model.hpp"
namespace Slic3r {
class Model;
namespace arr {
class Circle {
Point center_;
double radius_;
public:
inline Circle(): center_(0, 0), radius_(std::nan("")) {}
inline Circle(const Point& c, double r): center_(c), radius_(r) {}
inline double radius() const { return radius_; }
inline const Point& center() const { return center_; }
inline operator bool() { return !std::isnan(radius_); }
};
enum class BedShapeType {
BOX,
CIRCLE,
IRREGULAR,
WHO_KNOWS
};
struct BedShapeHint {
BedShapeType type;
/*union*/ struct { // I know but who cares...
Circle circ;
BoundingBox box;
Polyline polygon;
} shape;
};
BedShapeHint bedShape(const Polyline& bed);
struct WipeTowerInfo {
bool is_wipe_tower = false;
Vec2d pos;
Vec2d bb_size;
double rotation;
};
/**
* \brief Arranges the model objects on the screen.
*
* 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.
*
* \param model The model object with the 3D content.
* \param dist The minimum distance which is allowed for any pair of items
* on the print bed in any direction.
* \param bb The bounding box of the print bed. It corresponds to the 'bin'
* for bin packing.
* \param first_bin_only This parameter controls whether to place the
* 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 an object gets
* packed. The unsigned argument is the number of items remaining to pack.
* \param stopcondition A predicate returning true if abort is needed.
*/
bool arrange(Model &model,
WipeTowerInfo& wipe_tower_info,
coord_t min_obj_distance,
const Slic3r::Polyline& bed,
BedShapeHint bedhint,
bool first_bin_only,
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcondition);
/// This will find a suitable position for a new object instance and leave the
/// old items untouched.
void find_new_position(const Model& model,
ModelInstancePtrs instances_to_add,
coord_t min_obj_distance,
const Slic3r::Polyline& bed,
WipeTowerInfo& wti);
} // arr
} // Slic3r
#endif // MODELARRANGE_HPP

View File

@ -3429,36 +3429,24 @@ void GLCanvas3D::update_ui_from_settings()
arr::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const
GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const
{
arr::WipeTowerInfo wti;
WipeTowerInfo wti;
for (const GLVolume* vol : m_volumes.volumes) {
if (vol->is_wipe_tower) {
wti.is_wipe_tower = true;
wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"),
wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"),
m_config->opt_float("wipe_tower_y"));
wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle");
wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle");
const BoundingBoxf3& bb = vol->bounding_box();
wti.bb_size = Vec2d(bb.size()(0), bb.size()(1));
wti.m_bb_size = Vec2d(bb.size().x(), bb.size().y());
break;
}
}
return wti;
}
void GLCanvas3D::arrange_wipe_tower(const arr::WipeTowerInfo& wti) const
{
if (wti.is_wipe_tower) {
DynamicPrintConfig cfg;
cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = wti.pos(0);
cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = wti.pos(1);
cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = (180./M_PI) * wti.rotation;
wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
}
}
Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos)
{
float z0 = 0.0f;
@ -5745,5 +5733,14 @@ const SLAPrint* GLCanvas3D::sla_print() const
return (m_process == nullptr) ? nullptr : m_process->sla_print();
}
void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const
{
DynamicPrintConfig cfg;
cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = m_pos(X);
cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = m_pos(Y);
cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation;
wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
}
} // namespace GUI
} // namespace Slic3r

View File

@ -4,7 +4,6 @@
#include <stddef.h>
#include <memory>
#include "libslic3r/ModelArrange.hpp"
#include "3DScene.hpp"
#include "GLToolbar.hpp"
#include "Event.hpp"
@ -627,8 +626,27 @@ public:
int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; }
int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); }
arr::WipeTowerInfo get_wipe_tower_info() const;
void arrange_wipe_tower(const arr::WipeTowerInfo& wti) const;
class WipeTowerInfo {
protected:
Vec2d m_pos = {std::nan(""), std::nan("")};
Vec2d m_bb_size = {0., 0.};
double m_rotation = 0.;
friend class GLCanvas3D;
public:
inline operator bool() const
{
return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y());
}
inline const Vec2d& pos() const { return m_pos; }
inline double rotation() const { return m_rotation; }
inline const Vec2d bb_size() const { return m_bb_size; }
void apply_wipe_tower() const;
};
WipeTowerInfo get_wipe_tower_info() const;
// Returns the view ray line, in world coordinate, at the given mouse position.
Linef3 mouse_ray(const Point& mouse_pos);

View File

@ -359,9 +359,7 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons
assert(item);
const ItemType type = m_objects_model->GetItemType(item);
const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) :
m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item));
const int obj_idx = m_objects_model->GetObjectIdByItem(item);
const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1;
assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0));
@ -1037,12 +1035,17 @@ void ObjectList::get_settings_choice(const wxString& category_name)
{
wxArrayString names;
wxArrayInt selections;
wxDataViewItem item = GetSelection();
settings_menu_hierarchy settings_menu;
const bool is_part = m_objects_model->GetParent(GetSelection()) != wxDataViewItem(0);
const bool is_part = m_objects_model->GetItemType(item) & (itVolume | itLayer);
get_options_menu(settings_menu, is_part);
std::vector< std::pair<std::string, std::string> > *settings_list = nullptr;
if (!m_config)
m_config = &get_item_config(item);
assert(m_config);
auto opt_keys = m_config->keys();
for (auto& cat : settings_menu)
@ -1144,27 +1147,33 @@ void ObjectList::get_settings_choice(const wxString& category_name)
// Add settings item for object/sub-object and show them
show_settings(add_settings_item(GetSelection(), m_config));
if (!(m_objects_model->GetItemType(item) & (itObject | itVolume | itLayer)))
item = m_objects_model->GetTopParent(item);
show_settings(add_settings_item(item, m_config));
}
void ObjectList::get_freq_settings_choice(const wxString& bundle_name)
{
std::vector<std::string> options = get_options_for_bundle(bundle_name);
wxDataViewItem item = GetSelection();
/* Because of we couldn't edited layer_height for ItVolume from settings list,
* correct options according to the selected item type :
* remove "layer_height" option
*/
if ((m_objects_model->GetItemType(GetSelection()) & itVolume) && bundle_name == _("Layers and Perimeters")) {
if ((m_objects_model->GetItemType(item) & itVolume) && bundle_name == _("Layers and Perimeters")) {
const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height");
if (layer_height_it != options.end())
options.erase(layer_height_it);
}
if (!m_config)
m_config = &get_item_config(item);
assert(m_config);
auto opt_keys = m_config->keys();
take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(GetSelection()) & itObject ? _(L("Object")) : _(L("Sub-object"))));
take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(item) & (itVolume|itLayer) ? _(L("Sub-object")) : _(L("Object"))));
const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
for (auto& opt_key : options)
@ -1181,7 +1190,9 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name)
}
// Add settings item for object/sub-object and show them
show_settings(add_settings_item(GetSelection(), m_config));
if (!(m_objects_model->GetItemType(item) & (itObject | itVolume | itLayer)))
item = m_objects_model->GetTopParent(item);
show_settings(add_settings_item(item, m_config));
}
void ObjectList::show_settings(const wxDataViewItem settings_item)

View File

@ -2,6 +2,7 @@
#include <cstddef>
#include <algorithm>
#include <numeric>
#include <vector>
#include <string>
#include <regex>
@ -31,7 +32,6 @@
#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/GCode/PreviewData.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/ModelArrange.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/PrintConfig.hpp"
@ -1355,6 +1355,44 @@ struct Plater::priv
BackgroundSlicingProcess background_process;
bool suppressed_backround_processing_update { false };
// Cache the wti info
class WipeTower: public GLCanvas3D::WipeTowerInfo {
using ArrangePolygon = arrangement::ArrangePolygon;
friend priv;
public:
void apply_arrange_result(const Vec2crd& tr, double rotation)
{
m_pos = unscaled(tr); m_rotation = rotation;
apply_wipe_tower();
}
ArrangePolygon get_arrange_polygon() const
{
Polygon p({
{coord_t(0), coord_t(0)},
{scaled(m_bb_size(X)), coord_t(0)},
{scaled(m_bb_size)},
{coord_t(0), scaled(m_bb_size(Y))},
{coord_t(0), coord_t(0)},
});
ArrangePolygon ret;
ret.poly.contour = std::move(p);
ret.translation = scaled(m_pos);
ret.rotation = m_rotation;
return ret;
}
} wipetower;
WipeTower& updated_wipe_tower() {
auto wti = view3D->get_canvas3d()->get_wipe_tower_info();
wipetower.m_pos = wti.pos();
wipetower.m_rotation = wti.rotation();
wipetower.m_bb_size = wti.bb_size();
return wipetower;
}
// A class to handle UI jobs like arranging and optimizing rotation.
// These are not instant jobs, the user has to be informed about their
// state in the status progress indicator. On the other hand they are
@ -1365,59 +1403,65 @@ 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;
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);
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);
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; }
const priv &plater() const { 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 (!msg.empty())
plater().statusbar()->set_status_text(msg);
if(m_finalized) return;
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();
@ -1431,28 +1475,16 @@ struct Plater::priv
});
}
// 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()) {
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
@ -1461,20 +1493,20 @@ struct Plater::priv
// 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& ) {
} catch (std::exception &) {
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
@ -1482,16 +1514,18 @@ struct Plater::priv
}
}
// 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;
// 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)
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;
@ -1506,6 +1540,143 @@ struct Plater::priv
Rotoptimize
};
class ArrangeJob : public Job
{
using ArrangePolygon = arrangement::ArrangePolygon;
using ArrangePolygons = arrangement::ArrangePolygons;
// The gap between logical beds in the x axis expressed in ratio of
// the current bed width.
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
ArrangePolygons m_selected, m_unselected;
// clear m_selected and m_unselected, reserve space for next usage
void clear_input() {
const 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 */);
}
// Stride between logical beds
coord_t bed_stride() const {
double bedwidth = plater().bed_shape_bb().size().x();
return scaled((1. + LOGICAL_BED_GAP) * bedwidth);
}
// Set up arrange polygon for a ModelInstance and Wipe tower
template<class T> ArrangePolygon get_arrange_poly(T *obj) const {
ArrangePolygon ap = obj->get_arrange_polygon();
ap.priority = 0;
ap.bed_idx = ap.translation.x() / bed_stride();
ap.setter = [obj, this](const ArrangePolygon &p) {
if (p.is_arranged()) {
auto t = p.translation; t.x() += p.bed_idx * bed_stride();
obj->apply_arrange_result(t, p.rotation);
}
};
return ap;
}
// Prepare all objects on the bed regardless of the selection
void prepare_all() {
clear_input();
for (ModelObject *obj: plater().model.objects)
for (ModelInstance *mi : obj->instances)
m_selected.emplace_back(get_arrange_poly(mi));
auto& wti = plater().updated_wipe_tower();
if (wti) m_selected.emplace_back(get_arrange_poly(&wti));
}
// Prepare the selected and unselected items separately. If nothing is
// selected, behaves as if everything would be selected.
void prepare_selected() {
clear_input();
Model &model = plater().model;
coord_t stride = bed_stride();
std::vector<const Selection::InstanceIdxsList *>
obj_sel(model.objects.size(), nullptr);
for (auto &s : plater().get_selection().get_content())
if (s.first < int(obj_sel.size())) obj_sel[s.first] = &s.second;
// Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
ModelObject *mo = model.objects[oidx];
std::vector<bool> inst_sel(mo->instances.size(), false);
if (instlist)
for (auto inst_id : *instlist) inst_sel[inst_id] = true;
for (size_t i = 0; i < inst_sel.size(); ++i) {
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
inst_sel[i] ?
m_selected.emplace_back(std::move(ap)) :
m_unselected.emplace_back(std::move(ap));
}
}
auto& wti = plater().updated_wipe_tower();
if (wti) {
ArrangePolygon &&ap = get_arrange_poly(&wti);
plater().get_selection().is_wipe_tower() ?
m_selected.emplace_back(std::move(ap)) :
m_unselected.emplace_back(std::move(ap));
}
// If the selection was empty arrange everything
if (m_selected.empty()) m_selected.swap(m_unselected);
// The strides have to be removed from the fixed items. For the
// arrangeable (selected) items bed_idx is ignored and the
// translation is irrelevant.
for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
}
protected:
void prepare() override
{
wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
}
public:
using Job::Job;
int status_range() const override { return int(m_selected.size()); }
void process() override;
void finalize() override {
// Ignore the arrange result if aborted.
if (was_canceled()) return;
// Apply the arrange result to all selected objects
for (ArrangePolygon &ap : m_selected) ap.apply();
plater().update(false /*dont force_full_scene_refresh*/);
}
};
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.
@ -1515,49 +1686,19 @@ struct Plater::priv
priv * m_plater;
class ArrangeJob : public Job
{
int count = 0;
protected:
void prepare() override
{
count = 0;
for (auto obj : plater().model.objects)
count += int(obj->instances.size());
}
public:
//using Job::Job;
ArrangeJob(priv * pltr): Job(pltr) {}
int status_range() const override { return count; }
void set_count(int c) { count = c; }
void process() override;
} arrange_job/*{m_plater}*/;
class RotoptimizeJob : public Job
{
public:
//using Job::Job;
RotoptimizeJob(priv * pltr): Job(pltr) {}
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
// Register the instance of the class in the m_jobs container
// if it cannot run concurrently with other jobs in this group
std::vector<std::reference_wrapper<Job>> m_jobs/*{arrange_job,
rotoptimize_job}*/;
std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
rotoptimize_job};
public:
ExclusiveJobGroup(priv *_plater)
: m_plater(_plater)
, arrange_job(m_plater)
, rotoptimize_job(m_plater)
, m_jobs({arrange_job, rotoptimize_job})
{}
ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {}
void start(Jobs jid) {
m_plater->background_process.stop();
@ -1618,6 +1759,9 @@ struct Plater::priv
std::string get_config(const std::string &key) const;
BoundingBoxf bed_shape_bb() const;
BoundingBox scaled_bed_shape_bb() const;
arrangement::BedShapeHint get_bed_shape_hint() const;
void find_new_position(const ModelInstancePtrs &instances, coord_t min_d);
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
wxString get_export_file(GUI::FileType file_type);
@ -2240,7 +2384,7 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
Polyline bed; bed.points.reserve(bedpoints.size());
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info();
std::pair<bool, GLCanvas3D::WipeTowerInfo> wti = view3D->get_canvas3d()->get_wipe_tower_info();
arr::find_new_position(model, new_instances, min_obj_distance, bed, wti);
@ -2480,71 +2624,82 @@ void Plater::priv::sla_optimize_rotation() {
m_ui_jobs.start(Jobs::Rotoptimize);
}
void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() {
// TODO: we should decide whether to allow arrange when the search is
// running we should probably disable explicit slicing and background
// processing
arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const {
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
assert(bed_shape_opt);
if (!bed_shape_opt) return {};
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::find_new_position(const ModelInstancePtrs &instances,
coord_t min_d)
{
arrangement::ArrangePolygons movable, fixed;
for (const ModelObject *mo : model.objects)
for (const ModelInstance *inst : mo->instances) {
auto it = std::find(instances.begin(), instances.end(), inst);
auto arrpoly = inst->get_arrange_polygon();
if (it == instances.end())
fixed.emplace_back(std::move(arrpoly));
else
movable.emplace_back(std::move(arrpoly));
}
if (updated_wipe_tower())
fixed.emplace_back(wipetower.get_arrange_polygon());
arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint());
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::ArrangeJob::process() {
static const auto arrangestr = _(L("Arranging"));
auto &config = plater().config;
auto &view3D = plater().view3D;
auto &model = plater().model;
// 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(config);
dist = PrintConfig::min_object_distance(plater().config);
}
auto min_obj_distance = coord_t(dist / SCALING_FACTOR);
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>(
"bed_shape");
assert(bed_shape_opt);
auto & bedpoints = bed_shape_opt->values;
Polyline bed;
bed.points.reserve(bedpoints.size());
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
update_status(0, arrangestr);
arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info();
coord_t min_d = scaled(dist);
auto count = unsigned(m_selected.size());
arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint();
try {
arr::BedShapeHint hint;
// TODO: from Sasha from GUI or
hint.type = arr::BedShapeType::WHO_KNOWS;
arr::arrange(model,
wti,
min_obj_distance,
bed,
hint,
false, // create many piles not just one pile
[this](unsigned st) {
if (st > 0)
update_status(count - int(st), arrangestr);
},
[this]() { return was_canceled(); });
arrangement::arrange(m_selected, m_unselected, min_d, 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."));
_(L("Could not arrange model objects! "
"Some geometries may be invalid.")));
}
update_status(count,
// finalize just here.
update_status(int(count),
was_canceled() ? _(L("Arranging canceled."))
: _(L("Arranging done.")));
// it remains to move the wipe tower:
view3D->get_canvas3d()->arrange_wipe_tower(wti);
}
void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
void Plater::priv::RotoptimizeJob::process()
{
int obj_idx = plater().get_selected_object_idx();
if (obj_idx < 0) { return; }
@ -2561,15 +2716,6 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
},
[this]() { return was_canceled(); });
const auto *bed_shape_opt =
plater().config->opt<ConfigOptionPoints>("bed_shape");
assert(bed_shape_opt);
auto & bedpoints = bed_shape_opt->values;
Polyline bed;
bed.points.reserve(bedpoints.size());
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
double mindist = 6.0; // FIXME
@ -2591,12 +2737,7 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
oi->set_rotation(rt);
}
arr::WipeTowerInfo wti; // useless in SLA context
arr::find_new_position(plater().model,
o->instances,
coord_t(mindist / SCALING_FACTOR),
bed,
wti);
plater().find_new_position(o->instances, scaled(mindist));
// Correct the z offset of the object which was corrupted be
// the rotation
@ -3486,7 +3627,7 @@ void Plater::priv::set_bed_shape(const Pointfs& shape)
bool Plater::priv::can_delete() const
{
return !get_selection().is_empty() && !get_selection().is_wipe_tower();
return !get_selection().is_empty() && !get_selection().is_wipe_tower() && !m_ui_jobs.is_any_running();
}
bool Plater::priv::can_delete_all() const

View File

@ -552,6 +552,29 @@ void ObjectDataViewModelNode::msw_rescale()
update_settings_digest_bitmaps();
}
bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col)
{
switch (col)
{
case 0: {
DataViewBitmapText data;
data << variant;
m_bmp = data.GetBitmap();
m_name = data.GetText();
return true; }
case 1: {
const wxString & val = variant.GetString();
m_extruder = val == "0" ? _(L("default")) : val;
return true; }
case 2:
m_action_icon << variant;
return true;
default:
printf("MyObjectTreeModel::SetValue: wrong column");
}
return false;
}
void ObjectDataViewModelNode::SetIdx(const int& idx)
{
m_idx = idx;

View File

@ -301,27 +301,7 @@ public:
return m_children.GetCount();
}
bool SetValue(const wxVariant &variant, unsigned int col)
{
switch (col)
{
case 0:{
DataViewBitmapText data;
data << variant;
m_bmp = data.GetBitmap();
m_name = data.GetText();
return true;}
case 1:
m_extruder = variant.GetString();
return true;
case 2:
m_action_icon << variant;
return true;
default:
printf("MyObjectTreeModel::SetValue: wrong column");
}
return false;
}
bool SetValue(const wxVariant &variant, unsigned int col);
void SetBitmap(const wxBitmap &icon) { m_bmp = icon; }
const wxBitmap& GetBitmap() const { return m_bmp; }