Getting rid of AppController.
This commit is contained in:
parent
c227dad8cc
commit
a4e1ab2281
@ -108,6 +108,7 @@ add_library(libslic3r STATIC
|
|||||||
Model.cpp
|
Model.cpp
|
||||||
Model.hpp
|
Model.hpp
|
||||||
ModelArrange.hpp
|
ModelArrange.hpp
|
||||||
|
ModelArrange.cpp
|
||||||
MotionPlanner.cpp
|
MotionPlanner.cpp
|
||||||
MotionPlanner.hpp
|
MotionPlanner.hpp
|
||||||
MultiPoint.cpp
|
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
|
#define MODELARRANGE_HPP
|
||||||
|
|
||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
#include "SVG.hpp"
|
|
||||||
#include <libnest2d.h>
|
|
||||||
|
|
||||||
#include <numeric>
|
|
||||||
#include <ClipperUtils.hpp>
|
|
||||||
|
|
||||||
#include <boost/geometry/index/rtree.hpp>
|
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class Model;
|
||||||
|
|
||||||
namespace arr {
|
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 {
|
class Circle {
|
||||||
Point center_;
|
Point center_;
|
||||||
double radius_;
|
double radius_;
|
||||||
@ -556,9 +20,9 @@ public:
|
|||||||
inline double radius() const { return radius_; }
|
inline double radius() const { return radius_; }
|
||||||
inline const Point& center() const { return center_; }
|
inline const Point& center() const { return center_; }
|
||||||
inline operator bool() { return !std::isnan(radius_); }
|
inline operator bool() { return !std::isnan(radius_); }
|
||||||
inline operator lnCircle() {
|
// inline operator lnCircle() {
|
||||||
return lnCircle({center_(0), center_(1)}, radius_);
|
// return lnCircle({center_(0), center_(1)}, radius_);
|
||||||
}
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class BedShapeType {
|
enum class BedShapeType {
|
||||||
@ -577,109 +41,7 @@ struct BedShapeHint {
|
|||||||
} shape;
|
} shape;
|
||||||
};
|
};
|
||||||
|
|
||||||
BedShapeHint bedShape(const Polyline& bed) {
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Arranges the model objects on the screen.
|
* \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.
|
* packed. The unsigned argument is the number of items remaining to pack.
|
||||||
* \param stopcondition A predicate returning true if abort is needed.
|
* \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,
|
const Slic3r::Polyline& bed,
|
||||||
BedShapeHint bedhint,
|
BedShapeHint bedhint,
|
||||||
bool first_bin_only,
|
bool first_bin_only,
|
||||||
std::function<void(unsigned)> progressind,
|
std::function<void(unsigned)> progressind,
|
||||||
std::function<bool(void)> stopcondition)
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif // MODELARRANGE_HPP
|
#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/Time.hpp
|
||||||
Utils/HexFile.cpp
|
Utils/HexFile.cpp
|
||||||
Utils/HexFile.hpp
|
Utils/HexFile.hpp
|
||||||
AppController.hpp
|
|
||||||
AppController.cpp
|
|
||||||
AppControllerWx.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(libslic3r_gui libslic3r avrdude)
|
target_link_libraries(libslic3r_gui libslic3r avrdude)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#include "GUI.hpp"
|
#include "GUI.hpp"
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
#include "../AppController.hpp"
|
|
||||||
#include "WipeTowerDialog.hpp"
|
#include "WipeTowerDialog.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@ -453,23 +452,4 @@ void desktop_open_datadir_folder()
|
|||||||
#endif
|
#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 "Tab.hpp"
|
||||||
#include "PresetBundle.hpp"
|
#include "PresetBundle.hpp"
|
||||||
#include "../AppController.hpp"
|
|
||||||
#include "ProgressStatusBar.hpp"
|
#include "ProgressStatusBar.hpp"
|
||||||
#include "3DScene.hpp"
|
#include "3DScene.hpp"
|
||||||
#include "Print.hpp"
|
#include "Print.hpp"
|
||||||
@ -30,8 +29,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL
|
|||||||
m_no_plater(no_plater),
|
m_no_plater(no_plater),
|
||||||
m_loaded(loaded)
|
m_loaded(loaded)
|
||||||
{
|
{
|
||||||
m_appController = new Slic3r::AppController();
|
|
||||||
|
|
||||||
// Load the icon either from the exe, or from the ico file.
|
// Load the icon either from the exe, or from the ico file.
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
{
|
{
|
||||||
@ -59,14 +56,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL
|
|||||||
SLIC3R_VERSION +
|
SLIC3R_VERSION +
|
||||||
_(L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases")));
|
_(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;
|
m_loaded = true;
|
||||||
|
|
||||||
// initialize layout
|
// initialize layout
|
||||||
@ -373,7 +362,7 @@ void MainFrame::slice_to_png()
|
|||||||
{
|
{
|
||||||
// m_plater->stop_background_process();
|
// m_plater->stop_background_process();
|
||||||
// m_plater->async_apply_config();
|
// 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".
|
// 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 {
|
namespace Slic3r {
|
||||||
|
|
||||||
class ProgressStatusBar;
|
class ProgressStatusBar;
|
||||||
class AppController;
|
|
||||||
|
|
||||||
// #define _(s) Slic3r::GUI::I18N::translate((s))
|
// #define _(s) Slic3r::GUI::I18N::translate((s))
|
||||||
|
|
||||||
@ -54,7 +53,6 @@ class MainFrame : public wxFrame
|
|||||||
wxString m_qs_last_output_file = wxEmptyString;
|
wxString m_qs_last_output_file = wxEmptyString;
|
||||||
wxString m_last_config = wxEmptyString;
|
wxString m_last_config = wxEmptyString;
|
||||||
|
|
||||||
AppController* m_appController { nullptr };
|
|
||||||
std::map<std::string, Tab*> m_options_tabs;
|
std::map<std::string, Tab*> m_options_tabs;
|
||||||
|
|
||||||
wxMenuItem* m_menu_item_reslice_now { nullptr };
|
wxMenuItem* m_menu_item_reslice_now { nullptr };
|
||||||
@ -97,8 +95,6 @@ public:
|
|||||||
void select_tab(size_t tab) const;
|
void select_tab(size_t tab) const;
|
||||||
void select_view(const std::string& direction);
|
void select_view(const std::string& direction);
|
||||||
|
|
||||||
AppController* app_controller() { return m_appController; }
|
|
||||||
|
|
||||||
std::vector<PresetTab>& get_preset_tabs();
|
std::vector<PresetTab>& get_preset_tabs();
|
||||||
|
|
||||||
Plater* m_plater { nullptr };
|
Plater* m_plater { nullptr };
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/ModelArrange.hpp"
|
||||||
#include "libslic3r/Print.hpp"
|
#include "libslic3r/Print.hpp"
|
||||||
#include "libslic3r/SLAPrint.hpp"
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
#include "libslic3r/GCode/PreviewData.hpp"
|
#include "libslic3r/GCode/PreviewData.hpp"
|
||||||
@ -33,7 +34,7 @@
|
|||||||
#include "libslic3r/Format/STL.hpp"
|
#include "libslic3r/Format/STL.hpp"
|
||||||
#include "libslic3r/Format/AMF.hpp"
|
#include "libslic3r/Format/AMF.hpp"
|
||||||
#include "libslic3r/Format/3mf.hpp"
|
#include "libslic3r/Format/3mf.hpp"
|
||||||
#include "slic3r/AppController.hpp"
|
//#include "slic3r/AppController.hpp"
|
||||||
#include "GUI.hpp"
|
#include "GUI.hpp"
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
#include "GUI_ObjectList.hpp"
|
#include "GUI_ObjectList.hpp"
|
||||||
@ -887,6 +888,7 @@ struct Plater::priv
|
|||||||
wxGLCanvas *canvas3D; // TODO: Use GLCanvas3D when we can
|
wxGLCanvas *canvas3D; // TODO: Use GLCanvas3D when we can
|
||||||
Preview *preview;
|
Preview *preview;
|
||||||
BackgroundSlicingProcess background_process;
|
BackgroundSlicingProcess background_process;
|
||||||
|
std::atomic<bool> arranging;
|
||||||
|
|
||||||
wxTimer background_process_timer;
|
wxTimer background_process_timer;
|
||||||
|
|
||||||
@ -1470,13 +1472,86 @@ void Plater::priv::mirror(Axis axis)
|
|||||||
|
|
||||||
void Plater::priv::arrange()
|
void Plater::priv::arrange()
|
||||||
{
|
{
|
||||||
this->background_process.stop();
|
// don't do anything if currently arranging. Then this is a re-entrance
|
||||||
main_frame->app_controller()->arrange_model();
|
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();
|
this->schedule_background_process();
|
||||||
|
|
||||||
// ignore arrange failures on purpose: user has visual feedback and we don't need to warn him
|
// ignore arrange failures on purpose: user has visual feedback and we
|
||||||
// when parts don't fit in print bed
|
// don't need to warn him when parts don't fit in print bed
|
||||||
|
|
||||||
|
_3DScene::enable_toolbar_item(canvas3D, "arrange", can_arrange());
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1932,7 +2007,7 @@ bool Plater::priv::can_delete_all() const
|
|||||||
|
|
||||||
bool Plater::priv::can_arrange() const
|
bool Plater::priv::can_arrange() const
|
||||||
{
|
{
|
||||||
return !model.objects.empty();
|
return !model.objects.empty() && !arranging.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Plater::priv::can_mirror() const
|
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
|
Clone<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T
|
||||||
|
|
||||||
AppConfig* O_OBJECT_SLIC3R
|
AppConfig* O_OBJECT_SLIC3R
|
||||||
AppController* O_OBJECT_SLIC3R
|
|
||||||
PrintController* O_OBJECT_SLIC3R
|
|
||||||
Ref<AppConfig> O_OBJECT_SLIC3R_T
|
Ref<AppConfig> O_OBJECT_SLIC3R_T
|
||||||
BackgroundSlicingProcess* O_OBJECT_SLIC3R
|
BackgroundSlicingProcess* O_OBJECT_SLIC3R
|
||||||
Ref<BackgroundSlicingProcess> O_OBJECT_SLIC3R_T
|
Ref<BackgroundSlicingProcess> O_OBJECT_SLIC3R_T
|
||||||
|
Loading…
Reference in New Issue
Block a user