mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-12 21:09:00 +08:00
Merge branch 'dev_native' of https://github.com/Prusa3d/Slic3r into dev_native
This commit is contained in:
commit
4f200c5bd5
@ -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)
|
||||||
|
@ -3658,7 +3658,6 @@ void GLCanvas3D::reload_scene(bool force)
|
|||||||
{
|
{
|
||||||
if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr))
|
if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#if !ENABLE_USE_UNIQUE_GLCONTEXT
|
#if !ENABLE_USE_UNIQUE_GLCONTEXT
|
||||||
// ensures this canvas is current
|
// ensures this canvas is current
|
||||||
if (!set_current())
|
if (!set_current())
|
||||||
@ -3695,7 +3694,7 @@ void GLCanvas3D::reload_scene(bool force)
|
|||||||
|
|
||||||
if (m_regenerate_volumes)
|
if (m_regenerate_volumes)
|
||||||
{
|
{
|
||||||
if (m_config->has("nozzle_diameter"))
|
if (m_config->has("nozzle_diameter") && wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF)
|
||||||
{
|
{
|
||||||
// Should the wipe tower be visualized ?
|
// Should the wipe tower be visualized ?
|
||||||
unsigned int extruders_count = (unsigned int)dynamic_cast<const ConfigOptionFloats*>(m_config->option("nozzle_diameter"))->values.size();
|
unsigned int extruders_count = (unsigned int)dynamic_cast<const ConfigOptionFloats*>(m_config->option("nozzle_diameter"))->values.size();
|
||||||
@ -3717,7 +3716,6 @@ void GLCanvas3D::reload_scene(bool force)
|
|||||||
float depth = m_print->get_wipe_tower_depth();
|
float depth = m_print->get_wipe_tower_depth();
|
||||||
if (!m_print->is_step_done(psWipeTower))
|
if (!m_print->is_step_done(psWipeTower))
|
||||||
depth = (900.f/w) * (float)(extruders_count - 1) ;
|
depth = (900.f/w) * (float)(extruders_count - 1) ;
|
||||||
|
|
||||||
m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower),
|
m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower),
|
||||||
m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
|
m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
|
||||||
}
|
}
|
||||||
@ -5165,7 +5163,7 @@ void GLCanvas3D::_update_gizmos_data()
|
|||||||
|
|
||||||
bool enable_move_z = !m_selection.is_wipe_tower();
|
bool enable_move_z = !m_selection.is_wipe_tower();
|
||||||
m_gizmos.enable_grabber(Gizmos::Move, 2, enable_move_z);
|
m_gizmos.enable_grabber(Gizmos::Move, 2, enable_move_z);
|
||||||
bool enable_scale_xyz = m_selection.is_single_full_instance();
|
bool enable_scale_xyz = m_selection.is_single_full_instance() || m_selection.is_single_volume() || m_selection.is_single_modifier();
|
||||||
for (int i = 0; i < 6; ++i)
|
for (int i = 0; i < 6; ++i)
|
||||||
{
|
{
|
||||||
m_gizmos.enable_grabber(Gizmos::Scale, i, enable_scale_xyz);
|
m_gizmos.enable_grabber(Gizmos::Scale, i, enable_scale_xyz);
|
||||||
@ -6207,16 +6205,18 @@ void GLCanvas3D::_load_shells()
|
|||||||
++object_id;
|
++object_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// adds wipe tower's volume
|
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) {
|
||||||
double max_z = m_print->objects()[0]->model_object()->get_model()->bounding_box().max(2);
|
// adds wipe tower's volume
|
||||||
const PrintConfig& config = m_print->config();
|
double max_z = m_print->objects()[0]->model_object()->get_model()->bounding_box().max(2);
|
||||||
unsigned int extruders_count = config.nozzle_diameter.size();
|
const PrintConfig& config = m_print->config();
|
||||||
if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) {
|
unsigned int extruders_count = config.nozzle_diameter.size();
|
||||||
float depth = m_print->get_wipe_tower_depth();
|
if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) {
|
||||||
if (!m_print->is_step_done(psWipeTower))
|
float depth = m_print->get_wipe_tower_depth();
|
||||||
depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ;
|
if (!m_print->is_step_done(psWipeTower))
|
||||||
m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
|
depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ;
|
||||||
m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower), m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
|
m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
|
||||||
|
m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower), m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ void GLGizmoRotate::on_render(const GLCanvas3D::Selection& selection) const
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const BoundingBoxf3& box = selection.get_bounding_box();
|
const BoundingBoxf3& box = selection.get_bounding_box();
|
||||||
bool single_instance = selection.is_single_full_instance();
|
bool single_selection = selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume();
|
||||||
|
|
||||||
std::string axis;
|
std::string axis;
|
||||||
switch (m_axis)
|
switch (m_axis)
|
||||||
@ -353,7 +353,7 @@ void GLGizmoRotate::on_render(const GLCanvas3D::Selection& selection) const
|
|||||||
case Z: { axis = "Z: "; break; }
|
case Z: { axis = "Z: "; break; }
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((single_instance && (m_hover_id == 0)) || m_dragging)
|
if ((single_selection && (m_hover_id == 0)) || m_dragging)
|
||||||
set_tooltip(axis + format((float)Geometry::rad2deg(m_angle), 4) + "\u00B0");
|
set_tooltip(axis + format((float)Geometry::rad2deg(m_angle), 4) + "\u00B0");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -720,19 +720,26 @@ void GLGizmoScale3D::on_process_double_click()
|
|||||||
void GLGizmoScale3D::on_render(const GLCanvas3D::Selection& selection) const
|
void GLGizmoScale3D::on_render(const GLCanvas3D::Selection& selection) const
|
||||||
{
|
{
|
||||||
bool single_instance = selection.is_single_full_instance();
|
bool single_instance = selection.is_single_full_instance();
|
||||||
|
bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
|
||||||
|
bool single_selection = single_instance || single_volume;
|
||||||
|
|
||||||
|
Vec3f scale = 100.0f * Vec3f::Ones();
|
||||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||||
Vec3f scale = single_instance ? 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast<float>() : 100.0f * m_scale.cast<float>();
|
if (single_instance)
|
||||||
|
scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast<float>();
|
||||||
|
else if (single_volume)
|
||||||
|
scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast<float>();
|
||||||
#else
|
#else
|
||||||
Vec3f scale = single_instance ? 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_scaling_factor().cast<float>() : 100.0f * m_scale.cast<float>();
|
Vec3f scale = single_instance ? 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_scaling_factor().cast<float>() : 100.0f * m_scale.cast<float>();
|
||||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||||
|
|
||||||
if ((single_instance && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging)
|
if ((single_selection && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging)
|
||||||
set_tooltip("X: " + format(scale(0), 4) + "%");
|
set_tooltip("X: " + format(scale(0), 4) + "%");
|
||||||
else if ((single_instance && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging)
|
else if ((single_selection && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging)
|
||||||
set_tooltip("Y: " + format(scale(1), 4) + "%");
|
set_tooltip("Y: " + format(scale(1), 4) + "%");
|
||||||
else if ((single_instance && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging)
|
else if ((single_selection && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging)
|
||||||
set_tooltip("Z: " + format(scale(2), 4) + "%");
|
set_tooltip("Z: " + format(scale(2), 4) + "%");
|
||||||
else if ((single_instance && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9)))
|
else if ((single_selection && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9)))
|
||||||
|| m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging)
|
|| m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging)
|
||||||
{
|
{
|
||||||
std::string tooltip = "X: " + format(scale(0), 4) + "%\n";
|
std::string tooltip = "X: " + format(scale(0), 4) + "%\n";
|
||||||
@ -761,21 +768,31 @@ void GLGizmoScale3D::on_render(const GLCanvas3D::Selection& selection) const
|
|||||||
const GLVolume* v = selection.get_volume(*idxs.begin());
|
const GLVolume* v = selection.get_volume(*idxs.begin());
|
||||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||||
transform = v->world_matrix();
|
transform = v->world_matrix();
|
||||||
#else
|
|
||||||
transform = v->world_matrix().cast<double>();
|
|
||||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
|
||||||
|
|
||||||
// gets angles from first selected volume
|
// gets angles from first selected volume
|
||||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
|
||||||
angles = v->get_instance_rotation();
|
angles = v->get_instance_rotation();
|
||||||
#else
|
|
||||||
angles = v->get_rotation();
|
|
||||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
|
||||||
|
|
||||||
// consider rotation+mirror only components of the transform for offsets
|
// consider rotation+mirror only components of the transform for offsets
|
||||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
|
||||||
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror());
|
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror());
|
||||||
#else
|
#else
|
||||||
|
transform = v->world_matrix().cast<double>();
|
||||||
|
// gets angles from first selected volume
|
||||||
|
angles = v->get_rotation();
|
||||||
|
// consider rotation+mirror only components of the transform for offsets
|
||||||
|
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_mirror());
|
||||||
|
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||||
|
}
|
||||||
|
else if (single_volume)
|
||||||
|
{
|
||||||
|
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||||
|
box = v->bounding_box;
|
||||||
|
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||||
|
transform = v->world_matrix();
|
||||||
|
angles = Geometry::extract_euler_angles(transform);
|
||||||
|
// consider rotation+mirror only components of the transform for offsets
|
||||||
|
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror());
|
||||||
|
#else
|
||||||
|
transform = v->world_matrix().cast<double>();
|
||||||
|
angles = Geometry::extract_euler_angles(transform);
|
||||||
|
// consider rotation+mirror only components of the transform for offsets
|
||||||
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_mirror());
|
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_mirror());
|
||||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||||
}
|
}
|
||||||
|
@ -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>();
|
|
||||||
}
|
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
@ -71,6 +71,8 @@ ObjectList::ObjectList(wxWindow* parent) :
|
|||||||
Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, [this](wxDataViewEvent& e) {on_begin_drag(e); });
|
Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, [this](wxDataViewEvent& e) {on_begin_drag(e); });
|
||||||
Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, [this](wxDataViewEvent& e) {on_drop_possible(e); });
|
Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, [this](wxDataViewEvent& e) {on_drop_possible(e); });
|
||||||
Bind(wxEVT_DATAVIEW_ITEM_DROP, [this](wxDataViewEvent& e) {on_drop(e); });
|
Bind(wxEVT_DATAVIEW_ITEM_DROP, [this](wxDataViewEvent& e) {on_drop(e); });
|
||||||
|
|
||||||
|
Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED,[this](wxCommandEvent& e) {last_volume_is_deleted(e.GetInt()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectList::~ObjectList()
|
ObjectList::~ObjectList()
|
||||||
@ -88,6 +90,7 @@ void ObjectList::create_objects_ctrl()
|
|||||||
|
|
||||||
m_objects_model = new PrusaObjectDataViewModel;
|
m_objects_model = new PrusaObjectDataViewModel;
|
||||||
AssociateModel(m_objects_model);
|
AssociateModel(m_objects_model);
|
||||||
|
m_objects_model->SetAssociatedControl(this);
|
||||||
#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE
|
#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE
|
||||||
EnableDragSource(wxDF_UNICODETEXT);
|
EnableDragSource(wxDF_UNICODETEXT);
|
||||||
EnableDropTarget(wxDF_UNICODETEXT);
|
EnableDropTarget(wxDF_UNICODETEXT);
|
||||||
@ -96,7 +99,7 @@ void ObjectList::create_objects_ctrl()
|
|||||||
// column 0(Icon+Text) of the view control:
|
// column 0(Icon+Text) of the view control:
|
||||||
// And Icon can be consisting of several bitmaps
|
// And Icon can be consisting of several bitmaps
|
||||||
AppendColumn(new wxDataViewColumn(_(L("Name")), new PrusaBitmapTextRenderer(),
|
AppendColumn(new wxDataViewColumn(_(L("Name")), new PrusaBitmapTextRenderer(),
|
||||||
0, 250, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE));
|
0, 200, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE));
|
||||||
|
|
||||||
// column 1 of the view control:
|
// column 1 of the view control:
|
||||||
AppendColumn(create_objects_list_extruder_column(4));
|
AppendColumn(create_objects_list_extruder_column(4));
|
||||||
@ -701,7 +704,7 @@ void ObjectList::load_subobject(int type)
|
|||||||
parts_changed(obj_idx);
|
parts_changed(obj_idx);
|
||||||
|
|
||||||
for (int i = 0; i < part_names.size(); ++i) {
|
for (int i = 0; i < part_names.size(); ++i) {
|
||||||
const wxDataViewItem sel_item = m_objects_model->AddVolumeChild(item, part_names.Item(i), /**m_bmp_vector[*/type/*]*/);
|
const wxDataViewItem sel_item = m_objects_model->AddVolumeChild(item, part_names.Item(i), type);
|
||||||
|
|
||||||
if (i == part_names.size() - 1)
|
if (i == part_names.size() - 1)
|
||||||
select_item(sel_item);
|
select_item(sel_item);
|
||||||
@ -783,8 +786,11 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const int
|
|||||||
const auto& sz = BoundingBoxf(bed_shape).size();
|
const auto& sz = BoundingBoxf(bed_shape).size();
|
||||||
const auto side = 0.1 * std::max(sz(0), sz(1));
|
const auto side = 0.1 * std::max(sz(0), sz(1));
|
||||||
|
|
||||||
if (type_name == _("Box"))
|
if (type_name == _("Box")) {
|
||||||
mesh = make_cube(side, side, side);
|
mesh = make_cube(side, side, side);
|
||||||
|
// box sets the base coordinate at 0, 0, move to center of plate
|
||||||
|
mesh.translate(-side * 0.5, -side * 0.5, 0);
|
||||||
|
}
|
||||||
else if (type_name == _("Cylinder"))
|
else if (type_name == _("Cylinder"))
|
||||||
mesh = make_cylinder(0.5*side, side);
|
mesh = make_cylinder(0.5*side, side);
|
||||||
else if (type_name == _("Sphere"))
|
else if (type_name == _("Sphere"))
|
||||||
@ -1248,7 +1254,8 @@ void ObjectList::update_selections()
|
|||||||
{
|
{
|
||||||
sels.Add(m_objects_model->GetItemById(selection.get_object_idx()));
|
sels.Add(m_objects_model->GetItemById(selection.get_object_idx()));
|
||||||
}
|
}
|
||||||
else if (selection.is_single_volume() || selection.is_multiple_volume() || selection.is_multiple_full_object()) {
|
else if (selection.is_single_volume() || selection.is_modifier() ||
|
||||||
|
selection.is_multiple_volume() || selection.is_multiple_full_object()) {
|
||||||
for (auto idx : selection.get_volume_idxs()) {
|
for (auto idx : selection.get_volume_idxs()) {
|
||||||
const auto gl_vol = selection.get_volume(idx);
|
const auto gl_vol = selection.get_volume(idx);
|
||||||
if (selection.is_multiple_full_object())
|
if (selection.is_multiple_full_object())
|
||||||
@ -1434,5 +1441,19 @@ void ObjectList::change_part_type()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ObjectList::last_volume_is_deleted(const int obj_idx)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (obj_idx < 0 || (*m_objects).empty() || (*m_objects)[obj_idx]->volumes.empty())
|
||||||
|
return;
|
||||||
|
auto volume = (*m_objects)[obj_idx]->volumes[0];
|
||||||
|
|
||||||
|
// clear volume's config values
|
||||||
|
volume->config.clear();
|
||||||
|
|
||||||
|
// set a default extruder value, since user can't add it manually
|
||||||
|
volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||||
|
}
|
||||||
|
|
||||||
} //namespace GUI
|
} //namespace GUI
|
||||||
} //namespace Slic3r
|
} //namespace Slic3r
|
@ -149,6 +149,8 @@ public:
|
|||||||
|
|
||||||
ModelVolume* get_selected_model_volume();
|
ModelVolume* get_selected_model_volume();
|
||||||
void change_part_type();
|
void change_part_type();
|
||||||
|
|
||||||
|
void last_volume_is_deleted(const int obj_idx);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,6 +86,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent):
|
|||||||
|
|
||||||
if (option_name == "Rotation")
|
if (option_name == "Rotation")
|
||||||
def.min = -360;
|
def.min = -360;
|
||||||
|
else
|
||||||
|
def.min == -1000;
|
||||||
|
|
||||||
const std::string lower_name = boost::algorithm::to_lower_copy(option_name);
|
const std::string lower_name = boost::algorithm::to_lower_copy(option_name);
|
||||||
|
|
||||||
@ -164,7 +166,7 @@ int ObjectManipulation::ol_selection()
|
|||||||
void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& selection)
|
void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& selection)
|
||||||
{
|
{
|
||||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||||
if (selection.is_single_full_instance())
|
if (selection.is_single_full_instance() || selection.is_single_full_object())
|
||||||
#else
|
#else
|
||||||
if (selection.is_single_full_object())
|
if (selection.is_single_full_object())
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,7 @@ namespace Slic3r {
|
|||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
|
|
||||||
Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data)
|
Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process_func)
|
||||||
: m_canvas(nullptr)
|
: m_canvas(nullptr)
|
||||||
, m_double_slider_sizer(nullptr)
|
, m_double_slider_sizer(nullptr)
|
||||||
, m_label_view_type(nullptr)
|
, m_label_view_type(nullptr)
|
||||||
@ -43,6 +43,7 @@ Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print,
|
|||||||
, m_loaded(false)
|
, m_loaded(false)
|
||||||
, m_enabled(false)
|
, m_enabled(false)
|
||||||
, m_force_sliders_full_range(false)
|
, m_force_sliders_full_range(false)
|
||||||
|
, m_schedule_background_process(schedule_background_process_func)
|
||||||
{
|
{
|
||||||
if (init(notebook, config, print, gcode_preview_data))
|
if (init(notebook, config, print, gcode_preview_data))
|
||||||
{
|
{
|
||||||
@ -488,6 +489,7 @@ void Preview::create_double_slider()
|
|||||||
Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) {
|
Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) {
|
||||||
auto& config = wxGetApp().preset_bundle->project_config;
|
auto& config = wxGetApp().preset_bundle->project_config;
|
||||||
((config.option<ConfigOptionFloats>("colorprint_heights"))->values) = (m_slider->GetTicksValues());
|
((config.option<ConfigOptionFloats>("colorprint_heights"))->values) = (m_slider->GetTicksValues());
|
||||||
|
m_schedule_background_process();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,13 +531,16 @@ void Preview::fill_slider_values(std::vector<std::pair<int, double>> &values,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// All ticks that would end up outside the slider range should be erased.
|
// All ticks that would end up outside the slider range should be erased.
|
||||||
// TODO: this should probably be placed into more appropriate part of code,
|
// TODO: this should be placed into more appropriate part of code,
|
||||||
// this way it relies on the Preview tab being active.
|
// this function is e.g. not called when the last object is deleted
|
||||||
auto& config = wxGetApp().preset_bundle->project_config;
|
auto& config = wxGetApp().preset_bundle->project_config;
|
||||||
std::vector<double> &ticks_from_config = (config.option<ConfigOptionFloats>("colorprint_heights"))->values;
|
std::vector<double> &ticks_from_config = (config.option<ConfigOptionFloats>("colorprint_heights"))->values;
|
||||||
|
unsigned int old_size = ticks_from_config.size();
|
||||||
ticks_from_config.erase(std::remove_if(ticks_from_config.begin(), ticks_from_config.end(),
|
ticks_from_config.erase(std::remove_if(ticks_from_config.begin(), ticks_from_config.end(),
|
||||||
[values](double val) { return values.back().second < val; }),
|
[values](double val) { return values.back().second < val; }),
|
||||||
ticks_from_config.end());
|
ticks_from_config.end());
|
||||||
|
if (ticks_from_config.size() != old_size)
|
||||||
|
m_schedule_background_process();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Preview::set_double_slider_thumbs(const bool force_sliders_full_range,
|
void Preview::set_double_slider_thumbs(const bool force_sliders_full_range,
|
||||||
|
@ -40,6 +40,9 @@ class Preview : public wxPanel
|
|||||||
Print* m_print;
|
Print* m_print;
|
||||||
GCodePreviewData* m_gcode_preview_data;
|
GCodePreviewData* m_gcode_preview_data;
|
||||||
|
|
||||||
|
// Calling this function object forces Plater::schedule_background_process.
|
||||||
|
std::function<void()> m_schedule_background_process;
|
||||||
|
|
||||||
unsigned int m_number_extruders;
|
unsigned int m_number_extruders;
|
||||||
std::string m_preferred_color_mode;
|
std::string m_preferred_color_mode;
|
||||||
|
|
||||||
@ -50,7 +53,7 @@ class Preview : public wxPanel
|
|||||||
PrusaDoubleSlider* m_slider {nullptr};
|
PrusaDoubleSlider* m_slider {nullptr};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data);
|
Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process = [](){});
|
||||||
virtual ~Preview();
|
virtual ~Preview();
|
||||||
|
|
||||||
wxGLCanvas* get_wxglcanvas() { return m_canvas; }
|
wxGLCanvas* get_wxglcanvas() { return m_canvas; }
|
||||||
|
@ -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 };
|
||||||
@ -110,4 +106,4 @@ public:
|
|||||||
} // GUI
|
} // GUI
|
||||||
} //Slic3r
|
} //Slic3r
|
||||||
|
|
||||||
#endif // slic3r_MainFrame_hpp_
|
#endif // slic3r_MainFrame_hpp_
|
||||||
|
@ -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;
|
||||||
|
|
||||||
@ -1000,7 +1002,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) :
|
|||||||
_3DScene::add_canvas(canvas3D);
|
_3DScene::add_canvas(canvas3D);
|
||||||
_3DScene::allow_multisample(canvas3D, GLCanvas3DManager::can_multisample());
|
_3DScene::allow_multisample(canvas3D, GLCanvas3DManager::can_multisample());
|
||||||
notebook->AddPage(canvas3D, _(L("3D")));
|
notebook->AddPage(canvas3D, _(L("3D")));
|
||||||
preview = new GUI::Preview(notebook, config, &print, &gcode_preview_data);
|
preview = new GUI::Preview(notebook, config, &print, &gcode_preview_data, [this](){ schedule_background_process(); });
|
||||||
|
|
||||||
// XXX: If have OpenGL
|
// XXX: If have OpenGL
|
||||||
_3DScene::enable_picking(canvas3D, true);
|
_3DScene::enable_picking(canvas3D, true);
|
||||||
@ -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
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
|
|
||||||
wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent);
|
wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent);
|
||||||
|
wxDEFINE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent);
|
||||||
|
|
||||||
wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description,
|
wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description,
|
||||||
std::function<void(wxCommandEvent& event)> cb, const std::string& icon, wxEvtHandler* event_handler)
|
std::function<void(wxCommandEvent& event)> cb, const std::string& icon, wxEvtHandler* event_handler)
|
||||||
@ -564,12 +565,22 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item)
|
|||||||
// NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_
|
// NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_
|
||||||
// thus removing the node from it doesn't result in freeing it
|
// thus removing the node from it doesn't result in freeing it
|
||||||
if (node_parent) {
|
if (node_parent) {
|
||||||
|
if (node->m_type == itInstanceRoot)
|
||||||
|
{
|
||||||
|
for (int i = node->GetChildCount() - 1; i > 0; i--)
|
||||||
|
Delete(wxDataViewItem(node->GetNthChild(i)));
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
auto id = node_parent->GetChildren().Index(node);
|
auto id = node_parent->GetChildren().Index(node);
|
||||||
auto idx = node->GetIdx();
|
auto idx = node->GetIdx();
|
||||||
node_parent->GetChildren().Remove(node);
|
|
||||||
|
|
||||||
if (node->m_type == itVolume)
|
|
||||||
|
if (node->m_type == itVolume) {
|
||||||
node_parent->m_volumes_cnt--;
|
node_parent->m_volumes_cnt--;
|
||||||
|
DeleteSettings(item);
|
||||||
|
}
|
||||||
|
node_parent->GetChildren().Remove(node);
|
||||||
|
|
||||||
if (id > 0) {
|
if (id > 0) {
|
||||||
if(id == node_parent->GetChildCount()) id--;
|
if(id == node_parent->GetChildCount()) id--;
|
||||||
@ -600,21 +611,70 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item)
|
|||||||
obj_node->GetChildren().Remove(node_parent);
|
obj_node->GetChildren().Remove(node_parent);
|
||||||
delete node_parent;
|
delete node_parent;
|
||||||
ret_item = wxDataViewItem(obj_node);
|
ret_item = wxDataViewItem(obj_node);
|
||||||
ItemDeleted(ret_item, wxDataViewItem(node_parent));
|
|
||||||
|
|
||||||
#ifndef __WXGTK__
|
#ifndef __WXGTK__
|
||||||
if (obj_node->GetChildCount() == 0)
|
if (obj_node->GetChildCount() == 0)
|
||||||
obj_node->m_container = false;
|
obj_node->m_container = false;
|
||||||
#endif //__WXGTK__
|
#endif //__WXGTK__
|
||||||
|
ItemDeleted(ret_item, wxDataViewItem(node_parent));
|
||||||
return ret_item;
|
return ret_item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if there is last volume item after deleting, delete this last volume too
|
||||||
|
if (node_parent->GetChildCount() <= 3)
|
||||||
|
{
|
||||||
|
int vol_cnt = 0;
|
||||||
|
int vol_idx = 0;
|
||||||
|
for (int i = 0; i < node_parent->GetChildCount(); ++i) {
|
||||||
|
if (node_parent->GetNthChild(i)->GetType() == itVolume) {
|
||||||
|
vol_idx = i;
|
||||||
|
vol_cnt++;
|
||||||
|
}
|
||||||
|
if (vol_cnt > 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vol_cnt == 1) {
|
||||||
|
delete node;
|
||||||
|
ItemDeleted(parent, item);
|
||||||
|
|
||||||
|
PrusaObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx);
|
||||||
|
DeleteSettings(wxDataViewItem(last_child_node));
|
||||||
|
node_parent->GetChildren().Remove(last_child_node);
|
||||||
|
node_parent->m_volumes_cnt = 0;
|
||||||
|
delete last_child_node;
|
||||||
|
|
||||||
|
#ifndef __WXGTK__
|
||||||
|
if (node_parent->GetChildCount() == 0)
|
||||||
|
node_parent->m_container = false;
|
||||||
|
#endif //__WXGTK__
|
||||||
|
ItemDeleted(parent, wxDataViewItem(last_child_node));
|
||||||
|
|
||||||
|
wxCommandEvent event(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED);
|
||||||
|
auto it = find(m_objects.begin(), m_objects.end(), node_parent);
|
||||||
|
event.SetInt(it == m_objects.end() ? -1 : it - m_objects.begin());
|
||||||
|
wxPostEvent(m_ctrl, event);
|
||||||
|
|
||||||
|
ret_item = parent;
|
||||||
|
|
||||||
|
return ret_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto it = find(m_objects.begin(), m_objects.end(), node);
|
auto it = find(m_objects.begin(), m_objects.end(), node);
|
||||||
auto id = it - m_objects.begin();
|
auto id = it - m_objects.begin();
|
||||||
if (it != m_objects.end())
|
if (it != m_objects.end())
|
||||||
|
{
|
||||||
|
// Delete all sub-items
|
||||||
|
int i = m_objects[id]->GetChildCount() - 1;
|
||||||
|
while (i >= 0) {
|
||||||
|
Delete(wxDataViewItem(m_objects[id]->GetNthChild(i)));
|
||||||
|
i = m_objects[id]->GetChildCount() - 1;
|
||||||
|
}
|
||||||
m_objects.erase(it);
|
m_objects.erase(it);
|
||||||
|
}
|
||||||
if (id > 0) {
|
if (id > 0) {
|
||||||
if(id == m_objects.size()) id--;
|
if(id == m_objects.size()) id--;
|
||||||
ret_item = wxDataViewItem(m_objects[id]);
|
ret_item = wxDataViewItem(m_objects[id]);
|
||||||
@ -733,8 +793,8 @@ void PrusaObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto item = wxDataViewItem(node);
|
auto item = wxDataViewItem(node);
|
||||||
|
DeleteSettings(item);
|
||||||
children.RemoveAt(id);
|
children.RemoveAt(id);
|
||||||
root->m_volumes_cnt--;
|
|
||||||
|
|
||||||
// free the node
|
// free the node
|
||||||
delete node;
|
delete node;
|
||||||
@ -742,6 +802,7 @@ void PrusaObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent)
|
|||||||
// notify control
|
// notify control
|
||||||
ItemDeleted(parent, item);
|
ItemDeleted(parent, item);
|
||||||
}
|
}
|
||||||
|
root->m_volumes_cnt = 0;
|
||||||
|
|
||||||
// set m_containet to FALSE if parent has no child
|
// set m_containet to FALSE if parent has no child
|
||||||
#ifndef __WXGTK__
|
#ifndef __WXGTK__
|
||||||
@ -749,6 +810,21 @@ void PrusaObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent)
|
|||||||
#endif //__WXGTK__
|
#endif //__WXGTK__
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrusaObjectDataViewModel::DeleteSettings(const wxDataViewItem& parent)
|
||||||
|
{
|
||||||
|
PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID();
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
// if volume has a "settings"item, than delete it before volume deleting
|
||||||
|
if (node->GetChildCount() > 0 && node->GetNthChild(0)->GetType() == itSettings) {
|
||||||
|
auto settings_node = node->GetNthChild(0);
|
||||||
|
auto settings_item = wxDataViewItem(settings_node);
|
||||||
|
node->GetChildren().RemoveAt(0);
|
||||||
|
delete settings_node;
|
||||||
|
ItemDeleted(parent, settings_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx)
|
wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx)
|
||||||
{
|
{
|
||||||
if (obj_idx >= m_objects.size())
|
if (obj_idx >= m_objects.size())
|
||||||
@ -841,7 +917,7 @@ void PrusaObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType&
|
|||||||
type = itUndef;
|
type = itUndef;
|
||||||
|
|
||||||
PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
|
PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
|
||||||
if (!node || node->GetIdx() < 0 && !(node->GetType() & (itObject|itSettings|itInstanceRoot)))
|
if (!node || node->GetIdx() <-1 || node->GetIdx() ==-1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
idx = node->GetIdx();
|
idx = node->GetIdx();
|
||||||
|
@ -422,11 +422,16 @@ private:
|
|||||||
// PrusaObjectDataViewModel
|
// PrusaObjectDataViewModel
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// custom message the model sends to associated control to notify a last volume deleted from the object:
|
||||||
|
wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent);
|
||||||
|
|
||||||
class PrusaObjectDataViewModel :public wxDataViewModel
|
class PrusaObjectDataViewModel :public wxDataViewModel
|
||||||
{
|
{
|
||||||
std::vector<PrusaObjectDataViewModelNode*> m_objects;
|
std::vector<PrusaObjectDataViewModelNode*> m_objects;
|
||||||
std::vector<wxBitmap*> m_volume_bmps;
|
std::vector<wxBitmap*> m_volume_bmps;
|
||||||
|
|
||||||
|
wxDataViewCtrl* m_ctrl{ nullptr };
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PrusaObjectDataViewModel();
|
PrusaObjectDataViewModel();
|
||||||
~PrusaObjectDataViewModel();
|
~PrusaObjectDataViewModel();
|
||||||
@ -444,6 +449,7 @@ public:
|
|||||||
void DeleteAll();
|
void DeleteAll();
|
||||||
void DeleteChildren(wxDataViewItem& parent);
|
void DeleteChildren(wxDataViewItem& parent);
|
||||||
void DeleteVolumeChildren(wxDataViewItem& parent);
|
void DeleteVolumeChildren(wxDataViewItem& parent);
|
||||||
|
void DeleteSettings(const wxDataViewItem& parent);
|
||||||
wxDataViewItem GetItemById(int obj_idx);
|
wxDataViewItem GetItemById(int obj_idx);
|
||||||
wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx);
|
wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx);
|
||||||
wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx);
|
wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx);
|
||||||
@ -500,6 +506,8 @@ public:
|
|||||||
|
|
||||||
void SetVolumeBitmaps(const std::vector<wxBitmap*>& volume_bmps) { m_volume_bmps = volume_bmps; }
|
void SetVolumeBitmaps(const std::vector<wxBitmap*>& volume_bmps) { m_volume_bmps = volume_bmps; }
|
||||||
void SetVolumeType(const wxDataViewItem &item, const int type);
|
void SetVolumeType(const wxDataViewItem &item, const int type);
|
||||||
|
|
||||||
|
void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user