diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 9da362302..952b11ed0 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -108,6 +108,7 @@ add_library(libslic3r STATIC Model.cpp Model.hpp ModelArrange.hpp + ModelArrange.cpp MotionPlanner.cpp MotionPlanner.hpp MultiPoint.cpp diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp new file mode 100644 index 000000000..9d59238fa --- /dev/null +++ b/src/libslic3r/ModelArrange.cpp @@ -0,0 +1,763 @@ +#include "ModelArrange.hpp" +#include "Model.hpp" +#include "SVG.hpp" + +#include + +#include +#include + +#include + +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; +using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; +using ItemGroup = std::vector>; +template +using TPacker = typename placers::_NofitPolyPlacer; + +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 +objfunc(const PointImpl& bincenter, + const shapelike::Shapes& 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; + + 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 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 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 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 +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 AutoArranger {}; + +template +class _ArrBase { +protected: + + using Placer = TPacker; + using Selector = FirstFitSelection; + using Packer = Nester; + using PConfig = typename Packer::PlacementConfig; + using Distance = TCoord; + using Pile = sl::Shapes; + + 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 progressind, + std::function 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 inline IndexedPackGroup operator()(Args&&...args) { + m_rtree.clear(); + return m_pck.executeIndexed(std::forward(args)...); + } +}; + +template<> +class AutoArranger: public _ArrBase { +public: + + AutoArranger(const Box& bin, Distance dist, + std::function progressind, + std::function stopcond): + _ArrBase(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; + +inline lnCircle to_lnCircle(const Circle& circ) { + return lnCircle({circ.center()(0), circ.center()(1)}, circ.radius()); +} + +template<> +class AutoArranger: public _ArrBase { +public: + + AutoArranger(const lnCircle& bin, Distance dist, + std::function progressind, + std::function stopcond): + _ArrBase(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: public _ArrBase { +public: + AutoArranger(const PolygonImpl& bin, Distance dist, + std::function progressind, + std::function stopcond): + _ArrBase(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: public _ArrBase { +public: + + AutoArranger(Distance dist, std::function progressind, + std::function stopcond): + _ArrBase(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>; + +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 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 progressind, + std::function stopcondition) +{ + using ArrangeResult = _IndexedPackGroup; + + 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> 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(bbb.min(0)), + static_cast(bbb.min(1)) + }, + { + static_cast(bbb.max(0)), + static_cast(bbb.max(1)) + }); + + switch(bedhint.type) { + case BedShapeType::BOX: { + + // Create the arranger for the box shaped bed + AutoArranger 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 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(std::move(ctour)); + + AutoArranger

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(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; +} + +} +} diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index 380095024..78a400667 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -2,549 +2,13 @@ #define MODELARRANGE_HPP #include "Model.hpp" -#include "SVG.hpp" -#include - -#include -#include - -#include namespace Slic3r { + +class Model; + namespace arr { -using namespace libnest2d; - -std::string toString(const Model& model, bool holes = true) { - std::stringstream ss; - - ss << "{\n"; - - for(auto objptr : model.objects) { - if(!objptr) continue; - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(!objinst) continue; - - Slic3r::TriangleMesh tmpmesh = rmesh; - // CHECK_ME -> Is the following correct ? - tmpmesh.scale(objinst->get_scaling_factor()); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - for(auto& expoly_complex : expolys) { - - auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR); - if(tmp.empty()) continue; - auto expoly = tmp.front(); - expoly.contour.make_clockwise(); - for(auto& h : expoly.holes) h.make_counter_clockwise(); - - ss << "\t{\n"; - ss << "\t\t{\n"; - - for(auto v : expoly.contour.points) ss << "\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = expoly.contour.points.front(); - ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t},\n"; - - // Holes: - ss << "\t\t{\n"; - if(holes) for(auto h : expoly.holes) { - ss << "\t\t\t{\n"; - for(auto v : h.points) ss << "\t\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = h.points.front(); - ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t\t},\n"; - } - ss << "\t\t},\n"; - - ss << "\t},\n"; - } - } - } - - ss << "}\n"; - - return ss.str(); -} - -void toSVG(SVG& svg, const Model& model) { - for(auto objptr : model.objects) { - if(!objptr) continue; - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(!objinst) continue; - - Slic3r::TriangleMesh tmpmesh = rmesh; - tmpmesh.scale(objinst->get_scaling_factor()); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - svg.draw(expolys); - } - } -} - -namespace bgi = boost::geometry::index; - -using SpatElement = std::pair; -using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; -using ItemGroup = std::vector>; -template -using TPacker = typename placers::_NofitPolyPlacer; - -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 -objfunc(const PointImpl& bincenter, - const shapelike::Shapes& 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; - - 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 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 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 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 -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 AutoArranger {}; - -template -class _ArrBase { -protected: - - using Placer = TPacker; - using Selector = FirstFitSelection; - using Packer = Nester; - using PConfig = typename Packer::PlacementConfig; - using Distance = TCoord; - using Pile = sl::Shapes; - - 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 progressind, - std::function 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 inline IndexedPackGroup operator()(Args&&...args) { - m_rtree.clear(); - return m_pck.executeIndexed(std::forward(args)...); - } -}; - -template<> -class AutoArranger: public _ArrBase { -public: - - AutoArranger(const Box& bin, Distance dist, - std::function progressind, - std::function stopcond): - _ArrBase(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; - -template<> -class AutoArranger: public _ArrBase { -public: - - AutoArranger(const lnCircle& bin, Distance dist, - std::function progressind, - std::function stopcond): - _ArrBase(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: public _ArrBase { -public: - AutoArranger(const PolygonImpl& bin, Distance dist, - std::function progressind, - std::function stopcond): - _ArrBase(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: public _ArrBase { -public: - - AutoArranger(Distance dist, std::function progressind, - std::function stopcond): - _ArrBase(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>; - -ShapeData2D projectModelFromTop(const Slic3r::Model &model) { - ShapeData2D ret; - - auto s = std::accumulate(model.objects.begin(), model.objects.end(), size_t(0), - [](size_t s, ModelObject* o){ - return s + o->instances.size(); - }); - - ret.reserve(s); - - for(auto objptr : model.objects) { - if(objptr) { - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(objinst) { - Slic3r::TriangleMesh tmpmesh = rmesh; - ClipperLib::PolygonImpl pn; - - // CHECK_ME -> is the following correct ? - tmpmesh.scale(objinst->get_scaling_factor()); - - // TODO export the exact 2D projection - auto p = tmpmesh.convex_hull(); - - p.make_clockwise(); - p.append(p.first_point()); - pn.Contour = Slic3rMultiPoint_to_ClipperPath( p ); - - // Efficient conversion to item. - Item item(std::move(pn)); - - // Invalid geometries would throw exceptions when arranging - if(item.vertexCount() > 3) { - // CHECK_ME -> is the following correct or it should take in account all three rotations ? - item.rotation(objinst->get_rotation(Z)); - item.translation({ - ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), - ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) - }); - ret.emplace_back(objinst, item); - } - } - } - } - } - - return ret; -} - class Circle { Point center_; double radius_; @@ -556,9 +20,9 @@ public: inline double radius() const { return radius_; } inline const Point& center() const { return center_; } inline operator bool() { return !std::isnan(radius_); } - inline operator lnCircle() { - return lnCircle({center_(0), center_(1)}, radius_); - } +// inline operator lnCircle() { +// return lnCircle({center_(0), center_(1)}, radius_); +// } }; enum class BedShapeType { @@ -577,109 +41,7 @@ struct BedShapeHint { } shape; }; -BedShapeHint bedShape(const Polyline& bed) { - BedShapeHint ret; - - auto x = [](const Point& p) { return p(0); }; - auto y = [](const Point& p) { return p(1); }; - - auto width = [x](const BoundingBox& box) { - return x(box.max) - x(box.min); - }; - - auto height = [y](const BoundingBox& box) { - return y(box.max) - y(box.min); - }; - - auto area = [&width, &height](const BoundingBox& box) { - double w = width(box); - double h = height(box); - return w*h; - }; - - auto poly_area = [](Polyline p) { - Polygon pp; pp.points.reserve(p.points.size() + 1); - pp.points = std::move(p.points); - pp.points.emplace_back(pp.points.front()); - return std::abs(pp.area()); - }; - - auto distance_to = [x, y](const Point& p1, const Point& p2) { - double dx = x(p2) - x(p1); - double dy = y(p2) - y(p1); - return std::sqrt(dx*dx + dy*dy); - }; - - auto bb = bed.bounding_box(); - - auto isCircle = [bb, distance_to](const Polyline& polygon) { - auto center = bb.center(); - std::vector vertex_distances; - double avg_dist = 0; - for (auto pt: polygon.points) - { - double distance = distance_to(center, pt); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - - Circle ret(center, avg_dist); - for (auto el: vertex_distances) - { - if (abs(el - avg_dist) > 10 * SCALED_EPSILON) - ret = Circle(); - break; - } - - return ret; - }; - - auto parea = poly_area(bed); - - if( (1.0 - parea/area(bb)) < 1e-3 ) { - ret.type = BedShapeType::BOX; - ret.shape.box = bb; - } - else if(auto c = isCircle(bed)) { - ret.type = BedShapeType::CIRCLE; - ret.shape.circ = c; - } else { - ret.type = BedShapeType::IRREGULAR; - ret.shape.polygon = bed; - } - - // Determine the bed shape by hand - return ret; -} - -void applyResult( - IndexedPackGroup::value_type& group, - Coord batch_offset, - ShapeData2D& shapemap) -{ - for(auto& r : group) { - auto idx = r.first; // get the original item index - Item& item = r.second; // get the item itself - - // Get the model instance from the shapemap using the index - ModelInstance *inst_ptr = shapemap[idx].first; - - // Get the transformation data from the item object and scale it - // appropriately - auto off = item.translation(); - Radians rot = item.rotation(); - Vec3d foff(off.X*SCALING_FACTOR + batch_offset, - off.Y*SCALING_FACTOR, - inst_ptr->get_offset()(2)); - - // write the transformation data into the model instance - inst_ptr->set_rotation(Z, rot); - inst_ptr->set_offset(foff); - } -} - +BedShapeHint bedShape(const Polyline& bed); /** * \brief Arranges the model objects on the screen. @@ -707,112 +69,14 @@ void applyResult( * packed. The unsigned argument is the number of items remaining to pack. * \param stopcondition A predicate returning true if abort is needed. */ -bool arrange(Model &model, coordf_t min_obj_distance, +bool arrange(Model &model, coord_t min_obj_distance, const Slic3r::Polyline& bed, BedShapeHint bedhint, bool first_bin_only, std::function progressind, - std::function stopcondition) -{ - using ArrangeResult = _IndexedPackGroup; - - 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> 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(bbb.min(0)), - static_cast(bbb.min(1)) - }, - { - static_cast(bbb.max(0)), - static_cast(bbb.max(1)) - }); - - switch(bedhint.type) { - case BedShapeType::BOX: { - - // Create the arranger for the box shaped bed - AutoArranger 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 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(std::move(ctour)); - - AutoArranger

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(STRIDE_PADDING* - binbb.width()*SCALING_FACTOR); - Coord batch_offset = 0; - - for(auto& group : result) { - applyResult(group, batch_offset, shapemap); - - // Only the first pack group can be placed onto the print bed. The - // other objects which could not fit will be placed next to the - // print bed - batch_offset += stride; - } - } - - for(auto objptr : model.objects) objptr->invalidate_bounding_box(); - - return ret && result.size() == 1; -} + std::function stopcondition); } + } #endif // MODELARRANGE_HPP diff --git a/src/slic3r/AppController.cpp b/src/slic3r/AppController.cpp deleted file mode 100644 index 4cc87bb7e..000000000 --- a/src/slic3r/AppController.cpp +++ /dev/null @@ -1,364 +0,0 @@ -#include "AppController.hpp" - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - - -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("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 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 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( *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(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; - - 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(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); -} - -} diff --git a/src/slic3r/AppController.hpp b/src/slic3r/AppController.hpp deleted file mode 100644 index a34e5d035..000000000 --- a/src/slic3r/AppController.hpp +++ /dev/null @@ -1,414 +0,0 @@ -#ifndef APPCONTROLLER_HPP -#define APPCONTROLLER_HPP - -#include -#include -#include -#include -#include - -#include "GUI/ProgressIndicator.hpp" - -#include - -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; - -using FilePath = std::string; -using FilePathList = std::vector; - -/// 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; - - 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 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(); - } - - 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(); - } - -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 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 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; - - 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 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 diff --git a/src/slic3r/AppControllerWx.cpp b/src/slic3r/AppControllerWx.cpp deleted file mode 100644 index 25cd739f3..000000000 --- a/src/slic3r/AppControllerWx.cpp +++ /dev/null @@ -1,340 +0,0 @@ -#include "AppController.hpp" - -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -// 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(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(range)); - Base::states(static_cast(range)); - - Bind(PROGRESS_STATUS_UPDATE_EVENT, - &GuiProgressIndicator::_state, - this, m_id); - } - - virtual void state(float val) override { - state(static_cast(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(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(m_sbar->get_range())); - Base::states(static_cast(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(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(prsb, *ctl)); - } -} - -} diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 4ecccb041..f4bea7d36 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -123,9 +123,6 @@ add_library(libslic3r_gui STATIC Utils/Time.hpp Utils/HexFile.cpp Utils/HexFile.hpp - AppController.hpp - AppController.cpp - AppControllerWx.cpp ) target_link_libraries(libslic3r_gui libslic3r avrdude) diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index b6136a15c..5828e9a22 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -1,6 +1,5 @@ #include "GUI.hpp" #include "GUI_App.hpp" -#include "../AppController.hpp" #include "WipeTowerDialog.hpp" #include @@ -453,23 +452,4 @@ void desktop_open_datadir_folder() #endif } -namespace { -AppControllerPtr g_appctl; -} - -AppControllerPtr get_appctl() -{ - return g_appctl; -} - -void set_cli_appctl() -{ - g_appctl = std::make_shared(); -} - -void set_gui_appctl() -{ - g_appctl = std::make_shared(); -} - } } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index a378a067e..fe3cd48ed 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -11,7 +11,6 @@ #include "Tab.hpp" #include "PresetBundle.hpp" -#include "../AppController.hpp" #include "ProgressStatusBar.hpp" #include "3DScene.hpp" #include "Print.hpp" @@ -30,8 +29,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL m_no_plater(no_plater), m_loaded(loaded) { - m_appController = new Slic3r::AppController(); - // Load the icon either from the exe, or from the ico file. #if _WIN32 { @@ -59,14 +56,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL SLIC3R_VERSION + _(L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"))); - m_appController->set_model(&m_plater->model()); - m_appController->set_print(&m_plater->print()); - - GUI::set_gui_appctl(); - - // Make the global status bar and its progress indicator available in C++ - m_appController->set_global_progress_indicator(m_statusbar); - m_loaded = true; // initialize layout @@ -373,7 +362,7 @@ void MainFrame::slice_to_png() { // m_plater->stop_background_process(); // m_plater->async_apply_config(); - m_appController->print_ctl()->slice_to_png(); +// m_appController->print_ctl()->slice_to_png(); } // To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG". diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 04201b709..eb45e155e 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -20,7 +20,6 @@ class wxProgressDialog; namespace Slic3r { class ProgressStatusBar; -class AppController; // #define _(s) Slic3r::GUI::I18N::translate((s)) @@ -54,7 +53,6 @@ class MainFrame : public wxFrame wxString m_qs_last_output_file = wxEmptyString; wxString m_last_config = wxEmptyString; - AppController* m_appController { nullptr }; std::map m_options_tabs; wxMenuItem* m_menu_item_reslice_now { nullptr }; @@ -97,8 +95,6 @@ public: void select_tab(size_t tab) const; void select_view(const std::string& direction); - AppController* app_controller() { return m_appController; } - std::vector& get_preset_tabs(); Plater* m_plater { nullptr }; @@ -110,4 +106,4 @@ public: } // GUI } //Slic3r -#endif // slic3r_MainFrame_hpp_ \ No newline at end of file +#endif // slic3r_MainFrame_hpp_ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index edf59b27a..6b29e72f3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -25,6 +25,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/ModelArrange.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/GCode/PreviewData.hpp" @@ -33,7 +34,7 @@ #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" -#include "slic3r/AppController.hpp" +//#include "slic3r/AppController.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" @@ -887,6 +888,7 @@ struct Plater::priv wxGLCanvas *canvas3D; // TODO: Use GLCanvas3D when we can Preview *preview; BackgroundSlicingProcess background_process; + std::atomic arranging; wxTimer background_process_timer; @@ -1470,13 +1472,86 @@ void Plater::priv::mirror(Axis axis) void Plater::priv::arrange() { - this->background_process.stop(); - main_frame->app_controller()->arrange_model(); + // don't do anything if currently arranging. Then this is a re-entrance + if(arranging.load()) return; + + // Guard the arrange process + arranging.store(true); + + _3DScene::enable_toolbar_item(canvas3D, "arrange", can_arrange()); + + this->background_process.stop(); + unsigned count = 0; + for(auto obj : model.objects) count += obj->instances.size(); + + auto prev_range = statusbar()->get_range(); + statusbar()->set_range(count); + + auto statusfn = [this, count] (unsigned st, const std::string& msg) { + /* // In case we would run the arrange asynchronously + wxCommandEvent event(EVT_PROGRESS_BAR); + event.SetInt(st); + event.SetString(msg); + wxQueueEvent(this->q, event.Clone()); */ + statusbar()->set_progress(count - st); + statusbar()->set_status_text(msg); + + // ok, this is dangerous, but we are protected by the atomic flag + // 'arranging'. This call is needed for the cancel button to work. + wxYieldIfNeeded(); + }; + + statusbar()->set_cancel_callback([this, statusfn](){ + arranging.store(false); + statusfn(0, L("Arranging canceled")); + }); + + static const std::string arrangestr = L("Arranging"); + + // FIXME: I don't know how to obtain the minimum distance, it depends + // on printer technology. I guess the following should work but it crashes. + double dist = 6; //PrintConfig::min_object_distance(config); + + auto min_obj_distance = static_cast(dist/SCALING_FACTOR); + + const auto *bed_shape_opt = config->opt("bed_shape"); + + assert(bed_shape_opt); + auto& bedpoints = bed_shape_opt->values; + Polyline bed; bed.points.reserve(bedpoints.size()); + for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); + + statusfn(0, arrangestr); + + try { + arr::BedShapeHint hint; + + // TODO: from Sasha from GUI or + hint.type = arr::BedShapeType::WHO_KNOWS; + + arr::arrange(model, + min_obj_distance, + bed, + hint, + false, // create many piles not just one pile + [statusfn](unsigned st) { statusfn(st, arrangestr); }, + [this] () { return !arranging.load(); }); + } catch(std::exception& /*e*/) { + GUI::show_error(this->q, L("Could not arrange model objects! " + "Some geometries may be invalid.")); + } + + statusfn(0, L("Arranging done.")); + statusbar()->set_range(prev_range); + statusbar()->set_cancel_callback(); // remove cancel button + arranging.store(false); + this->schedule_background_process(); - // ignore arrange failures on purpose: user has visual feedback and we don't need to warn him - // when parts don't fit in print bed + // ignore arrange failures on purpose: user has visual feedback and we + // don't need to warn him when parts don't fit in print bed + _3DScene::enable_toolbar_item(canvas3D, "arrange", can_arrange()); update(); } @@ -1932,7 +2007,7 @@ bool Plater::priv::can_delete_all() const bool Plater::priv::can_arrange() const { - return !model.objects.empty(); + return !model.objects.empty() && !arranging.load(); } bool Plater::priv::can_mirror() const diff --git a/xs/xsp/AppController.xsp b/xs/xsp/AppController.xsp deleted file mode 100644 index 8156b0ad2..000000000 --- a/xs/xsp/AppController.xsp +++ /dev/null @@ -1,29 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#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(); -}; \ No newline at end of file diff --git a/xs/xsp/my.map b/xs/xsp/my.map index a91c40726..90a5feaf7 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -216,8 +216,6 @@ Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T AppConfig* O_OBJECT_SLIC3R -AppController* O_OBJECT_SLIC3R -PrintController* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T BackgroundSlicingProcess* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T