WIP on structuring arrange inputs
This commit is contained in:
parent
96f6fd2d9f
commit
19e6bf58dd
@ -276,8 +276,7 @@ using EigenVec = Eigen::Matrix<T, N, 1, Eigen::DontAlign>;
|
|||||||
// Semantics are the following:
|
// Semantics are the following:
|
||||||
// Upscaling (scaled()): only from floating point types (or Vec) to either
|
// Upscaling (scaled()): only from floating point types (or Vec) to either
|
||||||
// floating point or integer 'scaled coord' coordinates.
|
// floating point or integer 'scaled coord' coordinates.
|
||||||
// Downscaling (unscaled()): from arithmetic types (or Vec) to either
|
// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only
|
||||||
// floating point only
|
|
||||||
|
|
||||||
// Conversion definition from unscaled to floating point scaled
|
// Conversion definition from unscaled to floating point scaled
|
||||||
template<class Tout,
|
template<class Tout,
|
||||||
@ -286,25 +285,25 @@ template<class Tout,
|
|||||||
class = FloatingOnly<Tout>>
|
class = FloatingOnly<Tout>>
|
||||||
inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT
|
inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_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'.
|
// Conversion definition from unscaled to integer 'scaled coord'.
|
||||||
// TODO: is the rounding necessary ? Here it is to show that it can be different
|
// TODO: is the rounding necessary? Here it is commented out to show that
|
||||||
// but it does not have to be. Using std::round means loosing noexcept and
|
// it can be different for integers but it does not have to be. Using
|
||||||
// constexpr modifiers
|
// std::round means loosing noexcept and constexpr modifiers
|
||||||
template<class Tout = coord_t, class Tin, class = FloatingOnly<Tin>>
|
template<class Tout = coord_t, class Tin, class = FloatingOnly<Tin>>
|
||||||
inline SLIC3R_CONSTEXPR ScaledCoordOnly<Tout> scaled(const Tin &v) SLIC3R_NOEXCEPT
|
inline SLIC3R_CONSTEXPR ScaledCoordOnly<Tout> scaled(const Tin &v) SLIC3R_NOEXCEPT
|
||||||
{
|
{
|
||||||
//return static_cast<Tout>(std::round(v / SCALING_FACTOR));
|
//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)
|
// Conversion for Eigen vectors (N dimensional points)
|
||||||
template<class Tout = coord_t, class Tin, int N, class = FloatingOnly<Tin>>
|
template<class Tout = coord_t, class Tin, int N, class = FloatingOnly<Tin>>
|
||||||
inline EigenVec<ArithmeticOnly<Tout>, N> scaled(const EigenVec<Tin, N> &v)
|
inline EigenVec<ArithmeticOnly<Tout>, N> scaled(const EigenVec<Tin, N> &v)
|
||||||
{
|
{
|
||||||
return v.template cast<Tout>() / SCALING_FACTOR;
|
return v.template cast<Tout>() /*/ SCALING_FACTOR*/;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversion from arithmetic scaled type to floating point unscaled
|
// Conversion from arithmetic scaled type to floating point unscaled
|
||||||
@ -314,7 +313,7 @@ template<class Tout = double,
|
|||||||
class = FloatingOnly<Tout>>
|
class = FloatingOnly<Tout>>
|
||||||
inline SLIC3R_CONSTEXPR Tout unscaled(const Tin &v) SLIC3R_NOEXCEPT
|
inline SLIC3R_CONSTEXPR Tout unscaled(const Tin &v) SLIC3R_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
|
// Unscaling for Eigen vectors. Input base type can be arithmetic, output base
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
#include "Geometry.hpp"
|
#include "Geometry.hpp"
|
||||||
|
#include "MTUtils.hpp"
|
||||||
|
|
||||||
#include "Format/AMF.hpp"
|
#include "Format/AMF.hpp"
|
||||||
#include "Format/OBJ.hpp"
|
#include "Format/OBJ.hpp"
|
||||||
@ -1800,6 +1801,35 @@ void ModelInstance::transform_polygon(Polygon* polygon) const
|
|||||||
polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin
|
polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Polygon ModelInstance::get_arrange_polygon() const
|
||||||
|
{
|
||||||
|
static const double SIMPLIFY_TOLERANCE_MM = 0.1;
|
||||||
|
|
||||||
|
assert(m_inst);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
|
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
|
||||||
// ordered in the same order. In that case it is not necessary to kill the background processing.
|
// 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)
|
bool model_object_list_equal(const Model &model_old, const Model &model_new)
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
#include "TriangleMesh.hpp"
|
#include "TriangleMesh.hpp"
|
||||||
#include "Slicing.hpp"
|
#include "Slicing.hpp"
|
||||||
|
#include "ModelArrange.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -490,7 +491,7 @@ private:
|
|||||||
|
|
||||||
// A single instance of a ModelObject.
|
// A single instance of a ModelObject.
|
||||||
// Knows the affine transformation of an object.
|
// Knows the affine transformation of an object.
|
||||||
class ModelInstance : public ModelBase
|
class ModelInstance : public ModelBase, public arr::Arrangeable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum EPrintVolumeState : unsigned char
|
enum EPrintVolumeState : unsigned char
|
||||||
@ -552,6 +553,16 @@ public:
|
|||||||
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
|
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
|
||||||
|
|
||||||
bool is_printable() const { return print_volume_state == PVS_Inside; }
|
bool is_printable() const { return print_volume_state == PVS_Inside; }
|
||||||
|
|
||||||
|
virtual void set_arrange_result(Vec2d offs, double rot_rads) final
|
||||||
|
{
|
||||||
|
// write the transformation data into the model instance
|
||||||
|
set_rotation(Z, get_rotation(Z) + rot_rads);
|
||||||
|
set_offset(X, get_offset(X) + offs(X));
|
||||||
|
set_offset(Y, get_offset(Y) + offs(Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Polygon get_arrange_polygon() const final;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class Print;
|
friend class Print;
|
||||||
|
@ -42,6 +42,8 @@ namespace arr {
|
|||||||
|
|
||||||
using namespace libnest2d;
|
using namespace libnest2d;
|
||||||
|
|
||||||
|
using Shape = ClipperLib::Polygon;
|
||||||
|
|
||||||
// Only for debugging. Prints the model object vertices on stdout.
|
// Only for debugging. Prints the model object vertices on stdout.
|
||||||
//std::string toString(const Model& model, bool holes = true) {
|
//std::string toString(const Model& model, bool holes = true) {
|
||||||
// std::stringstream ss;
|
// std::stringstream ss;
|
||||||
@ -129,9 +131,7 @@ namespace bgi = boost::geometry::index;
|
|||||||
|
|
||||||
using SpatElement = std::pair<Box, unsigned>;
|
using SpatElement = std::pair<Box, unsigned>;
|
||||||
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
|
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
|
||||||
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
|
using ItemGroup = std::vector<std::reference_wrapper<_Item<Shape>>>;
|
||||||
template<class TBin>
|
|
||||||
using TPacker = typename placers::_NofitPolyPlacer<PolygonImpl, TBin>;
|
|
||||||
|
|
||||||
const double BIG_ITEM_TRESHOLD = 0.02;
|
const double BIG_ITEM_TRESHOLD = 0.02;
|
||||||
|
|
||||||
@ -156,10 +156,10 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) {
|
|||||||
// at the same time, it has to provide reasonable results.
|
// at the same time, it has to provide reasonable results.
|
||||||
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
|
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
|
||||||
objfunc(const PointImpl& bincenter,
|
objfunc(const PointImpl& bincenter,
|
||||||
const TMultiShape<PolygonImpl>& merged_pile,
|
const TMultiShape<Shape>& merged_pile,
|
||||||
const Box& pilebb,
|
const Box& pilebb,
|
||||||
const ItemGroup& items,
|
const ItemGroup& items,
|
||||||
const Item &item,
|
const _Item<Shape> &item,
|
||||||
double bin_area,
|
double bin_area,
|
||||||
double norm, // A norming factor for physical dimensions
|
double norm, // A norming factor for physical dimensions
|
||||||
// a spatial index to quickly get neighbors of the candidate item
|
// a spatial index to quickly get neighbors of the candidate item
|
||||||
@ -225,7 +225,7 @@ objfunc(const PointImpl& bincenter,
|
|||||||
mp.emplace_back(item.transformedShape());
|
mp.emplace_back(item.transformedShape());
|
||||||
auto chull = sl::convexHull(mp);
|
auto chull = sl::convexHull(mp);
|
||||||
|
|
||||||
placers::EdgeCache<PolygonImpl> ec(chull);
|
placers::EdgeCache<Shape> ec(chull);
|
||||||
|
|
||||||
double circ = ec.circumference() / norm;
|
double circ = ec.circumference() / norm;
|
||||||
double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm;
|
double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm;
|
||||||
@ -256,7 +256,7 @@ objfunc(const PointImpl& bincenter,
|
|||||||
|
|
||||||
for(auto& e : result) { // now get the score for the best alignment
|
for(auto& e : result) { // now get the score for the best alignment
|
||||||
auto idx = e.second;
|
auto idx = e.second;
|
||||||
Item& p = items[idx];
|
_Item<Shape>& p = items[idx];
|
||||||
auto parea = p.area();
|
auto parea = p.area();
|
||||||
if(std::abs(1.0 - parea/item.area()) < 1e-6) {
|
if(std::abs(1.0 - parea/item.area()) < 1e-6) {
|
||||||
auto bb = boundingBox(p.boundingBox(), ibb);
|
auto bb = boundingBox(p.boundingBox(), ibb);
|
||||||
@ -322,12 +322,12 @@ class _ArrBase {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
// Useful type shortcuts...
|
// Useful type shortcuts...
|
||||||
using Placer = TPacker<TBin>;
|
using Placer = typename placers::_NofitPolyPlacer<Shape, TBin>;
|
||||||
using Selector = FirstFitSelection;
|
using Selector = selections::_FirstFitSelection<Shape>;
|
||||||
using Packer = Nester<Placer, Selector>;
|
using Packer = Nester<Placer, Selector>;
|
||||||
using PConfig = typename Packer::PlacementConfig;
|
using PConfig = typename Packer::PlacementConfig;
|
||||||
using Distance = TCoord<PointImpl>;
|
using Distance = TCoord<PointImpl>;
|
||||||
using Pile = TMultiShape<PolygonImpl>;
|
using Pile = TMultiShape<Shape>;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
@ -373,7 +373,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
for(unsigned idx = 0; idx < items.size(); ++idx) {
|
for(unsigned idx = 0; idx < items.size(); ++idx) {
|
||||||
Item& itm = items[idx];
|
_Item<Shape>& itm = items[idx];
|
||||||
if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx});
|
if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx});
|
||||||
m_smallsrtree.insert({itm.boundingBox(), idx});
|
m_smallsrtree.insert({itm.boundingBox(), idx});
|
||||||
}
|
}
|
||||||
@ -382,13 +382,13 @@ public:
|
|||||||
m_pck.progressIndicator(progressind);
|
m_pck.progressIndicator(progressind);
|
||||||
m_pck.stopCondition(stopcond);
|
m_pck.stopCondition(stopcond);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class...Args> inline IndexedPackGroup operator()(Args&&...args) {
|
template<class...Args> inline _PackGroup<Shape> operator()(Args&&...args) {
|
||||||
m_rtree.clear();
|
m_rtree.clear();
|
||||||
return m_pck.executeIndexed(std::forward<Args>(args)...);
|
return m_pck.execute(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void preload(const PackGroup& pg) {
|
inline void preload(const _PackGroup<Shape>& pg) {
|
||||||
m_pconf.alignment = PConfig::Alignment::DONT_ALIGN;
|
m_pconf.alignment = PConfig::Alignment::DONT_ALIGN;
|
||||||
m_pconf.object_function = nullptr; // drop the special objectfunction
|
m_pconf.object_function = nullptr; // drop the special objectfunction
|
||||||
m_pck.preload(pg);
|
m_pck.preload(pg);
|
||||||
@ -396,14 +396,14 @@ public:
|
|||||||
// Build the rtree for queries to work
|
// Build the rtree for queries to work
|
||||||
for(const ItemGroup& grp : pg)
|
for(const ItemGroup& grp : pg)
|
||||||
for(unsigned idx = 0; idx < grp.size(); ++idx) {
|
for(unsigned idx = 0; idx < grp.size(); ++idx) {
|
||||||
Item& itm = grp[idx];
|
_Item<Shape>& itm = grp[idx];
|
||||||
m_rtree.insert({itm.boundingBox(), idx});
|
m_rtree.insert({itm.boundingBox(), idx});
|
||||||
}
|
}
|
||||||
|
|
||||||
m_pck.configure(m_pconf);
|
m_pck.configure(m_pconf);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_colliding(const Item& item) {
|
bool is_colliding(const _Item<Shape>& item) {
|
||||||
if(m_rtree.empty()) return false;
|
if(m_rtree.empty()) return false;
|
||||||
std::vector<SpatElement> result;
|
std::vector<SpatElement> result;
|
||||||
m_rtree.query(bgi::intersects(item.boundingBox()),
|
m_rtree.query(bgi::intersects(item.boundingBox()),
|
||||||
@ -425,7 +425,7 @@ public:
|
|||||||
// Here we set up the actual object function that calls the common
|
// Here we set up the actual object function that calls the common
|
||||||
// object function for all bin shapes than does an additional inside
|
// object function for all bin shapes than does an additional inside
|
||||||
// check for the arranged pile.
|
// check for the arranged pile.
|
||||||
m_pconf.object_function = [this, bin] (const Item &item) {
|
m_pconf.object_function = [this, bin] (const _Item<Shape> &item) {
|
||||||
|
|
||||||
auto result = objfunc(bin.center(),
|
auto result = objfunc(bin.center(),
|
||||||
m_merged_pile,
|
m_merged_pile,
|
||||||
@ -468,7 +468,7 @@ public:
|
|||||||
_ArrBase<lnCircle>(bin, dist, progressind, stopcond) {
|
_ArrBase<lnCircle>(bin, dist, progressind, stopcond) {
|
||||||
|
|
||||||
// As with the box, only the inside check is different.
|
// As with the box, only the inside check is different.
|
||||||
m_pconf.object_function = [this, &bin] (const Item &item) {
|
m_pconf.object_function = [this, &bin] (const _Item<Shape> &item) {
|
||||||
|
|
||||||
auto result = objfunc(bin.center(),
|
auto result = objfunc(bin.center(),
|
||||||
m_merged_pile,
|
m_merged_pile,
|
||||||
@ -483,7 +483,7 @@ public:
|
|||||||
|
|
||||||
double score = std::get<0>(result);
|
double score = std::get<0>(result);
|
||||||
|
|
||||||
auto isBig = [this](const Item& itm) {
|
auto isBig = [this](const _Item<Shape>& itm) {
|
||||||
return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ;
|
return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -512,7 +512,7 @@ public:
|
|||||||
std::function<bool(void)> stopcond = [](){return false;}):
|
std::function<bool(void)> stopcond = [](){return false;}):
|
||||||
_ArrBase<PolygonImpl>(bin, dist, progressind, stopcond)
|
_ArrBase<PolygonImpl>(bin, dist, progressind, stopcond)
|
||||||
{
|
{
|
||||||
m_pconf.object_function = [this, &bin] (const Item &item) {
|
m_pconf.object_function = [this, &bin] (const _Item<Shape> &item) {
|
||||||
|
|
||||||
auto binbb = sl::boundingBox(bin);
|
auto binbb = sl::boundingBox(bin);
|
||||||
auto result = objfunc(binbb.center(),
|
auto result = objfunc(binbb.center(),
|
||||||
@ -540,11 +540,11 @@ public:
|
|||||||
template<> class AutoArranger<bool>: public _ArrBase<Box> {
|
template<> class AutoArranger<bool>: public _ArrBase<Box> {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
AutoArranger(Distance dist, std::function<void(unsigned)> progressind,
|
AutoArranger(bool, Distance dist, std::function<void(unsigned)> progressind,
|
||||||
std::function<bool(void)> stopcond):
|
std::function<bool(void)> stopcond):
|
||||||
_ArrBase<Box>(Box(0, 0), dist, progressind, stopcond)
|
_ArrBase<Box>(Box(0, 0), dist, progressind, stopcond)
|
||||||
{
|
{
|
||||||
this->m_pconf.object_function = [this] (const Item &item) {
|
this->m_pconf.object_function = [this] (const _Item<Shape> &item) {
|
||||||
|
|
||||||
auto result = objfunc({0, 0},
|
auto result = objfunc({0, 0},
|
||||||
m_merged_pile,
|
m_merged_pile,
|
||||||
@ -782,18 +782,18 @@ BedShapeHint bedShape(const Polyline &bed) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1;
|
//static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1;
|
||||||
|
|
||||||
//template<class BinT>
|
template<class BinT>
|
||||||
//IndexedPackGroup _arrange(std::vector<std::reference_wrapper<Item>> &shapes,
|
_PackGroup<Shape> _arrange(std::vector<Shape> &shapes,
|
||||||
// const BinT & bin,
|
const BinT & bin,
|
||||||
// coord_t minobjd,
|
coord_t minobjd,
|
||||||
// std::function<void(unsigned)> prind,
|
std::function<void(unsigned)> prind,
|
||||||
// std::function<bool()> stopfn)
|
std::function<bool()> stopfn)
|
||||||
//{
|
{
|
||||||
// AutoArranger<BinT> arranger{bin, minobjd, prind, stopfn};
|
AutoArranger<BinT> arranger{bin, minobjd, prind, stopfn};
|
||||||
// return arranger(shapes.begin(), shapes.end());
|
return arranger(shapes.begin(), shapes.end());
|
||||||
//}
|
}
|
||||||
|
|
||||||
//template<class BinT>
|
//template<class BinT>
|
||||||
//IndexedPackGroup _arrange(std::vector<std::reference_wrapper<Item>> &shapes,
|
//IndexedPackGroup _arrange(std::vector<std::reference_wrapper<Item>> &shapes,
|
||||||
@ -845,11 +845,99 @@ static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1;
|
|||||||
// return arranger(shapes.begin(), shapes.end());
|
// return arranger(shapes.begin(), shapes.end());
|
||||||
//}
|
//}
|
||||||
|
|
||||||
inline SLIC3R_CONSTEXPR libnest2d::Coord stride_padding(Coord w)
|
inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w)
|
||||||
{
|
{
|
||||||
return w + w / 5;
|
return w + w / 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool arrange(ArrangeableRefs & arrangables,
|
||||||
|
coord_t min_obj_distance,
|
||||||
|
BedShapeHint bedhint,
|
||||||
|
std::function<void(unsigned)> progressind,
|
||||||
|
std::function<bool()> stopcondition)
|
||||||
|
{
|
||||||
|
bool ret = true;
|
||||||
|
|
||||||
|
std::vector<Shape> shapes;
|
||||||
|
shapes.reserve(arrangables.size());
|
||||||
|
size_t id = 0;
|
||||||
|
for (Arrangeable &iref : arrangables) {
|
||||||
|
Polygon p = iref.get_arrange_polygon();
|
||||||
|
|
||||||
|
p.reverse();
|
||||||
|
assert(!p.is_counter_clockwise());
|
||||||
|
|
||||||
|
Shape clpath(/*id++,*/ Slic3rMultiPoint_to_ClipperPath(p));
|
||||||
|
|
||||||
|
auto firstp = clpath.Contour.front(); clpath.Contour.emplace_back(firstp);
|
||||||
|
shapes.emplace_back(std::move(clpath));
|
||||||
|
}
|
||||||
|
|
||||||
|
_PackGroup<Shape> result;
|
||||||
|
|
||||||
|
auto& cfn = stopcondition;
|
||||||
|
|
||||||
|
// Integer ceiling the min distance from the bed perimeters
|
||||||
|
coord_t md = min_obj_distance - SCALED_EPSILON;
|
||||||
|
md = (md % 2) ? md / 2 + 1 : md / 2;
|
||||||
|
coord_t binwidth = 0;
|
||||||
|
|
||||||
|
switch (bedhint.type) {
|
||||||
|
case BedShapeType::BOX: {
|
||||||
|
// Create the arranger for the box shaped bed
|
||||||
|
BoundingBox bbb = bedhint.shape.box;
|
||||||
|
|
||||||
|
auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md,
|
||||||
|
ClipperLib::cInt{bbb.min(1)} - md},
|
||||||
|
{ClipperLib::cInt{bbb.max(0)} + md,
|
||||||
|
ClipperLib::cInt{bbb.max(1)} + md});
|
||||||
|
|
||||||
|
result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn);
|
||||||
|
binwidth = coord_t(binbb.width());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BedShapeType::CIRCLE: {
|
||||||
|
auto c = bedhint.shape.circ;
|
||||||
|
auto cc = to_lnCircle(c);
|
||||||
|
result = _arrange(shapes, cc, min_obj_distance, progressind, cfn);
|
||||||
|
binwidth = scaled(c.radius());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BedShapeType::IRREGULAR: {
|
||||||
|
auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon);
|
||||||
|
ClipperLib::Polygon irrbed = sl::create<PolygonImpl>(std::move(ctour));
|
||||||
|
result = _arrange(shapes, irrbed, min_obj_distance, progressind, cfn);
|
||||||
|
BoundingBox polybb(bedhint.shape.polygon);
|
||||||
|
binwidth = (polybb.max(X) - polybb.min(X));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BedShapeType::WHO_KNOWS: {
|
||||||
|
result = _arrange(shapes, false, min_obj_distance, progressind, cfn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(result.empty() || stopcondition()) return false;
|
||||||
|
|
||||||
|
ClipperLib::cInt stride = stride_padding(binwidth);
|
||||||
|
ClipperLib::cInt batch_offset = 0;
|
||||||
|
|
||||||
|
for (const auto &group : result) {
|
||||||
|
for (_Item<Shape> &itm : group) {
|
||||||
|
ClipperLib::IntPoint offs = itm.translation();
|
||||||
|
// arrangables[itm.id()].get().set_arrange_result({offs.X, offs.Y},
|
||||||
|
// itm.rotation());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the first pack group can be placed onto the print bed. The
|
||||||
|
// other objects which could not fit will be placed next to the
|
||||||
|
// print bed
|
||||||
|
batch_offset += stride;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
//// The final client function to arrange the Model. A progress indicator and
|
//// The final client function to arrange the Model. A progress indicator and
|
||||||
//// a stop predicate can be also be passed to control the process.
|
//// a stop predicate can be also be passed to control the process.
|
||||||
//bool arrange(Model &model, // The model with the geometries
|
//bool arrange(Model &model, // The model with the geometries
|
||||||
|
@ -32,8 +32,8 @@ enum class BedShapeType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct BedShapeHint {
|
struct BedShapeHint {
|
||||||
BedShapeType type;
|
BedShapeType type = BedShapeType::WHO_KNOWS;
|
||||||
/*union*/ struct { // I know but who cares...
|
/*union*/ struct { // I know but who cares... TODO: use variant from cpp17?
|
||||||
Circle circ;
|
Circle circ;
|
||||||
BoundingBox box;
|
BoundingBox box;
|
||||||
Polyline polygon;
|
Polyline polygon;
|
||||||
@ -42,24 +42,17 @@ struct BedShapeHint {
|
|||||||
|
|
||||||
BedShapeHint bedShape(const Polyline& bed);
|
BedShapeHint bedShape(const Polyline& bed);
|
||||||
|
|
||||||
class ArrangeItem {
|
class Arrangeable {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
virtual ~ArrangeItem() = default;
|
virtual ~Arrangeable() = default;
|
||||||
|
|
||||||
virtual void transform(Vec2d offset, double rotation_rads) = 0;
|
virtual void set_arrange_result(Vec2d offset, double rotation_rads) = 0;
|
||||||
|
|
||||||
virtual Polygon silhouette() const = 0;
|
virtual Polygon get_arrange_polygon() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ArrangeItems = std::vector<std::reference_wrapper<ArrangeItem>>;
|
using ArrangeableRefs = std::vector<std::reference_wrapper<Arrangeable>>;
|
||||||
|
|
||||||
//struct WipeTowerInfo {
|
|
||||||
// bool is_wipe_tower = false;
|
|
||||||
// Vec2d pos;
|
|
||||||
// Vec2d bb_size;
|
|
||||||
// double rotation;
|
|
||||||
//};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Arranges the model objects on the screen.
|
* \brief Arranges the model objects on the screen.
|
||||||
@ -96,7 +89,7 @@ using ArrangeItems = std::vector<std::reference_wrapper<ArrangeItem>>;
|
|||||||
// std::function<void(unsigned)> progressind,
|
// std::function<void(unsigned)> progressind,
|
||||||
// std::function<bool(void)> stopcondition);
|
// std::function<bool(void)> stopcondition);
|
||||||
|
|
||||||
bool arrange(ArrangeItems &items,
|
bool arrange(ArrangeableRefs &items,
|
||||||
coord_t min_obj_distance,
|
coord_t min_obj_distance,
|
||||||
BedShapeHint bedhint,
|
BedShapeHint bedhint,
|
||||||
std::function<void(unsigned)> progressind,
|
std::function<void(unsigned)> progressind,
|
||||||
@ -109,8 +102,8 @@ bool arrange(ArrangeItems &items,
|
|||||||
// coord_t min_obj_distance,
|
// coord_t min_obj_distance,
|
||||||
// const Slic3r::Polyline& bed,
|
// const Slic3r::Polyline& bed,
|
||||||
// WipeTowerInfo& wti);
|
// WipeTowerInfo& wti);
|
||||||
void find_new_position(ArrangeItems &items,
|
void find_new_position(ArrangeableRefs &items,
|
||||||
const ArrangeItems &instances_to_add,
|
const ArrangeableRefs &instances_to_add,
|
||||||
coord_t min_obj_distance,
|
coord_t min_obj_distance,
|
||||||
BedShapeHint bedhint);
|
BedShapeHint bedhint);
|
||||||
|
|
||||||
|
@ -3329,36 +3329,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) {
|
for (const GLVolume* vol : m_volumes.volumes) {
|
||||||
if (vol->is_wipe_tower) {
|
if (vol->is_wipe_tower) {
|
||||||
wti.is_wipe_tower = true;
|
wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"),
|
||||||
wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"),
|
|
||||||
m_config->opt_float("wipe_tower_y"));
|
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;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return wti;
|
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)
|
Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos)
|
||||||
{
|
{
|
||||||
float z0 = 0.0f;
|
float z0 = 0.0f;
|
||||||
@ -5751,5 +5739,16 @@ const SLAPrint* GLCanvas3D::sla_print() const
|
|||||||
return (m_process == nullptr) ? nullptr : m_process->sla_print();
|
return (m_process == nullptr) ? nullptr : m_process->sla_print();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GLCanvas3D::WipeTowerInfo::set_arrange_result(Vec2d offset, double rotation_rads)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
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 GUI
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
@ -611,9 +611,38 @@ public:
|
|||||||
|
|
||||||
int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; }
|
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(); }
|
int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); }
|
||||||
|
|
||||||
|
class WipeTowerInfo: public arr::Arrangeable {
|
||||||
|
Vec2d m_pos = {std::nan(""), std::nan("")};
|
||||||
|
Vec2d m_bb_size;
|
||||||
|
double m_rotation;
|
||||||
|
friend class GLCanvas3D;
|
||||||
|
public:
|
||||||
|
|
||||||
|
inline operator bool() const
|
||||||
|
{
|
||||||
|
return std::isnan(m_pos.x()) || std::isnan(m_pos.y());
|
||||||
|
}
|
||||||
|
|
||||||
arr::WipeTowerInfo get_wipe_tower_info() const;
|
virtual void set_arrange_result(Vec2d offset, double rotation_rads) final;
|
||||||
void arrange_wipe_tower(const arr::WipeTowerInfo& wti) const;
|
|
||||||
|
virtual Polygon get_arrange_polygon() const final
|
||||||
|
{
|
||||||
|
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)},
|
||||||
|
});
|
||||||
|
|
||||||
|
p.rotate(m_rotation);
|
||||||
|
p.translate(scaled(m_pos));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
WipeTowerInfo get_wipe_tower_info() const;
|
||||||
|
|
||||||
// Returns the view ray line, in world coordinate, at the given mouse position.
|
// Returns the view ray line, in world coordinate, at the given mouse position.
|
||||||
Linef3 mouse_ray(const Point& mouse_pos);
|
Linef3 mouse_ray(const Point& mouse_pos);
|
||||||
|
@ -1424,22 +1424,25 @@ struct Plater::priv
|
|||||||
priv * m_plater;
|
priv * m_plater;
|
||||||
|
|
||||||
class ArrangeJob : public Job
|
class ArrangeJob : public Job
|
||||||
{
|
{
|
||||||
int count = 0;
|
int m_count = 0;
|
||||||
|
GLCanvas3D::WipeTowerInfo m_wti;
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void prepare() override
|
void prepare() override
|
||||||
{
|
{
|
||||||
count = 0;
|
m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info();
|
||||||
|
m_count = bool(m_wti);
|
||||||
|
|
||||||
for (auto obj : plater().model.objects)
|
for (auto obj : plater().model.objects)
|
||||||
count += int(obj->instances.size());
|
m_count += int(obj->instances.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
//using Job::Job;
|
//using Job::Job;
|
||||||
ArrangeJob(priv * pltr): Job(pltr) {}
|
ArrangeJob(priv * pltr): Job(pltr) {}
|
||||||
int status_range() const override { return count; }
|
int status_range() const override { return m_count; }
|
||||||
void set_count(int c) { count = c; }
|
void set_count(int c) { m_count = c; }
|
||||||
void process() override;
|
void process() override;
|
||||||
} arrange_job/*{m_plater}*/;
|
} arrange_job/*{m_plater}*/;
|
||||||
|
|
||||||
@ -1525,6 +1528,7 @@ struct Plater::priv
|
|||||||
std::string get_config(const std::string &key) const;
|
std::string get_config(const std::string &key) const;
|
||||||
BoundingBoxf bed_shape_bb() const;
|
BoundingBoxf bed_shape_bb() const;
|
||||||
BoundingBox scaled_bed_shape_bb() const;
|
BoundingBox scaled_bed_shape_bb() const;
|
||||||
|
arr::BedShapeHint get_bed_shape_hint() const;
|
||||||
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
|
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);
|
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
|
||||||
wxString get_export_file(GUI::FileType file_type);
|
wxString get_export_file(GUI::FileType file_type);
|
||||||
@ -2171,9 +2175,9 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
|
|||||||
auto& bedpoints = bed_shape_opt->values;
|
auto& bedpoints = bed_shape_opt->values;
|
||||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
Polyline bed; bed.points.reserve(bedpoints.size());
|
||||||
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
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);
|
arr::find_new_position(model, new_instances, min_obj_distance, bed, wti);
|
||||||
|
|
||||||
// it remains to move the wipe tower:
|
// it remains to move the wipe tower:
|
||||||
@ -2400,61 +2404,60 @@ void Plater::priv::sla_optimize_rotation() {
|
|||||||
m_ui_jobs.start(Jobs::Rotoptimize);
|
m_ui_jobs.start(Jobs::Rotoptimize);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() {
|
arr::BedShapeHint Plater::priv::get_bed_shape_hint() const {
|
||||||
static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1;
|
arr::BedShapeHint bedshape;
|
||||||
|
|
||||||
class ArrItemModelInstance: public arr::ArrangeItem {
|
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
|
||||||
ModelInstance *m_inst = nullptr;
|
assert(bed_shape_opt);
|
||||||
public:
|
|
||||||
|
if (bed_shape_opt) {
|
||||||
ArrItemModelInstance() = default;
|
auto &bedpoints = bed_shape_opt->values;
|
||||||
ArrItemModelInstance(ModelInstance *inst) : m_inst(inst) {}
|
Polyline bedpoly; bedpoly.points.reserve(bedpoints.size());
|
||||||
|
for (auto &v : bedpoints) bedpoly.append(scaled(v));
|
||||||
virtual void transform(Vec2d offs, double rot_rads) override {
|
bedshape = arr::bedShape(bedpoly);
|
||||||
assert(m_inst);
|
}
|
||||||
|
|
||||||
// write the transformation data into the model instance
|
return bedshape;
|
||||||
m_inst->set_rotation(Z, rot_rads);
|
}
|
||||||
m_inst->set_offset(offs);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Polygon silhouette() const override {
|
|
||||||
assert(m_inst);
|
|
||||||
|
|
||||||
Vec3d rotation = m_inst->get_rotation();
|
|
||||||
rotation.z() = 0.;
|
|
||||||
Transform3d trafo_instance = Geometry::assemble_transform(
|
|
||||||
Vec3d::Zero(),
|
|
||||||
rotation,
|
|
||||||
m_inst->get_scaling_factor(),
|
|
||||||
m_inst->get_mirror());
|
|
||||||
|
|
||||||
Polygon p = m_inst->get_object()->convex_hull_2d(trafo_instance);
|
|
||||||
|
|
||||||
assert(!p.points.empty());
|
void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() {
|
||||||
|
static const auto arrangestr = _(L("Arranging"));
|
||||||
|
|
||||||
|
arr::ArrangeableRefs arrangeinput; arrangeinput.reserve(m_count);
|
||||||
|
for(ModelObject *mo : plater().model.objects)
|
||||||
|
for(ModelInstance *minst : mo->instances)
|
||||||
|
arrangeinput.emplace_back(std::ref(*minst));
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
arr::BedShapeHint bedshape = plater().get_bed_shape_hint();
|
||||||
|
|
||||||
|
try {
|
||||||
|
arr::arrange(arrangeinput,
|
||||||
|
min_obj_distance,
|
||||||
|
bedshape,
|
||||||
|
[this](unsigned st) {
|
||||||
|
if (st > 0)
|
||||||
|
update_status(m_count - int(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.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
update_status(m_count,
|
||||||
|
was_canceled() ? _(L("Arranging canceled."))
|
||||||
|
: _(L("Arranging done.")));
|
||||||
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Count all the items on the bin (all the object's instances)
|
|
||||||
auto count = std::accumulate(plater().model.objects.begin(),
|
|
||||||
plater().model.objects.end(),
|
|
||||||
size_t(0), [](size_t s, ModelObject* o)
|
|
||||||
{
|
|
||||||
return s + o->instances.size();
|
|
||||||
});
|
|
||||||
|
|
||||||
// std::vector<ArrItemInstance> items(size_t);
|
|
||||||
|
|
||||||
// TODO: we should decide whether to allow arrange when the search is
|
// TODO: we should decide whether to allow arrange when the search is
|
||||||
// running we should probably disable explicit slicing and background
|
// running we should probably disable explicit slicing and background
|
||||||
// processing
|
// processing
|
||||||
|
Loading…
Reference in New Issue
Block a user