Getting rid of AppController.
This commit is contained in:
parent
c227dad8cc
commit
a4e1ab2281
@ -108,6 +108,7 @@ add_library(libslic3r STATIC
|
||||
Model.cpp
|
||||
Model.hpp
|
||||
ModelArrange.hpp
|
||||
ModelArrange.cpp
|
||||
MotionPlanner.cpp
|
||||
MotionPlanner.hpp
|
||||
MultiPoint.cpp
|
||||
|
763
src/libslic3r/ModelArrange.cpp
Normal file
763
src/libslic3r/ModelArrange.cpp
Normal file
@ -0,0 +1,763 @@
|
||||
#include "ModelArrange.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "SVG.hpp"
|
||||
|
||||
#include <libnest2d.h>
|
||||
|
||||
#include <numeric>
|
||||
#include <ClipperUtils.hpp>
|
||||
|
||||
#include <boost/geometry/index/rtree.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace arr {
|
||||
|
||||
using namespace libnest2d;
|
||||
|
||||
std::string toString(const Model& model, bool holes = true) {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "{\n";
|
||||
|
||||
for(auto objptr : model.objects) {
|
||||
if(!objptr) continue;
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(!objinst) continue;
|
||||
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
// CHECK_ME -> Is the following correct ?
|
||||
tmpmesh.scale(objinst->get_scaling_factor());
|
||||
objinst->transform_mesh(&tmpmesh);
|
||||
ExPolygons expolys = tmpmesh.horizontal_projection();
|
||||
for(auto& expoly_complex : expolys) {
|
||||
|
||||
auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR);
|
||||
if(tmp.empty()) continue;
|
||||
auto expoly = tmp.front();
|
||||
expoly.contour.make_clockwise();
|
||||
for(auto& h : expoly.holes) h.make_counter_clockwise();
|
||||
|
||||
ss << "\t{\n";
|
||||
ss << "\t\t{\n";
|
||||
|
||||
for(auto v : expoly.contour.points) ss << "\t\t\t{"
|
||||
<< v(0) << ", "
|
||||
<< v(1) << "},\n";
|
||||
{
|
||||
auto v = expoly.contour.points.front();
|
||||
ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n";
|
||||
}
|
||||
ss << "\t\t},\n";
|
||||
|
||||
// Holes:
|
||||
ss << "\t\t{\n";
|
||||
if(holes) for(auto h : expoly.holes) {
|
||||
ss << "\t\t\t{\n";
|
||||
for(auto v : h.points) ss << "\t\t\t\t{"
|
||||
<< v(0) << ", "
|
||||
<< v(1) << "},\n";
|
||||
{
|
||||
auto v = h.points.front();
|
||||
ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n";
|
||||
}
|
||||
ss << "\t\t\t},\n";
|
||||
}
|
||||
ss << "\t\t},\n";
|
||||
|
||||
ss << "\t},\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ss << "}\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void toSVG(SVG& svg, const Model& model) {
|
||||
for(auto objptr : model.objects) {
|
||||
if(!objptr) continue;
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(!objinst) continue;
|
||||
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
tmpmesh.scale(objinst->get_scaling_factor());
|
||||
objinst->transform_mesh(&tmpmesh);
|
||||
ExPolygons expolys = tmpmesh.horizontal_projection();
|
||||
svg.draw(expolys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>>;
|
||||
template<class TBin>
|
||||
using TPacker = typename placers::_NofitPolyPlacer<PolygonImpl, TBin>;
|
||||
|
||||
const double BIG_ITEM_TRESHOLD = 0.02;
|
||||
|
||||
Box boundingBox(const Box& pilebb, const Box& ibb ) {
|
||||
auto& pminc = pilebb.minCorner();
|
||||
auto& pmaxc = pilebb.maxCorner();
|
||||
auto& iminc = ibb.minCorner();
|
||||
auto& imaxc = ibb.maxCorner();
|
||||
PointImpl 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);
|
||||
}
|
||||
|
||||
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
|
||||
objfunc(const PointImpl& bincenter,
|
||||
const shapelike::Shapes<PolygonImpl>& merged_pile,
|
||||
const Box& pilebb,
|
||||
const ItemGroup& items,
|
||||
const Item &item,
|
||||
double bin_area,
|
||||
double norm, // A norming factor for physical dimensions
|
||||
// a spatial index to quickly get neighbors of the candidate item
|
||||
const SpatIndex& spatindex,
|
||||
const SpatIndex& smalls_spatindex,
|
||||
const ItemGroup& remaining
|
||||
)
|
||||
{
|
||||
using Coord = TCoord<PointImpl>;
|
||||
|
||||
static const double ROUNDNESS_RATIO = 0.5;
|
||||
static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO;
|
||||
|
||||
// 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 = sl::boundingBox(item.transformedShape());
|
||||
|
||||
// Calculate the full bounding box of the pile with the candidate item
|
||||
auto fullbb = boundingBox(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;
|
||||
|
||||
if(isBig(item.area()) || spatindex.empty()) {
|
||||
// This branch is for the bigger items..
|
||||
|
||||
auto minc = ibb.minCorner(); // bottom left corner
|
||||
auto maxc = ibb.maxCorner(); // top right corner
|
||||
|
||||
// top left and bottom right corners
|
||||
auto top_left = PointImpl{getX(minc), getY(maxc)};
|
||||
auto bottom_right = PointImpl{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:
|
||||
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
|
||||
auto bindist = pl::distance(ibb.center(), bincenter) / norm;
|
||||
dist = 0.8*dist + 0.2*bindist;
|
||||
|
||||
// Density is the pack density: how big is the arranged pile
|
||||
double density = 0;
|
||||
|
||||
if(remaining.empty()) {
|
||||
|
||||
auto mp = merged_pile;
|
||||
mp.emplace_back(item.transformedShape());
|
||||
auto chull = sl::convexHull(mp);
|
||||
|
||||
placers::EdgeCache<PolygonImpl> ec(chull);
|
||||
|
||||
double circ = ec.circumference() / norm;
|
||||
double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm;
|
||||
score = 0.5*circ + 0.5*bcirc;
|
||||
|
||||
} else {
|
||||
// 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;
|
||||
|
||||
density = std::sqrt((fullbb.width() / norm )*
|
||||
(fullbb.height() / norm));
|
||||
auto querybb = item.boundingBox();
|
||||
|
||||
// Query the spatial index for the neighbors
|
||||
std::vector<SpatElement> result;
|
||||
result.reserve(spatindex.size());
|
||||
if(isBig(item.area())) {
|
||||
spatindex.query(bgi::intersects(querybb),
|
||||
std::back_inserter(result));
|
||||
} else {
|
||||
smalls_spatindex.query(bgi::intersects(querybb),
|
||||
std::back_inserter(result));
|
||||
}
|
||||
|
||||
for(auto& e : result) { // now get the score for the best alignment
|
||||
auto idx = e.second;
|
||||
Item& p = items[idx];
|
||||
auto parea = p.area();
|
||||
if(std::abs(1.0 - parea/item.area()) < 1e-6) {
|
||||
auto bb = boundingBox(p.boundingBox(), ibb);
|
||||
auto bbarea = bb.area();
|
||||
auto ascore = 1.0 - (item.area() + parea)/bbarea;
|
||||
|
||||
if(ascore < alignment_score) alignment_score = ascore;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
} else {
|
||||
// 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 = pl::distance(ibb.center(), bigbb.center()) / norm;
|
||||
}
|
||||
|
||||
return std::make_tuple(score, fullbb);
|
||||
}
|
||||
|
||||
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
|
||||
// arranger.useMinimumBoundigBoxRotation();
|
||||
pcfg.rotations = { 0.0 };
|
||||
|
||||
// The accuracy of optimization.
|
||||
// Goes from 0.0 to 1.0 and scales performance as well
|
||||
pcfg.accuracy = 0.65f;
|
||||
|
||||
pcfg.parallel = true;
|
||||
}
|
||||
|
||||
template<class TBin>
|
||||
class AutoArranger {};
|
||||
|
||||
template<class TBin>
|
||||
class _ArrBase {
|
||||
protected:
|
||||
|
||||
using Placer = TPacker<TBin>;
|
||||
using Selector = FirstFitSelection;
|
||||
using Packer = Nester<Placer, Selector>;
|
||||
using PConfig = typename Packer::PlacementConfig;
|
||||
using Distance = TCoord<PointImpl>;
|
||||
using Pile = sl::Shapes<PolygonImpl>;
|
||||
|
||||
Packer m_pck;
|
||||
PConfig m_pconf; // Placement configuration
|
||||
double m_bin_area;
|
||||
SpatIndex m_rtree;
|
||||
SpatIndex m_smallsrtree;
|
||||
double m_norm;
|
||||
Pile m_merged_pile;
|
||||
Box m_pilebb;
|
||||
ItemGroup m_remaining;
|
||||
ItemGroup m_items;
|
||||
public:
|
||||
|
||||
_ArrBase(const TBin& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind,
|
||||
std::function<bool(void)> stopcond):
|
||||
m_pck(bin, dist), m_bin_area(sl::area(bin)),
|
||||
m_norm(std::sqrt(sl::area(bin)))
|
||||
{
|
||||
fillConfig(m_pconf);
|
||||
|
||||
m_pconf.before_packing =
|
||||
[this](const Pile& 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_pck.progressIndicator(progressind);
|
||||
m_pck.stopCondition(stopcond);
|
||||
}
|
||||
|
||||
template<class...Args> inline IndexedPackGroup operator()(Args&&...args) {
|
||||
m_rtree.clear();
|
||||
return m_pck.executeIndexed(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class AutoArranger<Box>: public _ArrBase<Box> {
|
||||
public:
|
||||
|
||||
AutoArranger(const Box& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind,
|
||||
std::function<bool(void)> stopcond):
|
||||
_ArrBase<Box>(bin, dist, progressind, stopcond)
|
||||
{
|
||||
|
||||
m_pconf.object_function = [this, bin] (const Item &item) {
|
||||
|
||||
auto result = objfunc(bin.center(),
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
m_bin_area,
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
|
||||
double score = std::get<0>(result);
|
||||
auto& fullbb = std::get<1>(result);
|
||||
|
||||
double miss = Placer::overfit(fullbb, bin);
|
||||
miss = miss > 0? miss : 0;
|
||||
score += miss*miss;
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
using lnCircle = libnest2d::_Circle<libnest2d::PointImpl>;
|
||||
|
||||
inline lnCircle to_lnCircle(const Circle& circ) {
|
||||
return lnCircle({circ.center()(0), circ.center()(1)}, circ.radius());
|
||||
}
|
||||
|
||||
template<>
|
||||
class AutoArranger<lnCircle>: public _ArrBase<lnCircle> {
|
||||
public:
|
||||
|
||||
AutoArranger(const lnCircle& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind,
|
||||
std::function<bool(void)> stopcond):
|
||||
_ArrBase<lnCircle>(bin, dist, progressind, stopcond) {
|
||||
|
||||
m_pconf.object_function = [this, &bin] (const Item &item) {
|
||||
|
||||
auto result = objfunc(bin.center(),
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
m_bin_area,
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
|
||||
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, bin);
|
||||
if(miss < 0) miss = 0;
|
||||
score += miss*miss;
|
||||
}
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class AutoArranger<PolygonImpl>: public _ArrBase<PolygonImpl> {
|
||||
public:
|
||||
AutoArranger(const PolygonImpl& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind,
|
||||
std::function<bool(void)> stopcond):
|
||||
_ArrBase<PolygonImpl>(bin, dist, progressind, stopcond)
|
||||
{
|
||||
m_pconf.object_function = [this, &bin] (const Item &item) {
|
||||
|
||||
auto binbb = sl::boundingBox(bin);
|
||||
auto result = objfunc(binbb.center(),
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
m_bin_area,
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
double score = std::get<0>(result);
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
template<> // Specialization with no bin
|
||||
class AutoArranger<bool>: public _ArrBase<Box> {
|
||||
public:
|
||||
|
||||
AutoArranger(Distance dist, std::function<void(unsigned)> progressind,
|
||||
std::function<bool(void)> stopcond):
|
||||
_ArrBase<Box>(Box(0, 0), dist, progressind, stopcond)
|
||||
{
|
||||
this->m_pconf.object_function = [this] (const Item &item) {
|
||||
|
||||
auto result = objfunc({0, 0},
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
0,
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
return std::get<0>(result);
|
||||
};
|
||||
|
||||
this->m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
// A container which stores a pointer to the 3D object and its projected
|
||||
// 2D shape from top view.
|
||||
using ShapeData2D =
|
||||
std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
|
||||
|
||||
ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
|
||||
ShapeData2D ret;
|
||||
|
||||
auto s = std::accumulate(model.objects.begin(), model.objects.end(), size_t(0),
|
||||
[](size_t s, ModelObject* o){
|
||||
return s + o->instances.size();
|
||||
});
|
||||
|
||||
ret.reserve(s);
|
||||
|
||||
for(auto objptr : model.objects) {
|
||||
if(objptr) {
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(objinst) {
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
ClipperLib::PolygonImpl pn;
|
||||
|
||||
// CHECK_ME -> is the following correct ?
|
||||
tmpmesh.scale(objinst->get_scaling_factor());
|
||||
|
||||
// TODO export the exact 2D projection
|
||||
auto p = tmpmesh.convex_hull();
|
||||
|
||||
p.make_clockwise();
|
||||
p.append(p.first_point());
|
||||
pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
|
||||
|
||||
// Efficient conversion to item.
|
||||
Item item(std::move(pn));
|
||||
|
||||
// Invalid geometries would throw exceptions when arranging
|
||||
if(item.vertexCount() > 3) {
|
||||
// CHECK_ME -> is the following correct or it should take in account all three rotations ?
|
||||
item.rotation(objinst->get_rotation(Z));
|
||||
item.translation({
|
||||
ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR),
|
||||
ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR)
|
||||
});
|
||||
ret.emplace_back(objinst, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void applyResult(
|
||||
IndexedPackGroup::value_type& group,
|
||||
Coord batch_offset,
|
||||
ShapeData2D& shapemap)
|
||||
{
|
||||
for(auto& r : group) {
|
||||
auto idx = r.first; // get the original item index
|
||||
Item& item = r.second; // get the item itself
|
||||
|
||||
// Get the model instance from the shapemap using the index
|
||||
ModelInstance *inst_ptr = shapemap[idx].first;
|
||||
|
||||
// Get the transformation data from the item object and scale it
|
||||
// appropriately
|
||||
auto off = item.translation();
|
||||
Radians rot = item.rotation();
|
||||
Vec3d foff(off.X*SCALING_FACTOR + batch_offset,
|
||||
off.Y*SCALING_FACTOR,
|
||||
inst_ptr->get_offset()(2));
|
||||
|
||||
// write the transformation data into the model instance
|
||||
inst_ptr->set_rotation(Z, rot);
|
||||
inst_ptr->set_offset(foff);
|
||||
}
|
||||
}
|
||||
|
||||
BedShapeHint bedShape(const Polyline &bed) {
|
||||
BedShapeHint ret;
|
||||
|
||||
auto x = [](const Point& p) { return p(0); };
|
||||
auto y = [](const Point& p) { return p(1); };
|
||||
|
||||
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();
|
||||
|
||||
Circle ret(center, avg_dist);
|
||||
for (auto el: vertex_distances)
|
||||
{
|
||||
if (abs(el - avg_dist) > 10 * SCALED_EPSILON)
|
||||
ret = Circle();
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto parea = poly_area(bed);
|
||||
|
||||
if( (1.0 - parea/area(bb)) < 1e-3 ) {
|
||||
ret.type = BedShapeType::BOX;
|
||||
ret.shape.box = bb;
|
||||
}
|
||||
else if(auto c = isCircle(bed)) {
|
||||
ret.type = BedShapeType::CIRCLE;
|
||||
ret.shape.circ = c;
|
||||
} else {
|
||||
ret.type = BedShapeType::IRREGULAR;
|
||||
ret.shape.polygon = bed;
|
||||
}
|
||||
|
||||
// Determine the bed shape by hand
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool arrange(Model &model,
|
||||
coord_t min_obj_distance,
|
||||
const Polyline &bed,
|
||||
BedShapeHint bedhint,
|
||||
bool first_bin_only,
|
||||
std::function<void (unsigned)> progressind,
|
||||
std::function<bool ()> stopcondition)
|
||||
{
|
||||
using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
|
||||
|
||||
bool ret = true;
|
||||
|
||||
// Get the 2D projected shapes with their 3D model instance pointers
|
||||
auto shapemap = arr::projectModelFromTop(model);
|
||||
|
||||
// Copy the references for the shapes only as the arranger expects a
|
||||
// sequence of objects convertible to Item or ClipperPolygon
|
||||
std::vector<std::reference_wrapper<Item>> shapes;
|
||||
shapes.reserve(shapemap.size());
|
||||
std::for_each(shapemap.begin(), shapemap.end(),
|
||||
[&shapes] (ShapeData2D::value_type& it)
|
||||
{
|
||||
shapes.push_back(std::ref(it.second));
|
||||
});
|
||||
|
||||
IndexedPackGroup result;
|
||||
|
||||
// If there is no hint about the shape, we will try to guess
|
||||
if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed);
|
||||
|
||||
BoundingBox bbb(bed);
|
||||
|
||||
auto& cfn = stopcondition;
|
||||
|
||||
auto binbb = Box({
|
||||
static_cast<libnest2d::Coord>(bbb.min(0)),
|
||||
static_cast<libnest2d::Coord>(bbb.min(1))
|
||||
},
|
||||
{
|
||||
static_cast<libnest2d::Coord>(bbb.max(0)),
|
||||
static_cast<libnest2d::Coord>(bbb.max(1))
|
||||
});
|
||||
|
||||
switch(bedhint.type) {
|
||||
case BedShapeType::BOX: {
|
||||
|
||||
// Create the arranger for the box shaped bed
|
||||
AutoArranger<Box> arrange(binbb, min_obj_distance, progressind, cfn);
|
||||
|
||||
// Arrange and return the items with their respective indices within the
|
||||
// input sequence.
|
||||
result = arrange(shapes.begin(), shapes.end());
|
||||
break;
|
||||
}
|
||||
case BedShapeType::CIRCLE: {
|
||||
|
||||
auto c = bedhint.shape.circ;
|
||||
auto cc = to_lnCircle(c);
|
||||
|
||||
AutoArranger<lnCircle> arrange(cc, min_obj_distance, progressind, cfn);
|
||||
result = arrange(shapes.begin(), shapes.end());
|
||||
break;
|
||||
}
|
||||
case BedShapeType::IRREGULAR:
|
||||
case BedShapeType::WHO_KNOWS: {
|
||||
|
||||
using P = libnest2d::PolygonImpl;
|
||||
|
||||
auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
|
||||
P irrbed = sl::create<PolygonImpl>(std::move(ctour));
|
||||
|
||||
AutoArranger<P> arrange(irrbed, min_obj_distance, progressind, cfn);
|
||||
|
||||
// Arrange and return the items with their respective indices within the
|
||||
// input sequence.
|
||||
result = arrange(shapes.begin(), shapes.end());
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if(result.empty() || stopcondition()) return false;
|
||||
|
||||
if(first_bin_only) {
|
||||
applyResult(result.front(), 0, shapemap);
|
||||
} else {
|
||||
|
||||
const auto STRIDE_PADDING = 1.2;
|
||||
|
||||
Coord stride = static_cast<Coord>(STRIDE_PADDING*
|
||||
binbb.width()*SCALING_FACTOR);
|
||||
Coord batch_offset = 0;
|
||||
|
||||
for(auto& group : result) {
|
||||
applyResult(group, batch_offset, shapemap);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto objptr : model.objects) objptr->invalidate_bounding_box();
|
||||
|
||||
return ret && result.size() == 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -2,549 +2,13 @@
|
||||
#define MODELARRANGE_HPP
|
||||
|
||||
#include "Model.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include <libnest2d.h>
|
||||
|
||||
#include <numeric>
|
||||
#include <ClipperUtils.hpp>
|
||||
|
||||
#include <boost/geometry/index/rtree.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
|
||||
namespace arr {
|
||||
|
||||
using namespace libnest2d;
|
||||
|
||||
std::string toString(const Model& model, bool holes = true) {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "{\n";
|
||||
|
||||
for(auto objptr : model.objects) {
|
||||
if(!objptr) continue;
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(!objinst) continue;
|
||||
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
// CHECK_ME -> Is the following correct ?
|
||||
tmpmesh.scale(objinst->get_scaling_factor());
|
||||
objinst->transform_mesh(&tmpmesh);
|
||||
ExPolygons expolys = tmpmesh.horizontal_projection();
|
||||
for(auto& expoly_complex : expolys) {
|
||||
|
||||
auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR);
|
||||
if(tmp.empty()) continue;
|
||||
auto expoly = tmp.front();
|
||||
expoly.contour.make_clockwise();
|
||||
for(auto& h : expoly.holes) h.make_counter_clockwise();
|
||||
|
||||
ss << "\t{\n";
|
||||
ss << "\t\t{\n";
|
||||
|
||||
for(auto v : expoly.contour.points) ss << "\t\t\t{"
|
||||
<< v(0) << ", "
|
||||
<< v(1) << "},\n";
|
||||
{
|
||||
auto v = expoly.contour.points.front();
|
||||
ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n";
|
||||
}
|
||||
ss << "\t\t},\n";
|
||||
|
||||
// Holes:
|
||||
ss << "\t\t{\n";
|
||||
if(holes) for(auto h : expoly.holes) {
|
||||
ss << "\t\t\t{\n";
|
||||
for(auto v : h.points) ss << "\t\t\t\t{"
|
||||
<< v(0) << ", "
|
||||
<< v(1) << "},\n";
|
||||
{
|
||||
auto v = h.points.front();
|
||||
ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n";
|
||||
}
|
||||
ss << "\t\t\t},\n";
|
||||
}
|
||||
ss << "\t\t},\n";
|
||||
|
||||
ss << "\t},\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ss << "}\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void toSVG(SVG& svg, const Model& model) {
|
||||
for(auto objptr : model.objects) {
|
||||
if(!objptr) continue;
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(!objinst) continue;
|
||||
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
tmpmesh.scale(objinst->get_scaling_factor());
|
||||
objinst->transform_mesh(&tmpmesh);
|
||||
ExPolygons expolys = tmpmesh.horizontal_projection();
|
||||
svg.draw(expolys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>>;
|
||||
template<class TBin>
|
||||
using TPacker = typename placers::_NofitPolyPlacer<PolygonImpl, TBin>;
|
||||
|
||||
const double BIG_ITEM_TRESHOLD = 0.02;
|
||||
|
||||
Box boundingBox(const Box& pilebb, const Box& ibb ) {
|
||||
auto& pminc = pilebb.minCorner();
|
||||
auto& pmaxc = pilebb.maxCorner();
|
||||
auto& iminc = ibb.minCorner();
|
||||
auto& imaxc = ibb.maxCorner();
|
||||
PointImpl 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);
|
||||
}
|
||||
|
||||
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
|
||||
objfunc(const PointImpl& bincenter,
|
||||
const shapelike::Shapes<PolygonImpl>& merged_pile,
|
||||
const Box& pilebb,
|
||||
const ItemGroup& items,
|
||||
const Item &item,
|
||||
double bin_area,
|
||||
double norm, // A norming factor for physical dimensions
|
||||
// a spatial index to quickly get neighbors of the candidate item
|
||||
const SpatIndex& spatindex,
|
||||
const SpatIndex& smalls_spatindex,
|
||||
const ItemGroup& remaining
|
||||
)
|
||||
{
|
||||
using Coord = TCoord<PointImpl>;
|
||||
|
||||
static const double ROUNDNESS_RATIO = 0.5;
|
||||
static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO;
|
||||
|
||||
// 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 = sl::boundingBox(item.transformedShape());
|
||||
|
||||
// Calculate the full bounding box of the pile with the candidate item
|
||||
auto fullbb = boundingBox(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;
|
||||
|
||||
if(isBig(item.area()) || spatindex.empty()) {
|
||||
// This branch is for the bigger items..
|
||||
|
||||
auto minc = ibb.minCorner(); // bottom left corner
|
||||
auto maxc = ibb.maxCorner(); // top right corner
|
||||
|
||||
// top left and bottom right corners
|
||||
auto top_left = PointImpl{getX(minc), getY(maxc)};
|
||||
auto bottom_right = PointImpl{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:
|
||||
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
|
||||
auto bindist = pl::distance(ibb.center(), bincenter) / norm;
|
||||
dist = 0.8*dist + 0.2*bindist;
|
||||
|
||||
// Density is the pack density: how big is the arranged pile
|
||||
double density = 0;
|
||||
|
||||
if(remaining.empty()) {
|
||||
|
||||
auto mp = merged_pile;
|
||||
mp.emplace_back(item.transformedShape());
|
||||
auto chull = sl::convexHull(mp);
|
||||
|
||||
placers::EdgeCache<PolygonImpl> ec(chull);
|
||||
|
||||
double circ = ec.circumference() / norm;
|
||||
double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm;
|
||||
score = 0.5*circ + 0.5*bcirc;
|
||||
|
||||
} else {
|
||||
// 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;
|
||||
|
||||
density = std::sqrt((fullbb.width() / norm )*
|
||||
(fullbb.height() / norm));
|
||||
auto querybb = item.boundingBox();
|
||||
|
||||
// Query the spatial index for the neighbors
|
||||
std::vector<SpatElement> result;
|
||||
result.reserve(spatindex.size());
|
||||
if(isBig(item.area())) {
|
||||
spatindex.query(bgi::intersects(querybb),
|
||||
std::back_inserter(result));
|
||||
} else {
|
||||
smalls_spatindex.query(bgi::intersects(querybb),
|
||||
std::back_inserter(result));
|
||||
}
|
||||
|
||||
for(auto& e : result) { // now get the score for the best alignment
|
||||
auto idx = e.second;
|
||||
Item& p = items[idx];
|
||||
auto parea = p.area();
|
||||
if(std::abs(1.0 - parea/item.area()) < 1e-6) {
|
||||
auto bb = boundingBox(p.boundingBox(), ibb);
|
||||
auto bbarea = bb.area();
|
||||
auto ascore = 1.0 - (item.area() + parea)/bbarea;
|
||||
|
||||
if(ascore < alignment_score) alignment_score = ascore;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
} else {
|
||||
// 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 = pl::distance(ibb.center(), bigbb.center()) / norm;
|
||||
}
|
||||
|
||||
return std::make_tuple(score, fullbb);
|
||||
}
|
||||
|
||||
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
|
||||
// arranger.useMinimumBoundigBoxRotation();
|
||||
pcfg.rotations = { 0.0 };
|
||||
|
||||
// The accuracy of optimization.
|
||||
// Goes from 0.0 to 1.0 and scales performance as well
|
||||
pcfg.accuracy = 0.65f;
|
||||
|
||||
pcfg.parallel = true;
|
||||
}
|
||||
|
||||
template<class TBin>
|
||||
class AutoArranger {};
|
||||
|
||||
template<class TBin>
|
||||
class _ArrBase {
|
||||
protected:
|
||||
|
||||
using Placer = TPacker<TBin>;
|
||||
using Selector = FirstFitSelection;
|
||||
using Packer = Nester<Placer, Selector>;
|
||||
using PConfig = typename Packer::PlacementConfig;
|
||||
using Distance = TCoord<PointImpl>;
|
||||
using Pile = sl::Shapes<PolygonImpl>;
|
||||
|
||||
Packer m_pck;
|
||||
PConfig m_pconf; // Placement configuration
|
||||
double m_bin_area;
|
||||
SpatIndex m_rtree;
|
||||
SpatIndex m_smallsrtree;
|
||||
double m_norm;
|
||||
Pile m_merged_pile;
|
||||
Box m_pilebb;
|
||||
ItemGroup m_remaining;
|
||||
ItemGroup m_items;
|
||||
public:
|
||||
|
||||
_ArrBase(const TBin& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind,
|
||||
std::function<bool(void)> stopcond):
|
||||
m_pck(bin, dist), m_bin_area(sl::area(bin)),
|
||||
m_norm(std::sqrt(sl::area(bin)))
|
||||
{
|
||||
fillConfig(m_pconf);
|
||||
|
||||
m_pconf.before_packing =
|
||||
[this](const Pile& 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_pck.progressIndicator(progressind);
|
||||
m_pck.stopCondition(stopcond);
|
||||
}
|
||||
|
||||
template<class...Args> inline IndexedPackGroup operator()(Args&&...args) {
|
||||
m_rtree.clear();
|
||||
return m_pck.executeIndexed(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class AutoArranger<Box>: public _ArrBase<Box> {
|
||||
public:
|
||||
|
||||
AutoArranger(const Box& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind,
|
||||
std::function<bool(void)> stopcond):
|
||||
_ArrBase<Box>(bin, dist, progressind, stopcond)
|
||||
{
|
||||
|
||||
m_pconf.object_function = [this, bin] (const Item &item) {
|
||||
|
||||
auto result = objfunc(bin.center(),
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
m_bin_area,
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
|
||||
double score = std::get<0>(result);
|
||||
auto& fullbb = std::get<1>(result);
|
||||
|
||||
double miss = Placer::overfit(fullbb, bin);
|
||||
miss = miss > 0? miss : 0;
|
||||
score += miss*miss;
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
using lnCircle = libnest2d::_Circle<libnest2d::PointImpl>;
|
||||
|
||||
template<>
|
||||
class AutoArranger<lnCircle>: public _ArrBase<lnCircle> {
|
||||
public:
|
||||
|
||||
AutoArranger(const lnCircle& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind,
|
||||
std::function<bool(void)> stopcond):
|
||||
_ArrBase<lnCircle>(bin, dist, progressind, stopcond) {
|
||||
|
||||
m_pconf.object_function = [this, &bin] (const Item &item) {
|
||||
|
||||
auto result = objfunc(bin.center(),
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
m_bin_area,
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
|
||||
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, bin);
|
||||
if(miss < 0) miss = 0;
|
||||
score += miss*miss;
|
||||
}
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class AutoArranger<PolygonImpl>: public _ArrBase<PolygonImpl> {
|
||||
public:
|
||||
AutoArranger(const PolygonImpl& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind,
|
||||
std::function<bool(void)> stopcond):
|
||||
_ArrBase<PolygonImpl>(bin, dist, progressind, stopcond)
|
||||
{
|
||||
m_pconf.object_function = [this, &bin] (const Item &item) {
|
||||
|
||||
auto binbb = sl::boundingBox(bin);
|
||||
auto result = objfunc(binbb.center(),
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
m_bin_area,
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
double score = std::get<0>(result);
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
template<> // Specialization with no bin
|
||||
class AutoArranger<bool>: public _ArrBase<Box> {
|
||||
public:
|
||||
|
||||
AutoArranger(Distance dist, std::function<void(unsigned)> progressind,
|
||||
std::function<bool(void)> stopcond):
|
||||
_ArrBase<Box>(Box(0, 0), dist, progressind, stopcond)
|
||||
{
|
||||
this->m_pconf.object_function = [this] (const Item &item) {
|
||||
|
||||
auto result = objfunc({0, 0},
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
0,
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
return std::get<0>(result);
|
||||
};
|
||||
|
||||
this->m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
// A container which stores a pointer to the 3D object and its projected
|
||||
// 2D shape from top view.
|
||||
using ShapeData2D =
|
||||
std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
|
||||
|
||||
ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
|
||||
ShapeData2D ret;
|
||||
|
||||
auto s = std::accumulate(model.objects.begin(), model.objects.end(), size_t(0),
|
||||
[](size_t s, ModelObject* o){
|
||||
return s + o->instances.size();
|
||||
});
|
||||
|
||||
ret.reserve(s);
|
||||
|
||||
for(auto objptr : model.objects) {
|
||||
if(objptr) {
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(objinst) {
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
ClipperLib::PolygonImpl pn;
|
||||
|
||||
// CHECK_ME -> is the following correct ?
|
||||
tmpmesh.scale(objinst->get_scaling_factor());
|
||||
|
||||
// TODO export the exact 2D projection
|
||||
auto p = tmpmesh.convex_hull();
|
||||
|
||||
p.make_clockwise();
|
||||
p.append(p.first_point());
|
||||
pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
|
||||
|
||||
// Efficient conversion to item.
|
||||
Item item(std::move(pn));
|
||||
|
||||
// Invalid geometries would throw exceptions when arranging
|
||||
if(item.vertexCount() > 3) {
|
||||
// CHECK_ME -> is the following correct or it should take in account all three rotations ?
|
||||
item.rotation(objinst->get_rotation(Z));
|
||||
item.translation({
|
||||
ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR),
|
||||
ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR)
|
||||
});
|
||||
ret.emplace_back(objinst, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
class Circle {
|
||||
Point center_;
|
||||
double radius_;
|
||||
@ -556,9 +20,9 @@ public:
|
||||
inline double radius() const { return radius_; }
|
||||
inline const Point& center() const { return center_; }
|
||||
inline operator bool() { return !std::isnan(radius_); }
|
||||
inline operator lnCircle() {
|
||||
return lnCircle({center_(0), center_(1)}, radius_);
|
||||
}
|
||||
// inline operator lnCircle() {
|
||||
// return lnCircle({center_(0), center_(1)}, radius_);
|
||||
// }
|
||||
};
|
||||
|
||||
enum class BedShapeType {
|
||||
@ -577,109 +41,7 @@ struct BedShapeHint {
|
||||
} shape;
|
||||
};
|
||||
|
||||
BedShapeHint bedShape(const Polyline& bed) {
|
||||
BedShapeHint ret;
|
||||
|
||||
auto x = [](const Point& p) { return p(0); };
|
||||
auto y = [](const Point& p) { return p(1); };
|
||||
|
||||
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();
|
||||
|
||||
Circle ret(center, avg_dist);
|
||||
for (auto el: vertex_distances)
|
||||
{
|
||||
if (abs(el - avg_dist) > 10 * SCALED_EPSILON)
|
||||
ret = Circle();
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto parea = poly_area(bed);
|
||||
|
||||
if( (1.0 - parea/area(bb)) < 1e-3 ) {
|
||||
ret.type = BedShapeType::BOX;
|
||||
ret.shape.box = bb;
|
||||
}
|
||||
else if(auto c = isCircle(bed)) {
|
||||
ret.type = BedShapeType::CIRCLE;
|
||||
ret.shape.circ = c;
|
||||
} else {
|
||||
ret.type = BedShapeType::IRREGULAR;
|
||||
ret.shape.polygon = bed;
|
||||
}
|
||||
|
||||
// Determine the bed shape by hand
|
||||
return ret;
|
||||
}
|
||||
|
||||
void applyResult(
|
||||
IndexedPackGroup::value_type& group,
|
||||
Coord batch_offset,
|
||||
ShapeData2D& shapemap)
|
||||
{
|
||||
for(auto& r : group) {
|
||||
auto idx = r.first; // get the original item index
|
||||
Item& item = r.second; // get the item itself
|
||||
|
||||
// Get the model instance from the shapemap using the index
|
||||
ModelInstance *inst_ptr = shapemap[idx].first;
|
||||
|
||||
// Get the transformation data from the item object and scale it
|
||||
// appropriately
|
||||
auto off = item.translation();
|
||||
Radians rot = item.rotation();
|
||||
Vec3d foff(off.X*SCALING_FACTOR + batch_offset,
|
||||
off.Y*SCALING_FACTOR,
|
||||
inst_ptr->get_offset()(2));
|
||||
|
||||
// write the transformation data into the model instance
|
||||
inst_ptr->set_rotation(Z, rot);
|
||||
inst_ptr->set_offset(foff);
|
||||
}
|
||||
}
|
||||
|
||||
BedShapeHint bedShape(const Polyline& bed);
|
||||
|
||||
/**
|
||||
* \brief Arranges the model objects on the screen.
|
||||
@ -707,112 +69,14 @@ void applyResult(
|
||||
* 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, coordf_t min_obj_distance,
|
||||
bool arrange(Model &model, 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)
|
||||
{
|
||||
using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
|
||||
|
||||
bool ret = true;
|
||||
|
||||
// Get the 2D projected shapes with their 3D model instance pointers
|
||||
auto shapemap = arr::projectModelFromTop(model);
|
||||
|
||||
// Copy the references for the shapes only as the arranger expects a
|
||||
// sequence of objects convertible to Item or ClipperPolygon
|
||||
std::vector<std::reference_wrapper<Item>> shapes;
|
||||
shapes.reserve(shapemap.size());
|
||||
std::for_each(shapemap.begin(), shapemap.end(),
|
||||
[&shapes] (ShapeData2D::value_type& it)
|
||||
{
|
||||
shapes.push_back(std::ref(it.second));
|
||||
});
|
||||
|
||||
IndexedPackGroup result;
|
||||
|
||||
// If there is no hint about the shape, we will try to guess
|
||||
if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed);
|
||||
|
||||
BoundingBox bbb(bed);
|
||||
|
||||
auto& cfn = stopcondition;
|
||||
|
||||
auto binbb = Box({
|
||||
static_cast<libnest2d::Coord>(bbb.min(0)),
|
||||
static_cast<libnest2d::Coord>(bbb.min(1))
|
||||
},
|
||||
{
|
||||
static_cast<libnest2d::Coord>(bbb.max(0)),
|
||||
static_cast<libnest2d::Coord>(bbb.max(1))
|
||||
});
|
||||
|
||||
switch(bedhint.type) {
|
||||
case BedShapeType::BOX: {
|
||||
|
||||
// Create the arranger for the box shaped bed
|
||||
AutoArranger<Box> arrange(binbb, min_obj_distance, progressind, cfn);
|
||||
|
||||
// Arrange and return the items with their respective indices within the
|
||||
// input sequence.
|
||||
result = arrange(shapes.begin(), shapes.end());
|
||||
break;
|
||||
}
|
||||
case BedShapeType::CIRCLE: {
|
||||
|
||||
auto c = bedhint.shape.circ;
|
||||
auto cc = lnCircle(c);
|
||||
|
||||
AutoArranger<lnCircle> arrange(cc, min_obj_distance, progressind, cfn);
|
||||
result = arrange(shapes.begin(), shapes.end());
|
||||
break;
|
||||
}
|
||||
case BedShapeType::IRREGULAR:
|
||||
case BedShapeType::WHO_KNOWS: {
|
||||
|
||||
using P = libnest2d::PolygonImpl;
|
||||
|
||||
auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
|
||||
P irrbed = sl::create<PolygonImpl>(std::move(ctour));
|
||||
|
||||
AutoArranger<P> arrange(irrbed, min_obj_distance, progressind, cfn);
|
||||
|
||||
// Arrange and return the items with their respective indices within the
|
||||
// input sequence.
|
||||
result = arrange(shapes.begin(), shapes.end());
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if(result.empty() || stopcondition()) return false;
|
||||
|
||||
if(first_bin_only) {
|
||||
applyResult(result.front(), 0, shapemap);
|
||||
} else {
|
||||
|
||||
const auto STRIDE_PADDING = 1.2;
|
||||
|
||||
Coord stride = static_cast<Coord>(STRIDE_PADDING*
|
||||
binbb.width()*SCALING_FACTOR);
|
||||
Coord batch_offset = 0;
|
||||
|
||||
for(auto& group : result) {
|
||||
applyResult(group, batch_offset, shapemap);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto objptr : model.objects) objptr->invalidate_bounding_box();
|
||||
|
||||
return ret && result.size() == 1;
|
||||
}
|
||||
std::function<bool(void)> stopcondition);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
#endif // MODELARRANGE_HPP
|
||||
|
@ -1,364 +0,0 @@
|
||||
#include "AppController.hpp"
|
||||
|
||||
#include <slic3r/GUI/GUI.hpp>
|
||||
|
||||
#include <future>
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
#include <cstdarg>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <PrintConfig.hpp>
|
||||
#include <Print.hpp>
|
||||
#include <PrintExport.hpp>
|
||||
#include <Geometry.hpp>
|
||||
#include <Model.hpp>
|
||||
#include <ModelArrange.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppControllerGui::PriData {
|
||||
public:
|
||||
std::mutex m;
|
||||
std::thread::id ui_thread;
|
||||
|
||||
inline explicit PriData(std::thread::id uit): ui_thread(uit) {}
|
||||
};
|
||||
|
||||
AppControllerGui::AppControllerGui()
|
||||
:m_pri_data(new PriData(std::this_thread::get_id())) {}
|
||||
|
||||
AppControllerGui::~AppControllerGui() {
|
||||
m_pri_data.reset();
|
||||
}
|
||||
|
||||
bool AppControllerGui::is_main_thread() const
|
||||
{
|
||||
return m_pri_data->ui_thread == std::this_thread::get_id();
|
||||
}
|
||||
|
||||
// namespace GUI {
|
||||
// PresetBundle* get_preset_bundle();
|
||||
// }
|
||||
|
||||
static const PrintObjectStep STEP_SLICE = posSlice;
|
||||
static const PrintObjectStep STEP_PERIMETERS = posPerimeters;
|
||||
static const PrintObjectStep STEP_PREPARE_INFILL = posPrepareInfill;
|
||||
static const PrintObjectStep STEP_INFILL = posInfill;
|
||||
static const PrintObjectStep STEP_SUPPORTMATERIAL = posSupportMaterial;
|
||||
static const PrintStep STEP_SKIRT = psSkirt;
|
||||
static const PrintStep STEP_BRIM = psBrim;
|
||||
static const PrintStep STEP_WIPE_TOWER = psWipeTower;
|
||||
|
||||
ProgresIndicatorPtr AppControllerGui::global_progress_indicator() {
|
||||
ProgresIndicatorPtr ret;
|
||||
|
||||
m_pri_data->m.lock();
|
||||
ret = m_global_progressind;
|
||||
m_pri_data->m.unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AppControllerGui::global_progress_indicator(ProgresIndicatorPtr gpri)
|
||||
{
|
||||
m_pri_data->m.lock();
|
||||
m_global_progressind = gpri;
|
||||
m_pri_data->m.unlock();
|
||||
}
|
||||
|
||||
PrintController::PngExportData
|
||||
PrintController::query_png_export_data(const DynamicPrintConfig& conf)
|
||||
{
|
||||
PngExportData ret;
|
||||
|
||||
auto c = GUI::get_appctl();
|
||||
auto zippath = c->query_destination_path("Output zip file", "*.zip",
|
||||
"export-png",
|
||||
"out");
|
||||
|
||||
ret.zippath = zippath;
|
||||
|
||||
ret.width_mm = conf.opt_float("display_width");
|
||||
ret.height_mm = conf.opt_float("display_height");
|
||||
|
||||
ret.width_px = conf.opt_int("display_pixels_x");
|
||||
ret.height_px = conf.opt_int("display_pixels_y");
|
||||
|
||||
auto opt_corr = conf.opt<ConfigOptionFloats>("printer_correction");
|
||||
|
||||
if(opt_corr) {
|
||||
ret.corr_x = opt_corr->values[0];
|
||||
ret.corr_y = opt_corr->values[1];
|
||||
ret.corr_z = opt_corr->values[2];
|
||||
}
|
||||
|
||||
ret.exp_time_first_s = conf.opt_float("initial_exposure_time");
|
||||
ret.exp_time_s = conf.opt_float("exposure_time");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PrintController::slice(ProgresIndicatorPtr pri)
|
||||
{
|
||||
m_print->set_status_callback([pri](int st, const std::string& msg){
|
||||
pri->update(unsigned(st), msg);
|
||||
});
|
||||
|
||||
m_print->process();
|
||||
}
|
||||
|
||||
void PrintController::slice()
|
||||
{
|
||||
auto ctl = GUI::get_appctl();
|
||||
auto pri = ctl->global_progress_indicator();
|
||||
if(!pri) pri = ctl->create_progress_indicator(100, L("Slicing"));
|
||||
slice(pri);
|
||||
}
|
||||
|
||||
template<> class LayerWriter<Zipper> {
|
||||
Zipper m_zip;
|
||||
public:
|
||||
|
||||
inline LayerWriter(const std::string& zipfile_path): m_zip(zipfile_path) {}
|
||||
|
||||
inline void next_entry(const std::string& fname) { m_zip.next_entry(fname); }
|
||||
|
||||
inline std::string get_name() const { return m_zip.get_name(); }
|
||||
|
||||
template<class T> inline LayerWriter& operator<<(const T& arg) {
|
||||
m_zip.stream() << arg; return *this;
|
||||
}
|
||||
|
||||
inline void close() { m_zip.close(); }
|
||||
};
|
||||
|
||||
void PrintController::slice_to_png()
|
||||
{
|
||||
// using Pointf3 = Vec3d;
|
||||
|
||||
// auto ctl = GUI::get_appctl();
|
||||
// auto presetbundle = GUI::wxGetApp().preset_bundle;
|
||||
|
||||
// assert(presetbundle);
|
||||
|
||||
// // FIXME: this crashes in command line mode
|
||||
// auto pt = presetbundle->printers.get_selected_preset().printer_technology();
|
||||
// if(pt != ptSLA) {
|
||||
// ctl->report_issue(IssueType::ERR, L("Printer technology is not SLA!"),
|
||||
// L("Error"));
|
||||
// return;
|
||||
// }
|
||||
|
||||
// auto conf = presetbundle->full_config();
|
||||
// conf.validate();
|
||||
|
||||
// auto exd = query_png_export_data(conf);
|
||||
// if(exd.zippath.empty()) return;
|
||||
|
||||
// Print *print = m_print;
|
||||
|
||||
// try {
|
||||
// print->apply_config(conf);
|
||||
// print->validate();
|
||||
// } catch(std::exception& e) {
|
||||
// ctl->report_issue(IssueType::ERR, e.what(), "Error");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // TODO: copy the model and work with the copy only
|
||||
// bool correction = false;
|
||||
// if(exd.corr_x != 1.0 || exd.corr_y != 1.0 || exd.corr_z != 1.0) {
|
||||
// correction = true;
|
||||
//// print->invalidate_all_steps();
|
||||
|
||||
//// for(auto po : print->objects) {
|
||||
//// po->model_object()->scale(
|
||||
//// Pointf3(exd.corr_x, exd.corr_y, exd.corr_z)
|
||||
//// );
|
||||
//// po->model_object()->invalidate_bounding_box();
|
||||
//// po->reload_model_instances();
|
||||
//// po->invalidate_all_steps();
|
||||
//// }
|
||||
// }
|
||||
|
||||
// // Turn back the correction scaling on the model.
|
||||
// auto scale_back = [this, print, correction, exd]() {
|
||||
// if(correction) { // scale the model back
|
||||
//// print->invalidate_all_steps();
|
||||
//// for(auto po : print->objects) {
|
||||
//// po->model_object()->scale(
|
||||
//// Pointf3(1.0/exd.corr_x, 1.0/exd.corr_y, 1.0/exd.corr_z)
|
||||
//// );
|
||||
//// po->model_object()->invalidate_bounding_box();
|
||||
//// po->reload_model_instances();
|
||||
//// po->invalidate_all_steps();
|
||||
//// }
|
||||
// }
|
||||
// };
|
||||
|
||||
// auto print_bb = print->bounding_box();
|
||||
// Vec2d punsc = unscale(print_bb.size());
|
||||
|
||||
// // If the print does not fit into the print area we should cry about it.
|
||||
// if(px(punsc) > exd.width_mm || py(punsc) > exd.height_mm) {
|
||||
// std::stringstream ss;
|
||||
|
||||
// ss << L("Print will not fit and will be truncated!") << "\n"
|
||||
// << L("Width needed: ") << px(punsc) << " mm\n"
|
||||
// << L("Height needed: ") << py(punsc) << " mm\n";
|
||||
|
||||
// if(!ctl->report_issue(IssueType::WARN_Q, ss.str(), L("Warning"))) {
|
||||
// scale_back();
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// auto pri = ctl->create_progress_indicator(
|
||||
// 200, L("Slicing to zipped png files..."));
|
||||
|
||||
// pri->on_cancel([&print](){ print->cancel(); });
|
||||
|
||||
// try {
|
||||
// pri->update(0, L("Slicing..."));
|
||||
// slice(pri);
|
||||
// } catch (std::exception& e) {
|
||||
// ctl->report_issue(IssueType::ERR, e.what(), L("Exception occurred"));
|
||||
// scale_back();
|
||||
// if(print->canceled()) print->restart();
|
||||
// return;
|
||||
// }
|
||||
|
||||
// auto initstate = unsigned(pri->state());
|
||||
// print->set_status_callback([pri, initstate](int st, const std::string& msg)
|
||||
// {
|
||||
// pri->update(initstate + unsigned(st), msg);
|
||||
// });
|
||||
|
||||
// try {
|
||||
// print_to<FilePrinterFormat::PNG, Zipper>( *print, exd.zippath,
|
||||
// exd.width_mm, exd.height_mm,
|
||||
// exd.width_px, exd.height_px,
|
||||
// exd.exp_time_s, exd.exp_time_first_s);
|
||||
|
||||
// } catch (std::exception& e) {
|
||||
// ctl->report_issue(IssueType::ERR, e.what(), L("Exception occurred"));
|
||||
// }
|
||||
|
||||
// scale_back();
|
||||
// if(print->canceled()) print->restart();
|
||||
// print->set_status_default();
|
||||
}
|
||||
|
||||
const PrintConfig &PrintController::config() const
|
||||
{
|
||||
return m_print->config();
|
||||
}
|
||||
|
||||
void ProgressIndicator::message_fmt(
|
||||
const std::string &fmtstr, ...) {
|
||||
std::stringstream ss;
|
||||
va_list args;
|
||||
va_start(args, fmtstr);
|
||||
|
||||
auto fmt = fmtstr.begin();
|
||||
|
||||
while (*fmt != '\0') {
|
||||
if (*fmt == 'd') {
|
||||
int i = va_arg(args, int);
|
||||
ss << i << '\n';
|
||||
} else if (*fmt == 'c') {
|
||||
// note automatic conversion to integral type
|
||||
int c = va_arg(args, int);
|
||||
ss << static_cast<char>(c) << '\n';
|
||||
} else if (*fmt == 'f') {
|
||||
double d = va_arg(args, double);
|
||||
ss << d << '\n';
|
||||
}
|
||||
++fmt;
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
message(ss.str());
|
||||
}
|
||||
|
||||
void AppController::arrange_model()
|
||||
{
|
||||
using Coord = libnest2d::TCoord<libnest2d::PointImpl>;
|
||||
|
||||
auto ctl = GUI::get_appctl();
|
||||
|
||||
if(m_arranging.load()) return;
|
||||
|
||||
// to prevent UI reentrancies
|
||||
m_arranging.store(true);
|
||||
|
||||
unsigned count = 0;
|
||||
for(auto obj : m_model->objects) count += obj->instances.size();
|
||||
|
||||
auto pind = ctl->global_progress_indicator();
|
||||
|
||||
float pmax = 1.0;
|
||||
|
||||
if(pind) {
|
||||
pmax = pind->max();
|
||||
|
||||
// Set the range of the progress to the object count
|
||||
pind->max(count);
|
||||
|
||||
pind->on_cancel([this](){
|
||||
m_arranging.store(false);
|
||||
});
|
||||
}
|
||||
|
||||
auto dist = print_ctl()->config().min_object_distance();
|
||||
|
||||
// Create the arranger config
|
||||
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
|
||||
|
||||
auto& bedpoints = print_ctl()->config().bed_shape.values;
|
||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
||||
for(auto& v : bedpoints)
|
||||
bed.append(Point::new_scale(v(0), v(1)));
|
||||
|
||||
if(pind) pind->update(0, L("Arranging objects..."));
|
||||
|
||||
try {
|
||||
arr::BedShapeHint hint;
|
||||
// TODO: from Sasha from GUI
|
||||
hint.type = arr::BedShapeType::WHO_KNOWS;
|
||||
|
||||
arr::arrange(*m_model,
|
||||
min_obj_distance,
|
||||
bed,
|
||||
hint,
|
||||
false, // create many piles not just one pile
|
||||
[this, pind, &ctl, count](unsigned rem) {
|
||||
if(pind)
|
||||
pind->update(count - rem, L("Arranging objects..."));
|
||||
|
||||
ctl->process_events();
|
||||
}, [this] () { return !m_arranging.load(); });
|
||||
} catch(std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
ctl->report_issue(IssueType::ERR,
|
||||
L("Could not arrange model objects! "
|
||||
"Some geometries may be invalid."),
|
||||
L("Exception occurred"));
|
||||
}
|
||||
|
||||
// Restore previous max value
|
||||
if(pind) {
|
||||
pind->max(pmax);
|
||||
pind->update(0, m_arranging.load() ? L("Arranging done.") :
|
||||
L("Arranging canceled."));
|
||||
|
||||
pind->on_cancel(/*remove cancel function*/);
|
||||
}
|
||||
|
||||
m_arranging.store(false);
|
||||
}
|
||||
|
||||
}
|
@ -1,414 +0,0 @@
|
||||
#ifndef APPCONTROLLER_HPP
|
||||
#define APPCONTROLLER_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
|
||||
#include "GUI/ProgressIndicator.hpp"
|
||||
|
||||
#include <PrintConfig.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
class Print;
|
||||
class PrintObject;
|
||||
class PrintConfig;
|
||||
class ProgressStatusBar;
|
||||
class DynamicPrintConfig;
|
||||
|
||||
/// A Progress indicator object smart pointer
|
||||
using ProgresIndicatorPtr = std::shared_ptr<ProgressIndicator>;
|
||||
|
||||
using FilePath = std::string;
|
||||
using FilePathList = std::vector<FilePath>;
|
||||
|
||||
/// Common runtime issue types
|
||||
enum class IssueType {
|
||||
INFO,
|
||||
WARN,
|
||||
WARN_Q, // Warning with a question to continue
|
||||
ERR,
|
||||
FATAL
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A boilerplate class for creating application logic. It should provide
|
||||
* features as issue reporting and progress indication, etc...
|
||||
*
|
||||
* The lower lever UI independent classes can be manipulated with a subclass
|
||||
* of this controller class. We can also catch any exceptions that lower level
|
||||
* methods could throw and display appropriate errors and warnings.
|
||||
*
|
||||
* Note that the outer and the inner interface of this class is free from any
|
||||
* UI toolkit dependencies. We can implement it with any UI framework or make it
|
||||
* a cli client.
|
||||
*/
|
||||
class AppControllerBase {
|
||||
public:
|
||||
|
||||
using Ptr = std::shared_ptr<AppControllerBase>;
|
||||
|
||||
inline virtual ~AppControllerBase() {}
|
||||
|
||||
/**
|
||||
* @brief Query some paths from the user.
|
||||
*
|
||||
* It should display a file chooser dialog in case of a UI application.
|
||||
* @param title Title of a possible query dialog.
|
||||
* @param extensions Recognized file extensions.
|
||||
* @return Returns a list of paths chosen by the user.
|
||||
*/
|
||||
virtual FilePathList query_destination_paths(
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& functionid = "",
|
||||
const std::string& hint = "") const = 0;
|
||||
|
||||
/**
|
||||
* @brief Same as query_destination_paths but works for directories only.
|
||||
*/
|
||||
virtual FilePathList query_destination_dirs(
|
||||
const std::string& title,
|
||||
const std::string& functionid = "",
|
||||
const std::string& hint = "") const = 0;
|
||||
|
||||
/**
|
||||
* @brief Same as query_destination_paths but returns only one path.
|
||||
*/
|
||||
virtual FilePath query_destination_path(
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& functionid = "",
|
||||
const std::string& hint = "") const = 0;
|
||||
|
||||
/**
|
||||
* @brief Report an issue to the user be it fatal or recoverable.
|
||||
*
|
||||
* In a UI app this should display some message dialog.
|
||||
*
|
||||
* @param issuetype The type of the runtime issue.
|
||||
* @param description A somewhat longer description of the issue.
|
||||
* @param brief A very brief description. Can be used for message dialog
|
||||
* title.
|
||||
*/
|
||||
virtual bool report_issue(IssueType issuetype,
|
||||
const std::string& description,
|
||||
const std::string& brief) = 0;
|
||||
|
||||
/**
|
||||
* @brief Return the global progress indicator for the current controller.
|
||||
* Can be empty as well.
|
||||
*
|
||||
* Only one thread should use the global indicator at a time.
|
||||
*/
|
||||
virtual ProgresIndicatorPtr global_progress_indicator() = 0;
|
||||
|
||||
virtual void global_progress_indicator(ProgresIndicatorPtr gpri) = 0;
|
||||
|
||||
/**
|
||||
* @brief A predicate telling the caller whether it is the thread that
|
||||
* created the AppConroller object itself. This probably means that the
|
||||
* execution is in the UI thread. Otherwise it returns false meaning that
|
||||
* some worker thread called this function.
|
||||
* @return Return true for the same caller thread that created this
|
||||
* object and false for every other.
|
||||
*/
|
||||
virtual bool is_main_thread() const = 0;
|
||||
|
||||
/**
|
||||
* @brief The frontend supports asynch execution.
|
||||
*
|
||||
* A Graphic UI will support this, a CLI may not. This can be used in
|
||||
* subclass methods to decide whether to start threads for block free UI.
|
||||
*
|
||||
* Note that even a progress indicator's update called regularly can solve
|
||||
* the blocking UI problem in some cases even when an event loop is present.
|
||||
* This is how wxWidgets gauge work but creating a separate thread will make
|
||||
* the UI even more fluent.
|
||||
*
|
||||
* @return true if a job or method can be executed asynchronously, false
|
||||
* otherwise.
|
||||
*/
|
||||
virtual bool supports_asynch() const = 0;
|
||||
|
||||
virtual void process_events() = 0;
|
||||
|
||||
/**
|
||||
* @brief Create a new progress indicator and return a smart pointer to it.
|
||||
* @param statenum The number of states for the given procedure.
|
||||
* @param title The title of the procedure.
|
||||
* @param firstmsg The message for the first subtask to be displayed.
|
||||
* @return Smart pointer to the created object.
|
||||
*/
|
||||
virtual ProgresIndicatorPtr create_progress_indicator(
|
||||
unsigned statenum,
|
||||
const std::string& title,
|
||||
const std::string& firstmsg = "") const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implementation of AppControllerBase for the GUI app
|
||||
*/
|
||||
class AppControllerGui: public AppControllerBase {
|
||||
private:
|
||||
class PriData; // Some structure to store progress indication data
|
||||
|
||||
// Pimpl data for thread safe progress indication features
|
||||
std::unique_ptr<PriData> m_pri_data;
|
||||
|
||||
public:
|
||||
|
||||
AppControllerGui();
|
||||
|
||||
virtual ~AppControllerGui();
|
||||
|
||||
virtual FilePathList query_destination_paths(
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& functionid,
|
||||
const std::string& hint) const override;
|
||||
|
||||
virtual FilePathList query_destination_dirs(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return {}; }
|
||||
|
||||
virtual FilePath query_destination_path(
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& functionid,
|
||||
const std::string& hint) const override;
|
||||
|
||||
virtual bool report_issue(IssueType issuetype,
|
||||
const std::string& description,
|
||||
const std::string& brief = std::string()) override;
|
||||
|
||||
virtual ProgresIndicatorPtr global_progress_indicator() override;
|
||||
|
||||
virtual void global_progress_indicator(ProgresIndicatorPtr gpri) override;
|
||||
|
||||
virtual bool is_main_thread() const override;
|
||||
|
||||
virtual bool supports_asynch() const override;
|
||||
|
||||
virtual void process_events() override;
|
||||
|
||||
virtual ProgresIndicatorPtr create_progress_indicator(
|
||||
unsigned statenum,
|
||||
const std::string& title,
|
||||
const std::string& firstmsg) const override;
|
||||
|
||||
protected:
|
||||
|
||||
// This is a global progress indicator placeholder. In the Slic3r UI it can
|
||||
// contain the progress indicator on the statusbar.
|
||||
ProgresIndicatorPtr m_global_progressind;
|
||||
};
|
||||
|
||||
class AppControllerCli: public AppControllerBase {
|
||||
|
||||
class CliProgress : public ProgressIndicator {
|
||||
std::string m_msg, m_title;
|
||||
public:
|
||||
virtual void message(const std::string& msg) override {
|
||||
m_msg = msg;
|
||||
}
|
||||
|
||||
virtual void title(const std::string& title) override {
|
||||
m_title = title;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
AppControllerCli() {
|
||||
std::cout << "Cli AppController ready..." << std::endl;
|
||||
m_global_progressind = std::make_shared<CliProgress>();
|
||||
}
|
||||
|
||||
virtual ~AppControllerCli() {}
|
||||
|
||||
virtual FilePathList query_destination_paths(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*extensions*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return {}; }
|
||||
|
||||
virtual FilePathList query_destination_dirs(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return {}; }
|
||||
|
||||
virtual FilePath query_destination_path(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*extensions*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return "out.zip"; }
|
||||
|
||||
virtual bool report_issue(IssueType /*issuetype*/,
|
||||
const std::string& description,
|
||||
const std::string& brief) override {
|
||||
std::cerr << brief << ": " << description << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual ProgresIndicatorPtr global_progress_indicator() override {
|
||||
return m_global_progressind;
|
||||
}
|
||||
|
||||
virtual void global_progress_indicator(ProgresIndicatorPtr) override {}
|
||||
|
||||
virtual bool is_main_thread() const override { return true; }
|
||||
|
||||
virtual bool supports_asynch() const override { return false; }
|
||||
|
||||
virtual void process_events() override {}
|
||||
|
||||
virtual ProgresIndicatorPtr create_progress_indicator(
|
||||
unsigned /*statenum*/,
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*firstmsg*/) const override {
|
||||
return std::make_shared<CliProgress>();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// This is a global progress indicator placeholder. In the Slic3r UI it can
|
||||
// contain the progress indicator on the statusbar.
|
||||
ProgresIndicatorPtr m_global_progressind;
|
||||
};
|
||||
|
||||
class Zipper {
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
public:
|
||||
|
||||
Zipper(const std::string& zipfilepath);
|
||||
~Zipper();
|
||||
|
||||
void next_entry(const std::string& fname);
|
||||
|
||||
std::string get_name() const;
|
||||
|
||||
std::ostream& stream();
|
||||
|
||||
void close();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implementation of the printing logic.
|
||||
*/
|
||||
class PrintController {
|
||||
Print *m_print = nullptr;
|
||||
std::function<void()> m_rempools;
|
||||
protected:
|
||||
|
||||
// Data structure with the png export input data
|
||||
struct PngExportData {
|
||||
std::string zippath; // output zip file
|
||||
unsigned long width_px = 1440; // resolution - rows
|
||||
unsigned long height_px = 2560; // resolution columns
|
||||
double width_mm = 68.0, height_mm = 120.0; // dimensions in mm
|
||||
double exp_time_first_s = 35.0; // first exposure time
|
||||
double exp_time_s = 8.0; // global exposure time
|
||||
double corr_x = 1.0; // offsetting in x
|
||||
double corr_y = 1.0; // offsetting in y
|
||||
double corr_z = 1.0; // offsetting in y
|
||||
};
|
||||
|
||||
// Should display a dialog with the input fields for printing to png
|
||||
PngExportData query_png_export_data(const DynamicPrintConfig&);
|
||||
|
||||
// The previous export data, to pre-populate the dialog
|
||||
PngExportData m_prev_expdata;
|
||||
|
||||
void slice(ProgresIndicatorPtr pri);
|
||||
|
||||
public:
|
||||
|
||||
// Must be public for perl to use it
|
||||
explicit inline PrintController(Print *print): m_print(print) {}
|
||||
|
||||
PrintController(const PrintController&) = delete;
|
||||
PrintController(PrintController&&) = delete;
|
||||
|
||||
using Ptr = std::unique_ptr<PrintController>;
|
||||
|
||||
inline static Ptr create(Print *print) {
|
||||
return PrintController::Ptr( new PrintController(print) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Slice the loaded print scene.
|
||||
*/
|
||||
void slice();
|
||||
|
||||
/**
|
||||
* @brief Slice the print into zipped png files.
|
||||
*/
|
||||
void slice_to_png();
|
||||
|
||||
const PrintConfig& config() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Top level controller.
|
||||
*/
|
||||
class AppController {
|
||||
Model *m_model = nullptr;
|
||||
PrintController::Ptr printctl;
|
||||
std::atomic<bool> m_arranging;
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Get the print controller object.
|
||||
*
|
||||
* @return Return a raw pointer instead of a smart one for perl to be able
|
||||
* to use this function and access the print controller.
|
||||
*/
|
||||
PrintController * print_ctl() { return printctl.get(); }
|
||||
|
||||
/**
|
||||
* @brief Set a model object.
|
||||
*
|
||||
* @param model A raw pointer to the model object. This can be used from
|
||||
* perl.
|
||||
*/
|
||||
void set_model(Model *model) { m_model = model; }
|
||||
|
||||
/**
|
||||
* @brief Set the print object from perl.
|
||||
*
|
||||
* This will create a print controller that will then be accessible from
|
||||
* perl.
|
||||
* @param print A print object which can be a perl-ish extension as well.
|
||||
*/
|
||||
void set_print(Print *print) {
|
||||
printctl = PrintController::create(print);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set up a global progress indicator.
|
||||
*
|
||||
* In perl we have a progress indicating status bar on the bottom of the
|
||||
* window which is defined and created in perl. We can pass the ID-s of the
|
||||
* gauge and the statusbar id and make a wrapper implementation of the
|
||||
* ProgressIndicator interface so we can use this GUI widget from C++.
|
||||
*
|
||||
* This function should be called from perl.
|
||||
*
|
||||
* @param gauge_id The ID of the gague widget of the status bar.
|
||||
* @param statusbar_id The ID of the status bar.
|
||||
*/
|
||||
void set_global_progress_indicator(ProgressStatusBar *prs);
|
||||
|
||||
void arrange_model();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // APPCONTROLLER_HPP
|
@ -1,340 +0,0 @@
|
||||
#include "AppController.hpp"
|
||||
|
||||
#include <wx/stdstream.h>
|
||||
#include <wx/wfstream.h>
|
||||
#include <wx/zipstrm.h>
|
||||
|
||||
#include <thread>
|
||||
#include <future>
|
||||
|
||||
#include <slic3r/GUI/GUI.hpp>
|
||||
#include <slic3r/GUI/ProgressStatusBar.hpp>
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/gauge.h>
|
||||
#include <wx/statusbr.h>
|
||||
#include <wx/event.h>
|
||||
|
||||
// This source file implements the UI dependent methods of the AppControllers.
|
||||
// It will be clear what is needed to be reimplemented in case of a UI framework
|
||||
// change or a CLI client creation. In this particular case we use wxWidgets to
|
||||
// implement everything.
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool AppControllerGui::supports_asynch() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppControllerGui::process_events()
|
||||
{
|
||||
wxYieldIfNeeded();
|
||||
}
|
||||
|
||||
FilePathList AppControllerGui::query_destination_paths(
|
||||
const std::string &title,
|
||||
const std::string &extensions,
|
||||
const std::string &/*functionid*/,
|
||||
const std::string& hint) const
|
||||
{
|
||||
|
||||
wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
|
||||
dlg.SetWildcard(extensions);
|
||||
|
||||
dlg.SetFilename(hint);
|
||||
|
||||
FilePathList ret;
|
||||
|
||||
if(dlg.ShowModal() == wxID_OK) {
|
||||
wxArrayString paths;
|
||||
dlg.GetPaths(paths);
|
||||
for(auto& p : paths) ret.push_back(p.ToStdString());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FilePath AppControllerGui::query_destination_path(
|
||||
const std::string &title,
|
||||
const std::string &extensions,
|
||||
const std::string &/*functionid*/,
|
||||
const std::string& hint) const
|
||||
{
|
||||
wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
|
||||
dlg.SetWildcard(extensions);
|
||||
|
||||
dlg.SetFilename(hint);
|
||||
|
||||
FilePath ret;
|
||||
|
||||
if(dlg.ShowModal() == wxID_OK) {
|
||||
ret = FilePath(dlg.GetPath());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AppControllerGui::report_issue(IssueType issuetype,
|
||||
const std::string &description,
|
||||
const std::string &brief)
|
||||
{
|
||||
auto icon = wxICON_INFORMATION;
|
||||
auto style = wxOK|wxCENTRE;
|
||||
switch(issuetype) {
|
||||
case IssueType::INFO: break;
|
||||
case IssueType::WARN: icon = wxICON_WARNING; break;
|
||||
case IssueType::WARN_Q: icon = wxICON_WARNING; style |= wxCANCEL; break;
|
||||
case IssueType::ERR:
|
||||
case IssueType::FATAL: icon = wxICON_ERROR;
|
||||
}
|
||||
|
||||
auto ret = wxMessageBox(_(description), _(brief), icon | style);
|
||||
return ret != wxCANCEL;
|
||||
}
|
||||
|
||||
wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent);
|
||||
|
||||
struct Zipper::Impl {
|
||||
wxFileName fpath;
|
||||
wxFFileOutputStream zipfile;
|
||||
wxZipOutputStream zipstream;
|
||||
wxStdOutputStream pngstream;
|
||||
|
||||
Impl(const std::string& zipfile_path):
|
||||
fpath(zipfile_path),
|
||||
zipfile(zipfile_path),
|
||||
zipstream(zipfile),
|
||||
pngstream(zipstream)
|
||||
{
|
||||
if(!zipfile.IsOk())
|
||||
throw std::runtime_error(L("Cannot create zip file."));
|
||||
}
|
||||
};
|
||||
|
||||
Zipper::Zipper(const std::string &zipfilepath)
|
||||
{
|
||||
m_impl.reset(new Impl(zipfilepath));
|
||||
}
|
||||
|
||||
Zipper::~Zipper() {}
|
||||
|
||||
void Zipper::next_entry(const std::string &fname)
|
||||
{
|
||||
m_impl->zipstream.PutNextEntry(fname);
|
||||
}
|
||||
|
||||
std::string Zipper::get_name() const
|
||||
{
|
||||
return m_impl->fpath.GetName().ToStdString();
|
||||
}
|
||||
|
||||
std::ostream &Zipper::stream()
|
||||
{
|
||||
return m_impl->pngstream;
|
||||
}
|
||||
|
||||
void Zipper::close()
|
||||
{
|
||||
m_impl->zipstream.Close();
|
||||
m_impl->zipfile.Close();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/*
|
||||
* A simple thread safe progress dialog implementation that can be used from
|
||||
* the main thread as well.
|
||||
*/
|
||||
class GuiProgressIndicator:
|
||||
public ProgressIndicator, public wxEvtHandler {
|
||||
|
||||
wxProgressDialog m_gauge;
|
||||
using Base = ProgressIndicator;
|
||||
wxString m_message;
|
||||
int m_range; wxString m_title;
|
||||
bool m_is_asynch = false;
|
||||
|
||||
const int m_id = wxWindow::NewControlId();
|
||||
|
||||
// status update handler
|
||||
void _state( wxCommandEvent& evt) {
|
||||
unsigned st = evt.GetInt();
|
||||
m_message = evt.GetString();
|
||||
_state(st);
|
||||
}
|
||||
|
||||
// Status update implementation
|
||||
void _state( unsigned st) {
|
||||
if(!m_gauge.IsShown()) m_gauge.ShowModal();
|
||||
Base::state(st);
|
||||
if(!m_gauge.Update(static_cast<int>(st), m_message)) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// Setting whether it will be used from the UI thread or some worker thread
|
||||
inline void asynch(bool is) { m_is_asynch = is; }
|
||||
|
||||
/// Get the mode of parallel operation.
|
||||
inline bool asynch() const { return m_is_asynch; }
|
||||
|
||||
inline GuiProgressIndicator(int range, const wxString& title,
|
||||
const wxString& firstmsg) :
|
||||
m_gauge(title, firstmsg, range, wxTheApp->GetTopWindow(),
|
||||
wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT),
|
||||
|
||||
m_message(firstmsg),
|
||||
m_range(range), m_title(title)
|
||||
{
|
||||
Base::max(static_cast<float>(range));
|
||||
Base::states(static_cast<unsigned>(range));
|
||||
|
||||
Bind(PROGRESS_STATUS_UPDATE_EVENT,
|
||||
&GuiProgressIndicator::_state,
|
||||
this, m_id);
|
||||
}
|
||||
|
||||
virtual void state(float val) override {
|
||||
state(static_cast<unsigned>(val));
|
||||
}
|
||||
|
||||
void state(unsigned st) {
|
||||
// send status update event
|
||||
if(m_is_asynch) {
|
||||
auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, m_id);
|
||||
evt->SetInt(st);
|
||||
evt->SetString(m_message);
|
||||
wxQueueEvent(this, evt);
|
||||
} else _state(st);
|
||||
}
|
||||
|
||||
virtual void message(const std::string & msg) override {
|
||||
m_message = _(msg);
|
||||
}
|
||||
|
||||
virtual void messageFmt(const std::string& fmt, ...) {
|
||||
va_list arglist;
|
||||
va_start(arglist, fmt);
|
||||
m_message = wxString::Format(_(fmt), arglist);
|
||||
va_end(arglist);
|
||||
}
|
||||
|
||||
virtual void title(const std::string & title) override {
|
||||
m_title = _(title);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ProgresIndicatorPtr AppControllerGui::create_progress_indicator(
|
||||
unsigned statenum,
|
||||
const std::string& title,
|
||||
const std::string& firstmsg) const
|
||||
{
|
||||
auto pri =
|
||||
std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg);
|
||||
|
||||
// We set up the mode of operation depending of the creator thread's
|
||||
// identity
|
||||
pri->asynch(!is_main_thread());
|
||||
|
||||
return pri;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class Wrapper: public ProgressIndicator, public wxEvtHandler {
|
||||
ProgressStatusBar *m_sbar;
|
||||
using Base = ProgressIndicator;
|
||||
wxString m_message;
|
||||
AppControllerBase& m_ctl;
|
||||
|
||||
void showProgress(bool show = true) {
|
||||
m_sbar->show_progress(show);
|
||||
}
|
||||
|
||||
void _state(unsigned st) {
|
||||
if( st <= ProgressIndicator::max() ) {
|
||||
Base::state(st);
|
||||
m_sbar->set_status_text(m_message);
|
||||
m_sbar->set_progress(st);
|
||||
}
|
||||
}
|
||||
|
||||
// status update handler
|
||||
void _state( wxCommandEvent& evt) {
|
||||
unsigned st = evt.GetInt(); _state(st);
|
||||
}
|
||||
|
||||
const int id_ = wxWindow::NewControlId();
|
||||
|
||||
public:
|
||||
|
||||
inline Wrapper(ProgressStatusBar *sbar,
|
||||
AppControllerBase& ctl):
|
||||
m_sbar(sbar), m_ctl(ctl)
|
||||
{
|
||||
Base::max(static_cast<float>(m_sbar->get_range()));
|
||||
Base::states(static_cast<unsigned>(m_sbar->get_range()));
|
||||
|
||||
Bind(PROGRESS_STATUS_UPDATE_EVENT,
|
||||
&Wrapper::_state,
|
||||
this, id_);
|
||||
}
|
||||
|
||||
virtual void state(float val) override {
|
||||
state(unsigned(val));
|
||||
}
|
||||
|
||||
virtual void max(float val) override {
|
||||
if(val > 1.0) {
|
||||
m_sbar->set_range(static_cast<int>(val));
|
||||
ProgressIndicator::max(val);
|
||||
}
|
||||
}
|
||||
|
||||
void state(unsigned st) {
|
||||
if(!m_ctl.is_main_thread()) {
|
||||
auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, id_);
|
||||
evt->SetInt(st);
|
||||
wxQueueEvent(this, evt);
|
||||
} else {
|
||||
_state(st);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void message(const std::string & msg) override {
|
||||
m_message = _(msg);
|
||||
}
|
||||
|
||||
virtual void message_fmt(const std::string& fmt, ...) override {
|
||||
va_list arglist;
|
||||
va_start(arglist, fmt);
|
||||
m_message = wxString::Format(_(fmt), arglist);
|
||||
va_end(arglist);
|
||||
}
|
||||
|
||||
virtual void title(const std::string & /*title*/) override {}
|
||||
|
||||
virtual void on_cancel(CancelFn fn) override {
|
||||
m_sbar->set_cancel_callback(fn);
|
||||
Base::on_cancel(fn);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
void AppController::set_global_progress_indicator(ProgressStatusBar *prsb)
|
||||
{
|
||||
if(prsb) {
|
||||
auto ctl = GUI::get_appctl();
|
||||
ctl->global_progress_indicator(std::make_shared<Wrapper>(prsb, *ctl));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -123,9 +123,6 @@ add_library(libslic3r_gui STATIC
|
||||
Utils/Time.hpp
|
||||
Utils/HexFile.cpp
|
||||
Utils/HexFile.hpp
|
||||
AppController.hpp
|
||||
AppController.cpp
|
||||
AppControllerWx.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(libslic3r_gui libslic3r avrdude)
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "../AppController.hpp"
|
||||
#include "WipeTowerDialog.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
@ -453,23 +452,4 @@ void desktop_open_datadir_folder()
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace {
|
||||
AppControllerPtr g_appctl;
|
||||
}
|
||||
|
||||
AppControllerPtr get_appctl()
|
||||
{
|
||||
return g_appctl;
|
||||
}
|
||||
|
||||
void set_cli_appctl()
|
||||
{
|
||||
g_appctl = std::make_shared<AppControllerCli>();
|
||||
}
|
||||
|
||||
void set_gui_appctl()
|
||||
{
|
||||
g_appctl = std::make_shared<AppControllerGui>();
|
||||
}
|
||||
|
||||
} }
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
#include "Tab.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "../AppController.hpp"
|
||||
#include "ProgressStatusBar.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "Print.hpp"
|
||||
@ -30,8 +29,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL
|
||||
m_no_plater(no_plater),
|
||||
m_loaded(loaded)
|
||||
{
|
||||
m_appController = new Slic3r::AppController();
|
||||
|
||||
// Load the icon either from the exe, or from the ico file.
|
||||
#if _WIN32
|
||||
{
|
||||
@ -59,14 +56,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL
|
||||
SLIC3R_VERSION +
|
||||
_(L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases")));
|
||||
|
||||
m_appController->set_model(&m_plater->model());
|
||||
m_appController->set_print(&m_plater->print());
|
||||
|
||||
GUI::set_gui_appctl();
|
||||
|
||||
// Make the global status bar and its progress indicator available in C++
|
||||
m_appController->set_global_progress_indicator(m_statusbar);
|
||||
|
||||
m_loaded = true;
|
||||
|
||||
// initialize layout
|
||||
@ -373,7 +362,7 @@ void MainFrame::slice_to_png()
|
||||
{
|
||||
// m_plater->stop_background_process();
|
||||
// m_plater->async_apply_config();
|
||||
m_appController->print_ctl()->slice_to_png();
|
||||
// m_appController->print_ctl()->slice_to_png();
|
||||
}
|
||||
|
||||
// To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
|
||||
|
@ -20,7 +20,6 @@ class wxProgressDialog;
|
||||
namespace Slic3r {
|
||||
|
||||
class ProgressStatusBar;
|
||||
class AppController;
|
||||
|
||||
// #define _(s) Slic3r::GUI::I18N::translate((s))
|
||||
|
||||
@ -54,7 +53,6 @@ class MainFrame : public wxFrame
|
||||
wxString m_qs_last_output_file = wxEmptyString;
|
||||
wxString m_last_config = wxEmptyString;
|
||||
|
||||
AppController* m_appController { nullptr };
|
||||
std::map<std::string, Tab*> m_options_tabs;
|
||||
|
||||
wxMenuItem* m_menu_item_reslice_now { nullptr };
|
||||
@ -97,8 +95,6 @@ public:
|
||||
void select_tab(size_t tab) const;
|
||||
void select_view(const std::string& direction);
|
||||
|
||||
AppController* app_controller() { return m_appController; }
|
||||
|
||||
std::vector<PresetTab>& get_preset_tabs();
|
||||
|
||||
Plater* m_plater { nullptr };
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/ModelArrange.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "libslic3r/GCode/PreviewData.hpp"
|
||||
@ -33,7 +34,7 @@
|
||||
#include "libslic3r/Format/STL.hpp"
|
||||
#include "libslic3r/Format/AMF.hpp"
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "slic3r/AppController.hpp"
|
||||
//#include "slic3r/AppController.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
@ -887,6 +888,7 @@ struct Plater::priv
|
||||
wxGLCanvas *canvas3D; // TODO: Use GLCanvas3D when we can
|
||||
Preview *preview;
|
||||
BackgroundSlicingProcess background_process;
|
||||
std::atomic<bool> arranging;
|
||||
|
||||
wxTimer background_process_timer;
|
||||
|
||||
@ -1470,13 +1472,86 @@ void Plater::priv::mirror(Axis axis)
|
||||
|
||||
void Plater::priv::arrange()
|
||||
{
|
||||
this->background_process.stop();
|
||||
main_frame->app_controller()->arrange_model();
|
||||
// don't do anything if currently arranging. Then this is a re-entrance
|
||||
if(arranging.load()) return;
|
||||
|
||||
// Guard the arrange process
|
||||
arranging.store(true);
|
||||
|
||||
_3DScene::enable_toolbar_item(canvas3D, "arrange", can_arrange());
|
||||
|
||||
this->background_process.stop();
|
||||
unsigned count = 0;
|
||||
for(auto obj : model.objects) count += obj->instances.size();
|
||||
|
||||
auto prev_range = statusbar()->get_range();
|
||||
statusbar()->set_range(count);
|
||||
|
||||
auto statusfn = [this, count] (unsigned st, const std::string& msg) {
|
||||
/* // In case we would run the arrange asynchronously
|
||||
wxCommandEvent event(EVT_PROGRESS_BAR);
|
||||
event.SetInt(st);
|
||||
event.SetString(msg);
|
||||
wxQueueEvent(this->q, event.Clone()); */
|
||||
statusbar()->set_progress(count - st);
|
||||
statusbar()->set_status_text(msg);
|
||||
|
||||
// ok, this is dangerous, but we are protected by the atomic flag
|
||||
// 'arranging'. This call is needed for the cancel button to work.
|
||||
wxYieldIfNeeded();
|
||||
};
|
||||
|
||||
statusbar()->set_cancel_callback([this, statusfn](){
|
||||
arranging.store(false);
|
||||
statusfn(0, L("Arranging canceled"));
|
||||
});
|
||||
|
||||
static const std::string arrangestr = L("Arranging");
|
||||
|
||||
// FIXME: I don't know how to obtain the minimum distance, it depends
|
||||
// on printer technology. I guess the following should work but it crashes.
|
||||
double dist = 6; //PrintConfig::min_object_distance(config);
|
||||
|
||||
auto min_obj_distance = static_cast<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)));
|
||||
|
||||
statusfn(0, arrangestr);
|
||||
|
||||
try {
|
||||
arr::BedShapeHint hint;
|
||||
|
||||
// TODO: from Sasha from GUI or
|
||||
hint.type = arr::BedShapeType::WHO_KNOWS;
|
||||
|
||||
arr::arrange(model,
|
||||
min_obj_distance,
|
||||
bed,
|
||||
hint,
|
||||
false, // create many piles not just one pile
|
||||
[statusfn](unsigned st) { statusfn(st, arrangestr); },
|
||||
[this] () { return !arranging.load(); });
|
||||
} catch(std::exception& /*e*/) {
|
||||
GUI::show_error(this->q, L("Could not arrange model objects! "
|
||||
"Some geometries may be invalid."));
|
||||
}
|
||||
|
||||
statusfn(0, L("Arranging done."));
|
||||
statusbar()->set_range(prev_range);
|
||||
statusbar()->set_cancel_callback(); // remove cancel button
|
||||
arranging.store(false);
|
||||
|
||||
this->schedule_background_process();
|
||||
|
||||
// ignore arrange failures on purpose: user has visual feedback and we don't need to warn him
|
||||
// when parts don't fit in print bed
|
||||
// ignore arrange failures on purpose: user has visual feedback and we
|
||||
// don't need to warn him when parts don't fit in print bed
|
||||
|
||||
_3DScene::enable_toolbar_item(canvas3D, "arrange", can_arrange());
|
||||
update();
|
||||
}
|
||||
|
||||
@ -1932,7 +2007,7 @@ bool Plater::priv::can_delete_all() const
|
||||
|
||||
bool Plater::priv::can_arrange() const
|
||||
{
|
||||
return !model.objects.empty();
|
||||
return !model.objects.empty() && !arranging.load();
|
||||
}
|
||||
|
||||
bool Plater::priv::can_mirror() const
|
||||
|
@ -1,29 +0,0 @@
|
||||
%module{Slic3r::XS};
|
||||
|
||||
%{
|
||||
#include <xsinit.h>
|
||||
#include "slic3r/AppController.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "slic3r/GUI/ProgressStatusBar.hpp"
|
||||
%}
|
||||
|
||||
%name{Slic3r::PrintController} class PrintController {
|
||||
|
||||
PrintController(Print *print);
|
||||
|
||||
void slice_to_png();
|
||||
void slice();
|
||||
};
|
||||
|
||||
%name{Slic3r::AppController} class AppController {
|
||||
|
||||
AppController();
|
||||
|
||||
PrintController *print_ctl();
|
||||
void set_model(Model *model);
|
||||
void set_print(Print *print);
|
||||
void set_global_progress_indicator(ProgressStatusBar *prs);
|
||||
|
||||
void arrange_model();
|
||||
};
|
@ -216,8 +216,6 @@ Ref<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T
|
||||
Clone<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T
|
||||
|
||||
AppConfig* O_OBJECT_SLIC3R
|
||||
AppController* O_OBJECT_SLIC3R
|
||||
PrintController* O_OBJECT_SLIC3R
|
||||
Ref<AppConfig> O_OBJECT_SLIC3R_T
|
||||
BackgroundSlicingProcess* O_OBJECT_SLIC3R
|
||||
Ref<BackgroundSlicingProcess> O_OBJECT_SLIC3R_T
|
||||
|
Loading…
Reference in New Issue
Block a user