diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 57e4ed10d..f71e33636 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -34,6 +34,7 @@ #include "libslic3r/Config.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/ModelArrange.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" @@ -41,6 +42,7 @@ #include "libslic3r/Format/3mf.hpp" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/OBJ.hpp" +#include "libslic3r/Format/SL1.hpp" #include "libslic3r/Utils.hpp" #include "PrusaSlicer.hpp" @@ -53,12 +55,6 @@ using namespace Slic3r; -PrinterTechnology get_printer_technology(const DynamicConfig &config) -{ - const ConfigOptionEnum *opt = config.option>("printer_technology"); - return (opt == nullptr) ? ptUnknown : opt->value; -} - int CLI::run(int argc, char **argv) { // Switch boost::filesystem to utf8. @@ -86,13 +82,15 @@ int CLI::run(int argc, char **argv) m_extra_config.apply(m_config, true); m_extra_config.normalize(); + + PrinterTechnology printer_technology = Slic3r::printer_technology(m_config); bool start_gui = m_actions.empty() && // cutting transformations are setting an "export" action. std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); - PrinterTechnology printer_technology = get_printer_technology(m_extra_config); + const std::vector &load_configs = m_config.option("load", true)->values; // load config files supplied via --load @@ -113,7 +111,7 @@ int CLI::run(int argc, char **argv) return 1; } config.normalize(); - PrinterTechnology other_printer_technology = get_printer_technology(config); + PrinterTechnology other_printer_technology = Slic3r::printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { @@ -134,7 +132,7 @@ int CLI::run(int argc, char **argv) // When loading an AMF or 3MF, config is imported as well, including the printer technology. DynamicPrintConfig config; model = Model::read_from_file(file, &config, true); - PrinterTechnology other_printer_technology = get_printer_technology(config); + PrinterTechnology other_printer_technology = Slic3r::printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { @@ -161,9 +159,6 @@ int CLI::run(int argc, char **argv) // Normalizing after importing the 3MFs / AMFs m_print_config.normalize(); - if (printer_technology == ptUnknown) - printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA; - // Initialize full print configs for both the FFF and SLA technologies. FullPrintConfig fff_print_config; SLAFullPrintConfig sla_print_config; @@ -174,6 +169,7 @@ int CLI::run(int argc, char **argv) m_print_config.apply(fff_print_config, true); } else if (printer_technology == ptSLA) { // The default value has to be different from the one in fff mode. + sla_print_config.printer_technology.value = ptSLA; sla_print_config.output_filename_format.value = "[input_filename_base].sl1"; // The default bed shape should reflect the default display parameters @@ -186,8 +182,18 @@ int CLI::run(int argc, char **argv) m_print_config.apply(sla_print_config, true); } + std::string validity = m_print_config.validate(); + if (!validity.empty()) { + boost::nowide::cerr << "error: " << validity << std::endl; + return 1; + } + // Loop through transform options. bool user_center_specified = false; + Points bed = get_bed_shape(m_print_config); + ArrangeParams arrange_cfg; + arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config)); + for (auto const &opt_key : m_transforms) { if (opt_key == "merge") { Model m; @@ -197,29 +203,33 @@ int CLI::run(int argc, char **argv) // Rearrange instances unless --dont-arrange is supplied if (! m_config.opt_bool("dont_arrange")) { m.add_default_instances(); - const BoundingBoxf &bb = fff_print_config.bed_shape.values; - m.arrange_objects( - fff_print_config.min_object_distance(), - // If we are going to use the merged model for printing, honor - // the configured print bed for arranging, otherwise do it freely. - this->has_print_action() ? &bb : nullptr - ); + if (this->has_print_action()) + arrange_objects(m, bed, arrange_cfg); + else + arrange_objects(m, InfiniteBed{}, arrange_cfg); } m_models.clear(); m_models.emplace_back(std::move(m)); } else if (opt_key == "duplicate") { - const BoundingBoxf &bb = fff_print_config.bed_shape.values; for (auto &model : m_models) { const bool all_objects_have_instances = std::none_of( model.objects.begin(), model.objects.end(), [](ModelObject* o){ return o->instances.empty(); } ); - if (all_objects_have_instances) { - // if all input objects have defined position(s) apply duplication to the whole model - model.duplicate(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb); - } else { - model.add_default_instances(); - model.duplicate_objects(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb); + + int dups = m_config.opt_int("duplicate"); + if (!all_objects_have_instances) model.add_default_instances(); + + try { + if (dups > 1) { + // if all input objects have defined position(s) apply duplication to the whole model + duplicate(model, size_t(dups), bed, arrange_cfg); + } else { + arrange_objects(model, bed, arrange_cfg); + } + } catch (std::exception &ex) { + boost::nowide::cerr << "error: " << ex.what() << std::endl; + return 1; } } } else if (opt_key == "duplicate_grid") { @@ -413,7 +423,8 @@ int CLI::run(int argc, char **argv) std::string outfile = m_config.opt_string("output"); Print fff_print; SLAPrint sla_print; - + SL1Archive sla_archive(sla_print.printer_config()); + sla_print.set_printer(&sla_archive); sla_print.set_status_callback( [](const PrintBase::SlicingStatus& s) { @@ -423,11 +434,11 @@ int CLI::run(int argc, char **argv) PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); if (! m_config.opt_bool("dont_arrange")) { - //FIXME make the min_object_distance configurable. - model.arrange_objects(fff_print.config().min_object_distance()); - model.center_instances_around_point((! user_center_specified && m_print_config.has("bed_shape")) ? - BoundingBoxf(m_print_config.opt("bed_shape")->values).center() : - m_config.option("center")->value); + if (user_center_specified) { + Vec2d c = m_config.option("center")->value; + arrange_objects(model, InfiniteBed{scaled(c)}, arrange_cfg); + } else + arrange_objects(model, bed, arrange_cfg); } if (printer_technology == ptFFF) { for (auto* mo : model.objects) @@ -457,7 +468,7 @@ int CLI::run(int argc, char **argv) outfile = sla_print.output_filepath(outfile); // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata outfile_final = sla_print.print_statistics().finalize_output_path(outfile); - sla_print.export_raster(outfile_final); + sla_archive.export_print(outfile_final, sla_print); } if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final)) { boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; @@ -613,6 +624,8 @@ bool CLI::setup(int argc, char **argv) if (opt_loglevel != 0) set_logging_level(opt_loglevel->value); } + + std::string validity = m_config.validate(); // Initialize with defaults. for (const t_optiondef_map *options : { &cli_actions_config_def.options, &cli_transform_config_def.options, &cli_misc_config_def.options }) @@ -620,6 +633,11 @@ bool CLI::setup(int argc, char **argv) m_config.option(optdef.first, true); set_data_dir(m_config.opt_string("datadir")); + + if (!validity.empty()) { + boost::nowide::cerr << "error: " << validity << std::endl; + return false; + } return true; } diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 72e239a70..c447cfcd9 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -982,6 +982,9 @@ template inline double area(const S& poly, const PolygonTag& ) }); } +template +inline double area(const RawShapes& shapes, const MultiPolygonTag&); + template // Dispatching function inline double area(const S& sh) { diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index b6d7fcdcf..5eef28c40 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -27,6 +27,7 @@ using Coord = TCoord; using Box = _Box; using Segment = _Segment; using Circle = _Circle; +using MultiPolygon = TMultiShape; using Item = _Item; using Rectangle = _Rectangle; diff --git a/src/libnest2d/tools/svgtools.hpp b/src/libnest2d/tools/svgtools.hpp index e1ed1ad05..2a05b551d 100644 --- a/src/libnest2d/tools/svgtools.hpp +++ b/src/libnest2d/tools/svgtools.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include namespace libnest2d { namespace svg { @@ -48,23 +48,28 @@ public: conf_.width = static_cast(box.width()) / conf_.mm_in_coord_units; } - - void writeItem(const Item& item) { + + void writeShape(RawShape tsh) { if(svg_layers_.empty()) addLayer(); - auto tsh = item.transformedShape(); if(conf_.origo_location == BOTTOMLEFT) { auto d = static_cast( - std::round(conf_.height*conf_.mm_in_coord_units) ); - + std::round(conf_.height*conf_.mm_in_coord_units) ); + auto& contour = shapelike::contour(tsh); for(auto& v : contour) setY(v, -getY(v) + d); - + auto& holes = shapelike::holes(tsh); for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d); - + } - currentLayer() += shapelike::serialize(tsh, - 1.0/conf_.mm_in_coord_units) + "\n"; + currentLayer() += + shapelike::serialize(tsh, + 1.0 / conf_.mm_in_coord_units) + + "\n"; + } + + void writeItem(const Item& item) { + writeShape(item.transformedShape()); } void writePackGroup(const PackGroup& result) { diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index b8ef0bcdc..5b048b0ff 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -1,7 +1,6 @@ #include "Arrange.hpp" -#include "Geometry.hpp" +//#include "Geometry.hpp" #include "SVG.hpp" -#include "MTUtils.hpp" #include #include @@ -83,7 +82,7 @@ const double BIG_ITEM_TRESHOLD = 0.02; // Fill in the placer algorithm configuration with values carefully chosen for // Slic3r. template -void fillConfig(PConf& pcfg) { +void fill_config(PConf& pcfg) { // Align the arranged pile into the center of the bin pcfg.alignment = PConf::Alignment::CENTER; @@ -105,7 +104,7 @@ void fillConfig(PConf& pcfg) { // Apply penalty to object function result. This is used only when alignment // after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN) -double fixed_overfit(const std::tuple& result, const Box &binbb) +static double fixed_overfit(const std::tuple& result, const Box &binbb) { double score = std::get<0>(result); Box pilebb = std::get<1>(result); @@ -312,7 +311,7 @@ public: , m_bin_area(sl::area(bin)) , m_norm(std::sqrt(m_bin_area)) { - fillConfig(m_pconf); + fill_config(m_pconf); // Set up a callback that is called just before arranging starts // This functionality is provided by the Nester class (m_pack). @@ -363,6 +362,9 @@ public: m_item_count = 0; } + PConfig& config() { return m_pconf; } + const PConfig& config() const { return m_pconf; } + inline void preload(std::vector& fixeditems) { m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; auto bb = sl::boundingBox(m_bin); @@ -438,127 +440,6 @@ std::function AutoArranger::get_objfn() }; } -inline Circle to_lnCircle(const CircleBed& circ) { - return Circle({circ.center()(0), circ.center()(1)}, circ.radius()); -} - -// Get the type of bed geometry from a simple vector of points. -void BedShapeHint::reset(BedShapes type) -{ - if (m_type != type) { - if (m_type == bsIrregular) - m_bed.polygon.Slic3r::Polyline::~Polyline(); - else if (type == bsIrregular) - ::new (&m_bed.polygon) Polyline(); - } - - m_type = type; -} - -BedShapeHint::BedShapeHint(const Polyline &bed) { - auto x = [](const Point& p) { return p(X); }; - auto y = [](const Point& p) { return p(Y); }; - - 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(); - - CircleBed ret(center, avg_dist); - for(auto el : vertex_distances) - { - if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { - ret = CircleBed(); - break; - } - } - - return ret; - }; - - auto parea = poly_area(bed); - - if( (1.0 - parea/area(bb)) < 1e-3 ) { - m_type = BedShapes::bsBox; - m_bed.box = bb; - } - else if(auto c = isCircle(bed)) { - m_type = BedShapes::bsCircle; - m_bed.circ = c; - } else { - assert(m_type != BedShapes::bsIrregular); - m_type = BedShapes::bsIrregular; - ::new (&m_bed.polygon) Polyline(bed); - } -} - -BedShapeHint &BedShapeHint::operator=(BedShapeHint &&cpy) -{ - reset(cpy.m_type); - - switch(m_type) { - case bsBox: m_bed.box = std::move(cpy.m_bed.box); break; - case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break; - case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break; - case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break; - case bsUnknown: break; - } - - return *this; -} - -BedShapeHint &BedShapeHint::operator=(const BedShapeHint &cpy) -{ - reset(cpy.m_type); - - switch(m_type) { - case bsBox: m_bed.box = cpy.m_bed.box; break; - case bsCircle: m_bed.circ = cpy.m_bed.circ; break; - case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break; - case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break; - case bsUnknown: break; - } - - return *this; -} - template void remove_large_items(std::vector &items, Bin &&bin) { auto it = items.begin(); @@ -572,12 +453,12 @@ void _arrange( std::vector & shapes, std::vector & excludes, const BinT & bin, - coord_t minobjd, + const ArrangeParams & params, std::function progressfn, std::function stopfn) { // Integer ceiling the min distance from the bed perimeters - coord_t md = minobjd; + coord_t md = params.min_obj_distance; md = (md % 2) ? md / 2 + 1 : md / 2; auto corrected_bin = bin; @@ -585,7 +466,10 @@ void _arrange( AutoArranger arranger{corrected_bin, progressfn, stopfn}; - auto infl = coord_t(std::ceil(minobjd / 2.0)); + arranger.config().accuracy = params.accuracy; + arranger.config().parallel = params.parallel; + + auto infl = coord_t(std::ceil(params.min_obj_distance / 2.0)); for (Item& itm : shapes) itm.inflate(infl); for (Item& itm : excludes) itm.inflate(infl); @@ -603,44 +487,106 @@ void _arrange( for (Item &itm : inp) itm.inflate(-infl); } -// The final client function for arrangement. A progress indicator and -// a stop predicate can be also be passed to control the process. -void arrange(ArrangePolygons & arrangables, - const ArrangePolygons & excludes, - coord_t min_obj_dist, - const BedShapeHint & bedhint, - std::function progressind, - std::function stopcondition) +inline Box to_nestbin(const BoundingBox &bb) { return Box{{bb.min(X), bb.min(Y)}, {bb.max(X), bb.max(Y)}};} +inline Circle to_nestbin(const CircleBed &c) { return Circle({c.center()(0), c.center()(1)}, c.radius()); } +inline clppr::Polygon to_nestbin(const Polygon &p) { return sl::create(Slic3rMultiPoint_to_ClipperPath(p)); } +inline Box to_nestbin(const InfiniteBed &bed) { return Box::infinite({bed.center.x(), bed.center.y()}); } + +inline coord_t width(const BoundingBox& box) { return box.max.x() - box.min.x(); } +inline coord_t height(const BoundingBox& box) { return box.max.y() - box.min.y(); } +inline double area(const BoundingBox& box) { return double(width(box)) * height(box); } +inline double poly_area(const Points &pts) { return std::abs(Polygon::area(pts)); } +inline double distance_to(const Point& p1, const Point& p2) +{ + double dx = p2.x() - p1.x(); + double dy = p2.y() - p1.y(); + return std::sqrt(dx*dx + dy*dy); +} + +static CircleBed to_circle(const Point ¢er, const Points& points) { + std::vector vertex_distances; + double avg_dist = 0; + + for (auto pt : points) + { + double distance = distance_to(center, pt); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + + CircleBed ret(center, avg_dist); + for(auto el : vertex_distances) + { + if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { + ret = {}; + break; + } + } + + return ret; +} + +// Create Item from Arrangeable +static void process_arrangeable(const ArrangePolygon &arrpoly, + std::vector & outp) +{ + Polygon p = arrpoly.poly.contour; + const Vec2crd &offs = arrpoly.translation; + double rotation = arrpoly.rotation; + + if (p.is_counter_clockwise()) p.reverse(); + + clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); + + if (!clpath.Contour.empty()) { + auto firstp = clpath.Contour.front(); + clpath.Contour.emplace_back(firstp); + } + + outp.emplace_back(std::move(clpath)); + outp.back().rotation(rotation); + outp.back().translation({offs.x(), offs.y()}); + outp.back().binId(arrpoly.bed_idx); + outp.back().priority(arrpoly.priority); +} + +template<> +void arrange(ArrangePolygons & items, + const ArrangePolygons &excludes, + const Points & bed, + const ArrangeParams & params) +{ + if (bed.empty()) + arrange(items, excludes, InfiniteBed{}, params); + else if (bed.size() == 1) + arrange(items, excludes, InfiniteBed{bed.front()}, params); + else { + auto bb = BoundingBox(bed); + CircleBed circ = to_circle(bb.center(), bed); + auto parea = poly_area(bed); + + if ((1.0 - parea / area(bb)) < 1e-3) + arrange(items, excludes, bb, params); + else if (!std::isnan(circ.radius())) + arrange(items, excludes, circ, params); + else + arrange(items, excludes, Polygon(bed), params); + } +} + +template +void arrange(ArrangePolygons & arrangables, + const ArrangePolygons &excludes, + const BedT & bed, + const ArrangeParams & params) { namespace clppr = ClipperLib; std::vector items, fixeditems; items.reserve(arrangables.size()); - // Create Item from Arrangeable - auto process_arrangeable = [](const ArrangePolygon &arrpoly, - std::vector & outp) - { - Polygon p = arrpoly.poly.contour; - const Vec2crd &offs = arrpoly.translation; - double rotation = arrpoly.rotation; - - if (p.is_counter_clockwise()) p.reverse(); - - clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); - - if (!clpath.Contour.empty()) { - auto firstp = clpath.Contour.front(); - clpath.Contour.emplace_back(firstp); - } - - outp.emplace_back(std::move(clpath)); - outp.back().rotation(rotation); - outp.back().translation({offs.x(), offs.y()}); - outp.back().binId(arrpoly.bed_idx); - outp.back().priority(arrpoly.priority); - }; - for (ArrangePolygon &arrangeable : arrangables) process_arrangeable(arrangeable, items); @@ -649,45 +595,10 @@ void arrange(ArrangePolygons & arrangables, for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON)); - auto &cfn = stopcondition; - auto &pri = progressind; + auto &cfn = params.stopcondition; + auto &pri = params.progressind; - switch (bedhint.get_type()) { - case bsBox: { - // Create the arranger for the box shaped bed - BoundingBox bbb = bedhint.get_box(); - Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; - - _arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn); - break; - } - case bsCircle: { - auto cc = to_lnCircle(bedhint.get_circle()); - - _arrange(items, fixeditems, cc, min_obj_dist, pri, cfn); - break; - } - case bsIrregular: { - auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular()); - auto irrbed = sl::create(std::move(ctour)); - BoundingBox polybb(bedhint.get_irregular()); - - _arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn); - break; - } - case bsInfinite: { - const InfiniteBed& nobin = bedhint.get_infinite(); - auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()}); - - _arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn); - break; - } - case bsUnknown: { - // We know nothing about the bed, let it be infinite and zero centered - _arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn); - break; - } - } + _arrange(items, fixeditems, to_nestbin(bed), params, pri, cfn); for(size_t i = 0; i < items.size(); ++i) { clppr::IntPoint tr = items[i].translation(); @@ -697,15 +608,10 @@ void arrange(ArrangePolygons & arrangables, } } -// Arrange, without the fixed items (excludes) -void arrange(ArrangePolygons & inp, - coord_t min_d, - const BedShapeHint & bedhint, - std::function prfn, - std::function stopfn) -{ - arrange(inp, {}, min_d, bedhint, prfn, stopfn); -} +template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms); +template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms); +template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms); +template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms); } // namespace arr } // namespace Slic3r diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 1cfe1c907..352e9e1cf 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -1,12 +1,10 @@ -#ifndef MODELARRANGE_HPP -#define MODELARRANGE_HPP +#ifndef ARRANGE_HPP +#define ARRANGE_HPP #include "ExPolygon.hpp" #include "BoundingBox.hpp" -namespace Slic3r { - -namespace arrangement { +namespace Slic3r { namespace arrangement { /// A geometry abstraction for a circular print bed. Similarly to BoundingBox. class CircleBed { @@ -15,96 +13,16 @@ class CircleBed { public: inline CircleBed(): center_(0, 0), radius_(std::nan("")) {} - inline CircleBed(const Point& c, double r): center_(c), radius_(r) {} + explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {} inline double radius() const { return radius_; } inline const Point& center() const { return center_; } - inline operator bool() { return !std::isnan(radius_); } }; /// Representing an unbounded bed. -struct InfiniteBed { Point center; }; - -/// Types of print bed shapes. -enum BedShapes { - bsBox, - bsCircle, - bsIrregular, - bsInfinite, - bsUnknown -}; - -/// Info about the print bed for the arrange() function. This is a variant -/// holding one of the four shapes a bed can be. -class BedShapeHint { - BedShapes m_type = BedShapes::bsInfinite; - - // The union neither calls constructors nor destructors of its members. - // The only member with non-trivial constructor / destructor is the polygon, - // a placement new / delete needs to be called over it. - union BedShape_u { // TODO: use variant from cpp17? - CircleBed circ; - BoundingBox box; - Polyline polygon; - InfiniteBed infbed{}; - ~BedShape_u() {} - BedShape_u() {} - } m_bed; - - // Reset the type, allocate m_bed properly - void reset(BedShapes type); - -public: - - BedShapeHint(){} - - /// Get a bed shape hint for arrange() from a naked Polyline. - explicit BedShapeHint(const Polyline &polyl); - explicit BedShapeHint(const BoundingBox &bb) - { - m_type = bsBox; m_bed.box = bb; - } - - explicit BedShapeHint(const CircleBed &c) - { - m_type = bsCircle; m_bed.circ = c; - } - - explicit BedShapeHint(const InfiniteBed &ibed) - { - m_type = bsInfinite; m_bed.infbed = ibed; - } - - ~BedShapeHint() - { - if (m_type == BedShapes::bsIrregular) - m_bed.polygon.Slic3r::Polyline::~Polyline(); - } - - BedShapeHint(const BedShapeHint &cpy) { *this = cpy; } - BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); } - - BedShapeHint &operator=(const BedShapeHint &cpy); - BedShapeHint& operator=(BedShapeHint &&cpy); - - BedShapes get_type() const { return m_type; } - - const BoundingBox &get_box() const - { - assert(m_type == bsBox); return m_bed.box; - } - const CircleBed &get_circle() const - { - assert(m_type == bsCircle); return m_bed.circ; - } - const Polyline &get_irregular() const - { - assert(m_type == bsIrregular); return m_bed.polygon; - } - const InfiniteBed &get_infinite() const - { - assert(m_type == bsInfinite); return m_bed.infbed; - } +struct InfiniteBed { + Point center; + explicit InfiniteBed(const Point &p = {0, 0}): center{p} {} }; /// A logical bed representing an object not being arranged. Either the arrange @@ -125,9 +43,14 @@ struct ArrangePolygon { ExPolygon poly; /// The 2D silhouette to be arranged Vec2crd translation{0, 0}; /// The translation of the poly double rotation{0.0}; /// The rotation of the poly in radians + coord_t inflation = 0; /// Arrange with inflated polygon int bed_idx{UNARRANGED}; /// To which logical bed does poly belong... int priority{0}; + // If empty, any rotation is allowed (currently unsupported) + // If only a zero is there, no rotation is allowed + std::vector allowed_rotations = {0.}; + /// Optional setter function which can store arbitrary data in its closure std::function setter = nullptr; @@ -140,6 +63,30 @@ struct ArrangePolygon { using ArrangePolygons = std::vector; +struct ArrangeParams { + + /// The minimum distance which is allowed for any + /// pair of items on the print bed in any direction. + coord_t min_obj_distance = 0.; + + /// The accuracy of optimization. + /// Goes from 0.0 to 1.0 and scales performance as well + float accuracy = 0.65f; + + /// Allow parallel execution. + bool parallel = true; + + /// Progress indicator callback called when an object gets packed. + /// The unsigned argument is the number of items remaining to pack. + std::function progressind; + + /// A predicate returning true if abort is needed. + std::function stopcondition; + + ArrangeParams() = default; + explicit ArrangeParams(coord_t md) : min_obj_distance(md) {} +}; + /** * \brief Arranges the input polygons. * @@ -150,33 +97,23 @@ using ArrangePolygons = std::vector; * \param items Input vector of ArrangePolygons. The transformation, rotation * and bin_idx fields will be changed after the call finished and can be used * to apply the result on the input polygon. - * - * \param min_obj_distance The minimum distance which is allowed for any - * pair of items on the print bed in any direction. - * - * \param bedhint Info about the shape and type of the bed. - * - * \param progressind Progress indicator callback called when - * an object gets packed. The unsigned argument is the number of items - * remaining to pack. - * - * \param stopcondition A predicate returning true if abort is needed. */ -void arrange(ArrangePolygons & items, - coord_t min_obj_distance, - const BedShapeHint & bedhint, - std::function progressind = nullptr, - std::function stopcondition = nullptr); +template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams ¶ms = {}); -/// Same as the previous, only that it takes unmovable items as an -/// additional argument. Those will be considered as already arranged objects. -void arrange(ArrangePolygons & items, - const ArrangePolygons & excludes, - coord_t min_obj_distance, - const BedShapeHint & bedhint, - std::function progressind = nullptr, - std::function stopcondition = nullptr); +// A dispatch function that determines the bed shape from a set of points. +template<> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Points &bed, const ArrangeParams ¶ms); + +extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms); +extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms); +extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms); +extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms); + +inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } +inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } +inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } +inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } +inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } + +}} // namespace Slic3r::arrangement -} // arr -} // Slic3r #endif // MODELARRANGE_HPP diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 4fbe72163..2216d7888 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -186,6 +186,11 @@ inline bool empty(const BoundingBox3Base &bb) return ! bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1) || bb.min(2) >= bb.max(2); } +inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; } +inline BoundingBox3 scaled(const BoundingBoxf3 &bb) { return {scaled(bb.min), scaled(bb.max)}; } +inline BoundingBoxf unscaled(const BoundingBox &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } +inline BoundingBoxf3 unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } + } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 2af10d024..b55602454 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -77,6 +77,8 @@ add_library(libslic3r STATIC Format/PRUS.hpp Format/STL.cpp Format/STL.hpp + Format/SL1.hpp + Format/SL1.cpp GCode/Analyzer.cpp GCode/Analyzer.hpp GCode/ThumbnailData.cpp @@ -122,6 +124,8 @@ add_library(libslic3r STATIC Line.hpp Model.cpp Model.hpp + ModelArrange.hpp + ModelArrange.cpp CustomGCode.cpp CustomGCode.hpp Arrange.hpp @@ -162,6 +166,8 @@ add_library(libslic3r STATIC SLAPrint.hpp Slicing.cpp Slicing.hpp + SlicesToTriangleMesh.hpp + SlicesToTriangleMesh.cpp SlicingAdaptive.cpp SlicingAdaptive.hpp SupportMaterial.cpp @@ -177,6 +183,8 @@ add_library(libslic3r STATIC Tesselate.hpp TriangleMesh.cpp TriangleMesh.hpp + TriangulateWall.hpp + TriangulateWall.cpp utils.cpp Utils.hpp Time.cpp @@ -191,6 +199,7 @@ add_library(libslic3r STATIC SimplifyMesh.hpp SimplifyMeshImpl.hpp SimplifyMesh.cpp + MarchingSquares.hpp ${OpenVDBUtils_SOURCES} SLA/Common.hpp SLA/Common.cpp @@ -208,10 +217,11 @@ add_library(libslic3r STATIC SLA/Rotfinder.cpp SLA/BoostAdapter.hpp SLA/SpatIndex.hpp - SLA/Raster.hpp - SLA/Raster.cpp - SLA/RasterWriter.hpp - SLA/RasterWriter.cpp + SLA/RasterBase.hpp + SLA/RasterBase.cpp + SLA/AGGRaster.hpp + SLA/RasterToPolygons.hpp + SLA/RasterToPolygons.cpp SLA/ConcaveHull.hpp SLA/ConcaveHull.cpp SLA/Hollowing.hpp diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp new file mode 100644 index 000000000..ba5e89330 --- /dev/null +++ b/src/libslic3r/Format/SL1.cpp @@ -0,0 +1,171 @@ +#include "SL1.hpp" +#include "GCode/ThumbnailData.hpp" +#include "libslic3r/Time.hpp" + +#include +#include + +#include "libslic3r/Zipper.hpp" +#include "libslic3r/SLAPrint.hpp" + +namespace Slic3r { + +using ConfMap = std::map; + +namespace { + +std::string to_ini(const ConfMap &m) +{ + std::string ret; + for (auto ¶m : m) ret += param.first + " = " + param.second + "\n"; + + return ret; +} + +std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) +{ + std::string ret; + + if (cfg.has(key)) { + auto opt = cfg.option(key); + if (opt) ret = opt->serialize(); + } + + return ret; +} + +void fill_iniconf(ConfMap &m, const SLAPrint &print) +{ + auto &cfg = print.full_print_config(); + m["layerHeight"] = get_cfg_value(cfg, "layer_height"); + m["expTime"] = get_cfg_value(cfg, "exposure_time"); + m["expTimeFirst"] = get_cfg_value(cfg, "initial_exposure_time"); + m["materialName"] = get_cfg_value(cfg, "sla_material_settings_id"); + m["printerModel"] = get_cfg_value(cfg, "printer_model"); + m["printerVariant"] = get_cfg_value(cfg, "printer_variant"); + m["printerProfile"] = get_cfg_value(cfg, "printer_settings_id"); + m["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id"); + m["fileCreationTimestamp"] = Utils::utc_timestamp(); + m["prusaSlicerVersion"] = SLIC3R_BUILD_ID; + + SLAPrintStatistics stats = print.print_statistics(); + // Set statistics values to the printer + + double used_material = (stats.objects_used_material + + stats.support_used_material) / 1000; + + int num_fade = print.default_object_config().faded_layers.getInt(); + num_fade = num_fade >= 0 ? num_fade : 0; + + m["usedMaterial"] = std::to_string(used_material); + m["numFade"] = std::to_string(num_fade); + m["numSlow"] = std::to_string(stats.slow_layers_count); + m["numFast"] = std::to_string(stats.fast_layers_count); + m["printTime"] = std::to_string(stats.estimated_print_time); + + m["action"] = "print"; +} + +void fill_slicerconf(ConfMap &m, const SLAPrint &print) +{ + using namespace std::literals::string_view_literals; + + // Sorted list of config keys, which shall not be stored into the ini. + static constexpr auto banned_keys = { + "compatible_printers"sv, + "compatible_prints"sv, + "print_host"sv, + "printhost_apikey"sv, + "printhost_cafile"sv + }; + + assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); + auto is_banned = [](const std::string &key) { + return std::binary_search(banned_keys.begin(), banned_keys.end(), key); + }; + + auto &cfg = print.full_print_config(); + for (const std::string &key : cfg.keys()) + if (! is_banned(key) && ! cfg.option(key)->is_nil()) + m[key] = cfg.opt_serialize(key); + +} + +} // namespace + +uqptr SL1Archive::create_raster() const +{ + sla::RasterBase::Resolution res; + sla::RasterBase::PixelDim pxdim; + std::array mirror; + + double w = m_cfg.display_width.getFloat(); + double h = m_cfg.display_height.getFloat(); + auto pw = size_t(m_cfg.display_pixels_x.getInt()); + auto ph = size_t(m_cfg.display_pixels_y.getInt()); + + mirror[X] = m_cfg.display_mirror_x.getBool(); + mirror[Y] = m_cfg.display_mirror_y.getBool(); + + auto ro = m_cfg.display_orientation.getInt(); + sla::RasterBase::Orientation orientation = + ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait : + sla::RasterBase::roLandscape; + + if (orientation == sla::RasterBase::roPortrait) { + std::swap(w, h); + std::swap(pw, ph); + } + + res = sla::RasterBase::Resolution{pw, ph}; + pxdim = sla::RasterBase::PixelDim{w / pw, h / ph}; + sla::RasterBase::Trafo tr{orientation, mirror}; + + double gamma = m_cfg.gamma_correction.getFloat(); + + return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr); +} + +sla::EncodedRaster SL1Archive::encode_raster(const sla::RasterBase &rst) const +{ + return rst.encode(sla::PNGRasterEncoder()); +} + +void SL1Archive::export_print(Zipper& zipper, + const SLAPrint &print, + const std::string &prjname) +{ + std::string project = + prjname.empty() ? + boost::filesystem::path(zipper.get_filename()).stem().string() : + prjname; + + ConfMap iniconf, slicerconf; + fill_iniconf(iniconf, print); + + iniconf["jobDir"] = project; + + fill_slicerconf(slicerconf, print); + + try { + zipper.add_entry("config.ini"); + zipper << to_ini(iniconf); + zipper.add_entry("prusaslicer.ini"); + zipper << to_ini(slicerconf); + + size_t i = 0; + for (const sla::EncodedRaster &rst : m_layers) { + + std::string imgname = project + string_printf("%.5d", i++) + "." + + rst.extension(); + + zipper.add_entry(imgname.c_str(), rst.data(), rst.size()); + } + } catch(std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + // Rethrow the exception + throw; + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp new file mode 100644 index 000000000..1b9e95392 --- /dev/null +++ b/src/libslic3r/Format/SL1.hpp @@ -0,0 +1,44 @@ +#ifndef ARCHIVETRAITS_HPP +#define ARCHIVETRAITS_HPP + +#include + +#include "libslic3r/Zipper.hpp" +#include "libslic3r/SLAPrint.hpp" + +namespace Slic3r { + +class SL1Archive: public SLAPrinter { + SLAPrinterConfig m_cfg; + +protected: + uqptr create_raster() const override; + sla::EncodedRaster encode_raster(const sla::RasterBase &rst) const override; + +public: + + SL1Archive() = default; + explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {} + explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {} + + void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = ""); + void export_print(const std::string &fname, const SLAPrint &print, const std::string &projectname = "") + { + Zipper zipper(fname); + export_print(zipper, print, projectname); + } + + void apply(const SLAPrinterConfig &cfg) override + { + auto diff = m_cfg.diff(cfg); + if (!diff.empty()) { + m_cfg.apply_only(cfg, diff); + m_layers = {}; + } + } +}; + + +} // namespace Slic3r::sla + +#endif // ARCHIVETRAITS_HPP diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 3402d2c85..294f9ae6f 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -11,6 +11,7 @@ #include "libslic3r.h" #include "Point.hpp" +#include "BoundingBox.hpp" namespace Slic3r { @@ -75,143 +76,6 @@ public: } }; -/// An std compatible random access iterator which uses indices to the -/// source vector thus resistant to invalidation caused by relocations. It -/// also "knows" its container. No comparison is neccesary to the container -/// "end()" iterator. The template can be instantiated with a different -/// value type than that of the container's but the types must be -/// compatible. E.g. a base class of the contained objects is compatible. -/// -/// For a constant iterator, one can instantiate this template with a value -/// type preceded with 'const'. -template -class IndexBasedIterator -{ - static const size_t NONE = size_t(-1); - - std::reference_wrapper m_index_ref; - size_t m_idx = NONE; - -public: - using value_type = Value; - using pointer = Value *; - using reference = Value &; - using difference_type = long; - using iterator_category = std::random_access_iterator_tag; - - inline explicit IndexBasedIterator(Vector &index, size_t idx) - : m_index_ref(index), m_idx(idx) - {} - - // Post increment - inline IndexBasedIterator operator++(int) - { - IndexBasedIterator cpy(*this); - ++m_idx; - return cpy; - } - - inline IndexBasedIterator operator--(int) - { - IndexBasedIterator cpy(*this); - --m_idx; - return cpy; - } - - inline IndexBasedIterator &operator++() - { - ++m_idx; - return *this; - } - - inline IndexBasedIterator &operator--() - { - --m_idx; - return *this; - } - - inline IndexBasedIterator &operator+=(difference_type l) - { - m_idx += size_t(l); - return *this; - } - - inline IndexBasedIterator operator+(difference_type l) - { - auto cpy = *this; - cpy += l; - return cpy; - } - - inline IndexBasedIterator &operator-=(difference_type l) - { - m_idx -= size_t(l); - return *this; - } - - inline IndexBasedIterator operator-(difference_type l) - { - auto cpy = *this; - cpy -= l; - return cpy; - } - - operator difference_type() { return difference_type(m_idx); } - - /// Tesing the end of the container... this is not possible with std - /// iterators. - inline bool is_end() const - { - return m_idx >= m_index_ref.get().size(); - } - - inline Value &operator*() const - { - assert(m_idx < m_index_ref.get().size()); - return m_index_ref.get().operator[](m_idx); - } - - inline Value *operator->() const - { - assert(m_idx < m_index_ref.get().size()); - return &m_index_ref.get().operator[](m_idx); - } - - /// If both iterators point past the container, they are equal... - inline bool operator==(const IndexBasedIterator &other) - { - size_t e = m_index_ref.get().size(); - return m_idx == other.m_idx || (m_idx >= e && other.m_idx >= e); - } - - inline bool operator!=(const IndexBasedIterator &other) - { - return !(*this == other); - } - - inline bool operator<=(const IndexBasedIterator &other) - { - return (m_idx < other.m_idx) || (*this == other); - } - - inline bool operator<(const IndexBasedIterator &other) - { - return m_idx < other.m_idx && (*this != other); - } - - inline bool operator>=(const IndexBasedIterator &other) - { - return m_idx > other.m_idx || *this == other; - } - - inline bool operator>(const IndexBasedIterator &other) - { - return m_idx > other.m_idx && *this != other; - } -}; - /// A very simple range concept implementation with iterator-like objects. template class Range { @@ -252,97 +116,6 @@ template struct remove_cvref template using remove_cvref_t = typename remove_cvref::type; -// A shorter C++14 style form of the enable_if metafunction -template -using enable_if_t = typename std::enable_if::type; - -// ///////////////////////////////////////////////////////////////////////////// -// Type safe conversions to and from scaled and unscaled coordinates -// ///////////////////////////////////////////////////////////////////////////// - -// A meta-predicate which is true for integers wider than or equal to coord_t -template struct is_scaled_coord -{ - static const SLIC3R_CONSTEXPR bool value = - std::is_integral::value && - std::numeric_limits::digits >= - std::numeric_limits::digits; -}; - -// Meta predicates for floating, 'scaled coord' and generic arithmetic types -template -using FloatingOnly = enable_if_t::value, O>; - -template -using ScaledCoordOnly = enable_if_t::value, O>; - -template -using IntegerOnly = enable_if_t::value, O>; - -template -using ArithmeticOnly = enable_if_t::value, O>; - -// Semantics are the following: -// Upscaling (scaled()): only from floating point types (or Vec) to either -// floating point or integer 'scaled coord' coordinates. -// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only - -// Conversion definition from unscaled to floating point scaled -template> -inline constexpr FloatingOnly scaled(const Tin &v) noexcept -{ - return Tout(v / Tin(SCALING_FACTOR)); -} - -// Conversion definition from unscaled to integer 'scaled coord'. -// TODO: is the rounding necessary? Here it is commented out to show that -// it can be different for integers but it does not have to be. Using -// std::round means loosing noexcept and constexpr modifiers -template> -inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept -{ - //return static_cast(std::round(v / SCALING_FACTOR)); - return Tout(v / Tin(SCALING_FACTOR)); -} - -// Conversion for Eigen vectors (N dimensional points) -template, - int...EigenArgs> -inline Eigen::Matrix, N, EigenArgs...> -scaled(const Eigen::Matrix &v) -{ - return (v / SCALING_FACTOR).template cast(); -} - -// Conversion from arithmetic scaled type to floating point unscaled -template, - class = FloatingOnly> -inline constexpr Tout unscaled(const Tin &v) noexcept -{ - return Tout(v * Tout(SCALING_FACTOR)); -} - -// Unscaling for Eigen vectors. Input base type can be arithmetic, output base -// type can only be floating point. -template, - class = FloatingOnly, - int...EigenArgs> -inline constexpr Eigen::Matrix -unscaled(const Eigen::Matrix &v) noexcept -{ - return v.template cast() * SCALING_FACTOR; -} - template // Arbitrary allocator can be used inline IntegerOnly> reserve_vector(I capacity) { @@ -353,10 +126,10 @@ inline IntegerOnly> reserve_vector(I capacity) } /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html -template +template> inline std::vector linspace_vector(const ArithmeticOnly &start, const T &stop, - const IntegerOnly &n) + const I &n) { std::vector vals(n, T()); diff --git a/src/libslic3r/MarchingSquares.hpp b/src/libslic3r/MarchingSquares.hpp new file mode 100644 index 000000000..d5f07fbde --- /dev/null +++ b/src/libslic3r/MarchingSquares.hpp @@ -0,0 +1,448 @@ +#ifndef MARCHINGSQUARES_HPP +#define MARCHINGSQUARES_HPP + +#include +#include +#include +#include +#include + +namespace marchsq { + +// Marks a square in the grid +struct Coord { + long r = 0, c = 0; + + Coord() = default; + explicit Coord(long s) : r(s), c(s) {} + Coord(long _r, long _c): r(_r), c(_c) {} + + size_t seq(const Coord &res) const { return r * res.c + c; } + Coord& operator+=(const Coord& b) { r += b.r; c += b.c; return *this; } + Coord operator+(const Coord& b) const { Coord a = *this; a += b; return a; } +}; + +// Closed ring of cell coordinates +using Ring = std::vector; + +// Specialize this struct to register a raster type for the Marching squares alg +template struct _RasterTraits { + + // The type of pixel cell in the raster + using ValueType = typename T::ValueType; + + // Value at a given position + static ValueType get(const T &raster, size_t row, size_t col); + + // Number of rows and cols of the raster + static size_t rows(const T &raster); + static size_t cols(const T &raster); +}; + +// Specialize this to use parellel loops within the algorithm +template struct _Loop { + template static void for_each(It from, It to, Fn &&fn) + { + for (auto it = from; it < to; ++it) fn(*it, size_t(it - from)); + } +}; + +namespace __impl { + +template using RasterTraits = _RasterTraits>; +template using TRasterValue = typename RasterTraits::ValueType; + +template size_t rows(const T &raster) +{ + return RasterTraits::rows(raster); +} + +template size_t cols(const T &raster) +{ + return RasterTraits::cols(raster); +} + +template TRasterValue isoval(const T &rst, const Coord &crd) +{ + return RasterTraits::get(rst, crd.r, crd.c); +} + +template +void for_each(ExecutionPolicy&& policy, It from, It to, Fn &&fn) +{ + _Loop::for_each(from, to, fn); +} + +// Type of squares (tiles) depending on which vertices are inside an ROI +// The vertices would be marked a, b, c, d in counter clockwise order from the +// bottom left vertex of a square. +// d --- c +// | | +// | | +// a --- b +enum class SquareTag : uint8_t { +// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + none, a, b, ab, c, ac, bc, abc, d, ad, bd, abd, cd, acd, bcd, full +}; + +template constexpr std::underlying_type_t _t(E e) noexcept +{ + return static_cast>(e); +} + +enum class Dir: uint8_t { left, down, right, up, none}; + +static const constexpr Dir NEXT_CCW[] = { + /* 00 */ Dir::none, // SquareTag::none (empty square, nowhere to go) + /* 01 */ Dir::left, // SquareTag::a + /* 02 */ Dir::down, // SquareTag::b + /* 03 */ Dir::left, // SquareTag::ab + /* 04 */ Dir::right, // SquareTag::c + /* 05 */ Dir::none, // SquareTag::ac (ambiguous case) + /* 06 */ Dir::down, // SquareTag::bc + /* 07 */ Dir::left, // SquareTag::abc + /* 08 */ Dir::up, // SquareTag::d + /* 09 */ Dir::up, // SquareTag::ad + /* 10 */ Dir::none, // SquareTag::bd (ambiguous case) + /* 11 */ Dir::up, // SquareTag::abd + /* 12 */ Dir::right, // SquareTag::cd + /* 13 */ Dir::right, // SquareTag::acd + /* 14 */ Dir::down, // SquareTag::bcd + /* 15 */ Dir::none // SquareTag::full (full covered, nowhere to go) +}; + +static const constexpr uint8_t PREV_CCW[] = { + /* 00 */ 1 << _t(Dir::none), + /* 01 */ 1 << _t(Dir::up), + /* 02 */ 1 << _t(Dir::left), + /* 03 */ 1 << _t(Dir::left), + /* 04 */ 1 << _t(Dir::down), + /* 05 */ 1 << _t(Dir::up) | 1 << _t(Dir::down), + /* 06 */ 1 << _t(Dir::down), + /* 07 */ 1 << _t(Dir::down), + /* 08 */ 1 << _t(Dir::right), + /* 09 */ 1 << _t(Dir::up), + /* 10 */ 1 << _t(Dir::left) | 1 << _t(Dir::right), + /* 11 */ 1 << _t(Dir::left), + /* 12 */ 1 << _t(Dir::right), + /* 13 */ 1 << _t(Dir::up), + /* 14 */ 1 << _t(Dir::right), + /* 15 */ 1 << _t(Dir::none) +}; + +const constexpr uint8_t DIRMASKS[] = { + /*left: */ 0x01, /*down*/ 0x12, /*right */0x21, /*up*/ 0x10, /*none*/ 0x00 +}; + +inline Coord step(const Coord &crd, Dir d) +{ + uint8_t dd = DIRMASKS[uint8_t(d)]; + return {crd.r - 1 + (dd & 0x0f), crd.c - 1 + (dd >> 4)}; +} + +template class Grid { + const Rst * m_rst = nullptr; + Coord m_cellsize, m_res_1, m_window, m_gridsize, m_grid_1; + std::vector m_tags; // Assign tags to each square + + Coord rastercoord(const Coord &crd) const + { + return {(crd.r - 1) * m_window.r, (crd.c - 1) * m_window.c}; + } + + Coord bl(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, 0}; } + Coord br(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, m_res_1.c}; } + Coord tr(const Coord &crd) const { return tl(crd) + Coord{0, m_res_1.c}; } + Coord tl(const Coord &crd) const { return rastercoord(crd); } + + bool is_within(const Coord &crd) + { + long R = rows(*m_rst), C = cols(*m_rst); + return crd.r >= 0 && crd.r < R && crd.c >= 0 && crd.c < C; + }; + + // Calculate the tag for a cell (or square). The cell coordinates mark the + // top left vertex of a square in the raster. v is the isovalue + uint8_t get_tag_for_cell(const Coord &cell, TRasterValue v) + { + Coord sqr[] = {bl(cell), br(cell), tr(cell), tl(cell)}; + + uint8_t t = ((is_within(sqr[0]) && isoval(*m_rst, sqr[0]) >= v)) + + ((is_within(sqr[1]) && isoval(*m_rst, sqr[1]) >= v) << 1) + + ((is_within(sqr[2]) && isoval(*m_rst, sqr[2]) >= v) << 2) + + ((is_within(sqr[3]) && isoval(*m_rst, sqr[3]) >= v) << 3); + + assert(t < 16); + return t; + } + + // Get a cell coordinate from a sequential index + Coord coord(size_t i) const + { + return {long(i) / m_gridsize.c, long(i) % m_gridsize.c}; + } + + size_t seq(const Coord &crd) const { return crd.seq(m_gridsize); } + + bool is_visited(size_t idx, Dir d = Dir::none) const + { + SquareTag t = get_tag(idx); + uint8_t ref = d == Dir::none ? PREV_CCW[_t(t)] : uint8_t(1 << _t(d)); + return t == SquareTag::full || t == SquareTag::none || + ((m_tags[idx] & 0xf0) >> 4) == ref; + } + + void set_visited(size_t idx, Dir d = Dir::none) + { + m_tags[idx] |= (1 << (_t(d)) << 4); + } + + bool is_ambiguous(size_t idx) const + { + SquareTag t = get_tag(idx); + return t == SquareTag::ac || t == SquareTag::bd; + } + + // Search for a new starting square + size_t search_start_cell(size_t i = 0) const + { + // Skip ambiguous tags as starting tags due to unknown previous + // direction. + while ((i < m_tags.size()) && (is_visited(i) || is_ambiguous(i))) ++i; + + return i; + } + + SquareTag get_tag(size_t idx) const { return SquareTag(m_tags[idx] & 0x0f); } + + Dir next_dir(Dir prev, SquareTag tag) const + { + // Treat ambiguous cases as two separate regions in one square. + switch (tag) { + case SquareTag::ac: + switch (prev) { + case Dir::down: return Dir::right; + case Dir::up: return Dir::left; + default: assert(false); return Dir::none; + } + case SquareTag::bd: + switch (prev) { + case Dir::right: return Dir::up; + case Dir::left: return Dir::down; + default: assert(false); return Dir::none; + } + default: + return NEXT_CCW[uint8_t(tag)]; + } + + return Dir::none; + } + + struct CellIt { + Coord crd; Dir dir= Dir::none; const Rst *grid = nullptr; + + TRasterValue operator*() const { return isoval(*grid, crd); } + CellIt& operator++() { crd = step(crd, dir); return *this; } + CellIt operator++(int) { CellIt it = *this; ++(*this); return it; } + bool operator!=(const CellIt &it) { return crd.r != it.crd.r || crd.c != it.crd.c; } + + using value_type = TRasterValue; + using pointer = TRasterValue *; + using reference = TRasterValue &; + using difference_type = long; + using iterator_category = std::forward_iterator_tag; + }; + + // Two cell iterators representing an edge of a square. This is then + // used for binary search for the first active pixel on the edge. + struct Edge { CellIt from, to; }; + + Edge _edge(const Coord &ringvertex) const + { + size_t idx = ringvertex.r; + Coord cell = coord(idx); + uint8_t tg = m_tags[ringvertex.r]; + SquareTag t = SquareTag(tg & 0x0f); + + switch (t) { + case SquareTag::a: + case SquareTag::ab: + case SquareTag::abc: + return {{tl(cell), Dir::down, m_rst}, {bl(cell)}}; + case SquareTag::b: + case SquareTag::bc: + case SquareTag::bcd: + return {{bl(cell), Dir::right, m_rst}, {br(cell)}}; + case SquareTag::c: + return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; + case SquareTag::ac: + switch (Dir(ringvertex.c)) { + case Dir::left: return {{tl(cell), Dir::down, m_rst}, {bl(cell)}}; + case Dir::right: return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; + default: assert(false); + } + case SquareTag::d: + case SquareTag::ad: + case SquareTag::abd: + return {{tr(cell), Dir::left, m_rst}, {tl(cell)}}; + case SquareTag::bd: + switch (Dir(ringvertex.c)) { + case Dir::down: return {{bl(cell), Dir::right, m_rst}, {br(cell)}}; + case Dir::up: return {{tr(cell), Dir::left, m_rst}, {tl(cell)}}; + default: assert(false); + } + case SquareTag::cd: + case SquareTag::acd: + return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; + case SquareTag::full: + case SquareTag::none: { + Coord crd{tl(cell) + Coord{m_cellsize.r / 2, m_cellsize.c / 2}}; + return {{crd, Dir::none, m_rst}, crd}; + } + } + + return {}; + } + + Edge edge(const Coord &ringvertex) const + { + const long R = rows(*m_rst), C = cols(*m_rst); + const long R_1 = R - 1, C_1 = C - 1; + + Edge e = _edge(ringvertex); + e.to.dir = e.from.dir; + ++e.to; + + e.from.crd.r = std::min(e.from.crd.r, R_1); + e.from.crd.r = std::max(e.from.crd.r, 0l); + e.from.crd.c = std::min(e.from.crd.c, C_1); + e.from.crd.c = std::max(e.from.crd.c, 0l); + + e.to.crd.r = std::min(e.to.crd.r, R); + e.to.crd.r = std::max(e.to.crd.r, 0l); + e.to.crd.c = std::min(e.to.crd.c, C); + e.to.crd.c = std::max(e.to.crd.c, 0l); + + return e; + } + +public: + explicit Grid(const Rst &rst, const Coord &cellsz, const Coord &overlap) + : m_rst{&rst} + , m_cellsize{cellsz} + , m_res_1{m_cellsize.r - 1, m_cellsize.c - 1} + , m_window{overlap.r < cellsz.r ? cellsz.r - overlap.r : cellsz.r, + overlap.c < cellsz.c ? cellsz.c - overlap.c : cellsz.c} + , m_gridsize{2 + (long(rows(rst)) - overlap.r) / m_window.r, + 2 + (long(cols(rst)) - overlap.c) / m_window.c} + , m_tags(m_gridsize.r * m_gridsize.c, 0) + {} + + // Go through the cells and mark them with the appropriate tag. + template + void tag_grid(ExecutionPolicy &&policy, TRasterValue isoval) + { + // parallel for r + for_each (std::forward(policy), + m_tags.begin(), m_tags.end(), + [this, isoval](uint8_t& tag, size_t idx) { + tag = get_tag_for_cell(coord(idx), isoval); + }); + } + + // Scan for the rings on the tagged grid. Each ring vertex stores the + // sequential index of the cell and the next direction (Dir). + // This info can be used later to calculate the exact raster coordinate. + std::vector scan_rings() + { + std::vector rings; + size_t startidx = 0; + while ((startidx = search_start_cell(startidx)) < m_tags.size()) { + Ring ring; + + size_t idx = startidx; + Dir prev = Dir::none, next = next_dir(prev, get_tag(idx)); + + while (next != Dir::none && !is_visited(idx, prev)) { + Coord ringvertex{long(idx), long(next)}; + ring.emplace_back(ringvertex); + set_visited(idx, prev); + + idx = seq(step(coord(idx), next)); + prev = next; + next = next_dir(next, get_tag(idx)); + } + + // To prevent infinite loops in case of degenerate input + if (next == Dir::none) m_tags[startidx] = _t(SquareTag::none); + + if (ring.size() > 1) { + ring.pop_back(); + rings.emplace_back(ring); + } + } + + return rings; + } + + // Calculate the exact raster position from the cells which store the + // sequantial index of the square and the next direction + template + void interpolate_rings(ExecutionPolicy && policy, + std::vector &rings, + TRasterValue isov) + { + for_each(std::forward(policy), + rings.begin(), rings.end(), [this, isov] (Ring &ring, size_t) + { + for (Coord &ringvertex : ring) { + Edge e = edge(ringvertex); + + CellIt found = std::lower_bound(e.from, e.to, isov); + ringvertex = found.crd; + } + }); + } +}; + +template +std::vector execute_with_policy(ExecutionPolicy && policy, + const Raster & raster, + TRasterValue isoval, + Coord windowsize = {}) +{ + if (!rows(raster) || !cols(raster)) return {}; + + size_t ratio = cols(raster) / rows(raster); + + if (!windowsize.r) windowsize.r = 2; + if (!windowsize.c) + windowsize.c = std::max(2l, long(windowsize.r * ratio)); + + Coord overlap{1}; + + Grid grid{raster, windowsize, overlap}; + + grid.tag_grid(std::forward(policy), isoval); + std::vector rings = grid.scan_rings(); + grid.interpolate_rings(std::forward(policy), rings, isoval); + + return rings; +} + +template +std::vector execute(const Raster &raster, + TRasterValue isoval, + Coord windowsize = {}) +{ + return execute_with_policy(nullptr, raster, isoval, windowsize); +} + +} // namespace __impl + +using __impl::execute_with_policy; +using __impl::execute; + +} // namespace marchsq + +#endif // MARCHINGSQUARES_HPP diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 59df8eb61..619d80c43 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,4 +1,5 @@ #include "Model.hpp" +#include "ModelArrange.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" @@ -355,116 +356,6 @@ TriangleMesh Model::mesh() const return mesh; } -static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out) -{ - if (sizes.empty()) - // return if the list is empty or the following call to BoundingBoxf constructor will lead to a crash - return true; - - // we supply unscaled data to arrange() - bool result = Slic3r::Geometry::arrange( - sizes.size(), // number of parts - BoundingBoxf(sizes).max, // width and height of a single cell - dist, // distance between cells - bb, // bounding box of the area to fill - out // output positions - ); - - if (!result && bb != nullptr) { - // Try to arrange again ignoring bb - result = Slic3r::Geometry::arrange( - sizes.size(), // number of parts - BoundingBoxf(sizes).max, // width and height of a single cell - dist, // distance between cells - nullptr, // bounding box of the area to fill - out // output positions - ); - } - - return result; -} - -/* arrange objects preserving their instance count - but altering their instance positions */ -bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) -{ - size_t count = 0; - for (auto obj : objects) count += obj->instances.size(); - - arrangement::ArrangePolygons input; - ModelInstancePtrs instances; - input.reserve(count); - instances.reserve(count); - for (ModelObject *mo : objects) - for (ModelInstance *minst : mo->instances) { - input.emplace_back(minst->get_arrange_polygon()); - instances.emplace_back(minst); - } - - arrangement::BedShapeHint bedhint; - coord_t bedwidth = 0; - - if (bb) { - bedwidth = scaled(bb->size().x()); - bedhint = arrangement::BedShapeHint( - BoundingBox(scaled(bb->min), scaled(bb->max))); - } - - arrangement::arrange(input, scaled(dist), bedhint); - - bool ret = true; - coord_t stride = bedwidth + bedwidth / 5; - - for(size_t i = 0; i < input.size(); ++i) { - if (input[i].bed_idx != 0) ret = false; - if (input[i].bed_idx >= 0) { - input[i].translation += Vec2crd{input[i].bed_idx * stride, 0}; - instances[i]->apply_arrange_result(input[i].translation.cast(), - input[i].rotation); - } - } - - return ret; -} - -// Duplicate the entire model preserving instance relative positions. -void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) -{ - Pointfs model_sizes(copies_num-1, to_2d(this->bounding_box().size())); - Pointfs positions; - if (! _arrange(model_sizes, dist, bb, positions)) - throw std::invalid_argument("Cannot duplicate part as the resulting objects would not fit on the print bed.\n"); - - // note that this will leave the object count unaltered - - for (ModelObject *o : this->objects) { - // make a copy of the pointers in order to avoid recursion when appending their copies - ModelInstancePtrs instances = o->instances; - for (const ModelInstance *i : instances) { - for (const Vec2d &pos : positions) { - ModelInstance *instance = o->add_instance(*i); - instance->set_offset(instance->get_offset() + Vec3d(pos(0), pos(1), 0.0)); - } - } - o->invalidate_bounding_box(); - } -} - -/* this will append more instances to each object - and then automatically rearrange everything */ -void Model::duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) -{ - for (ModelObject *o : this->objects) { - // make a copy of the pointers in order to avoid recursion when appending their copies - ModelInstancePtrs instances = o->instances; - for (const ModelInstance *i : instances) - for (size_t k = 2; k <= copies_num; ++ k) - o->add_instance(*i); - } - - this->arrange_objects(dist, bb); -} - void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist) { if (this->objects.size() > 1) throw "Grid duplication is not supported with multiple objects"; @@ -1991,6 +1882,7 @@ void check_model_ids_equal(const Model &model1, const Model &model2) } } } + #endif /* NDEBUG */ } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 2ddad9e59..a0c5f4e8a 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -802,11 +802,9 @@ public: bool center_instances_around_point(const Vec2d &point); void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } TriangleMesh mesh() const; - bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); + // Croaks if the duplicated objects do not fit the print bed. - void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); - void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); - void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); + void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); bool looks_like_multipart_object() const; void convert_multipart_object(unsigned int max_extruders); @@ -822,6 +820,7 @@ public: std::string propose_export_file_name_and_path(const std::string &new_extension) const; private: + explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }; void assign_new_unique_ids_recursive(); void update_links_bottom_up_recursive(); @@ -831,7 +830,7 @@ private: template void serialize(Archive &ar) { Internal::StaticSerializationWrapper wipe_tower_wrapper(wipe_tower); ar(materials, objects, wipe_tower_wrapper); - } + } }; #undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp new file mode 100644 index 000000000..85aa25a5f --- /dev/null +++ b/src/libslic3r/ModelArrange.cpp @@ -0,0 +1,83 @@ +#include "ModelArrange.hpp" +#include "MTUtils.hpp" + +namespace Slic3r { + +arrangement::ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances) +{ + size_t count = 0; + for (auto obj : model.objects) count += obj->instances.size(); + + ArrangePolygons input; + input.reserve(count); + instances.clear(); instances.reserve(count); + for (ModelObject *mo : model.objects) + for (ModelInstance *minst : mo->instances) { + input.emplace_back(minst->get_arrange_polygon()); + instances.emplace_back(minst); + } + + return input; +} + +bool apply_arrange_polys(ArrangePolygons &input, ModelInstancePtrs &instances, VirtualBedFn vfn) +{ + bool ret = true; + + for(size_t i = 0; i < input.size(); ++i) { + if (input[i].bed_idx != 0) { ret = false; if (vfn) vfn(input[i]); } + if (input[i].bed_idx >= 0) + instances[i]->apply_arrange_result(input[i].translation.cast(), + input[i].rotation); + } + + return ret; +} + +Slic3r::arrangement::ArrangePolygon get_arrange_poly(const Model &model) +{ + ArrangePolygon ap; + Points &apts = ap.poly.contour.points; + for (const ModelObject *mo : model.objects) + for (const ModelInstance *minst : mo->instances) { + ArrangePolygon obj_ap = minst->get_arrange_polygon(); + ap.poly.contour.rotate(obj_ap.rotation); + ap.poly.contour.translate(obj_ap.translation.x(), obj_ap.translation.y()); + const Points &pts = obj_ap.poly.contour.points; + std::copy(pts.begin(), pts.end(), std::back_inserter(apts)); + } + + apts = Geometry::convex_hull(apts); + return ap; +} + +void duplicate(Model &model, Slic3r::arrangement::ArrangePolygons &copies, VirtualBedFn vfn) +{ + for (ModelObject *o : model.objects) { + // make a copy of the pointers in order to avoid recursion when appending their copies + ModelInstancePtrs instances = o->instances; + o->instances.clear(); + for (const ModelInstance *i : instances) { + for (arrangement::ArrangePolygon &ap : copies) { + if (ap.bed_idx != 0) vfn(ap); + ModelInstance *instance = o->add_instance(*i); + Vec2d pos = unscale(ap.translation); + instance->set_offset(instance->get_offset() + to_3d(pos, 0.)); + } + } + o->invalidate_bounding_box(); + } +} + +void duplicate_objects(Model &model, size_t copies_num) +{ + for (ModelObject *o : model.objects) { + // make a copy of the pointers in order to avoid recursion when appending their copies + ModelInstancePtrs instances = o->instances; + for (const ModelInstance *i : instances) + for (size_t k = 2; k <= copies_num; ++ k) + o->add_instance(*i); + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp new file mode 100644 index 000000000..d65b0fd6d --- /dev/null +++ b/src/libslic3r/ModelArrange.hpp @@ -0,0 +1,68 @@ +#ifndef MODELARRANGE_HPP +#define MODELARRANGE_HPP + +#include +#include + +namespace Slic3r { + +using arrangement::ArrangePolygon; +using arrangement::ArrangePolygons; +using arrangement::ArrangeParams; +using arrangement::InfiniteBed; +using arrangement::CircleBed; + +// Do something with ArrangePolygons in virtual beds +using VirtualBedFn = std::function; + +[[noreturn]] inline void throw_if_out_of_bed(arrangement::ArrangePolygon&) +{ + throw std::runtime_error("Objects could not fit on the bed"); +} + +ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances); +ArrangePolygon get_arrange_poly(const Model &model); +bool apply_arrange_polys(ArrangePolygons &polys, ModelInstancePtrs &instances, VirtualBedFn); + +void duplicate(Model &model, ArrangePolygons &copies, VirtualBedFn); +void duplicate_objects(Model &model, size_t copies_num); + +template +bool arrange_objects(Model & model, + const TBed & bed, + const ArrangeParams ¶ms, + VirtualBedFn vfn = throw_if_out_of_bed) +{ + ModelInstancePtrs instances; + auto&& input = get_arrange_polys(model, instances); + arrangement::arrange(input, bed, params); + + return apply_arrange_polys(input, instances, vfn); +} + +template +void duplicate(Model & model, + size_t copies_num, + const TBed & bed, + const ArrangeParams ¶ms, + VirtualBedFn vfn = throw_if_out_of_bed) +{ + ArrangePolygons copies(copies_num, get_arrange_poly(model)); + arrangement::arrange(copies, bed, params); + duplicate(model, copies, vfn); +} + +template +void duplicate_objects(Model & model, + size_t copies_num, + const TBed & bed, + const ArrangeParams ¶ms, + VirtualBedFn vfn = throw_if_out_of_bed) +{ + duplicate_objects(model, copies_num); + arrange_objects(model, bed, params, vfn); +} + +} + +#endif // MODELARRANGE_HPP diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index e095f1c75..7a6b31395 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -288,6 +288,72 @@ private: std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf); + +// ///////////////////////////////////////////////////////////////////////////// +// Type safe conversions to and from scaled and unscaled coordinates +// ///////////////////////////////////////////////////////////////////////////// + +// Semantics are the following: +// Upscaling (scaled()): only from floating point types (or Vec) to either +// floating point or integer 'scaled coord' coordinates. +// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only + +// Conversion definition from unscaled to floating point scaled +template> +inline constexpr FloatingOnly scaled(const Tin &v) noexcept +{ + return Tout(v / Tin(SCALING_FACTOR)); +} + +// Conversion definition from unscaled to integer 'scaled coord'. +// TODO: is the rounding necessary? Here it is commented out to show that +// it can be different for integers but it does not have to be. Using +// std::round means loosing noexcept and constexpr modifiers +template> +inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept +{ + //return static_cast(std::round(v / SCALING_FACTOR)); + return Tout(v / Tin(SCALING_FACTOR)); +} + +// Conversion for Eigen vectors (N dimensional points) +template, + int...EigenArgs> +inline Eigen::Matrix, N, EigenArgs...> +scaled(const Eigen::Matrix &v) +{ + return (v / SCALING_FACTOR).template cast(); +} + +// Conversion from arithmetic scaled type to floating point unscaled +template, + class = FloatingOnly> +inline constexpr Tout unscaled(const Tin &v) noexcept +{ + return Tout(v * Tout(SCALING_FACTOR)); +} + +// Unscaling for Eigen vectors. Input base type can be arithmetic, output base +// type can only be floating point. +template, + class = FloatingOnly, + int...EigenArgs> +inline constexpr Eigen::Matrix +unscaled(const Eigen::Matrix &v) noexcept +{ + return v.template cast() * SCALING_FACTOR; +} + } // namespace Slic3r // start Boost diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index e1e299144..48e63dab3 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -48,12 +48,12 @@ int64_t Polygon::area2x() const } */ -double Polygon::area() const +double Polygon::area(const Points &points) { size_t n = points.size(); if (n < 3) return 0.; - + double a = 0.; for (size_t i = 0, j = n - 1; i < n; ++i) { a += ((double)points[j](0) + (double)points[i](0)) * ((double)points[i](1) - (double)points[j](1)); @@ -62,6 +62,11 @@ double Polygon::area() const return 0.5 * a; } +double Polygon::area() const +{ + return Polygon::area(points); +} + bool Polygon::is_counter_clockwise() const { return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this)); diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 0f8457ebd..c6678e2d8 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -22,6 +22,7 @@ public: const Point& operator[](Points::size_type idx) const { return this->points[idx]; } Polygon() {} + virtual ~Polygon() = default; explicit Polygon(const Points &points) : MultiPoint(points) {} Polygon(std::initializer_list points) : MultiPoint(points) {} Polygon(const Polygon &other) : MultiPoint(other.points) {} @@ -46,7 +47,8 @@ public: // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline split_at_first_point() const { return this->split_at_index(0); } Points equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); } - + + static double area(const Points &pts); double area() const; bool is_counter_clockwise() const; bool is_clockwise() const; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c7a7a9c8e..229e37527 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3060,6 +3060,42 @@ DynamicPrintConfig* DynamicPrintConfig::new_from_defaults_keys(const std::vector return out; } +double min_object_distance(const ConfigBase &cfg) +{ + double ret = 0.; + + if (printer_technology(cfg) == ptSLA) ret = 6.; + else { + auto ecr_opt = cfg.option("extruder_clearance_radius"); + auto dd_opt = cfg.option("duplicate_distance"); + auto co_opt = cfg.option("complete_objects"); + + if (!ecr_opt || !dd_opt || !co_opt) ret = 0.; + else { + // min object distance is max(duplicate_distance, clearance_radius) + ret = (co_opt->value && ecr_opt->value > dd_opt->value) ? + ecr_opt->value : dd_opt->value; + } + } + + return ret; +} + +PrinterTechnology printer_technology(const ConfigBase &cfg) +{ + const ConfigOptionEnum *opt = cfg.option>("printer_technology"); + + if (opt) return opt->value; + + const ConfigOptionBool *export_opt = cfg.option("export_sla"); + if (export_opt && export_opt->getBool()) return ptSLA; + + export_opt = cfg.option("export_gcode"); + if (export_opt && export_opt->getBool()) return ptFFF; + + return ptUnknown; +} + void DynamicPrintConfig::normalize() { if (this->has("extruder")) { @@ -3130,22 +3166,6 @@ std::string DynamicPrintConfig::validate() } } -double PrintConfig::min_object_distance() const -{ - return PrintConfig::min_object_distance(static_cast(this)); -} - -double PrintConfig::min_object_distance(const ConfigBase *config) -{ - double extruder_clearance_radius = config->option("extruder_clearance_radius")->getFloat(); - double duplicate_distance = config->option("duplicate_distance")->getFloat(); - - // min object distance is max(duplicate_distance, clearance_radius) - return (config->option("complete_objects")->getBool() && extruder_clearance_radius > duplicate_distance) - ? extruder_clearance_radius - : duplicate_distance; -} - //FIXME localize this function. std::string FullPrintConfig::validate() { @@ -3555,8 +3575,39 @@ void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std:: } } +static Points to_points(const std::vector &dpts) +{ + Points pts; pts.reserve(dpts.size()); + for (auto &v : dpts) + pts.emplace_back( coord_t(scale_(v.x())), coord_t(scale_(v.y())) ); + return pts; } +Points get_bed_shape(const DynamicPrintConfig &config) +{ + const auto *bed_shape_opt = config.opt("bed_shape"); + if (!bed_shape_opt) { + + // Here, it is certain that the bed shape is missing, so an infinite one + // has to be used, but still, the center of bed can be queried + if (auto center_opt = config.opt("center")) + return { scaled(center_opt->value) }; + + return {}; + } + + return to_points(bed_shape_opt->values); +} + +Points get_bed_shape(const PrintConfig &cfg) +{ + return to_points(cfg.bed_shape.values); +} + +Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.bed_shape.values); } + +} // namespace Slic3r + #include CEREAL_REGISTER_TYPE(Slic3r::DynamicPrintConfig) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index ca509e37a..c2ecc461e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -194,6 +194,9 @@ extern const PrintConfigDef print_config_def; class StaticPrintConfig; +PrinterTechnology printer_technology(const ConfigBase &cfg); +double min_object_distance(const ConfigBase &cfg); + // Slic3r dynamic configuration, used to override the configuration // per object, per modification volume or per printing material. // The dynamic configuration is also used to store user modifications of the print global parameters, @@ -749,8 +752,6 @@ class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig STATIC_PRINT_CONFIG_CACHE_DERIVED(PrintConfig) PrintConfig() : MachineEnvelopeConfig(0), GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); } public: - double min_object_distance() const; - static double min_object_distance(const ConfigBase *config); ConfigOptionBool avoid_crossing_perimeters; ConfigOptionPoints bed_shape; @@ -1305,6 +1306,10 @@ private: static PrintAndCLIConfigDef s_def; }; +Points get_bed_shape(const DynamicPrintConfig &cfg); +Points get_bed_shape(const PrintConfig &cfg); +Points get_bed_shape(const SLAPrinterConfig &cfg); + } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp new file mode 100644 index 000000000..37baed9e8 --- /dev/null +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -0,0 +1,222 @@ +#ifndef AGGRASTER_HPP +#define AGGRASTER_HPP + +#include +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/MTUtils.hpp" +#include + +// For rasterizing +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Slic3r { + +inline const Polygon& contour(const ExPolygon& p) { return p.contour; } +inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } + +inline const Polygons& holes(const ExPolygon& p) { return p.holes; } +inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } + +namespace sla { + +template struct Colors { + static const Color White; + static const Color Black; +}; + +template const Color Colors::White = Color{255}; +template const Color Colors::Black = Color{0}; + +template*/> class Renderer, + class Rasterizer = agg::rasterizer_scanline_aa<>, + class Scanline = agg::scanline_p8> +class AGGRaster: public RasterBase { +public: + using TColor = typename PixelRenderer::color_type; + using TValue = typename TColor::value_type; + using TPixel = typename PixelRenderer::pixel_type; + using TRawBuffer = agg::rendering_buffer; + +protected: + + Resolution m_resolution; + PixelDim m_pxdim_scaled; // used for scaled coordinate polygons + + std::vector m_buf; + agg::rendering_buffer m_rbuf; + + PixelRenderer m_pixrenderer; + + agg::renderer_base m_raw_renderer; + Renderer> m_renderer; + + Trafo m_trafo; + Scanline m_scanlines; + Rasterizer m_rasterizer; + + void flipy(agg::path_storage &path) const + { + path.flip_y(0, double(m_resolution.height_px)); + } + + void flipx(agg::path_storage &path) const + { + path.flip_x(0, double(m_resolution.width_px)); + } + + double getPx(const Point &p) { return p(0) * m_pxdim_scaled.w_mm; } + double getPy(const Point &p) { return p(1) * m_pxdim_scaled.h_mm; } + agg::path_storage to_path(const Polygon &poly) { return to_path(poly.points); } + double getPx(const ClipperLib::IntPoint &p) { return p.X * m_pxdim_scaled.w_mm; } + double getPy(const ClipperLib::IntPoint& p) { return p.Y * m_pxdim_scaled.h_mm; } + + template agg::path_storage _to_path(const PointVec& v) + { + agg::path_storage path; + + auto it = v.begin(); + path.move_to(getPx(*it), getPy(*it)); + while(++it != v.end()) path.line_to(getPx(*it), getPy(*it)); + path.line_to(getPx(v.front()), getPy(v.front())); + + return path; + } + + template agg::path_storage _to_path_flpxy(const PointVec& v) + { + agg::path_storage path; + + auto it = v.begin(); + path.move_to(getPy(*it), getPx(*it)); + while(++it != v.end()) path.line_to(getPy(*it), getPx(*it)); + path.line_to(getPy(v.front()), getPx(v.front())); + + return path; + } + + template agg::path_storage to_path(const PointVec &v) + { + auto path = m_trafo.flipXY ? _to_path_flpxy(v) : _to_path(v); + + path.translate_all_paths(m_trafo.center_x * m_pxdim_scaled.w_mm, + m_trafo.center_y * m_pxdim_scaled.h_mm); + + if(m_trafo.mirror_x) flipx(path); + if(m_trafo.mirror_y) flipy(path); + + return path; + } + + template void _draw(const P &poly) + { + m_rasterizer.reset(); + + m_rasterizer.add_path(to_path(contour(poly))); + for(auto& h : holes(poly)) m_rasterizer.add_path(to_path(h)); + + agg::render_scanlines(m_rasterizer, m_scanlines, m_renderer); + } + +public: + template AGGRaster(const Resolution &res, + const PixelDim & pd, + const Trafo & trafo, + const TColor & foreground, + const TColor & background, + GammaFn && gammafn) + : m_resolution(res) + , m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm) + , m_buf(res.pixels()) + , m_rbuf(reinterpret_cast(m_buf.data()), + unsigned(res.width_px), + unsigned(res.height_px), + int(res.width_px *PixelRenderer::num_components)) + , m_pixrenderer(m_rbuf) + , m_raw_renderer(m_pixrenderer) + , m_renderer(m_raw_renderer) + , m_trafo(trafo) + { + m_renderer.color(foreground); + clear(background); + + m_rasterizer.gamma(gammafn); + } + + Trafo trafo() const override { return m_trafo; } + Resolution resolution() const override { return m_resolution; } + PixelDim pixel_dimensions() const override + { + return {SCALING_FACTOR / m_pxdim_scaled.w_mm, + SCALING_FACTOR / m_pxdim_scaled.h_mm}; + } + + void draw(const ExPolygon &poly) override { _draw(poly); } + void draw(const ClipperLib::Polygon &poly) override { _draw(poly); } + + EncodedRaster encode(RasterEncoder encoder) const override + { + return encoder(m_buf.data(), m_resolution.width_px, m_resolution.height_px, 1); + } + + void clear(const TColor color) { m_raw_renderer.clear(color); } +}; + +/* + * Captures an anti-aliased monochrome canvas where vectorial + * polygons can be rasterized. Fill color is always white and the background is + * black. Contours are anti-aliased. + * + * A gamma function can be specified at compile time to make it more flexible. + */ +using _RasterGrayscaleAA = + AGGRaster; + +class RasterGrayscaleAA : public _RasterGrayscaleAA { + using Base = _RasterGrayscaleAA; + using typename Base::TColor; + using typename Base::TValue; +public: + template + RasterGrayscaleAA(const RasterBase::Resolution &res, + const RasterBase::PixelDim & pd, + const RasterBase::Trafo & trafo, + GammaFn && fn) + : Base(res, pd, trafo, Colors::White, Colors::Black, + std::forward(fn)) + {} + + uint8_t read_pixel(size_t col, size_t row) const + { + static_assert(std::is_same::value, "Not grayscale pix"); + + uint8_t px; + Base::m_buf[row * Base::resolution().width_px + col].get(px); + return px; + } + + void clear() { Base::clear(Colors::Black); } +}; + +class RasterGrayscaleAAGammaPower: public RasterGrayscaleAA { +public: + RasterGrayscaleAAGammaPower(const RasterBase::Resolution &res, + const RasterBase::PixelDim & pd, + const RasterBase::Trafo & trafo, + double gamma = 1.) + : RasterGrayscaleAA(res, pd, trafo, agg::gamma_power(gamma)) + {} +}; + +}} // namespace Slic3r::sla + +#endif // AGGRASTER_HPP diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index cf1786758..d933ef5ed 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -11,6 +11,8 @@ #include "Tesselate.hpp" #include "MTUtils.hpp" +#include "TriangulateWall.hpp" + // For debugging: // #include // #include @@ -27,186 +29,27 @@ namespace Slic3r { namespace sla { namespace { -/// This function will return a triangulation of a sheet connecting an upper -/// and a lower plate given as input polygons. It will not triangulate the -/// plates themselves only the sheet. The caller has to specify the lower and -/// upper z levels in world coordinates as well as the offset difference -/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the -/// offset difference is negative, the resulting triangle orientation will be -/// reversed. -/// -/// IMPORTANT: This is not a universal triangulation algorithm. It assumes -/// that the lower and upper polygons are offsetted versions of the same -/// original polygon. In general, it assumes that one of the polygons is -/// completely inside the other. The offset difference is the reference -/// distance from the inner polygon's perimeter to the outer polygon's -/// perimeter. The real distance will be variable as the clipper offset has -/// different strategies (rounding, etc...). This algorithm should have -/// O(2n + 3m) complexity where n is the number of upper vertices and m is the -/// number of lower vertices. Contour3D walls( const Polygon &lower, const Polygon &upper, double lower_z_mm, - double upper_z_mm, - double offset_difference_mm, - ThrowOnCancel thr = [] {}) + double upper_z_mm) { + Wall w = triangulate_wall(lower, upper, lower_z_mm, upper_z_mm); + Contour3D ret; - - if(upper.points.size() < 3 || lower.size() < 3) return ret; - - // The concept of the algorithm is relatively simple. It will try to find - // the closest vertices from the upper and the lower polygon and use those - // as starting points. Then it will create the triangles sequentially using - // an edge from the upper polygon and a vertex from the lower or vice versa, - // depending on the resulting triangle's quality. - // The quality is measured by a scalar value. So far it looks like it is - // enough to derive it from the slope of the triangle's two edges connecting - // the upper and the lower part. A reference slope is calculated from the - // height and the offset difference. - - // Offset in the index array for the ceiling - const auto offs = upper.points.size(); - - // Shorthand for the vertex arrays - auto& upts = upper.points, &lpts = lower.points; - auto& rpts = ret.points; auto& ind = ret.faces3; - - // If the Z levels are flipped, or the offset difference is negative, we - // will interpret that as the triangles normals should be inverted. - bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0; - - // Copy the points into the mesh, convert them from 2D to 3D - rpts.reserve(upts.size() + lpts.size()); - ind.reserve(2 * upts.size() + 2 * lpts.size()); - for (auto &p : upts) - rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); - for (auto &p : lpts) - rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); - - // Create pointing indices into vertex arrays. u-upper, l-lower - size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; - - // Simple squared distance calculation. - auto distfn = [](const Vec3d& p1, const Vec3d& p2) { - auto p = p1 - p2; return p.transpose() * p; - }; - - // We need to find the closest point on lower polygon to the first point on - // the upper polygon. These will be our starting points. - double distmin = std::numeric_limits::max(); - for(size_t l = lidx; l < rpts.size(); ++l) { - thr(); - double d = distfn(rpts[l], rpts[uidx]); - if(d < distmin) { lidx = l; distmin = d; } - } - - // Set up lnextidx to be ahead of lidx in cyclic mode - lnextidx = lidx + 1; - if(lnextidx == rpts.size()) lnextidx = offs; - - // This will be the flip switch to toggle between upper and lower triangle - // creation mode - enum class Proceed { - UPPER, // A segment from the upper polygon and one vertex from the lower - LOWER // A segment from the lower polygon and one vertex from the upper - } proceed = Proceed::UPPER; - - // Flags to help evaluating loop termination. - bool ustarted = false, lstarted = false; - - // The variables for the fitness values, one for the actual and one for the - // previous. - double current_fit = 0, prev_fit = 0; - - // Every triangle of the wall has two edges connecting the upper plate with - // the lower plate. From the length of these two edges and the zdiff we - // can calculate the momentary squared offset distance at a particular - // position on the wall. The average of the differences from the reference - // (squared) offset distance will give us the driving fitness value. - const double offsdiff2 = std::pow(offset_difference_mm, 2); - const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2); - - // Mark the current vertex iterator positions. If the iterators return to - // the same position, the loop can be terminated. - size_t uendidx = uidx, lendidx = lidx; - - do { thr(); // check throw if canceled - - prev_fit = current_fit; - - switch(proceed) { // proceed depending on the current state - case Proceed::UPPER: - if(!ustarted || uidx != uendidx) { // there are vertices remaining - // Get the 3D vertices in order - const Vec3d& p_up1 = rpts[uidx]; - const Vec3d& p_low = rpts[lidx]; - const Vec3d& p_up2 = rpts[unextidx]; - - // Calculate fitness: the average of the two connecting edges - double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); - double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2); - current_fit = (std::abs(a) + std::abs(b)) / 2; - - if(current_fit > prev_fit) { // fit is worse than previously - proceed = Proceed::LOWER; - } else { // good to go, create the triangle - inverted - ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) - : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); - - // Increment the iterators, rotate if necessary - ++uidx; ++unextidx; - if(unextidx == offs) unextidx = 0; - if(uidx == offs) uidx = 0; - - ustarted = true; // mark the movement of the iterators - // so that the comparison to uendidx can be made correctly - } - } else proceed = Proceed::LOWER; - - break; - case Proceed::LOWER: - // Mode with lower segment, upper vertex. Same structure: - if(!lstarted || lidx != lendidx) { - const Vec3d& p_low1 = rpts[lidx]; - const Vec3d& p_low2 = rpts[lnextidx]; - const Vec3d& p_up = rpts[uidx]; - - double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); - double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); - current_fit = (std::abs(a) + std::abs(b)) / 2; - - if(current_fit > prev_fit) { - proceed = Proceed::UPPER; - } else { - inverted - ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) - : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); - - ++lidx; ++lnextidx; - if(lnextidx == rpts.size()) lnextidx = offs; - if(lidx == rpts.size()) lidx = offs; - - lstarted = true; - } - } else proceed = Proceed::UPPER; - - break; - } // end of switch - } while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx); - + ret.points = std::move(w.first); + ret.faces3 = std::move(w.second); + return ret; } // Same as walls() but with identical higher and lower polygons. Contour3D inline straight_walls(const Polygon &plate, double lo_z, - double hi_z, - ThrowOnCancel thr) + double hi_z) { - return walls(plate, plate, lo_z, hi_z, .0 /*offset_diff*/, thr); + return walls(plate, plate, lo_z, hi_z); } // Function to cut tiny connector cavities for a given polygon. The input poly @@ -534,10 +377,8 @@ bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg, top_poly = pdiff.front(); double z_min = -cfg.wing_height, z_max = 0; - double offset_difference = -wing_distance; - pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max, - offset_difference, thr)); - + pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max)); + thr(); pad.merge(triangulate_expolygon_3d(inner_base, z_min, NORMALS_UP)); return true; @@ -555,17 +396,17 @@ Contour3D create_outer_pad_geometry(const ExPolygons & skeleton, offset_contour_only(pad_part, -scaled(cfg.bottom_offset())); if (bottom_poly.empty()) continue; - + thr(); + double z_min = -cfg.height, z_max = 0; - ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min, - cfg.bottom_offset(), thr)); + ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min)); if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr)) z_max = -cfg.wing_height; for (auto &h : bottom_poly.holes) - ret.merge(straight_walls(h, z_max, z_min, thr)); - + ret.merge(straight_walls(h, z_max, z_min)); + ret.merge(triangulate_expolygon_3d(bottom_poly, z_min, NORMALS_DOWN)); ret.merge(triangulate_expolygon_3d(top_poly, NORMALS_UP)); } @@ -581,11 +422,12 @@ Contour3D create_inner_pad_geometry(const ExPolygons & skeleton, double z_max = 0., z_min = -cfg.height; for (const ExPolygon &pad_part : skeleton) { - ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr)); + thr(); + ret.merge(straight_walls(pad_part.contour, z_max, z_min)); for (auto &h : pad_part.holes) - ret.merge(straight_walls(h, z_max, z_min, thr)); - + ret.merge(straight_walls(h, z_max, z_min)); + ret.merge(triangulate_expolygon_3d(pad_part, z_min, NORMALS_DOWN)); ret.merge(triangulate_expolygon_3d(pad_part, z_max, NORMALS_UP)); } diff --git a/src/libslic3r/SLA/Raster.cpp b/src/libslic3r/SLA/Raster.cpp deleted file mode 100644 index 9b7f1db7d..000000000 --- a/src/libslic3r/SLA/Raster.cpp +++ /dev/null @@ -1,320 +0,0 @@ -#ifndef SLARASTER_CPP -#define SLARASTER_CPP - -#include - -#include -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/MTUtils.hpp" -#include - -// For rasterizing -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -// Experimental minz image write: -#include - -namespace Slic3r { - -inline const Polygon& contour(const ExPolygon& p) { return p.contour; } -inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } - -inline const Polygons& holes(const ExPolygon& p) { return p.holes; } -inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } - -namespace sla { - -const Raster::TMirroring Raster::NoMirror = {false, false}; -const Raster::TMirroring Raster::MirrorX = {true, false}; -const Raster::TMirroring Raster::MirrorY = {false, true}; -const Raster::TMirroring Raster::MirrorXY = {true, true}; - - -using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24; -using TRawRenderer = agg::renderer_base; -using TPixel = TPixelRenderer::color_type; -using TRawBuffer = agg::rendering_buffer; -using TBuffer = std::vector; - -using TRendererAA = agg::renderer_scanline_aa_solid; - -class Raster::Impl { -public: - - static const TPixel ColorWhite; - static const TPixel ColorBlack; - - using Format = Raster::RawData; - -private: - Raster::Resolution m_resolution; - Raster::PixelDim m_pxdim_scaled; // used for scaled coordinate polygons - TBuffer m_buf; - TRawBuffer m_rbuf; - TPixelRenderer m_pixfmt; - TRawRenderer m_raw_renderer; - TRendererAA m_renderer; - - std::function m_gammafn; - Trafo m_trafo; - - inline void flipy(agg::path_storage& path) const { - path.flip_y(0, double(m_resolution.height_px)); - } - - inline void flipx(agg::path_storage& path) const { - path.flip_x(0, double(m_resolution.width_px)); - } - -public: - inline Impl(const Raster::Resolution & res, - const Raster::PixelDim & pd, - const Trafo &trafo) - : m_resolution(res) - , m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm) - , m_buf(res.pixels()) - , m_rbuf(reinterpret_cast(m_buf.data()), - unsigned(res.width_px), - unsigned(res.height_px), - int(res.width_px * TPixelRenderer::num_components)) - , m_pixfmt(m_rbuf) - , m_raw_renderer(m_pixfmt) - , m_renderer(m_raw_renderer) - , m_trafo(trafo) - { - m_renderer.color(ColorWhite); - - if (trafo.gamma > 0) m_gammafn = agg::gamma_power(trafo.gamma); - else m_gammafn = agg::gamma_threshold(0.5); - - clear(); - } - - template void draw(const P &poly) { - agg::rasterizer_scanline_aa<> ras; - agg::scanline_p8 scanlines; - - ras.gamma(m_gammafn); - - ras.add_path(to_path(contour(poly))); - for(auto& h : holes(poly)) ras.add_path(to_path(h)); - - agg::render_scanlines(ras, scanlines, m_renderer); - } - - inline void clear() { - m_raw_renderer.clear(ColorBlack); - } - - inline TBuffer& buffer() { return m_buf; } - inline const TBuffer& buffer() const { return m_buf; } - - - inline const Raster::Resolution resolution() { return m_resolution; } - inline const Raster::PixelDim pixdim() - { - return {SCALING_FACTOR / m_pxdim_scaled.w_mm, - SCALING_FACTOR / m_pxdim_scaled.h_mm}; - } - -private: - inline double getPx(const Point& p) { - return p(0) * m_pxdim_scaled.w_mm; - } - - inline double getPy(const Point& p) { - return p(1) * m_pxdim_scaled.h_mm; - } - - inline agg::path_storage to_path(const Polygon& poly) - { - return to_path(poly.points); - } - - inline double getPx(const ClipperLib::IntPoint& p) { - return p.X * m_pxdim_scaled.w_mm; - } - - inline double getPy(const ClipperLib::IntPoint& p) { - return p.Y * m_pxdim_scaled.h_mm; - } - - template agg::path_storage _to_path(const PointVec& v) - { - agg::path_storage path; - - auto it = v.begin(); - path.move_to(getPx(*it), getPy(*it)); - while(++it != v.end()) path.line_to(getPx(*it), getPy(*it)); - path.line_to(getPx(v.front()), getPy(v.front())); - - return path; - } - - template agg::path_storage _to_path_flpxy(const PointVec& v) - { - agg::path_storage path; - - auto it = v.begin(); - path.move_to(getPy(*it), getPx(*it)); - while(++it != v.end()) path.line_to(getPy(*it), getPx(*it)); - path.line_to(getPy(v.front()), getPx(v.front())); - - return path; - } - - template agg::path_storage to_path(const PointVec &v) - { - auto path = m_trafo.flipXY ? _to_path_flpxy(v) : _to_path(v); - - path.translate_all_paths(m_trafo.origin_x * m_pxdim_scaled.w_mm, - m_trafo.origin_y * m_pxdim_scaled.h_mm); - - if(m_trafo.mirror_x) flipx(path); - if(m_trafo.mirror_y) flipy(path); - - return path; - } - -}; - -const TPixel Raster::Impl::ColorWhite = TPixel(255); -const TPixel Raster::Impl::ColorBlack = TPixel(0); - -Raster::Raster() { reset(); } - -Raster::Raster(const Raster::Resolution &r, - const Raster::PixelDim & pd, - const Raster::Trafo & tr) -{ - reset(r, pd, tr); -} - -Raster::~Raster() = default; - -Raster::Raster(Raster &&m) = default; -Raster &Raster::operator=(Raster &&) = default; - -void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, - const Trafo &trafo) -{ - m_impl.reset(); - m_impl.reset(new Impl(r, pd, trafo)); -} - -void Raster::reset() -{ - m_impl.reset(); -} - -Raster::Resolution Raster::resolution() const -{ - if (m_impl) return m_impl->resolution(); - - return Resolution{0, 0}; -} - -Raster::PixelDim Raster::pixel_dimensions() const -{ - if (m_impl) return m_impl->pixdim(); - - return PixelDim{0., 0.}; -} - -void Raster::clear() -{ - assert(m_impl); - m_impl->clear(); -} - -void Raster::draw(const ExPolygon &expoly) -{ - assert(m_impl); - m_impl->draw(expoly); -} - -void Raster::draw(const ClipperLib::Polygon &poly) -{ - assert(m_impl); - m_impl->draw(poly); -} - -uint8_t Raster::read_pixel(size_t x, size_t y) const -{ - assert (m_impl); - TPixel::value_type px; - m_impl->buffer()[y * resolution().width_px + x].get(px); - return px; -} - -PNGImage & PNGImage::serialize(const Raster &raster) -{ - size_t s = 0; - m_buffer.clear(); - - void *rawdata = tdefl_write_image_to_png_file_in_memory( - get_internals(raster).buffer().data(), - int(raster.resolution().width_px), - int(raster.resolution().height_px), 1, &s); - - // On error, data() will return an empty vector. No other info can be - // retrieved from miniz anyway... - if (rawdata == nullptr) return *this; - - auto ptr = static_cast(rawdata); - - m_buffer.reserve(s); - std::copy(ptr, ptr + s, std::back_inserter(m_buffer)); - - MZ_FREE(rawdata); - return *this; -} - -std::ostream &operator<<(std::ostream &stream, const Raster::RawData &bytes) -{ - stream.write(reinterpret_cast(bytes.data()), - std::streamsize(bytes.size())); - - return stream; -} - -Raster::RawData::~RawData() = default; - -PPMImage & PPMImage::serialize(const Raster &raster) -{ - auto header = std::string("P5 ") + - std::to_string(raster.resolution().width_px) + " " + - std::to_string(raster.resolution().height_px) + " " + "255 "; - - const auto &impl = get_internals(raster); - auto sz = impl.buffer().size() * sizeof(TBuffer::value_type); - size_t s = sz + header.size(); - - m_buffer.clear(); - m_buffer.reserve(s); - - auto buff = reinterpret_cast(impl.buffer().data()); - std::copy(header.begin(), header.end(), std::back_inserter(m_buffer)); - std::copy(buff, buff+sz, std::back_inserter(m_buffer)); - - return *this; -} - -const Raster::Impl &Raster::RawData::get_internals(const Raster &raster) -{ - return *raster.m_impl; -} - -} // namespace sla -} // namespace Slic3r - -#endif // SLARASTER_CPP diff --git a/src/libslic3r/SLA/Raster.hpp b/src/libslic3r/SLA/Raster.hpp deleted file mode 100644 index 4d76b3290..000000000 --- a/src/libslic3r/SLA/Raster.hpp +++ /dev/null @@ -1,157 +0,0 @@ -#ifndef SLA_RASTER_HPP -#define SLA_RASTER_HPP - -#include -#include -#include -#include -#include -#include - -#include - -namespace ClipperLib { struct Polygon; } - -namespace Slic3r { -namespace sla { - -/** - * @brief Raster captures an anti-aliased monochrome canvas where vectorial - * polygons can be rasterized. Fill color is always white and the background is - * black. Contours are anti-aliased. - * - * It also supports saving the raster data into a standard output stream in raw - * or PNG format. - */ -class Raster { - class Impl; - std::unique_ptr m_impl; -public: - - // Raw byte buffer paired with its size. Suitable for compressed image data. - class RawData - { - protected: - std::vector m_buffer; - const Impl& get_internals(const Raster& raster); - public: - RawData() = default; - RawData(std::vector&& data): m_buffer(std::move(data)) {} - virtual ~RawData(); - - RawData(const RawData &) = delete; - RawData &operator=(const RawData &) = delete; - - RawData(RawData &&) = default; - RawData &operator=(RawData &&) = default; - - size_t size() const { return m_buffer.size(); } - const uint8_t * data() const { return m_buffer.data(); } - - virtual RawData& serialize(const Raster &/*raster*/) { return *this; } - virtual std::string get_file_extension() const = 0; - }; - - /// Type that represents a resolution in pixels. - struct Resolution { - size_t width_px; - size_t height_px; - - inline Resolution(size_t w = 0, size_t h = 0) - : width_px(w), height_px(h) - {} - - inline size_t pixels() const { return width_px * height_px; } - }; - - /// Types that represents the dimension of a pixel in millimeters. - struct PixelDim { - double w_mm; - double h_mm; - inline PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0): - w_mm(px_width_mm), h_mm(px_height_mm) {} - }; - - enum Orientation { roLandscape, roPortrait }; - - using TMirroring = std::array; - static const TMirroring NoMirror; - static const TMirroring MirrorX; - static const TMirroring MirrorY; - static const TMirroring MirrorXY; - - struct Trafo { - bool mirror_x = false, mirror_y = false, flipXY = false; - coord_t origin_x = 0, origin_y = 0; - - // If gamma is zero, thresholding will be performed which disables AA. - double gamma = 1.; - - // Portrait orientation will make sure the drawed polygons are rotated - // by 90 degrees. - Trafo(Orientation o = roLandscape, const TMirroring &mirror = NoMirror) - // XY flipping implicitly does an X mirror - : mirror_x(o == roPortrait ? !mirror[0] : mirror[0]) - , mirror_y(!mirror[1]) // Makes raster origin to be top left corner - , flipXY(o == roPortrait) - {} - }; - - Raster(); - Raster(const Resolution &r, - const PixelDim & pd, - const Trafo & tr = {}); - - Raster(const Raster& cpy) = delete; - Raster& operator=(const Raster& cpy) = delete; - Raster(Raster&& m); - Raster& operator=(Raster&&); - ~Raster(); - - /// Reallocated everything for the given resolution and pixel dimension. - void reset(const Resolution& r, - const PixelDim& pd, - const Trafo &tr = {}); - - /** - * Release the allocated resources. Drawing in this state ends in - * unspecified behavior. - */ - void reset(); - - /// Get the resolution of the raster. - Resolution resolution() const; - PixelDim pixel_dimensions() const; - - /// Clear the raster with black color. - void clear(); - - /// Draw a polygon with holes. - void draw(const ExPolygon& poly); - void draw(const ClipperLib::Polygon& poly); - - uint8_t read_pixel(size_t w, size_t h) const; - - inline bool empty() const { return ! bool(m_impl); } - -}; - -class PNGImage: public Raster::RawData { -public: - PNGImage& serialize(const Raster &raster) override; - std::string get_file_extension() const override { return "png"; } -}; - -class PPMImage: public Raster::RawData { -public: - PPMImage& serialize(const Raster &raster) override; - std::string get_file_extension() const override { return "ppm"; } -}; - -std::ostream& operator<<(std::ostream &stream, const Raster::RawData &bytes); - -} // sla -} // Slic3r - - -#endif // SLARASTER_HPP diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp new file mode 100644 index 000000000..581e84880 --- /dev/null +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -0,0 +1,89 @@ +#ifndef SLARASTER_CPP +#define SLARASTER_CPP + +#include + +#include +#include + +// minz image write: +#include + +namespace Slic3r { namespace sla { + +const RasterBase::TMirroring RasterBase::NoMirror = {false, false}; +const RasterBase::TMirroring RasterBase::MirrorX = {true, false}; +const RasterBase::TMirroring RasterBase::MirrorY = {false, true}; +const RasterBase::TMirroring RasterBase::MirrorXY = {true, true}; + +EncodedRaster PNGRasterEncoder::operator()(const void *ptr, size_t w, size_t h, + size_t num_components) +{ + std::vector buf; + size_t s = 0; + + void *rawdata = tdefl_write_image_to_png_file_in_memory( + ptr, int(w), int(h), int(num_components), &s); + + // On error, data() will return an empty vector. No other info can be + // retrieved from miniz anyway... + if (rawdata == nullptr) return EncodedRaster({}, "png"); + + auto pptr = static_cast(rawdata); + + buf.reserve(s); + std::copy(pptr, pptr + s, std::back_inserter(buf)); + + MZ_FREE(rawdata); + return EncodedRaster(std::move(buf), "png"); +} + +std::ostream &operator<<(std::ostream &stream, const EncodedRaster &bytes) +{ + stream.write(reinterpret_cast(bytes.data()), + std::streamsize(bytes.size())); + + return stream; +} + +EncodedRaster PPMRasterEncoder::operator()(const void *ptr, size_t w, size_t h, + size_t num_components) +{ + std::vector buf; + + auto header = std::string("P5 ") + + std::to_string(w) + " " + + std::to_string(h) + " " + "255 "; + + auto sz = w * h * num_components; + size_t s = sz + header.size(); + + buf.reserve(s); + + auto buff = reinterpret_cast(ptr); + std::copy(header.begin(), header.end(), std::back_inserter(buf)); + std::copy(buff, buff+sz, std::back_inserter(buf)); + + return EncodedRaster(std::move(buf), "ppm"); +} + +std::unique_ptr create_raster_grayscale_aa( + const RasterBase::Resolution &res, + const RasterBase::PixelDim & pxdim, + double gamma, + const RasterBase::Trafo & tr) +{ + std::unique_ptr rst; + + if (gamma > 0) + rst = std::make_unique(res, pxdim, tr, gamma); + else + rst = std::make_unique(res, pxdim, tr, agg::gamma_threshold(.5)); + + return rst; +} + +} // namespace sla +} // namespace Slic3r + +#endif // SLARASTER_CPP diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp new file mode 100644 index 000000000..431c731a6 --- /dev/null +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -0,0 +1,124 @@ +#ifndef SLA_RASTERBASE_HPP +#define SLA_RASTERBASE_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ClipperLib { struct Polygon; } + +namespace Slic3r { + +template using uqptr = std::unique_ptr; +template using shptr = std::shared_ptr; +template using wkptr = std::weak_ptr; + +namespace sla { + +// Raw byte buffer paired with its size. Suitable for compressed image data. +class EncodedRaster { +protected: + std::vector m_buffer; + std::string m_ext; +public: + EncodedRaster() = default; + explicit EncodedRaster(std::vector &&buf, std::string ext) + : m_buffer(std::move(buf)), m_ext(std::move(ext)) + {} + + size_t size() const { return m_buffer.size(); } + const void * data() const { return m_buffer.data(); } + const char * extension() const { return m_ext.c_str(); } +}; + +using RasterEncoder = + std::function; + +class RasterBase { +public: + + enum Orientation { roLandscape, roPortrait }; + + using TMirroring = std::array; + static const TMirroring NoMirror; + static const TMirroring MirrorX; + static const TMirroring MirrorY; + static const TMirroring MirrorXY; + + struct Trafo { + bool mirror_x = false, mirror_y = false, flipXY = false; + coord_t center_x = 0, center_y = 0; + + // Portrait orientation will make sure the drawed polygons are rotated + // by 90 degrees. + Trafo(Orientation o = roLandscape, const TMirroring &mirror = NoMirror) + // XY flipping implicitly does an X mirror + : mirror_x(o == roPortrait ? !mirror[0] : mirror[0]) + , mirror_y(!mirror[1]) // Makes raster origin to be top left corner + , flipXY(o == roPortrait) + {} + + TMirroring get_mirror() const { return { (roPortrait ? !mirror_x : mirror_x), mirror_y}; } + Orientation get_orientation() const { return flipXY ? roPortrait : roLandscape; } + Point get_center() const { return {center_x, center_y}; } + }; + + /// Type that represents a resolution in pixels. + struct Resolution { + size_t width_px = 0; + size_t height_px = 0; + + Resolution(size_t w = 0, size_t h = 0) : width_px(w), height_px(h) {} + size_t pixels() const { return width_px * height_px; } + }; + + /// Types that represents the dimension of a pixel in millimeters. + struct PixelDim { + double w_mm = 0.; + double h_mm = 0.; + + PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0) + : w_mm(px_width_mm), h_mm(px_height_mm) + {} + }; + + virtual ~RasterBase() = default; + + /// Draw a polygon with holes. + virtual void draw(const ExPolygon& poly) = 0; + virtual void draw(const ClipperLib::Polygon& poly) = 0; + + /// Get the resolution of the raster. + virtual Resolution resolution() const = 0; + virtual PixelDim pixel_dimensions() const = 0; + virtual Trafo trafo() const = 0; + + virtual EncodedRaster encode(RasterEncoder encoder) const = 0; +}; + +struct PNGRasterEncoder { + EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components); +}; + +struct PPMRasterEncoder { + EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components); +}; + +std::ostream& operator<<(std::ostream &stream, const EncodedRaster &bytes); + +// If gamma is zero, thresholding will be performed which disables AA. +uqptr create_raster_grayscale_aa( + const RasterBase::Resolution &res, + const RasterBase::PixelDim & pxdim, + double gamma = 1.0, + const RasterBase::Trafo & tr = {}); + +}} // namespace Slic3r::sla + +#endif // SLARASTERBASE_HPP diff --git a/src/libslic3r/SLA/RasterToPolygons.cpp b/src/libslic3r/SLA/RasterToPolygons.cpp new file mode 100644 index 000000000..cd84a3cb4 --- /dev/null +++ b/src/libslic3r/SLA/RasterToPolygons.cpp @@ -0,0 +1,91 @@ +#include "RasterToPolygons.hpp" + +#include "AGGRaster.hpp" +#include "libslic3r/MarchingSquares.hpp" +#include "MTUtils.hpp" +#include "ClipperUtils.hpp" + +namespace marchsq { + +// Specialize this struct to register a raster type for the Marching squares alg +template<> struct _RasterTraits { + using Rst = Slic3r::sla::RasterGrayscaleAA; + + // The type of pixel cell in the raster + using ValueType = uint8_t; + + // Value at a given position + static uint8_t get(const Rst &rst, size_t row, size_t col) { return rst.read_pixel(col, row); } + + // Number of rows and cols of the raster + static size_t rows(const Rst &rst) { return rst.resolution().height_px; } + static size_t cols(const Rst &rst) { return rst.resolution().width_px; } +}; + +} // namespace Slic3r::marchsq + +namespace Slic3r { namespace sla { + +template void foreach_vertex(ExPolygon &poly, Fn &&fn) +{ + for (auto &p : poly.contour.points) fn(p); + for (auto &h : poly.holes) + for (auto &p : h.points) fn(p); +} + +ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, Vec2i windowsize) +{ + size_t rows = rst.resolution().height_px, cols = rst.resolution().width_px; + + if (rows < 2 || cols < 2) return {}; + + Polygons polys; + long w_rows = std::max(2l, long(windowsize.y())); + long w_cols = std::max(2l, long(windowsize.x())); + + std::vector rings = + marchsq::execute(rst, 128, {w_rows, w_cols}); + + polys.reserve(rings.size()); + + auto pxd = rst.pixel_dimensions(); + pxd.w_mm = (rst.resolution().width_px * pxd.w_mm) / (rst.resolution().width_px - 1); + pxd.h_mm = (rst.resolution().height_px * pxd.h_mm) / (rst.resolution().height_px - 1); + + for (const marchsq::Ring &ring : rings) { + Polygon poly; Points &pts = poly.points; + pts.reserve(ring.size()); + + for (const marchsq::Coord &crd : ring) + pts.emplace_back(scaled(crd.c * pxd.w_mm), scaled(crd.r * pxd.h_mm)); + + polys.emplace_back(poly); + } + + // reverse the raster transformations + ExPolygons unioned = union_ex(polys); + coord_t width = scaled(cols * pxd.h_mm), height = scaled(rows * pxd.w_mm); + + auto tr = rst.trafo(); + for (ExPolygon &expoly : unioned) { + if (tr.mirror_y) + foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); }); + + if (tr.mirror_x) + foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); }); + + expoly.translate(-tr.center_x, -tr.center_y); + + if (tr.flipXY) + foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); }); + + if ((tr.mirror_x + tr.mirror_y + tr.flipXY) % 2) { + expoly.contour.reverse(); + for (auto &h : expoly.holes) h.reverse(); + } + } + + return unioned; +} + +}} // namespace Slic3r diff --git a/src/libslic3r/SLA/RasterToPolygons.hpp b/src/libslic3r/SLA/RasterToPolygons.hpp new file mode 100644 index 000000000..c0e1f4114 --- /dev/null +++ b/src/libslic3r/SLA/RasterToPolygons.hpp @@ -0,0 +1,15 @@ +#ifndef RASTERTOPOLYGONS_HPP +#define RASTERTOPOLYGONS_HPP + +#include "libslic3r/ExPolygon.hpp" + +namespace Slic3r { +namespace sla { + +class RasterGrayscaleAA; + +ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, Vec2i windowsize = {2, 2}); + +}} // namespace Slic3r::sla + +#endif // RASTERTOPOLYGONS_HPP diff --git a/src/libslic3r/SLA/RasterWriter.cpp b/src/libslic3r/SLA/RasterWriter.cpp deleted file mode 100644 index 13aef7d8a..000000000 --- a/src/libslic3r/SLA/RasterWriter.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include - -#include - -#include "libslic3r/PrintConfig.hpp" -#include -#include - -#include "ExPolygon.hpp" -#include - -#include -#include - -namespace Slic3r { namespace sla { - -void RasterWriter::write_ini(const std::map &m, std::string &ini) -{ - for (auto ¶m : m) ini += param.first + " = " + param.second + "\n"; -} - -std::string RasterWriter::create_ini_content(const std::string& projectname) const -{ - std::string out("action = print\njobDir = "); - out += projectname + "\n"; - write_ini(m_config, out); - return out; -} - -RasterWriter::RasterWriter(const Raster::Resolution &res, - const Raster::PixelDim & pixdim, - const Raster::Trafo & trafo, - double gamma) - : m_res(res), m_pxdim(pixdim), m_trafo(trafo), m_gamma(gamma) -{} - -void RasterWriter::save(const std::string &fpath, const std::string &prjname) -{ - try { - Zipper zipper(fpath); // zipper with no compression - save(zipper, prjname); - zipper.finalize(); - } catch(std::exception& e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - // Rethrow the exception - throw; - } -} - -void RasterWriter::save(Zipper &zipper, const std::string &prjname) -{ - try { - std::string project = - prjname.empty() ? - boost::filesystem::path(zipper.get_filename()).stem().string() : - prjname; - - zipper.add_entry("config.ini"); - - zipper << create_ini_content(project); - - zipper.add_entry("prusaslicer.ini"); - std::string prusaslicer_ini; - write_ini(m_slicer_config, prusaslicer_ini); - zipper << prusaslicer_ini; - - for(unsigned i = 0; i < m_layers_rst.size(); i++) - { - if(m_layers_rst[i].rawbytes.size() > 0) { - char lyrnum[6]; - std::sprintf(lyrnum, "%.5d", i); - auto zfilename = project + lyrnum + ".png"; - - // Add binary entry to the zipper - zipper.add_entry(zfilename, - m_layers_rst[i].rawbytes.data(), - m_layers_rst[i].rawbytes.size()); - } - } - } catch(std::exception& e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - // Rethrow the exception - throw; - } -} - -namespace { - -std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) -{ - std::string ret; - - if (cfg.has(key)) { - auto opt = cfg.option(key); - if (opt) ret = opt->serialize(); - } - - return ret; -} - -void append_full_config(const DynamicPrintConfig &cfg, std::map &keys) -{ - using namespace std::literals::string_view_literals; - - // Sorted list of config keys, which shall not be stored into the ini. - static constexpr auto banned_keys = { - "compatible_printers"sv, - "compatible_prints"sv, - "print_host"sv, - "printhost_apikey"sv, - "printhost_cafile"sv - }; - - assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); - auto is_banned = [](const std::string &key) { - return std::binary_search(banned_keys.begin(), banned_keys.end(), key); - }; - - for (const std::string &key : cfg.keys()) - if (! is_banned(key) && ! cfg.option(key)->is_nil()) - keys[key] = cfg.opt_serialize(key); -} - -} // namespace - -void RasterWriter::set_config(const DynamicPrintConfig &cfg) -{ - m_config["layerHeight"] = get_cfg_value(cfg, "layer_height"); - m_config["expTime"] = get_cfg_value(cfg, "exposure_time"); - m_config["expTimeFirst"] = get_cfg_value(cfg, "initial_exposure_time"); - m_config["materialName"] = get_cfg_value(cfg, "sla_material_settings_id"); - m_config["printerModel"] = get_cfg_value(cfg, "printer_model"); - m_config["printerVariant"] = get_cfg_value(cfg, "printer_variant"); - m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id"); - m_config["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id"); - m_config["fileCreationTimestamp"] = Utils::utc_timestamp(); - m_config["prusaSlicerVersion"] = SLIC3R_BUILD_ID; - append_full_config(cfg, m_slicer_config); -} - -void RasterWriter::set_statistics(const PrintStatistics &stats) -{ - m_config["usedMaterial"] = std::to_string(stats.used_material); - m_config["numFade"] = std::to_string(stats.num_fade); - m_config["numSlow"] = std::to_string(stats.num_slow); - m_config["numFast"] = std::to_string(stats.num_fast); - m_config["printTime"] = std::to_string(stats.estimated_print_time_s); -} - -} // namespace sla -} // namespace Slic3r diff --git a/src/libslic3r/SLA/RasterWriter.hpp b/src/libslic3r/SLA/RasterWriter.hpp deleted file mode 100644 index 75162893d..000000000 --- a/src/libslic3r/SLA/RasterWriter.hpp +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef SLA_RASTERWRITER_HPP -#define SLA_RASTERWRITER_HPP - -// For png export of the sliced model -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace Slic3r { - -class DynamicPrintConfig; - -namespace sla { - -// API to write the zipped sla output layers and metadata. -// Implementation uses PNG raster output. -// Be aware that if a large number of layers are allocated, it can very well -// exhaust the available memory especially on 32 bit platform. -// This class is designed to be used in parallel mode. Layers have an ID and -// each layer can be written and compressed independently (in parallel). -// At the end when all layers where written, the save method can be used to -// write out the result into a zipped archive. -class RasterWriter -{ -public: - - // Used for addressing parameters of set_statistics() - struct PrintStatistics - { - double used_material = 0.; - double estimated_print_time_s = 0.; - size_t num_fade = 0; - size_t num_slow = 0; - size_t num_fast = 0; - }; - -private: - - // A struct to bind the raster image data and its compressed bytes together. - struct Layer { - Raster raster; - PNGImage rawbytes; - - Layer() = default; - - // The image is big, do not copy by accident - Layer(const Layer&) = delete; - Layer& operator=(const Layer&) = delete; - - Layer(Layer &&m) = default; - Layer &operator=(Layer &&) = default; - }; - - // We will save the compressed PNG data into RawBytes type buffers in - // parallel. Later we can write every layer to the disk sequentially. - std::vector m_layers_rst; - Raster::Resolution m_res; - Raster::PixelDim m_pxdim; - Raster::Trafo m_trafo; - double m_gamma; - - std::map m_config; - std::map m_slicer_config; - - static void write_ini(const std::map &m, std::string &ini); - std::string create_ini_content(const std::string& projectname) const; - -public: - - // SLARasterWriter is using Raster in custom mirroring mode - RasterWriter(const Raster::Resolution &res, - const Raster::PixelDim & pixdim, - const Raster::Trafo & trafo, - double gamma = 1.); - - RasterWriter(const RasterWriter& ) = delete; - RasterWriter& operator=(const RasterWriter&) = delete; - RasterWriter(RasterWriter&& m) = default; - RasterWriter& operator=(RasterWriter&&) = default; - - inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } - inline unsigned layers() const { return unsigned(m_layers_rst.size()); } - - template void draw_polygon(const Poly& p, unsigned lyr) - { - assert(lyr < m_layers_rst.size()); - m_layers_rst[lyr].raster.draw(p); - } - - inline void begin_layer(unsigned lyr) { - if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); - m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_trafo); - } - - inline void begin_layer() { - m_layers_rst.emplace_back(); - m_layers_rst.front().raster.reset(m_res, m_pxdim, m_trafo); - } - - inline void finish_layer(unsigned lyr_id) { - assert(lyr_id < m_layers_rst.size()); - m_layers_rst[lyr_id].rawbytes.serialize(m_layers_rst[lyr_id].raster); - m_layers_rst[lyr_id].raster.reset(); - } - - inline void finish_layer() { - if(!m_layers_rst.empty()) { - m_layers_rst.back().rawbytes.serialize(m_layers_rst.back().raster); - m_layers_rst.back().raster.reset(); - } - } - - void save(const std::string &fpath, const std::string &prjname = ""); - void save(Zipper &zipper, const std::string &prjname = ""); - - void set_statistics(const PrintStatistics &statistics); - - void set_config(const DynamicPrintConfig &cfg); -}; - -} // namespace sla -} // namespace Slic3r - -#endif // SLARASTERWRITER_HPP diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 4ec5aae29..2402207a8 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -227,6 +227,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con m_material_config.apply_only(config, material_diff, true); // Handle changes to object config defaults m_default_object_config.apply_only(config, object_diff, true); + + if (m_printer) m_printer->apply(m_printer_config); struct ModelObjectStatus { enum Status { @@ -482,7 +484,6 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con } if(m_objects.empty()) { - m_printer.reset(); m_printer_input = {}; m_print_statistics = {}; } @@ -657,6 +658,12 @@ std::string SLAPrint::validate() const return ""; } +void SLAPrint::set_printer(SLAPrinter *arch) +{ + invalidate_step(slapsRasterize); + m_printer = arch; +} + bool SLAPrint::invalidate_step(SLAPrintStep step) { bool invalidated = Inherited::invalidate_step(step); @@ -676,7 +683,7 @@ void SLAPrint::process() // Assumption: at this point the print objects should be populated only with // the model objects we have to process and the instances are also filtered - Steps printsteps{this}; + Steps printsteps(this); // We want to first process all objects... std::vector level1_obj_steps = { @@ -729,7 +736,7 @@ void SLAPrint::process() throw_if_canceled(); po->set_done(step); } - + incr = printsteps.progressrange(step); } } @@ -754,7 +761,7 @@ void SLAPrint::process() throw_if_canceled(); set_done(currentstep); } - + st += printsteps.progressrange(currentstep); } @@ -855,36 +862,6 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector mirror; - - double w = m_printer_config.display_width.getFloat(); - double h = m_printer_config.display_height.getFloat(); - auto pw = size_t(m_printer_config.display_pixels_x.getInt()); - auto ph = size_t(m_printer_config.display_pixels_y.getInt()); - - mirror[X] = m_printer_config.display_mirror_x.getBool(); - mirror[Y] = m_printer_config.display_mirror_y.getBool(); - - auto orientation = get_printer_orientation(); - if (orientation == sla::Raster::roPortrait) { - std::swap(w, h); - std::swap(pw, ph); - } - - res = sla::Raster::Resolution{pw, ph}; - pxdim = sla::Raster::PixelDim{w / pw, h / ph}; - sla::Raster::Trafo tr{orientation, mirror}; - tr.gamma = m_printer_config.gamma_correction.getFloat(); - - m_printer.reset(new sla::RasterWriter(res, pxdim, tr)); - m_printer->set_config(m_full_print_config); - return *m_printer; -} - // Returns true if an object step is done on all objects and there's at least one object. bool SLAPrint::is_step_done(SLAPrintObjectStep step) const { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 70f773f6b..a27207565 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -3,7 +3,7 @@ #include #include "PrintBase.hpp" -#include "SLA/RasterWriter.hpp" +#include "SLA/RasterBase.hpp" #include "SLA/SupportTree.hpp" #include "Point.hpp" #include "MTUtils.hpp" @@ -369,6 +369,31 @@ struct SLAPrintStatistics } }; +class SLAPrinter { +protected: + std::vector m_layers; + + virtual uqptr create_raster() const = 0; + virtual sla::EncodedRaster encode_raster(const sla::RasterBase &rst) const = 0; + +public: + virtual ~SLAPrinter() = default; + + virtual void apply(const SLAPrinterConfig &cfg) = 0; + + // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid); + template void draw_layers(size_t layer_num, Fn &&drawfn) + { + m_layers.resize(layer_num); + sla::ccr::enumerate(m_layers.begin(), m_layers.end(), + [this, &drawfn](sla::EncodedRaster& enc, size_t idx) { + auto rst = create_raster(); + drawfn(*rst, idx); + enc = encode_raster(*rst); + }); + } +}; + /** * @brief This class is the high level FSM for the SLA printing process. * @@ -403,18 +428,6 @@ public: // Returns true if the last step was finished with success. bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } - inline void export_raster(const std::string& fpath, - const std::string& projectname = "") - { - if(m_printer) m_printer->save(fpath, projectname); - } - - inline void export_raster(Zipper &zipper, - const std::string& projectname = "") - { - if(m_printer) m_printer->save(zipper, projectname); - } - const PrintObjects& objects() const { return m_objects; } const SLAPrintConfig& print_config() const { return m_print_config; } @@ -445,14 +458,15 @@ public: std::vector m_transformed_slices; - template void transformed_slices(Container&& c) { + template void transformed_slices(Container&& c) + { m_transformed_slices = std::forward(c); } friend class SLAPrint::Steps; public: - + explicit PrintLayer(coord_t lvl) : m_level(lvl) {} // for being sorted in their container (see m_printer_input) @@ -474,8 +488,11 @@ public: // The aggregated and leveled print records from various objects. // TODO: use this structure for the preview in the future. const std::vector& print_layers() const { return m_printer_input; } - + + void set_printer(SLAPrinter *archiver); + private: + // Implement same logic as in SLAPrintObject bool invalidate_step(SLAPrintStep st); @@ -491,13 +508,13 @@ private: std::vector m_stepmask; // Ready-made data for rasterization. - std::vector m_printer_input; - - // The printer itself - std::unique_ptr m_printer; - + std::vector m_printer_input; + + // The archive object which collects the raster images after slicing + SLAPrinter *m_printer = nullptr; + // Estimated print time, material consumed. - SLAPrintStatistics m_print_statistics; + SLAPrintStatistics m_print_statistics; class StatusReporter { @@ -512,15 +529,6 @@ private: double status() const { return m_st; } } m_report_status; - - sla::RasterWriter &init_printer(); - - inline sla::Raster::Orientation get_printer_orientation() const - { - auto ro = m_printer_config.display_orientation.getInt(); - return ro == sla::Raster::roPortrait ? sla::Raster::roPortrait : - sla::Raster::roLandscape; - } friend SLAPrintObject; }; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 01220a633..e421e9c1d 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -816,16 +816,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // Rasterizing the model objects, and their supports void SLAPrint::Steps::rasterize() { - if(canceled()) return; - - auto &print_statistics = m_print->m_print_statistics; - auto &printer_input = m_print->m_printer_input; - - // Set up the printer, allocate space for all the layers - sla::RasterWriter &printer = m_print->init_printer(); - - auto lvlcnt = unsigned(printer_input.size()); - printer.layers(lvlcnt); + if(canceled() || !m_print->m_printer) return; // coefficient to map the rasterization state (0-99) to the allocated // portion (slot) of the process state @@ -837,7 +828,7 @@ void SLAPrint::Steps::rasterize() // pst: previous state double pst = current_status(); - double increment = (slot * sd) / printer_input.size(); + double increment = (slot * sd) / m_print->m_printer_input.size(); double dstatus = current_status(); sla::ccr::SpinningMutex slck; @@ -845,20 +836,14 @@ void SLAPrint::Steps::rasterize() // procedure to process one height level. This will run in parallel auto lvlfn = - [this, &slck, &printer, increment, &dstatus, &pst] - (PrintLayer& printlayer, size_t idx) + [this, &slck, increment, &dstatus, &pst] + (sla::RasterBase& raster, size_t idx) { + PrintLayer& printlayer = m_print->m_printer_input[idx]; if(canceled()) return; - auto level_id = unsigned(idx); - // Switch to the appropriate layer in the printer - printer.begin_layer(level_id); - - for(const ClipperLib::Polygon& poly : printlayer.transformed_slices()) - printer.draw_polygon(poly, level_id); - - // Finish the layer for later saving it. - printer.finish_layer(level_id); + for (const ClipperLib::Polygon& poly : printlayer.transformed_slices()) + raster.draw(poly); // Status indication guarded with the spinlock { @@ -875,24 +860,8 @@ void SLAPrint::Steps::rasterize() // last minute escape if(canceled()) return; - // Sequential version (for testing) - // for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l); - // Print all the layers in parallel - sla::ccr::enumerate(printer_input.begin(), printer_input.end(), lvlfn); - - // Set statistics values to the printer - sla::RasterWriter::PrintStatistics stats; - stats.used_material = (print_statistics.objects_used_material + - print_statistics.support_used_material) / 1000; - - int num_fade = m_print->m_default_object_config.faded_layers.getInt(); - stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0); - stats.num_fast = print_statistics.fast_layers_count; - stats.num_slow = print_statistics.slow_layers_count; - stats.estimated_print_time_s = print_statistics.estimated_print_time; - - printer.set_statistics(stats); + m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn); } std::string SLAPrint::Steps::label(SLAPrintObjectStep step) diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp index d3341bc14..19b64d4a9 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -46,7 +46,7 @@ private: void apply_printer_corrections(SLAPrintObject &po, SliceOrigin o); public: - Steps(SLAPrint *print); + explicit Steps(SLAPrint *print); void hollow_model(SLAPrintObject &po); void drill_holes (SLAPrintObject &po); diff --git a/src/libslic3r/SlicesToTriangleMesh.cpp b/src/libslic3r/SlicesToTriangleMesh.cpp new file mode 100644 index 000000000..d6a546961 --- /dev/null +++ b/src/libslic3r/SlicesToTriangleMesh.cpp @@ -0,0 +1,128 @@ + +#include "SlicesToTriangleMesh.hpp" + +#include "libslic3r/MTUtils.hpp" +#include "libslic3r/SLA/Contour3D.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Tesselate.hpp" + +#include +#include + +namespace Slic3r { + +inline sla::Contour3D wall_strip(const Polygon &poly, + double lower_z_mm, + double upper_z_mm) +{ + sla::Contour3D ret; + + size_t startidx = ret.points.size(); + size_t offs = poly.points.size(); + + ret.points.reserve(ret.points.size() + 2 *offs); + + for (const Point &p : poly.points) + ret.points.emplace_back(to_3d(unscaled(p), lower_z_mm)); + + for (const Point &p : poly.points) + ret.points.emplace_back(to_3d(unscaled(p), upper_z_mm)); + + for (size_t i = startidx + 1; i < startidx + offs; ++i) { + ret.faces3.emplace_back(i - 1, i, i + offs - 1); + ret.faces3.emplace_back(i, i + offs, i + offs - 1); + } + + ret.faces3.emplace_back(startidx + offs - 1, startidx, startidx + 2 * offs - 1); + ret.faces3.emplace_back(startidx, startidx + offs, startidx + 2 * offs - 1); + + return ret; +} + +// Same as walls() but with identical higher and lower polygons. +sla::Contour3D inline straight_walls(const Polygon &plate, + double lo_z, + double hi_z) +{ + return wall_strip(plate, lo_z, hi_z); +} + +sla::Contour3D inline straight_walls(const ExPolygon &plate, + double lo_z, + double hi_z) +{ + sla::Contour3D ret; + ret.merge(straight_walls(plate.contour, lo_z, hi_z)); + for (auto &h : plate.holes) ret.merge(straight_walls(h, lo_z, hi_z)); + return ret; +} + +sla::Contour3D inline straight_walls(const ExPolygons &slice, + double lo_z, + double hi_z) +{ + sla::Contour3D ret; + for (const ExPolygon &poly : slice) + ret.merge(straight_walls(poly, lo_z, hi_z)); + + return ret; +} + +sla::Contour3D slices_to_triangle_mesh(const std::vector &slices, + double zmin, + const std::vector & grid) +{ + assert(slices.size() == grid.size()); + + using Layers = std::vector; + std::vector layers(slices.size()); + size_t len = slices.size() - 1; + + tbb::parallel_for(size_t(0), len, [&slices, &layers, &grid](size_t i) { + const ExPolygons &upper = slices[i + 1]; + const ExPolygons &lower = slices[i]; + + ExPolygons dff1 = diff_ex(lower, upper); + ExPolygons dff2 = diff_ex(upper, lower); + layers[i].merge(triangulate_expolygons_3d(dff1, grid[i], NORMALS_UP)); + layers[i].merge(triangulate_expolygons_3d(dff2, grid[i], NORMALS_DOWN)); + layers[i].merge(straight_walls(upper, grid[i], grid[i + 1])); + + }); + + sla::Contour3D ret = tbb::parallel_reduce( + tbb::blocked_range(layers.begin(), layers.end()), + sla::Contour3D{}, + [](const tbb::blocked_range& r, sla::Contour3D init) { + for(auto it = r.begin(); it != r.end(); ++it ) init.merge(*it); + return init; + }, + []( const sla::Contour3D &a, const sla::Contour3D &b ) { + sla::Contour3D res{a}; res.merge(b); return res; + }); + + ret.merge(triangulate_expolygons_3d(slices.front(), zmin, NORMALS_DOWN)); + ret.merge(straight_walls(slices.front(), zmin, grid.front())); + ret.merge(triangulate_expolygons_3d(slices.back(), grid.back(), NORMALS_UP)); + + return ret; +} + +void slices_to_triangle_mesh(TriangleMesh & mesh, + const std::vector &slices, + double zmin, + double lh, + double ilh) +{ + std::vector wall_meshes(slices.size()); + std::vector grid(slices.size(), zmin + ilh); + + for (size_t i = 1; i < grid.size(); ++i) grid[i] = grid[i - 1] + lh; + + sla::Contour3D cntr = slices_to_triangle_mesh(slices, zmin, grid); + mesh.merge(sla::to_triangle_mesh(cntr)); + mesh.repaired = true; + mesh.require_shared_vertices(); +} + +} // namespace Slic3r diff --git a/src/libslic3r/SlicesToTriangleMesh.hpp b/src/libslic3r/SlicesToTriangleMesh.hpp new file mode 100644 index 000000000..133312d56 --- /dev/null +++ b/src/libslic3r/SlicesToTriangleMesh.hpp @@ -0,0 +1,24 @@ +#ifndef SLICESTOTRIANGLEMESH_HPP +#define SLICESTOTRIANGLEMESH_HPP + +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/ExPolygon.hpp" + +namespace Slic3r { + +void slices_to_triangle_mesh(TriangleMesh & mesh, + const std::vector &slices, + double zmin, + double lh, + double ilh); + +inline TriangleMesh slices_to_triangle_mesh( + const std::vector &slices, double zmin, double lh, double ilh) +{ + TriangleMesh out; slices_to_triangle_mesh(out, slices, zmin, lh, ilh); + return out; +} + +} // namespace Slic3r + +#endif // SLICESTOTRIANGLEMESH_HPP diff --git a/src/libslic3r/TriangulateWall.cpp b/src/libslic3r/TriangulateWall.cpp new file mode 100644 index 000000000..ec2945b10 --- /dev/null +++ b/src/libslic3r/TriangulateWall.cpp @@ -0,0 +1,133 @@ +#include "TriangulateWall.hpp" +#include "MTUtils.hpp" + +namespace Slic3r { + +class Ring { + size_t idx = 0, nextidx = 1, startidx = 0, begin = 0, end = 0; + +public: + explicit Ring(size_t from, size_t to) : begin(from), end(to) { init(begin); } + + size_t size() const { return end - begin; } + std::pair pos() const { return {idx, nextidx}; } + bool is_lower() const { return idx < size(); } + + void inc() + { + if (nextidx != startidx) nextidx++; + if (nextidx == end) nextidx = begin; + idx ++; + if (idx == end) idx = begin; + } + + void init(size_t pos) + { + startidx = begin + (pos - begin) % size(); + idx = startidx; + nextidx = begin + (idx + 1 - begin) % size(); + } + + bool is_finished() const { return nextidx == idx; } +}; + +static double sq_dst(const Vec3d &v1, const Vec3d& v2) +{ + Vec3d v = v1 - v2; + return v.x() * v.x() + v.y() * v.y() /*+ v.z() * v.z()*/; +} + +static double score(const Ring& onring, const Ring &offring, + const std::vector &pts) +{ + double a = sq_dst(pts[onring.pos().first], pts[offring.pos().first]); + double b = sq_dst(pts[onring.pos().second], pts[offring.pos().first]); + return (std::abs(a) + std::abs(b)) / 2.; +} + +class Triangulator { + const std::vector *pts; + Ring *onring, *offring; + + double calc_score() const + { + return Slic3r::score(*onring, *offring, *pts); + } + + void synchronize_rings() + { + Ring lring = *offring; + auto minsc = Slic3r::score(*onring, lring, *pts); + size_t imin = lring.pos().first; + + lring.inc(); + + while(!lring.is_finished()) { + double score = Slic3r::score(*onring, lring, *pts); + if (score < minsc) { minsc = score; imin = lring.pos().first; } + lring.inc(); + } + + offring->init(imin); + } + + void emplace_indices(std::vector &indices) + { + Vec3i tr{int(onring->pos().first), int(onring->pos().second), + int(offring->pos().first)}; + if (onring->is_lower()) std::swap(tr(0), tr(1)); + indices.emplace_back(tr); + } + +public: + void run(std::vector &indices) + { + synchronize_rings(); + + double score = 0, prev_score = 0; + while (!onring->is_finished() || !offring->is_finished()) { + prev_score = score; + if (onring->is_finished() || (score = calc_score()) > prev_score) { + std::swap(onring, offring); + } else { + emplace_indices(indices); + onring->inc(); + } + } + } + + explicit Triangulator(const std::vector *points, + Ring & lower, + Ring & upper) + : pts{points}, onring{&upper}, offring{&lower} + {} +}; + +Wall triangulate_wall( + const Polygon & lower, + const Polygon & upper, + double lower_z_mm, + double upper_z_mm) +{ + if (upper.points.size() < 3 || lower.points.size() < 3) return {}; + + Wall wall; + auto &pts = wall.first; + auto &ind = wall.second; + + pts.reserve(lower.points.size() + upper.points.size()); + for (auto &p : lower.points) + wall.first.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); + for (auto &p : upper.points) + wall.first.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); + + ind.reserve(2 * (lower.size() + upper.size())); + + Ring lring{0, lower.points.size()}, uring{lower.points.size(), pts.size()}; + Triangulator t{&pts, lring, uring}; + t.run(ind); + + return wall; +} + +} // namespace Slic3r diff --git a/src/libslic3r/TriangulateWall.hpp b/src/libslic3r/TriangulateWall.hpp new file mode 100644 index 000000000..68bf4b0ac --- /dev/null +++ b/src/libslic3r/TriangulateWall.hpp @@ -0,0 +1,17 @@ +#ifndef TRIANGULATEWALL_HPP +#define TRIANGULATEWALL_HPP + +#include "libslic3r/Polygon.hpp" + +namespace Slic3r { + +using Wall = std::pair, std::vector>; + +Wall triangulate_wall( + const Polygon & lower, + const Polygon & upper, + double lower_z_mm, + double upper_z_mm); +} + +#endif // TRIANGULATEWALL_HPP diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index a5b53584d..02f022083 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -17,90 +17,14 @@ namespace Slic3r { -class Zipper::Impl { +class Zipper::Impl: public MZ_Archive { public: - mz_zip_archive arch; std::string m_zipname; - static std::string get_errorstr(mz_zip_error mz_err) - { - switch (mz_err) - { - case MZ_ZIP_NO_ERROR: - return "no error"; - case MZ_ZIP_UNDEFINED_ERROR: - return L("undefined error"); - case MZ_ZIP_TOO_MANY_FILES: - return L("too many files"); - case MZ_ZIP_FILE_TOO_LARGE: - return L("file too large"); - case MZ_ZIP_UNSUPPORTED_METHOD: - return L("unsupported method"); - case MZ_ZIP_UNSUPPORTED_ENCRYPTION: - return L("unsupported encryption"); - case MZ_ZIP_UNSUPPORTED_FEATURE: - return L("unsupported feature"); - case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: - return L("failed finding central directory"); - case MZ_ZIP_NOT_AN_ARCHIVE: - return L("not a ZIP archive"); - case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: - return L("invalid header or archive is corrupted"); - case MZ_ZIP_UNSUPPORTED_MULTIDISK: - return L("unsupported multidisk archive"); - case MZ_ZIP_DECOMPRESSION_FAILED: - return L("decompression failed or archive is corrupted"); - case MZ_ZIP_COMPRESSION_FAILED: - return L("compression failed"); - case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: - return L("unexpected decompressed size"); - case MZ_ZIP_CRC_CHECK_FAILED: - return L("CRC-32 check failed"); - case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: - return L("unsupported central directory size"); - case MZ_ZIP_ALLOC_FAILED: - return L("allocation failed"); - case MZ_ZIP_FILE_OPEN_FAILED: - return L("file open failed"); - case MZ_ZIP_FILE_CREATE_FAILED: - return L("file create failed"); - case MZ_ZIP_FILE_WRITE_FAILED: - return L("file write failed"); - case MZ_ZIP_FILE_READ_FAILED: - return L("file read failed"); - case MZ_ZIP_FILE_CLOSE_FAILED: - return L("file close failed"); - case MZ_ZIP_FILE_SEEK_FAILED: - return L("file seek failed"); - case MZ_ZIP_FILE_STAT_FAILED: - return L("file stat failed"); - case MZ_ZIP_INVALID_PARAMETER: - return L("invalid parameter"); - case MZ_ZIP_INVALID_FILENAME: - return L("invalid filename"); - case MZ_ZIP_BUF_TOO_SMALL: - return L("buffer too small"); - case MZ_ZIP_INTERNAL_ERROR: - return L("internal error"); - case MZ_ZIP_FILE_NOT_FOUND: - return L("file not found"); - case MZ_ZIP_ARCHIVE_TOO_LARGE: - return L("archive is too large"); - case MZ_ZIP_VALIDATION_FAILED: - return L("validation failed"); - case MZ_ZIP_WRITE_CALLBACK_FAILED: - return L("write calledback failed"); - default: - break; - } - - return "unknown error"; - } - std::string formatted_errorstr() const { return L("Error with zip archive") + " " + m_zipname + ": " + - get_errorstr(arch.m_last_error) + "!"; + get_errorstr() + "!"; } SLIC3R_NORETURN void blow_up() const @@ -167,7 +91,7 @@ void Zipper::add_entry(const std::string &name) m_entry = name; } -void Zipper::add_entry(const std::string &name, const uint8_t *data, size_t l) +void Zipper::add_entry(const std::string &name, const void *data, size_t l) { if(!m_impl->is_alive()) return; diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index be1e69b5c..bbaf2f05e 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -28,7 +28,7 @@ public: // Will blow up in a runtime exception if the file cannot be created. explicit Zipper(const std::string& zipfname, - e_compression level = NO_COMPRESSION); + e_compression level = FAST_COMPRESSION); ~Zipper(); // No copies allwed, this is a file resource... @@ -49,7 +49,7 @@ public: /// Add a new binary file entry with an instantly given byte buffer. /// This method throws exactly like finish_entry() does. - void add_entry(const std::string& name, const std::uint8_t* data, size_t l); + void add_entry(const std::string& name, const void* data, size_t bytes); // Writing data to the archive works like with standard streams. The target // within the zip file is the entry created with the add_entry method. diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 1cf946f8b..5012762a9 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "Technologies.hpp" #include "Semver.hpp" @@ -247,6 +248,37 @@ static inline bool is_approx(Number value, Number test_value) return std::fabs(double(value) - double(test_value)) < double(EPSILON); } +// A meta-predicate which is true for integers wider than or equal to coord_t +template struct is_scaled_coord +{ + static const constexpr bool value = + std::is_integral::value && + std::numeric_limits::digits >= + std::numeric_limits::digits; +}; + +// Meta predicates for floating, 'scaled coord' and generic arithmetic types +// Can be used to restrict templates to work for only the specified set of types. +// parameter T is the type we want to restrict +// parameter O (Optional defaults to T) is the type that the whole expression +// will be evaluated to. +// e.g. template FloatingOnly is_nan(T val); +// The whole template will be defined only for floating point types and the +// return type will be bool. +// For more info how to use, see docs for std::enable_if +// +template +using FloatingOnly = std::enable_if_t::value, O>; + +template +using ScaledCoordOnly = std::enable_if_t::value, O>; + +template +using IntegerOnly = std::enable_if_t::value, O>; + +template +using ArithmeticOnly = std::enable_if_t::value, O>; + } // namespace Slic3r #endif diff --git a/src/libslic3r/miniz_extension.cpp b/src/libslic3r/miniz_extension.cpp index 17cc136fc..76b4cb4e5 100644 --- a/src/libslic3r/miniz_extension.cpp +++ b/src/libslic3r/miniz_extension.cpp @@ -1,9 +1,17 @@ +#include + #include "miniz_extension.hpp" #if defined(_MSC_VER) || defined(__MINGW64__) #include "boost/nowide/cstdio.hpp" #endif +#include "I18N.hpp" + +//! macro used to mark string used at localization, +//! return same string +#define L(s) Slic3r::I18N::translate(s) + namespace Slic3r { namespace { @@ -68,4 +76,84 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname) bool close_zip_reader(mz_zip_archive *zip) { return close_zip(zip, true); } bool close_zip_writer(mz_zip_archive *zip) { return close_zip(zip, false); } +MZ_Archive::MZ_Archive() +{ + mz_zip_zero_struct(&arch); } + +std::string MZ_Archive::get_errorstr(mz_zip_error mz_err) +{ + switch (mz_err) + { + case MZ_ZIP_NO_ERROR: + return "no error"; + case MZ_ZIP_UNDEFINED_ERROR: + return L("undefined error"); + case MZ_ZIP_TOO_MANY_FILES: + return L("too many files"); + case MZ_ZIP_FILE_TOO_LARGE: + return L("file too large"); + case MZ_ZIP_UNSUPPORTED_METHOD: + return L("unsupported method"); + case MZ_ZIP_UNSUPPORTED_ENCRYPTION: + return L("unsupported encryption"); + case MZ_ZIP_UNSUPPORTED_FEATURE: + return L("unsupported feature"); + case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: + return L("failed finding central directory"); + case MZ_ZIP_NOT_AN_ARCHIVE: + return L("not a ZIP archive"); + case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: + return L("invalid header or archive is corrupted"); + case MZ_ZIP_UNSUPPORTED_MULTIDISK: + return L("unsupported multidisk archive"); + case MZ_ZIP_DECOMPRESSION_FAILED: + return L("decompression failed or archive is corrupted"); + case MZ_ZIP_COMPRESSION_FAILED: + return L("compression failed"); + case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: + return L("unexpected decompressed size"); + case MZ_ZIP_CRC_CHECK_FAILED: + return L("CRC-32 check failed"); + case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: + return L("unsupported central directory size"); + case MZ_ZIP_ALLOC_FAILED: + return L("allocation failed"); + case MZ_ZIP_FILE_OPEN_FAILED: + return L("file open failed"); + case MZ_ZIP_FILE_CREATE_FAILED: + return L("file create failed"); + case MZ_ZIP_FILE_WRITE_FAILED: + return L("file write failed"); + case MZ_ZIP_FILE_READ_FAILED: + return L("file read failed"); + case MZ_ZIP_FILE_CLOSE_FAILED: + return L("file close failed"); + case MZ_ZIP_FILE_SEEK_FAILED: + return L("file seek failed"); + case MZ_ZIP_FILE_STAT_FAILED: + return L("file stat failed"); + case MZ_ZIP_INVALID_PARAMETER: + return L("invalid parameter"); + case MZ_ZIP_INVALID_FILENAME: + return L("invalid filename"); + case MZ_ZIP_BUF_TOO_SMALL: + return L("buffer too small"); + case MZ_ZIP_INTERNAL_ERROR: + return L("internal error"); + case MZ_ZIP_FILE_NOT_FOUND: + return L("file not found"); + case MZ_ZIP_ARCHIVE_TOO_LARGE: + return L("archive is too large"); + case MZ_ZIP_VALIDATION_FAILED: + return L("validation failed"); + case MZ_ZIP_WRITE_CALLBACK_FAILED: + return L("write calledback failed"); + default: + break; + } + + return "unknown error"; +} + +} // namespace Slic3r diff --git a/src/libslic3r/miniz_extension.hpp b/src/libslic3r/miniz_extension.hpp index 8d0967cbc..006226bf2 100644 --- a/src/libslic3r/miniz_extension.hpp +++ b/src/libslic3r/miniz_extension.hpp @@ -11,6 +11,25 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8); bool close_zip_reader(mz_zip_archive *zip); bool close_zip_writer(mz_zip_archive *zip); -} +class MZ_Archive { +public: + mz_zip_archive arch; + + MZ_Archive(); + + static std::string get_errorstr(mz_zip_error mz_err); + + std::string get_errorstr() const + { + return get_errorstr(arch.m_last_error) + "!"; + } + + bool is_alive() const + { + return arch.m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + } +}; + +} // namespace Slic3r #endif // MINIZ_EXTENSION_HPP diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 26f8de674..6695ba764 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -144,12 +144,19 @@ set(SLIC3R_GUI_SOURCES GUI/UpdateDialogs.hpp GUI/FirmwareDialog.cpp GUI/FirmwareDialog.hpp - GUI/ProgressIndicator.hpp - GUI/ProgressStatusBar.hpp - GUI/ProgressStatusBar.cpp GUI/PrintHostDialogs.cpp GUI/PrintHostDialogs.hpp - GUI/Job.hpp + GUI/Jobs/Job.hpp + GUI/Jobs/Job.cpp + GUI/Jobs/ArrangeJob.hpp + GUI/Jobs/ArrangeJob.cpp + GUI/Jobs/RotoptimizeJob.hpp + GUI/Jobs/RotoptimizeJob.cpp + GUI/Jobs/SLAImportJob.hpp + GUI/Jobs/SLAImportJob.cpp + GUI/Jobs/ProgressIndicator.hpp + GUI/ProgressStatusBar.hpp + GUI/ProgressStatusBar.cpp GUI/Mouse3DController.cpp GUI/Mouse3DController.hpp GUI/DoubleSlider.cpp @@ -179,6 +186,8 @@ set(SLIC3R_GUI_SOURCES Utils/HexFile.cpp Utils/HexFile.hpp Utils/Thread.hpp + Utils/SLAImport.hpp + Utils/SLAImport.cpp ) if (APPLE) diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 9ab16755d..c8c344caa 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -19,6 +19,7 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/GCode/PreviewData.hpp" +#include "libslic3r/Format/SL1.hpp" #include "libslic3r/libslic3r.h" #include @@ -153,7 +154,7 @@ void BackgroundSlicingProcess::process_sla() const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); Zipper zipper(export_path); - m_sla_print->export_raster(zipper); + m_sla_archive.export_print(zipper, *m_sla_print); if (m_thumbnail_cb != nullptr) { @@ -491,9 +492,9 @@ void BackgroundSlicingProcess::prepare_upload() m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); } else { m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); - + Zipper zipper{source_path.string()}; - m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string()); + m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string()); if (m_thumbnail_cb != nullptr) { ThumbnailsList thumbnails; diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index a926e702b..d3fca88fc 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -10,6 +10,7 @@ #include #include "libslic3r/Print.hpp" +#include "libslic3r/Format/SL1.hpp" #include "slic3r/Utils/PrintHost.hpp" @@ -19,6 +20,7 @@ class DynamicPrintConfig; class GCodePreviewData; class Model; class SLAPrint; +class SL1Archive; class SlicingStatusEvent : public wxEvent { @@ -47,7 +49,7 @@ public: ~BackgroundSlicingProcess(); void set_fff_print(Print *print) { m_fff_print = print; } - void set_sla_print(SLAPrint *print) { m_sla_print = print; } + void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); } void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; } void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } #if ENABLE_GCODE_VIEWER @@ -157,7 +159,8 @@ private: // Data structure, to which the G-code export writes its annotations. GCodePreviewData *m_gcode_preview_data = nullptr; // Callback function, used to write thumbnails into gcode. - ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; + ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; + SL1Archive m_sla_archive; #if ENABLE_GCODE_VIEWER GCodeProcessor::Result* m_gcode_result = nullptr; #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index aac22be6f..3d8d288d1 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -624,12 +624,6 @@ void GCodeViewer::render_shells() const } void GCodeViewer::render_overlay() const -{ - render_legend(); - render_toolbar(); -} - -void GCodeViewer::render_legend() const { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); static const float ICON_BORDER_SIZE = 25.0f; @@ -803,10 +797,6 @@ void GCodeViewer::render_legend() const ImGui::PopStyleVar(); } -void GCodeViewer::render_toolbar() const -{ -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 70e20ce0b..6e7cc2f17 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -160,7 +160,7 @@ private: std::vector m_extruder_ids; Extrusions m_extrusions; Shells m_shells; - mutable EViewType m_view_type{ EViewType::FeatureType }; + EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; public: @@ -208,8 +208,6 @@ private: void render_toolpaths() const; void render_shells() const; void render_overlay() const; - void render_legend() const; - void render_toolbar() const; }; } // namespace GUI diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 794226520..aee71f16e 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -321,20 +322,41 @@ bool GUI_App::on_init_inner() set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); app_config = new AppConfig(); - preset_bundle = new PresetBundle(); - - // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory - // supplied as argument to --datadir; in that case we should still run the wizard - preset_bundle->setup_directories(); // load settings app_conf_exists = app_config->exists(); if (app_conf_exists) { app_config->load(); } + + std::string msg = Http::tls_global_init(); + wxRichMessageDialog + dlg(nullptr, + wxString::Format(_(L("%s\nDo you want to continue?")), _(msg)), + "PrusaSlicer", wxICON_QUESTION | wxYES_NO); + + bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes"; + std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location"); + ssl_accept = ssl_accept && ssl_cert_store == Http::tls_system_cert_store(); + + dlg.ShowCheckBox(_(L("Remember my choice"))); + if (!msg.empty() && !ssl_accept) { + if (dlg.ShowModal() != wxID_YES) return false; + app_config->set("tls_cert_store_accepted", + dlg.IsCheckBoxChecked() ? "yes" : "no"); + app_config->set("tls_accepted_cert_store_location", + dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); + } + app_config->set("version", SLIC3R_VERSION); app_config->save(); + + preset_bundle = new PresetBundle(); + + // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory + // supplied as argument to --datadir; in that case we should still run the wizard + preset_bundle->setup_directories(); #ifdef __WXMSW__ associate_3mf_files(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 6297cace3..1f2ce0221 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2071,37 +2071,40 @@ void ObjectList::load_shape_object(const std::string& type_name) // Create mesh BoundingBoxf3 bb; TriangleMesh mesh = create_mesh(type_name, bb); + load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name)); +} +void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name) +{ // Add mesh to model as a new object Model& model = wxGetApp().plater()->model(); - const wxString name = _(L("Shape")) + "-" + _(type_name); #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - + std::vector object_idxs; ModelObject* new_object = model.add_object(); new_object->name = into_u8(name); new_object->add_instance(); // each object should have at list one instance - + ModelVolume* new_volume = new_object->add_volume(mesh); new_volume->name = into_u8(name); // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); new_object->invalidate_bounding_box(); - + new_object->center_around_origin(); new_object->ensure_on_bed(); - + const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb(); new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -new_object->origin_translation(2))); - + object_idxs.push_back(model.objects.size() - 1); #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - + paste_objects_into_list(object_idxs); #ifdef _DEBUG diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 609411cd5..72e130737 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -24,6 +24,7 @@ class ConfigOptionsGroup; class DynamicPrintConfig; class ModelObject; class ModelVolume; +class TriangleMesh; enum class ModelVolumeType : int; // FIXME: broken build on mac os because of this is missing: @@ -265,6 +266,7 @@ public: void load_part(ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void load_shape_object(const std::string &type_name); + void load_mesh_object(const TriangleMesh &mesh, const wxString &name); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); diff --git a/src/slic3r/GUI/Job.hpp b/src/slic3r/GUI/Job.hpp deleted file mode 100644 index ac31b9bdb..000000000 --- a/src/slic3r/GUI/Job.hpp +++ /dev/null @@ -1,155 +0,0 @@ -#ifndef JOB_HPP -#define JOB_HPP - -#include - -#include -#include -#include - -#include - -#include - -namespace Slic3r { namespace GUI { - -// A class to handle UI jobs like arranging and optimizing rotation. -// These are not instant jobs, the user has to be informed about their -// state in the status progress indicator. On the other hand they are -// separated from the background slicing process. Ideally, these jobs should -// run when the background process is not running. -// -// TODO: A mechanism would be useful for blocking the plater interactions: -// objects would be frozen for the user. In case of arrange, an animation -// could be shown, or with the optimize orientations, partial results -// could be displayed. -class Job : public wxEvtHandler -{ - int m_range = 100; - boost::thread m_thread; - std::atomic m_running{false}, m_canceled{false}; - bool m_finalized = false; - std::shared_ptr m_progress; - - void run() - { - m_running.store(true); - process(); - m_running.store(false); - - // ensure to call the last status to finalize the job - update_status(status_range(), ""); - } - -protected: - // status range for a particular job - virtual int status_range() const { return 100; } - - // status update, to be used from the work thread (process() method) - void update_status(int st, const wxString &msg = "") - { - auto evt = new wxThreadEvent(); - evt->SetInt(st); - evt->SetString(msg); - wxQueueEvent(this, evt); - } - - bool was_canceled() const { return m_canceled.load(); } - - // Launched just before start(), a job can use it to prepare internals - virtual void prepare() {} - - // Launched when the job is finished. It refreshes the 3Dscene by def. - virtual void finalize() { m_finalized = true; } - - -public: - Job(std::shared_ptr pri) : m_progress(pri) - { - Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { - auto msg = evt.GetString(); - if (!msg.empty()) - m_progress->set_status_text(msg.ToUTF8().data()); - - if (m_finalized) return; - - m_progress->set_progress(evt.GetInt()); - if (evt.GetInt() == status_range()) { - // set back the original range and cancel callback - m_progress->set_range(m_range); - m_progress->set_cancel_callback(); - wxEndBusyCursor(); - - finalize(); - - // dont do finalization again for the same process - m_finalized = true; - } - }); - } - - bool is_finalized() const { return m_finalized; } - - Job(const Job &) = delete; - Job(Job &&) = delete; - Job &operator=(const Job &) = delete; - Job &operator=(Job &&) = delete; - - virtual void process() = 0; - - void start() - { // Start the job. No effect if the job is already running - if (!m_running.load()) { - prepare(); - - // Save the current status indicatior range and push the new one - m_range = m_progress->get_range(); - m_progress->set_range(status_range()); - - // init cancellation flag and set the cancel callback - m_canceled.store(false); - m_progress->set_cancel_callback( - [this]() { m_canceled.store(true); }); - - m_finalized = false; - - // Changing cursor to busy - wxBeginBusyCursor(); - - try { // Execute the job - m_thread = create_thread([this] { this->run(); }); - } catch (std::exception &) { - update_status(status_range(), - _(L("ERROR: not enough resources to " - "execute a new job."))); - } - - // The state changes will be undone when the process hits the - // last status value, in the status update handler (see ctor) - } - } - - // To wait for the running job and join the threads. False is - // returned if the timeout has been reached and the job is still - // running. Call cancel() before this fn if you want to explicitly - // end the job. - bool join(int timeout_ms = 0) - { - if (!m_thread.joinable()) return true; - - if (timeout_ms <= 0) - m_thread.join(); - else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) - return false; - - return true; - } - - bool is_running() const { return m_running.load(); } - void cancel() { m_canceled.store(true); } -}; - -} -} - -#endif // JOB_HPP diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp new file mode 100644 index 000000000..00b9fb654 --- /dev/null +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -0,0 +1,223 @@ +#include "ArrangeJob.hpp" + +#include "libslic3r/MTUtils.hpp" + +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI.hpp" + +namespace Slic3r { namespace GUI { + +// Cache the wti info +class WipeTower: public GLCanvas3D::WipeTowerInfo { + using ArrangePolygon = arrangement::ArrangePolygon; +public: + explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti) + : GLCanvas3D::WipeTowerInfo(wti) + {} + + explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti) + : GLCanvas3D::WipeTowerInfo(std::move(wti)) + {} + + void apply_arrange_result(const Vec2d& tr, double rotation) + { + m_pos = unscaled(tr); m_rotation = rotation; + apply_wipe_tower(); + } + + ArrangePolygon get_arrange_polygon() const + { + Polygon ap({ + {coord_t(0), coord_t(0)}, + {scaled(m_bb_size(X)), coord_t(0)}, + {scaled(m_bb_size)}, + {coord_t(0), scaled(m_bb_size(Y))}, + {coord_t(0), coord_t(0)}, + }); + + ArrangePolygon ret; + ret.poly.contour = std::move(ap); + ret.translation = scaled(m_pos); + ret.rotation = m_rotation; + ret.priority++; + return ret; + } +}; + +static WipeTower get_wipe_tower(Plater &plater) +{ + return WipeTower{plater.canvas3D()->get_wipe_tower_info()}; +} + +void ArrangeJob::clear_input() +{ + const Model &model = m_plater->model(); + + size_t count = 0, cunprint = 0; // To know how much space to reserve + for (auto obj : model.objects) + for (auto mi : obj->instances) + mi->printable ? count++ : cunprint++; + + m_selected.clear(); + m_unselected.clear(); + m_unprintable.clear(); + m_selected.reserve(count + 1 /* for optional wti */); + m_unselected.reserve(count + 1 /* for optional wti */); + m_unprintable.reserve(cunprint /* for optional wti */); +} + +double ArrangeJob::bed_stride() const { + double bedwidth = m_plater->bed_shape_bb().size().x(); + return scaled((1. + LOGICAL_BED_GAP) * bedwidth); +} + +void ArrangeJob::prepare_all() { + clear_input(); + + for (ModelObject *obj: m_plater->model().objects) + for (ModelInstance *mi : obj->instances) { + ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable; + cont.emplace_back(get_arrange_poly(mi)); + } + + if (auto wti = get_wipe_tower(*m_plater)) + m_selected.emplace_back(wti.get_arrange_polygon()); +} + +void ArrangeJob::prepare_selected() { + clear_input(); + + Model &model = m_plater->model(); + double stride = bed_stride(); + + std::vector + obj_sel(model.objects.size(), nullptr); + + for (auto &s : m_plater->get_selection().get_content()) + if (s.first < int(obj_sel.size())) + obj_sel[size_t(s.first)] = &s.second; + + // Go through the objects and check if inside the selection + for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { + const Selection::InstanceIdxsList * instlist = obj_sel[oidx]; + ModelObject *mo = model.objects[oidx]; + + std::vector inst_sel(mo->instances.size(), false); + + if (instlist) + for (auto inst_id : *instlist) + inst_sel[size_t(inst_id)] = true; + + for (size_t i = 0; i < inst_sel.size(); ++i) { + ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); + + ArrangePolygons &cont = mo->instances[i]->printable ? + (inst_sel[i] ? m_selected : + m_unselected) : + m_unprintable; + + cont.emplace_back(std::move(ap)); + } + } + + if (auto wti = get_wipe_tower(*m_plater)) { + ArrangePolygon &&ap = get_arrange_poly(&wti); + + m_plater->get_selection().is_wipe_tower() ? + m_selected.emplace_back(std::move(ap)) : + m_unselected.emplace_back(std::move(ap)); + } + + // If the selection was empty arrange everything + if (m_selected.empty()) m_selected.swap(m_unselected); + + // The strides have to be removed from the fixed items. For the + // arrangeable (selected) items bed_idx is ignored and the + // translation is irrelevant. + for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; +} + +void ArrangeJob::prepare() +{ + wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); +} + +void ArrangeJob::process() +{ + static const auto arrangestr = _(L("Arranging")); + + double dist = min_object_distance(*m_plater->config()); + + arrangement::ArrangeParams params; + params.min_obj_distance = scaled(dist); + + auto count = unsigned(m_selected.size() + m_unprintable.size()); + Points bedpts = get_bed_shape(*m_plater->config()); + + params.stopcondition = [this]() { return was_canceled(); }; + + try { + params.progressind = [this, count](unsigned st) { + st += m_unprintable.size(); + if (st > 0) update_status(int(count - st), arrangestr); + }; + + arrangement::arrange(m_selected, m_unselected, bedpts, params); + + params.progressind = [this, count](unsigned st) { + if (st > 0) update_status(int(count - st), arrangestr); + }; + + arrangement::arrange(m_unprintable, {}, bedpts, params); + } catch (std::exception & /*e*/) { + GUI::show_error(m_plater, + _(L("Could not arrange model objects! " + "Some geometries may be invalid."))); + } + + // finalize just here. + update_status(int(count), + was_canceled() ? _(L("Arranging canceled.")) + : _(L("Arranging done."))); +} + +void ArrangeJob::finalize() { + // Ignore the arrange result if aborted. + if (was_canceled()) return; + + // Unprintable items go to the last virtual bed + int beds = 0; + + // Apply the arrange result to all selected objects + for (ArrangePolygon &ap : m_selected) { + beds = std::max(ap.bed_idx, beds); + ap.apply(); + } + + // Get the virtual beds from the unselected items + for (ArrangePolygon &ap : m_unselected) + beds = std::max(ap.bed_idx, beds); + + // Move the unprintable items to the last virtual bed. + for (ArrangePolygon &ap : m_unprintable) { + ap.bed_idx += beds + 1; + ap.apply(); + } + + m_plater->update(); + + Job::finalize(); +} + +arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &plater) +{ + return WipeTower{plater.canvas3D()->get_wipe_tower_info()}.get_arrange_polygon(); +} + +void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap) +{ + WipeTower{plater.canvas3D()->get_wipe_tower_info()}.apply_arrange_result(ap.translation.cast(), ap.rotation); +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp new file mode 100644 index 000000000..bd097af6b --- /dev/null +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -0,0 +1,77 @@ +#ifndef ARRANGEJOB_HPP +#define ARRANGEJOB_HPP + +#include "Job.hpp" +#include "libslic3r/Arrange.hpp" + +namespace Slic3r { namespace GUI { + +class Plater; + +class ArrangeJob : public Job +{ + Plater *m_plater; + + using ArrangePolygon = arrangement::ArrangePolygon; + using ArrangePolygons = arrangement::ArrangePolygons; + + // The gap between logical beds in the x axis expressed in ratio of + // the current bed width. + static const constexpr double LOGICAL_BED_GAP = 1. / 5.; + + ArrangePolygons m_selected, m_unselected, m_unprintable; + + // clear m_selected and m_unselected, reserve space for next usage + void clear_input(); + + // Stride between logical beds + double bed_stride() const; + + // Set up arrange polygon for a ModelInstance and Wipe tower + template ArrangePolygon get_arrange_poly(T *obj) const + { + ArrangePolygon ap = obj->get_arrange_polygon(); + ap.priority = 0; + ap.bed_idx = ap.translation.x() / bed_stride(); + ap.setter = [obj, this](const ArrangePolygon &p) { + if (p.is_arranged()) { + Vec2d t = p.translation.cast(); + t.x() += p.bed_idx * bed_stride(); + obj->apply_arrange_result(t, p.rotation); + } + }; + return ap; + } + + // Prepare all objects on the bed regardless of the selection + void prepare_all(); + + // Prepare the selected and unselected items separately. If nothing is + // selected, behaves as if everything would be selected. + void prepare_selected(); + +protected: + + void prepare() override; + +public: + ArrangeJob(std::shared_ptr pri, Plater *plater) + : Job{std::move(pri)}, m_plater{plater} + {} + + int status_range() const override + { + return int(m_selected.size() + m_unprintable.size()); + } + + void process() override; + + void finalize() override; +}; + +arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &); +void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap); + +}} // namespace Slic3r::GUI + +#endif // ARRANGEJOB_HPP diff --git a/src/slic3r/GUI/Jobs/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp new file mode 100644 index 000000000..cc2cb75f1 --- /dev/null +++ b/src/slic3r/GUI/Jobs/Job.cpp @@ -0,0 +1,121 @@ +#include + +#include "Job.hpp" +#include + +namespace Slic3r { + +void GUI::Job::run() +{ + m_running.store(true); + process(); + m_running.store(false); + + // ensure to call the last status to finalize the job + update_status(status_range(), ""); +} + +void GUI::Job::update_status(int st, const wxString &msg) +{ + auto evt = new wxThreadEvent(); + evt->SetInt(st); + evt->SetString(msg); + wxQueueEvent(this, evt); +} + +GUI::Job::Job(std::shared_ptr pri) + : m_progress(std::move(pri)) +{ + Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { + auto msg = evt.GetString(); + if (!msg.empty()) + m_progress->set_status_text(msg.ToUTF8().data()); + + if (m_finalized) return; + + m_progress->set_progress(evt.GetInt()); + if (evt.GetInt() == status_range()) { + // set back the original range and cancel callback + m_progress->set_range(m_range); + m_progress->set_cancel_callback(); + wxEndBusyCursor(); + + finalize(); + + // dont do finalization again for the same process + m_finalized = true; + } + }); +} + +void GUI::Job::start() +{ // Start the job. No effect if the job is already running + if (!m_running.load()) { + prepare(); + + // Save the current status indicatior range and push the new one + m_range = m_progress->get_range(); + m_progress->set_range(status_range()); + + // init cancellation flag and set the cancel callback + m_canceled.store(false); + m_progress->set_cancel_callback( + [this]() { m_canceled.store(true); }); + + m_finalized = false; + + // Changing cursor to busy + wxBeginBusyCursor(); + + try { // Execute the job + m_thread = create_thread([this] { this->run(); }); + } catch (std::exception &) { + update_status(status_range(), + _(L("ERROR: not enough resources to " + "execute a new job."))); + } + + // The state changes will be undone when the process hits the + // last status value, in the status update handler (see ctor) + } +} + +bool GUI::Job::join(int timeout_ms) +{ + if (!m_thread.joinable()) return true; + + if (timeout_ms <= 0) + m_thread.join(); + else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) + return false; + + return true; +} + +void GUI::ExclusiveJobGroup::start(size_t jid) { + assert(jid < m_jobs.size()); + stop_all(); + m_jobs[jid]->start(); +} + +void GUI::ExclusiveJobGroup::join_all(int wait_ms) +{ + std::vector aborted(m_jobs.size(), false); + + for (size_t jid = 0; jid < m_jobs.size(); ++jid) + aborted[jid] = m_jobs[jid]->join(wait_ms); + + if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; })) + BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; +} + +bool GUI::ExclusiveJobGroup::is_any_running() const +{ + return std::any_of(m_jobs.begin(), m_jobs.end(), + [](const std::unique_ptr &j) { + return j->is_running(); + }); +} + +} + diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp new file mode 100644 index 000000000..130ca2ed9 --- /dev/null +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -0,0 +1,110 @@ +#ifndef JOB_HPP +#define JOB_HPP + +#include + +#include +#include + +#include "ProgressIndicator.hpp" + +#include + +#include + +namespace Slic3r { namespace GUI { + +// A class to handle UI jobs like arranging and optimizing rotation. +// These are not instant jobs, the user has to be informed about their +// state in the status progress indicator. On the other hand they are +// separated from the background slicing process. Ideally, these jobs should +// run when the background process is not running. +// +// TODO: A mechanism would be useful for blocking the plater interactions: +// objects would be frozen for the user. In case of arrange, an animation +// could be shown, or with the optimize orientations, partial results +// could be displayed. +class Job : public wxEvtHandler +{ + int m_range = 100; + boost::thread m_thread; + std::atomic m_running{false}, m_canceled{false}; + bool m_finalized = false; + std::shared_ptr m_progress; + + void run(); + +protected: + // status range for a particular job + virtual int status_range() const { return 100; } + + // status update, to be used from the work thread (process() method) + void update_status(int st, const wxString &msg = ""); + + bool was_canceled() const { return m_canceled.load(); } + + // Launched just before start(), a job can use it to prepare internals + virtual void prepare() {} + + // Launched when the job is finished. It refreshes the 3Dscene by def. + virtual void finalize() { m_finalized = true; } + +public: + Job(std::shared_ptr pri); + + bool is_finalized() const { return m_finalized; } + + Job(const Job &) = delete; + Job(Job &&) = delete; + Job &operator=(const Job &) = delete; + Job &operator=(Job &&) = delete; + + virtual void process() = 0; + + void start(); + + // To wait for the running job and join the threads. False is + // returned if the timeout has been reached and the job is still + // running. Call cancel() before this fn if you want to explicitly + // end the job. + bool join(int timeout_ms = 0); + + bool is_running() const { return m_running.load(); } + void cancel() { m_canceled.store(true); } +}; + +// Jobs defined inside the group class will be managed so that only one can +// run at a time. Also, the background process will be stopped if a job is +// started. +class ExclusiveJobGroup +{ + static const int ABORT_WAIT_MAX_MS = 10000; + + std::vector> m_jobs; + +protected: + virtual void before_start() {} + +public: + virtual ~ExclusiveJobGroup() = default; + + size_t add_job(std::unique_ptr &&job) + { + m_jobs.emplace_back(std::move(job)); + return m_jobs.size() - 1; + } + + void start(size_t jid); + + void cancel_all() { for (auto& j : m_jobs) j->cancel(); } + + void join_all(int wait_ms = 0); + + void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } + + bool is_any_running() const; +}; + +}} // namespace Slic3r::GUI + +#endif // JOB_HPP diff --git a/src/slic3r/GUI/ProgressIndicator.hpp b/src/slic3r/GUI/Jobs/ProgressIndicator.hpp similarity index 100% rename from src/slic3r/GUI/ProgressIndicator.hpp rename to src/slic3r/GUI/Jobs/ProgressIndicator.hpp diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp new file mode 100644 index 000000000..4cc34e71c --- /dev/null +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -0,0 +1,68 @@ +#include "RotoptimizeJob.hpp" + +#include "libslic3r/MTUtils.hpp" +#include "libslic3r/SLA/Rotfinder.hpp" +#include "libslic3r/MinAreaBoundingBox.hpp" + +#include "slic3r/GUI/Plater.hpp" + +namespace Slic3r { namespace GUI { + +void RotoptimizeJob::process() +{ + int obj_idx = m_plater->get_selected_object_idx(); + if (obj_idx < 0) { return; } + + ModelObject *o = m_plater->model().objects[size_t(obj_idx)]; + + auto r = sla::find_best_rotation( + *o, + .005f, + [this](unsigned s) { + if (s < 100) + update_status(int(s), + _(L("Searching for optimal orientation"))); + }, + [this]() { return was_canceled(); }); + + + double mindist = 6.0; // FIXME + + if (!was_canceled()) { + for(ModelInstance * oi : o->instances) { + oi->set_rotation({r[X], r[Y], r[Z]}); + + auto trmatrix = oi->get_transformation().get_matrix(); + Polygon trchull = o->convex_hull_2d(trmatrix); + + MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); + double phi = rotbb.angle_to_X(); + + // The box should be landscape + if(rotbb.width() < rotbb.height()) phi += PI / 2; + + Vec3d rt = oi->get_rotation(); rt(Z) += phi; + + oi->set_rotation(rt); + } + + m_plater->find_new_position(o->instances, scaled(mindist)); + + // Correct the z offset of the object which was corrupted be + // the rotation + o->ensure_on_bed(); + } + + update_status(100, was_canceled() ? _(L("Orientation search canceled.")) : + _(L("Orientation found."))); +} + +void RotoptimizeJob::finalize() +{ + if (!was_canceled()) + m_plater->update(); + + Job::finalize(); +} + +}} diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp new file mode 100644 index 000000000..983c43c68 --- /dev/null +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -0,0 +1,24 @@ +#ifndef ROTOPTIMIZEJOB_HPP +#define ROTOPTIMIZEJOB_HPP + +#include "Job.hpp" + +namespace Slic3r { namespace GUI { + +class Plater; + +class RotoptimizeJob : public Job +{ + Plater *m_plater; +public: + RotoptimizeJob(std::shared_ptr pri, Plater *plater) + : Job{std::move(pri)}, m_plater{plater} + {} + + void process() override; + void finalize() override; +}; + +}} // namespace Slic3r::GUI + +#endif // ROTOPTIMIZEJOB_HPP diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp new file mode 100644 index 000000000..e791bf94e --- /dev/null +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -0,0 +1,226 @@ +#include "SLAImportJob.hpp" + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/AppConfig.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/Utils/SLAImport.hpp" + +#include +#include +#include +#include +#include + +namespace Slic3r { namespace GUI { + +enum class Sel { modelAndProfile, profileOnly, modelOnly}; + +class ImportDlg: public wxDialog { + wxFilePickerCtrl *m_filepicker; + wxComboBox *m_import_dropdown, *m_quality_dropdown; + +public: + ImportDlg(Plater *plater) + : wxDialog{plater, wxID_ANY, "Import SLA archive"} + { + auto szvert = new wxBoxSizer{wxVERTICAL}; + auto szfilepck = new wxBoxSizer{wxHORIZONTAL}; + + m_filepicker = new wxFilePickerCtrl(this, wxID_ANY, + from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")), + "SL1 archive files (*.sl1, *.zip)|*.sl1;*.SL1;*.zip;*.ZIP", + wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + szfilepck->Add(new wxStaticText(this, wxID_ANY, _(L("Import file: "))), 0, wxALIGN_CENTER); + szfilepck->Add(m_filepicker, 1); + szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5); + + auto szchoices = new wxBoxSizer{wxHORIZONTAL}; + + static const std::vector inp_choices = { + _(L("Import model and profile")), + _(L("Import profile only")), + _(L("Import model only")) + }; + + m_import_dropdown = new wxComboBox( + this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize, + inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); + + szchoices->Add(m_import_dropdown); + szchoices->Add(new wxStaticText(this, wxID_ANY, _(L("Quality: "))), 0, wxALIGN_CENTER | wxALL, 5); + + static const std::vector qual_choices = { + _(L("Accurate")), + _(L("Balanced")), + _(L("Quick")) + }; + + m_quality_dropdown = new wxComboBox( + this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize, + qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); + szchoices->Add(m_quality_dropdown); + + m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { + if (get_selection() == Sel::profileOnly) + m_quality_dropdown->Disable(); + else m_quality_dropdown->Enable(); + }); + + szvert->Add(szchoices, 0, wxALL, 5); + szvert->AddStretchSpacer(1); + auto szbtn = new wxBoxSizer(wxHORIZONTAL); + szbtn->Add(new wxButton{this, wxID_CANCEL}); + szbtn->Add(new wxButton{this, wxID_OK}); + szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5); + + SetSizerAndFit(szvert); + } + + Sel get_selection() const + { + int sel = m_import_dropdown->GetSelection(); + return Sel(std::min(int(Sel::modelOnly), std::max(0, sel))); + } + + Vec2i get_marchsq_windowsize() const + { + enum { Accurate, Balanced, Fast}; + + switch(m_quality_dropdown->GetSelection()) + { + case Fast: return {8, 8}; + case Balanced: return {4, 4}; + default: + case Accurate: + return {2, 2}; + } + } + + wxString get_path() const + { + return m_filepicker->GetPath(); + } +}; + +class SLAImportJob::priv { +public: + Plater *plater; + + Sel sel = Sel::modelAndProfile; + + TriangleMesh mesh; + DynamicPrintConfig profile; + wxString path; + Vec2i win = {2, 2}; + std::string err; + + priv(Plater *plt): plater{plt} {} +}; + +SLAImportJob::SLAImportJob(std::shared_ptr pri, Plater *plater) + : Job{std::move(pri)}, p{std::make_unique(plater)} +{} + +SLAImportJob::~SLAImportJob() = default; + +void SLAImportJob::process() +{ + auto progr = [this](int s) { + if (s < 100) update_status(int(s), _(L("Importing SLA archive"))); + return !was_canceled(); + }; + + if (p->path.empty()) return; + + std::string path = p->path.ToUTF8().data(); + try { + switch (p->sel) { + case Sel::modelAndProfile: + import_sla_archive(path, p->win, p->mesh, p->profile, progr); + break; + case Sel::modelOnly: + import_sla_archive(path, p->win, p->mesh, progr); + break; + case Sel::profileOnly: + import_sla_archive(path, p->profile); + break; + } + + } catch (std::exception &ex) { + p->err = ex.what(); + } + + update_status(100, was_canceled() ? _(L("Importing canceled.")) : + _(L("Importing done."))); +} + +void SLAImportJob::reset() +{ + p->sel = Sel::modelAndProfile; + p->mesh = {}; + p->profile = {}; + p->win = {2, 2}; + p->path.Clear(); +} + +void SLAImportJob::prepare() +{ + reset(); + + ImportDlg dlg{p->plater}; + + if (dlg.ShowModal() == wxID_OK) { + auto path = dlg.get_path(); + auto nm = wxFileName(path); + p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : path.ToUTF8(); + p->sel = dlg.get_selection(); + p->win = dlg.get_marchsq_windowsize(); + } else { + p->path = ""; + } +} + +void SLAImportJob::finalize() +{ + // Ignore the arrange result if aborted. + if (was_canceled()) return; + + if (!p->err.empty()) { + show_error(p->plater, p->err); + p->err = ""; + return; + } + + std::string name = wxFileName(p->path).GetName().ToUTF8().data(); + + if (!p->profile.empty()) { + const ModelObjectPtrs& objects = p->plater->model().objects; + for (auto object : objects) + if (object->volumes.size() > 1) + { + Slic3r::GUI::show_info(nullptr, + _(L("You cannot load SLA project with a multi-part object on the bed")) + "\n\n" + + _(L("Please check your object list before preset changing.")), + _(L("Attention!")) ); + return; + } + + DynamicPrintConfig config = {}; + config.apply(SLAFullPrintConfig::defaults()); + config += std::move(p->profile); + + wxGetApp().preset_bundle->load_config_model(name, std::move(config)); + wxGetApp().load_current_presets(); + } + + if (!p->mesh.empty()) + p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name); + + reset(); +} + +}} diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.hpp b/src/slic3r/GUI/Jobs/SLAImportJob.hpp new file mode 100644 index 000000000..cff6cc899 --- /dev/null +++ b/src/slic3r/GUI/Jobs/SLAImportJob.hpp @@ -0,0 +1,31 @@ +#ifndef SLAIMPORTJOB_HPP +#define SLAIMPORTJOB_HPP + +#include "Job.hpp" + +namespace Slic3r { namespace GUI { + +class Plater; + +class SLAImportJob : public Job { + class priv; + + std::unique_ptr p; + +public: + SLAImportJob(std::shared_ptr pri, Plater *plater); + ~SLAImportJob(); + + void process() override; + + void reset(); + +protected: + void prepare() override; + + void finalize() override; +}; + +}} // namespace Slic3r::GUI + +#endif // SLAIMPORTJOB_HPP diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6a1964506..f7d7a6cac 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -589,6 +589,11 @@ void MainFrame::init_menubar() append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")), [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr, [this](){return m_plater != nullptr; }, this); + + append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")), + [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr, + [this](){return m_plater != nullptr; }, this); + import_menu->AppendSeparator(); append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")), [this](wxCommandEvent&) { load_config_file(); }, "import_config", nullptr, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3a59ce86d..b64f705dd 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -36,7 +36,6 @@ #include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/SLA/Hollowing.hpp" -#include "libslic3r/SLA/Rotfinder.hpp" #include "libslic3r/SLA/SupportPoint.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Print.hpp" @@ -44,13 +43,6 @@ #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Utils.hpp" -//#include "libslic3r/ClipperUtils.hpp" - -// #include "libnest2d/optimizers/nlopt/genetic.hpp" -// #include "libnest2d/backends/clipper/geometries.hpp" -// #include "libnest2d/utils/rotcalipers.hpp" -#include "libslic3r/MinAreaBoundingBox.hpp" - #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" @@ -69,7 +61,9 @@ #include "Camera.hpp" #include "Mouse3DController.hpp" #include "Tab.hpp" -#include "Job.hpp" +#include "Jobs/ArrangeJob.hpp" +#include "Jobs/RotoptimizeJob.hpp" +#include "Jobs/SLAImportJob.hpp" #include "PresetBundle.hpp" #include "BackgroundSlicingProcess.hpp" #include "ProgressStatusBar.hpp" @@ -1488,311 +1482,44 @@ struct Plater::priv BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; - // Cache the wti info - class WipeTower: public GLCanvas3D::WipeTowerInfo { - using ArrangePolygon = arrangement::ArrangePolygon; - friend priv; - public: - - void apply_arrange_result(const Vec2d& tr, double rotation) - { - m_pos = unscaled(tr); m_rotation = rotation; - apply_wipe_tower(); - } - - ArrangePolygon get_arrange_polygon() const - { - Polygon p({ - {coord_t(0), coord_t(0)}, - {scaled(m_bb_size(X)), coord_t(0)}, - {scaled(m_bb_size)}, - {coord_t(0), scaled(m_bb_size(Y))}, - {coord_t(0), coord_t(0)}, - }); - - ArrangePolygon ret; - ret.poly.contour = std::move(p); - ret.translation = scaled(m_pos); - ret.rotation = m_rotation; - ret.priority++; - return ret; - } - } wipetower; - - WipeTower& updated_wipe_tower() { - auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); - wipetower.m_pos = wti.pos(); - wipetower.m_rotation = wti.rotation(); - wipetower.m_bb_size = wti.bb_size(); - return wipetower; - } - - // A class to handle UI jobs like arranging and optimizing rotation. - // These are not instant jobs, the user has to be informed about their - // state in the status progress indicator. On the other hand they are - // separated from the background slicing process. Ideally, these jobs should - // run when the background process is not running. - // - // TODO: A mechanism would be useful for blocking the plater interactions: - // objects would be frozen for the user. In case of arrange, an animation - // could be shown, or with the optimize orientations, partial results - // could be displayed. - class PlaterJob: public Job - { - priv *m_plater; - protected: - - priv & plater() { return *m_plater; } - const priv &plater() const { return *m_plater; } - - // Launched when the job is finished. It refreshes the 3Dscene by def. - void finalize() override - { - // Do a full refresh of scene tree, including regenerating - // all the GLVolumes. FIXME The update function shall just - // reload the modified matrices. - if (!Job::was_canceled()) - plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH)); - - Job::finalize(); - } - - public: - PlaterJob(priv *_plater) - : Job(_plater->statusbar()), m_plater(_plater) - {} - }; - - enum class Jobs : size_t { - Arrange, - Rotoptimize - }; - - class ArrangeJob : public PlaterJob - { - using ArrangePolygon = arrangement::ArrangePolygon; - using ArrangePolygons = arrangement::ArrangePolygons; - - // The gap between logical beds in the x axis expressed in ratio of - // the current bed width. - static const constexpr double LOGICAL_BED_GAP = 1. / 5.; - - ArrangePolygons m_selected, m_unselected, m_unprintable; - - // clear m_selected and m_unselected, reserve space for next usage - void clear_input() { - const Model &model = plater().model; - - size_t count = 0, cunprint = 0; // To know how much space to reserve - for (auto obj : model.objects) - for (auto mi : obj->instances) - mi->printable ? count++ : cunprint++; - - m_selected.clear(); - m_unselected.clear(); - m_unprintable.clear(); - m_selected.reserve(count + 1 /* for optional wti */); - m_unselected.reserve(count + 1 /* for optional wti */); - m_unprintable.reserve(cunprint /* for optional wti */); - } - - // Stride between logical beds - double bed_stride() const { - double bedwidth = plater().bed_shape_bb().size().x(); - return scaled((1. + LOGICAL_BED_GAP) * bedwidth); - } - - // Set up arrange polygon for a ModelInstance and Wipe tower - template ArrangePolygon get_arrange_poly(T *obj) const { - ArrangePolygon ap = obj->get_arrange_polygon(); - ap.priority = 0; - ap.bed_idx = ap.translation.x() / bed_stride(); - ap.setter = [obj, this](const ArrangePolygon &p) { - if (p.is_arranged()) { - Vec2d t = p.translation.cast(); - t.x() += p.bed_idx * bed_stride(); - obj->apply_arrange_result(t, p.rotation); - } - }; - return ap; - } - - // Prepare all objects on the bed regardless of the selection - void prepare_all() { - clear_input(); - - for (ModelObject *obj: plater().model.objects) - for (ModelInstance *mi : obj->instances) { - ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable; - cont.emplace_back(get_arrange_poly(mi)); - } - - auto& wti = plater().updated_wipe_tower(); - if (wti) m_selected.emplace_back(get_arrange_poly(&wti)); - } - - // Prepare the selected and unselected items separately. If nothing is - // selected, behaves as if everything would be selected. - void prepare_selected() { - clear_input(); - - Model &model = plater().model; - coord_t stride = bed_stride(); - - std::vector - obj_sel(model.objects.size(), nullptr); - - for (auto &s : plater().get_selection().get_content()) - if (s.first < int(obj_sel.size())) - obj_sel[size_t(s.first)] = &s.second; - - // Go through the objects and check if inside the selection - for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { - const Selection::InstanceIdxsList * instlist = obj_sel[oidx]; - ModelObject *mo = model.objects[oidx]; - - std::vector inst_sel(mo->instances.size(), false); - - if (instlist) - for (auto inst_id : *instlist) - inst_sel[size_t(inst_id)] = true; - - for (size_t i = 0; i < inst_sel.size(); ++i) { - ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); - - ArrangePolygons &cont = mo->instances[i]->printable ? - (inst_sel[i] ? m_selected : - m_unselected) : - m_unprintable; - - cont.emplace_back(std::move(ap)); - } - } - - auto& wti = plater().updated_wipe_tower(); - if (wti) { - ArrangePolygon &&ap = get_arrange_poly(&wti); - - plater().get_selection().is_wipe_tower() ? - m_selected.emplace_back(std::move(ap)) : - m_unselected.emplace_back(std::move(ap)); - } - - // If the selection was empty arrange everything - if (m_selected.empty()) m_selected.swap(m_unselected); - - // The strides have to be removed from the fixed items. For the - // arrangeable (selected) items bed_idx is ignored and the - // translation is irrelevant. - for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; - } - - protected: - - void prepare() override - { - wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); - } - - public: - using PlaterJob::PlaterJob; - - int status_range() const override - { - return int(m_selected.size() + m_unprintable.size()); - } - - void process() override; - - void finalize() override { - // Ignore the arrange result if aborted. - if (was_canceled()) return; - - // Unprintable items go to the last virtual bed - int beds = 0; - - // Apply the arrange result to all selected objects - for (ArrangePolygon &ap : m_selected) { - beds = std::max(ap.bed_idx, beds); - ap.apply(); - } - - // Get the virtual beds from the unselected items - for (ArrangePolygon &ap : m_unselected) - beds = std::max(ap.bed_idx, beds); - - // Move the unprintable items to the last virtual bed. - for (ArrangePolygon &ap : m_unprintable) { - ap.bed_idx += beds + 1; - ap.apply(); - } - - plater().update(); - } - }; - - class RotoptimizeJob : public PlaterJob - { - public: - using PlaterJob::PlaterJob; - void process() override; - }; - - // Jobs defined inside the group class will be managed so that only one can // run at a time. Also, the background process will be stopped if a job is - // started. - class ExclusiveJobGroup { - - static const int ABORT_WAIT_MAX_MS = 10000; - - priv * m_plater; - - ArrangeJob arrange_job{m_plater}; - RotoptimizeJob rotoptimize_job{m_plater}; - - // To create a new job, just define a new subclass of Job, implement - // the process and the optional prepare() and finalize() methods - // Register the instance of the class in the m_jobs container - // if it cannot run concurrently with other jobs in this group - - std::vector> m_jobs{arrange_job, - rotoptimize_job}; - + // started. It is up the the plater to ensure that the background slicing + // can't be restarted while a ui job is still running. + class Jobs: public ExclusiveJobGroup + { + priv *m; + size_t m_arrange_id, m_rotoptimize_id, m_sla_import_id; + + void before_start() override { m->background_process.stop(); } + public: - ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {} - - void start(Jobs jid) { - m_plater->background_process.stop(); - stop_all(); - m_jobs[size_t(jid)].get().start(); - } - - void cancel_all() { for (Job& j : m_jobs) j.cancel(); } - - void join_all(int wait_ms = 0) + Jobs(priv *_m) : m(_m) { - std::vector aborted(m_jobs.size(), false); - - for (size_t jid = 0; jid < m_jobs.size(); ++jid) - aborted[jid] = m_jobs[jid].get().join(wait_ms); - - if (!all_of(aborted)) - BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; + m_arrange_id = add_job(std::make_unique(m->statusbar(), m->q)); + m_rotoptimize_id = add_job(std::make_unique(m->statusbar(), m->q)); + m_sla_import_id = add_job(std::make_unique(m->statusbar(), m->q)); } - - void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } - - const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; } - - bool is_any_running() const + + void arrange() { - return std::any_of(m_jobs.begin(), - m_jobs.end(), - [](const Job &j) { return j.is_running(); }); + m->take_snapshot(_(L("Arrange"))); + start(m_arrange_id); } - - } m_ui_jobs{this}; + + void optimize_rotation() + { + m->take_snapshot(_(L("Optimize Rotation"))); + start(m_rotoptimize_id); + } + + void import_sla_arch() + { + m->take_snapshot(_(L("Import SLA archive"))); + start(m_sla_import_id); + } + + } m_ui_jobs; bool delayed_scene_refresh; std::string delayed_error_message; @@ -1811,10 +1538,10 @@ struct Plater::priv priv(Plater *q, MainFrame *main_frame); ~priv(); - enum class UpdateParams { - FORCE_FULL_SCREEN_REFRESH = 1, - FORCE_BACKGROUND_PROCESSING_UPDATE = 2, - POSTPONE_VALIDATION_ERROR_MESSAGE = 4, + enum class UpdateParams { + FORCE_FULL_SCREEN_REFRESH = 1, + FORCE_BACKGROUND_PROCESSING_UPDATE = 2, + POSTPONE_VALIDATION_ERROR_MESSAGE = 4, }; void update(unsigned int flags = 0); void select_view(const std::string& direction); @@ -1850,9 +1577,7 @@ struct Plater::priv std::string get_config(const std::string &key) const; BoundingBoxf bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const; - arrangement::BedShapeHint get_bed_shape_hint() const; - void find_new_position(const ModelInstancePtrs &instances, coord_t min_d); std::vector load_files(const std::vector& input_files, bool load_model, bool load_config); std::vector load_model_objects(const ModelObjectPtrs &model_objects); wxString get_export_file(GUI::FileType file_type); @@ -1870,8 +1595,6 @@ struct Plater::priv void delete_object_from_model(size_t obj_idx); void reset(); void mirror(Axis axis); - void arrange(); - void sla_optimize_rotation(); void split_object(); void split_volume(); void scale_selection_to_fit_print_volume(); @@ -2038,6 +1761,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers" })) , sidebar(new Sidebar(q)) + , m_ui_jobs(this) , delayed_scene_refresh(false) , view_toolbar(GLToolbar::Radio, "View") , m_project_filename(wxEmptyString) @@ -2124,14 +1848,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas(); + // 3DScene events: view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this); view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); - view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); + view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); }); - view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); + view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event &evt) { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); @@ -2156,7 +1881,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); }); - view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); }); @@ -2824,40 +2549,12 @@ void Plater::priv::mirror(Axis axis) view3D->mirror_selection(axis); } -void Plater::priv::arrange() -{ - this->take_snapshot(_L("Arrange")); - m_ui_jobs.start(Jobs::Arrange); -} - - -// This method will find an optimal orientation for the currently selected item -// Very similar in nature to the arrange method above... -void Plater::priv::sla_optimize_rotation() { - this->take_snapshot(_L("Optimize Rotation")); - m_ui_jobs.start(Jobs::Rotoptimize); -} - -arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { - - const auto *bed_shape_opt = config->opt("bed_shape"); - assert(bed_shape_opt); - - if (!bed_shape_opt) return {}; - - auto &bedpoints = bed_shape_opt->values; - Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); - for (auto &v : bedpoints) bedpoly.append(scaled(v)); - - return arrangement::BedShapeHint(bedpoly); -} - -void Plater::priv::find_new_position(const ModelInstancePtrs &instances, +void Plater::find_new_position(const ModelInstancePtrs &instances, coord_t min_d) { arrangement::ArrangePolygons movable, fixed; - - for (const ModelObject *mo : model.objects) + + for (const ModelObject *mo : p->model.objects) for (const ModelInstance *inst : mo->instances) { auto it = std::find(instances.begin(), instances.end(), inst); auto arrpoly = inst->get_arrange_polygon(); @@ -2867,11 +2564,12 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances, else movable.emplace_back(std::move(arrpoly)); } - - if (updated_wipe_tower()) - fixed.emplace_back(wipetower.get_arrange_polygon()); - - arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint()); + + if (p->view3D->get_canvas3d()->get_wipe_tower_info()) + fixed.emplace_back(get_wipe_tower_arrangepoly(*this)); + + arrangement::arrange(movable, fixed, get_bed_shape(*config()), + arrangement::ArrangeParams{min_d}); for (size_t i = 0; i < instances.size(); ++i) if (movable[i].bed_idx == 0) @@ -2879,95 +2577,6 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances, movable[i].rotation); } -void Plater::priv::ArrangeJob::process() { - static const auto 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); - if (plater().printer_technology == ptFFF) { - dist = PrintConfig::min_object_distance(plater().config); - } - - coord_t min_d = scaled(dist); - auto count = unsigned(m_selected.size() + m_unprintable.size()); - arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); - - auto stopfn = [this]() { return was_canceled(); }; - - try { - arrangement::arrange(m_selected, m_unselected, min_d, bedshape, - [this, count](unsigned st) { - st += m_unprintable.size(); - if (st > 0) update_status(int(count - st), arrangestr); - }, stopfn); - arrangement::arrange(m_unprintable, {}, min_d, bedshape, - [this, count](unsigned st) { - if (st > 0) update_status(int(count - st), arrangestr); - }, stopfn); - } catch (std::exception & /*e*/) { - GUI::show_error(plater().q, - _L("Could not arrange model objects! " - "Some geometries may be invalid.")); - } - - // finalize just here. - update_status(int(count), - was_canceled() ? _L("Arranging canceled.") - : _L("Arranging done.")); -} - -void Plater::priv::RotoptimizeJob::process() -{ - int obj_idx = plater().get_selected_object_idx(); - if (obj_idx < 0) { return; } - - ModelObject *o = plater().model.objects[size_t(obj_idx)]; - - auto r = sla::find_best_rotation( - *o, - .005f, - [this](unsigned s) { - if (s < 100) - update_status(int(s), - _L("Searching for optimal orientation")); - }, - [this]() { return was_canceled(); }); - - - double mindist = 6.0; // FIXME - - if (!was_canceled()) { - for(ModelInstance * oi : o->instances) { - oi->set_rotation({r[X], r[Y], r[Z]}); - - auto trmatrix = oi->get_transformation().get_matrix(); - Polygon trchull = o->convex_hull_2d(trmatrix); - - MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); - double r = rotbb.angle_to_X(); - - // The box should be landscape - if(rotbb.width() < rotbb.height()) r += PI / 2; - - Vec3d rt = oi->get_rotation(); rt(Z) += r; - - oi->set_rotation(rt); - } - - plater().find_new_position(o->instances, scaled(mindist)); - - // Correct the z offset of the object which was corrupted be - // the rotation - o->ensure_on_bed(); - } - - update_status(100, - was_canceled() ? _L("Orientation search canceled.") - : _L("Orientation found.")); -} - - void Plater::priv::split_object() { int obj_idx = get_selected_object_idx(); @@ -3608,7 +3217,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) } // update plater with new config - wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config()); + q->on_config_change(wxGetApp().preset_bundle->full_config()); /* Settings list can be changed after printer preset changing, so * update all settings items for all item had it. * Furthermore, Layers editing is implemented only for FFF printers @@ -4055,8 +3664,12 @@ bool Plater::priv::complit_init_sla_object_menu() sla_object_menu.AppendSeparator(); // Add the automatic rotation sub-menu - append_menu_item(&sla_object_menu, wxID_ANY, _L("Optimize orientation"), _L("Optimize the rotation of the object for better print results."), - [this](wxCommandEvent&) { sla_optimize_rotation(); }); + append_menu_item( + &sla_object_menu, wxID_ANY, _(L("Optimize orientation")), + _(L("Optimize the rotation of the object for better print results.")), + [this](wxCommandEvent &) { + m_ui_jobs.optimize_rotation(); + }); return true; } @@ -4660,6 +4273,11 @@ void Plater::add_model() load_files(paths, true, false); } +void Plater::import_sl1_archive() +{ + p->m_ui_jobs.import_sla_arch(); +} + void Plater::extract_config_from_project() { wxString input_file; @@ -4752,7 +4370,7 @@ void Plater::increase_instances(size_t num) sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num); if (p->get_config("autocenter") == "1") - p->arrange(); + arrange(); p->update(); @@ -5486,6 +5104,11 @@ bool Plater::is_export_gcode_scheduled() const return p->background_process.is_export_scheduled(); } +const Selection &Plater::get_selection() const +{ + return p->get_selection(); +} + int Plater::get_selected_object_idx() { return p->get_selected_object_idx(); @@ -5511,6 +5134,11 @@ BoundingBoxf Plater::bed_shape_bb() const return p->bed_shape_bb(); } +void Plater::arrange() +{ + p->m_ui_jobs.arrange(); +} + void Plater::set_current_canvas_as_dirty() { p->set_current_canvas_as_dirty(); @@ -5533,6 +5161,8 @@ PrinterTechnology Plater::printer_technology() const return p->printer_technology; } +const DynamicPrintConfig * Plater::config() const { return p->config; } + void Plater::set_printer_technology(PrinterTechnology printer_technology) { p->printer_technology = printer_technology; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index efdaa75cc..2ac4f23c1 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -9,8 +9,10 @@ #include #include "Preset.hpp" +#include "Selection.hpp" #include "libslic3r/BoundingBox.hpp" +#include "Jobs/Job.hpp" #include "wxExtensions.hpp" class wxButton; @@ -157,6 +159,7 @@ public: void load_project(); void load_project(const wxString& filename); void add_model(); + void import_sl1_archive(); void extract_config_from_project(); std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true); @@ -252,12 +255,16 @@ public: void set_project_filename(const wxString& filename); bool is_export_gcode_scheduled() const; - + + const Selection& get_selection() const; int get_selected_object_idx(); bool is_single_full_object_selection() const; GLCanvas3D* canvas3D(); GLCanvas3D* get_current_canvas3D(); BoundingBoxf bed_shape_bb() const; + + void arrange(); + void find_new_position(const ModelInstancePtrs &instances, coord_t min_d); void set_current_canvas_as_dirty(); #if ENABLE_NON_STATIC_CANVAS_MANAGER @@ -266,6 +273,7 @@ public: #endif // ENABLE_NON_STATIC_CANVAS_MANAGER PrinterTechnology printer_technology() const; + const DynamicPrintConfig * config() const; void set_printer_technology(PrinterTechnology printer_technology); void copy_selection_to_clipboard(); @@ -371,6 +379,7 @@ private: bool m_was_scheduled; }; -}} +} // namespace GUI +} // namespace Slic3r #endif diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index faeb7a34e..15b10deeb 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -6,7 +6,7 @@ #include #include -#include "ProgressIndicator.hpp" +#include "Jobs/ProgressIndicator.hpp" class wxTimer; class wxGauge; diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 30e25abe2..101654c56 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -18,6 +18,8 @@ #include #endif +#define L(s) s + #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" @@ -32,7 +34,8 @@ namespace Slic3r { struct CurlGlobalInit { static std::unique_ptr instance; - + std::string message; + CurlGlobalInit() { #ifdef OPENSSL_CERT_OVERRIDE // defined if SLIC3R_STATIC=ON @@ -57,21 +60,39 @@ struct CurlGlobalInit ssl_cafile = X509_get_default_cert_file(); int replace = true; - - if (!ssl_cafile || !fs::exists(fs::path(ssl_cafile))) - for (const char * bundle : CA_BUNDLES) { - if (fs::exists(fs::path(bundle))) { - ::setenv(SSL_CA_FILE, bundle, replace); + if (!ssl_cafile || !fs::exists(fs::path(ssl_cafile))) { + const char * bundle = nullptr; + for (const char * b : CA_BUNDLES) { + if (fs::exists(fs::path(b))) { + ::setenv(SSL_CA_FILE, bundle = b, replace); break; } } - BOOST_LOG_TRIVIAL(info) - << "Detected OpenSSL root CA store: " << ::getenv(SSL_CA_FILE); + if (!bundle) + message = L("Could not detect system SSL certificate store. " + "PrusaSlicer will be unable to establish secure " + "network connections."); + else + message = string_printf( + L("PrusaSlicer detected system SSL certificate store in: %s"), + bundle); -#endif + message += string_printf( + L("\nTo specify the system certificate store manually, please " + "set the %s environment variable to the correct CA bundle " + "and restart the application."), + SSL_CA_FILE); + } + +#endif // OPENSSL_CERT_OVERRIDE - ::curl_global_init(CURL_GLOBAL_DEFAULT); + if (CURLcode ec = ::curl_global_init(CURL_GLOBAL_DEFAULT)) { + message = L("CURL init has failed. PrusaSlicer will be unable to establish " + "network connections. See logs for additional details."); + + BOOST_LOG_TRIVIAL(error) << ::curl_easy_strerror(ec); + } } ~CurlGlobalInit() { ::curl_global_cleanup(); } @@ -132,8 +153,7 @@ Http::priv::priv(const std::string &url) , limit(0) , cancel(false) { - if (!CurlGlobalInit::instance) - CurlGlobalInit::instance = std::make_unique(); + Http::tls_global_init(); if (curl == nullptr) { throw std::runtime_error(std::string("Could not construct Curl object")); @@ -494,7 +514,26 @@ bool Http::ca_file_supported() ::CURL *curl = ::curl_easy_init(); bool res = priv::ca_file_supported(curl); if (curl != nullptr) { ::curl_easy_cleanup(curl); } - return res; + return res; +} + +std::string Http::tls_global_init() +{ + if (!CurlGlobalInit::instance) + CurlGlobalInit::instance = std::make_unique(); + + return CurlGlobalInit::instance->message; +} + +std::string Http::tls_system_cert_store() +{ + std::string ret; + +#ifdef OPENSSL_CERT_OVERRIDE + ret = ::getenv(X509_get_default_cert_file_env()); +#endif + + return ret; } std::string Http::url_encode(const std::string &str) diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index 076fa4a0c..f16236279 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -100,6 +100,10 @@ public: // Tells whether current backend supports seting up a CA file using ca_file() static bool ca_file_supported(); + + // Return empty string on success or error message on fail. + static std::string tls_global_init(); + static std::string tls_system_cert_store(); // converts the given string to an url_encoded_string static std::string url_encode(const std::string &str); diff --git a/src/slic3r/Utils/SLAImport.cpp b/src/slic3r/Utils/SLAImport.cpp new file mode 100644 index 000000000..442025a77 --- /dev/null +++ b/src/slic3r/Utils/SLAImport.cpp @@ -0,0 +1,314 @@ +#include "SLAImport.hpp" + +#include + +#include "libslic3r/SlicesToTriangleMesh.hpp" +#include "libslic3r/MarchingSquares.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/MTUtils.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/SLA/RasterBase.hpp" +#include "libslic3r/miniz_extension.hpp" + +#include +#include +#include + +#include +#include + +namespace marchsq { + +// Specialize this struct to register a raster type for the Marching squares alg +template<> struct _RasterTraits { + using Rst = wxImage; + + // The type of pixel cell in the raster + using ValueType = uint8_t; + + // Value at a given position + static uint8_t get(const Rst &rst, size_t row, size_t col) + { + return rst.GetRed(col, row); + } + + // Number of rows and cols of the raster + static size_t rows(const Rst &rst) { return rst.GetHeight(); } + static size_t cols(const Rst &rst) { return rst.GetWidth(); } +}; + +} // namespace marchsq + +namespace Slic3r { + +namespace { + +struct ArchiveData { + boost::property_tree::ptree profile, config; + std::vector images; +}; + +static const constexpr char *CONFIG_FNAME = "config.ini"; +static const constexpr char *PROFILE_FNAME = "prusaslicer.ini"; + +boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, + MZ_Archive & zip) +{ + std::string buf(size_t(entry.m_uncomp_size), '\0'); + + if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, + buf.data(), buf.size(), 0)) + throw std::runtime_error(zip.get_errorstr()); + + boost::property_tree::ptree tree; + std::stringstream ss(buf); + boost::property_tree::read_ini(ss, tree); + return tree; +} + +sla::EncodedRaster read_png(const mz_zip_archive_file_stat &entry, + MZ_Archive & zip, + const std::string & name) +{ + std::vector buf(entry.m_uncomp_size); + + if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, + buf.data(), buf.size(), 0)) + throw std::runtime_error(zip.get_errorstr()); + + return sla::EncodedRaster(std::move(buf), + name.empty() ? entry.m_filename : name); +} + +ArchiveData extract_sla_archive(const std::string &zipfname, + const std::string &exclude) +{ + ArchiveData arch; + + // Little RAII + struct Arch: public MZ_Archive { + Arch(const std::string &fname) { + if (!open_zip_reader(&arch, fname)) + throw std::runtime_error(get_errorstr()); + } + + ~Arch() { close_zip_reader(&arch); } + } zip (zipfname); + + mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch); + + for (mz_uint i = 0; i < num_entries; ++i) + { + mz_zip_archive_file_stat entry; + + if (mz_zip_reader_file_stat(&zip.arch, i, &entry)) + { + std::string name = entry.m_filename; + boost::algorithm::to_lower(name); + + if (boost::algorithm::contains(name, exclude)) continue; + + if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip); + if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip); + + if (boost::filesystem::path(name).extension().string() == ".png") { + auto it = std::lower_bound( + arch.images.begin(), arch.images.end(), sla::EncodedRaster({}, name), + [](const sla::EncodedRaster &r1, const sla::EncodedRaster &r2) { + return std::less()(r1.extension(), r2.extension()); + }); + + arch.images.insert(it, read_png(entry, zip, name)); + } + } + } + + return arch; +} + +ExPolygons rings_to_expolygons(const std::vector &rings, + double px_w, double px_h) +{ + ExPolygons polys; polys.reserve(rings.size()); + + for (const marchsq::Ring &ring : rings) { + Polygon poly; Points &pts = poly.points; + pts.reserve(ring.size()); + + for (const marchsq::Coord &crd : ring) + pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h)); + + polys.emplace_back(poly); + } + + // reverse the raster transformations + return union_ex(polys); +} + +template void foreach_vertex(ExPolygon &poly, Fn &&fn) +{ + for (auto &p : poly.contour.points) fn(p); + for (auto &h : poly.holes) + for (auto &p : h.points) fn(p); +} + +void invert_raster_trafo(ExPolygons & expolys, + const sla::RasterBase::Trafo &trafo, + coord_t width, + coord_t height) +{ + for (auto &expoly : expolys) { + if (trafo.mirror_y) + foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); }); + + if (trafo.mirror_x) + foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); }); + + expoly.translate(-trafo.center_x, -trafo.center_y); + + if (trafo.flipXY) + foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); }); + + if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) { + expoly.contour.reverse(); + for (auto &h : expoly.holes) h.reverse(); + } + } +} + +struct RasterParams { + sla::RasterBase::Trafo trafo; // Raster transformations + coord_t width, height; // scaled raster dimensions (not resolution) + double px_h, px_w; // pixel dimesions + marchsq::Coord win; // marching squares window size +}; + +RasterParams get_raster_params(const DynamicPrintConfig &cfg) +{ + auto *opt_disp_cols = cfg.option("display_pixels_x"); + auto *opt_disp_rows = cfg.option("display_pixels_y"); + auto *opt_disp_w = cfg.option("display_width"); + auto *opt_disp_h = cfg.option("display_height"); + auto *opt_mirror_x = cfg.option("display_mirror_x"); + auto *opt_mirror_y = cfg.option("display_mirror_y"); + auto *opt_orient = cfg.option>("display_orientation"); + + if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h || + !opt_mirror_x || !opt_mirror_y || !opt_orient) + throw std::runtime_error("Invalid SL1 file"); + + RasterParams rstp; + + rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1); + rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1); + + sla::RasterBase::Trafo trafo{opt_orient->value == sladoLandscape ? + sla::RasterBase::roLandscape : + sla::RasterBase::roPortrait, + {opt_mirror_x->value, opt_mirror_y->value}}; + + rstp.height = scaled(opt_disp_h->value); + rstp.width = scaled(opt_disp_w->value); + + return rstp; +} + +struct SliceParams { double layerh = 0., initial_layerh = 0.; }; + +SliceParams get_slice_params(const DynamicPrintConfig &cfg) +{ + auto *opt_layerh = cfg.option("layer_height"); + auto *opt_init_layerh = cfg.option("initial_layer_height"); + + if (!opt_layerh || !opt_init_layerh) + throw std::runtime_error("Invalid SL1 file"); + + return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()}; +} + +std::vector extract_slices_from_sla_archive( + ArchiveData & arch, + const RasterParams & rstp, + std::function progr) +{ + auto jobdir = arch.config.get("jobDir"); + for (auto &c : jobdir) c = std::tolower(c); + + std::vector slices(arch.images.size()); + + struct Status + { + double incr, val, prev; + bool stop = false; + tbb::spin_mutex mutex; + } st {100. / slices.size(), 0., 0.}; + + tbb::parallel_for(size_t(0), arch.images.size(), + [&arch, &slices, &st, &rstp, progr](size_t i) { + // Status indication guarded with the spinlock + { + std::lock_guard lck(st.mutex); + if (st.stop) return; + + st.val += st.incr; + double curr = std::round(st.val); + if (curr > st.prev) { + st.prev = curr; + st.stop = !progr(int(curr)); + } + } + + auto &buf = arch.images[i]; + wxMemoryInputStream stream{buf.data(), buf.size()}; + wxImage img{stream}; + + auto rings = marchsq::execute(img, 128, rstp.win); + ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h); + + // Invert the raster transformations indicated in + // the profile metadata + invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height); + + slices[i] = std::move(expolys); + }); + + if (st.stop) slices = {}; + + return slices; +} + +} // namespace + +void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out) +{ + ArchiveData arch = extract_sla_archive(zipfname, "png"); + out.load(arch.profile); +} + +void import_sla_archive( + const std::string & zipfname, + Vec2i windowsize, + TriangleMesh & out, + DynamicPrintConfig & profile, + std::function progr) +{ + // Ensure minimum window size for marching squares + windowsize.x() = std::max(2, windowsize.x()); + windowsize.y() = std::max(2, windowsize.y()); + + ArchiveData arch = extract_sla_archive(zipfname, "thumbnail"); + profile.load(arch.profile); + + RasterParams rstp = get_raster_params(profile); + rstp.win = {windowsize.y(), windowsize.x()}; + + SliceParams slicp = get_slice_params(profile); + + std::vector slices = + extract_slices_from_sla_archive(arch, rstp, progr); + + if (!slices.empty()) + out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh); +} + +} // namespace Slic3r diff --git a/src/slic3r/Utils/SLAImport.hpp b/src/slic3r/Utils/SLAImport.hpp new file mode 100644 index 000000000..a819bd7e7 --- /dev/null +++ b/src/slic3r/Utils/SLAImport.hpp @@ -0,0 +1,36 @@ +#ifndef SLAIMPORT_HPP +#define SLAIMPORT_HPP + +#include + +#include +#include +#include + +namespace Slic3r { + +class TriangleMesh; +class DynamicPrintConfig; + +void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); + +void import_sla_archive( + const std::string & zipfname, + Vec2i windowsize, + TriangleMesh & out, + DynamicPrintConfig & profile, + std::function progr = [](int) { return true; }); + +inline void import_sla_archive( + const std::string & zipfname, + Vec2i windowsize, + TriangleMesh & out, + std::function progr = [](int) { return true; }) +{ + DynamicPrintConfig profile; + import_sla_archive(zipfname, windowsize, out, profile, progr); +} + +} + +#endif // SLAIMPORT_HPP diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index 4ee43e2b1..09ca730ec 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -12,6 +12,7 @@ #include #include +#include using namespace std; @@ -167,8 +168,7 @@ void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r object->add_volume(std::move(t)); object->add_instance(); } - model.arrange_objects(PrintConfig::min_object_distance(&config)); - model.center_instances_around_point(Slic3r::Vec2d(100, 100)); + arrange_objects(model, InfiniteBed{}, ArrangeParams{ scaled(min_object_distance(config))}); for (ModelObject *mo : model.objects) { mo->ensure_on_bed(); print.auto_assign_extruders(mo); diff --git a/tests/fff_print/test_model.cpp b/tests/fff_print/test_model.cpp index 3d3b54ef0..45a080f31 100644 --- a/tests/fff_print/test_model.cpp +++ b/tests/fff_print/test_model.cpp @@ -2,6 +2,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Model.hpp" +#include "libslic3r/ModelArrange.hpp" #include #include @@ -41,8 +42,7 @@ SCENARIO("Model construction", "[Model]") { } } model_object->add_instance(); - model.arrange_objects(PrintConfig::min_object_distance(&config)); - model.center_instances_around_point(Slic3r::Vec2d(100, 100)); + arrange_objects(model, InfiniteBed{scaled(Vec2d(100, 100))}, ArrangeParams{scaled(min_object_distance(config))}); model_object->ensure_on_bed(); print.auto_assign_extruders(model_object); THEN("Print works?") { diff --git a/tests/libnest2d/libnest2d_tests_main.cpp b/tests/libnest2d/libnest2d_tests_main.cpp index c7259ae53..e0692f4e7 100644 --- a/tests/libnest2d/libnest2d_tests_main.cpp +++ b/tests/libnest2d/libnest2d_tests_main.cpp @@ -472,32 +472,30 @@ TEST_CASE("ArrangeRectanglesLoose", "[Nesting]") namespace { using namespace libnest2d; -template -void exportSVG(std::vector>& result, const Bin& bin, int idx = 0) { - std::string loc = "out"; +template +void exportSVG(const char *loc, It from, It to) { - static std::string svg_header = - R"raw( + static const char* svg_header = +R"raw( )raw"; - int i = idx; - auto r = result; // for(auto r : result) { - std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out); + std::fstream out(loc, std::fstream::out); if(out.is_open()) { out << svg_header; - Item rbin( RectangleItem(bin.width(), bin.height()) ); - for(unsigned j = 0; j < rbin.vertexCount(); j++) { - auto v = rbin.vertex(j); - setY(v, -getY(v)/SCALE + 500 ); - setX(v, getX(v)/SCALE); - rbin.setVertex(j, v); - } - out << shapelike::serialize(rbin.rawShape()) << std::endl; - for(Item& sh : r) { - Item tsh(sh.transformedShape()); +// Item rbin( RectangleItem(bin.width(), bin.height()) ); +// for(unsigned j = 0; j < rbin.vertexCount(); j++) { +// auto v = rbin.vertex(j); +// setY(v, -getY(v)/SCALE + 500 ); +// setX(v, getX(v)/SCALE); +// rbin.setVertex(j, v); +// } +// out << shapelike::serialize(rbin.rawShape()) << std::endl; + for(auto it = from; it != to; ++it) { + const Item &itm = *it; + Item tsh(itm.transformedShape()); for(unsigned j = 0; j < tsh.vertexCount(); j++) { auto v = tsh.vertex(j); setY(v, -getY(v)/SCALE + 500); @@ -509,10 +507,16 @@ void exportSVG(std::vector>& result, const Bin& bin out << "\n" << std::endl; } out.close(); - + // i++; // } } + +template +void exportSVG(std::vector>& result, int idx = 0) { + exportSVG((std::string("out") + std::to_string(idx) + ".svg").c_str(), + result.begin(), result.end()); +} } TEST_CASE("BottomLeftStressTest", "[Geometry]") { @@ -541,7 +545,7 @@ TEST_CASE("BottomLeftStressTest", "[Geometry]") { valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); if(!valid) { std::cout << "error index: " << i << std::endl; - exportSVG(result, bin, i); + exportSVG(result, i); } REQUIRE(valid); } else { @@ -894,7 +898,7 @@ void testNfp(const std::vector& testdata) { int TEST_CASEcase = 0; - auto& exportfun = exportSVG; + auto& exportfun = exportSVG; auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){ TEST_CASEcase++; @@ -941,7 +945,7 @@ void testNfp(const std::vector& testdata) { std::ref(stationary), std::ref(tmp), std::ref(infp) }; - exportfun(inp, bin, TEST_CASEcase*i++); + exportfun(inp, TEST_CASEcase*i++); } REQUIRE(touching); @@ -1096,3 +1100,91 @@ TEST_CASE("MinAreaBBWithRotatingCalipers", "[Geometry]") { REQUIRE(succ); } } + +template MultiPolygon merged_pile(It from, It to, int bin_id) +{ + MultiPolygon pile; + pile.reserve(size_t(to - from)); + + for (auto it = from; it != to; ++it) { + if (it->binId() == bin_id) pile.emplace_back(it->transformedShape()); + } + + return nfp::merge(pile); +} + +TEST_CASE("Test for bed center distance optimization", "[Nesting], [NestKernels]") +{ + static const constexpr ClipperLib::cInt W = 10000000; + + // Get the input items and define the bin. + std::vector input(9, {W, W}); + + auto bin = Box::infinite(); + + NfpPlacer::Config pconfig; + + pconfig.object_function = [](const Item &item) -> double { + return pl::magnsq(item.boundingBox().center()); + }; + + size_t bins = nest(input, bin, 0, NestConfig{pconfig}); + + REQUIRE(bins == 1); + + // Gather the items into piles of arranged polygons... + MultiPolygon pile; + pile.reserve(input.size()); + + for (auto &itm : input) { + REQUIRE(itm.binId() == 0); + pile.emplace_back(itm.transformedShape()); + } + + MultiPolygon m = merged_pile(input.begin(), input.end(), 0); + + REQUIRE(m.size() == 1); + + REQUIRE(sl::area(m) == Approx(9. * W * W)); +} + +TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]") +{ + static const constexpr ClipperLib::cInt W = 10000000; + static const constexpr size_t N = 100; + + // Get the input items and define the bin. + std::vector input(N, {W, W}); + + auto bin = Box::infinite(); + + NfpPlacer::Config pconfig; + pconfig.rotations = {0.}; + Box pile_box; + pconfig.before_packing = + [&pile_box](const MultiPolygon &pile, + const _ItemGroup &/*packed_items*/, + const _ItemGroup &/*remaining_items*/) { + pile_box = sl::boundingBox(pile); + }; + + pconfig.object_function = [&pile_box](const Item &item) -> double { + Box b = sl::boundingBox(item.boundingBox(), pile_box); + double area = b.area() / (W * W); + return -area; + }; + + size_t bins = nest(input, bin, 0, NestConfig{pconfig}); + + // To debug: + exportSVG<1000000>("out", input.begin(), input.end()); + + REQUIRE(bins == 1); + + MultiPolygon pile = merged_pile(input.begin(), input.end(), 0); + Box bb = sl::boundingBox(pile); + + // Here the result shall be a stairway of boxes + REQUIRE(pile.size() == N); + REQUIRE(bb.area() == N * N * W * W); +} diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 2353414f9..b41dbf8ba 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests test_stl.cpp test_meshsimplify.cpp test_meshboolean.cpp + test_marchingsquares.cpp test_timeutils.cpp ) diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp new file mode 100644 index 000000000..9912ff2ca --- /dev/null +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -0,0 +1,371 @@ +#define NOMINMAX + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace Slic3r; + +static double area(const sla::RasterBase::PixelDim &pxd) +{ + return pxd.w_mm * pxd.h_mm; +} + +static Slic3r::sla::RasterGrayscaleAA create_raster( + const sla::RasterBase::Resolution &res, + double disp_w = 100., + double disp_h = 100.) +{ + sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + + auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); + sla::RasterBase::Trafo trafo; + trafo.center_x = bb.center().x(); + trafo.center_y = bb.center().y(); + + return sla::RasterGrayscaleAA{res, pixdim, trafo, agg::gamma_threshold(.5)}; +} + +static ExPolygon square(double a, Point center = {0, 0}) +{ + ExPolygon poly; + coord_t V = scaled(a / 2.); + + poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}}; + poly.translate(center.x(), center.y()); + + return poly; +} + +static ExPolygon square_with_hole(double a, Point center = {0, 0}) +{ + ExPolygon poly = square(a); + + poly.holes.emplace_back(); + coord_t V = scaled(a / 4.); + poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}}; + + poly.translate(center.x(), center.y()); + + return poly; +} + +static ExPolygons circle_with_hole(double r, Point center = {0, 0}) { + + ExPolygon poly; + + std::vector pis = linspace_vector(0., 2 * PI, 100); + + coord_t rs = scaled(r); + for (double phi : pis) { + poly.contour.points.emplace_back(rs * std::cos(phi), rs * std::sin(phi)); + } + + poly.holes.emplace_back(poly.contour); + poly.holes.front().reverse(); + for (auto &p : poly.holes.front().points) p /= 2; + + poly.translate(center.x(), center.y()); + + return {poly}; +} + +static const Vec2i W4x4 = {4, 4}; +static const Vec2i W2x2 = {2, 2}; + +template +static void test_expolys(Rst && rst, + const ExPolygons & ref, + Vec2i window, + const std::string &name = "test") +{ + for (const ExPolygon &expoly : ref) rst.draw(expoly); + + std::fstream out(name + ".png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst, window); + + SVG svg(name + ".svg"); + svg.draw(extracted); + svg.draw(ref, "green"); + svg.Close(); + + double max_rel_err = 0.1; + sla::RasterBase::PixelDim pxd = rst.pixel_dimensions(); + double max_abs_err = area(pxd) * scaled(1.) * scaled(1.); + + BoundingBox ref_bb; + for (auto &expoly : ref) ref_bb.merge(expoly.contour.bounding_box()); + + double max_displacement = 4. * (std::pow(pxd.h_mm, 2) + std::pow(pxd.w_mm, 2)); + max_displacement *= scaled(1.) * scaled(1.); + + REQUIRE(extracted.size() == ref.size()); + for (size_t i = 0; i < ref.size(); ++i) { + REQUIRE(extracted[i].contour.is_counter_clockwise()); + REQUIRE(extracted[i].holes.size() == ref[i].holes.size()); + + for (auto &h : extracted[i].holes) REQUIRE(h.is_clockwise()); + + double refa = ref[i].area(); + double abs_err = std::abs(extracted[i].area() - refa); + double rel_err = abs_err / refa; + + REQUIRE((rel_err <= max_rel_err || abs_err <= max_abs_err)); + + BoundingBox bb; + for (auto &expoly : extracted) bb.merge(expoly.contour.bounding_box()); + + Point d = bb.center() - ref_bb.center(); + REQUIRE(double(d.transpose() * d) <= max_displacement); + } +} + +TEST_CASE("Empty raster should result in empty polygons", "[MarchingSquares]") { + sla::RasterGrayscaleAAGammaPower rst{{}, {}, {}}; + ExPolygons extracted = sla::raster_to_polygons(rst); + REQUIRE(extracted.size() == 0); +} + +TEST_CASE("Marching squares directions", "[MarchingSquares]") { + marchsq::Coord crd{1, 1}; + + REQUIRE(step(crd, marchsq::__impl::Dir::left).r == 1); + REQUIRE(step(crd, marchsq::__impl::Dir::left).c == 0); + + REQUIRE(step(crd, marchsq::__impl::Dir::down).r == 2); + REQUIRE(step(crd, marchsq::__impl::Dir::down).c == 1); + + REQUIRE(step(crd, marchsq::__impl::Dir::right).r == 1); + REQUIRE(step(crd, marchsq::__impl::Dir::right).c == 2); + + REQUIRE(step(crd, marchsq::__impl::Dir::up).r == 0); + REQUIRE(step(crd, marchsq::__impl::Dir::up).c == 1); +} + +TEST_CASE("Fully covered raster should result in a rectangle", "[MarchingSquares]") { + auto rst = create_raster({4, 4}, 4., 4.); + + ExPolygon rect = square(4); + + SECTION("Full accuracy") { + test_expolys(rst, {rect}, W2x2, "fully_covered_full_acc"); + } + + SECTION("Half accuracy") { + test_expolys(rst, {rect}, W4x4, "fully_covered_half_acc"); + } +} + +TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") { + + sla::RasterBase::PixelDim pixdim{1, 1}; + + // We need one additional row and column to detect edges + sla::RasterGrayscaleAA rst{{4, 4}, pixdim, {}, agg::gamma_threshold(.5)}; + + // Draw a triangle from individual pixels + rst.draw(square(1., {0500000, 0500000})); + rst.draw(square(1., {1500000, 0500000})); + rst.draw(square(1., {2500000, 0500000})); + + rst.draw(square(1., {1500000, 1500000})); + rst.draw(square(1., {2500000, 1500000})); + + rst.draw(square(1., {2500000, 2500000})); + + std::fstream out("4x4.png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst); + + SVG svg("4x4.svg"); + svg.draw(extracted); + svg.Close(); + + REQUIRE(extracted.size() == 1); +} + +TEST_CASE("4x4 raster with two rings", "[MarchingSquares]") { + + sla::RasterBase::PixelDim pixdim{1, 1}; + + // We need one additional row and column to detect edges + sla::RasterGrayscaleAA rst{{5, 5}, pixdim, {}, agg::gamma_threshold(.5)}; + + SECTION("Ambiguous case with 'ac' square") { + + // Draw a triangle from individual pixels + rst.draw(square(1., {3500000, 2500000})); + rst.draw(square(1., {3500000, 3500000})); + rst.draw(square(1., {2500000, 3500000})); + + rst.draw(square(1., {2500000, 1500000})); + rst.draw(square(1., {1500000, 1500000})); + rst.draw(square(1., {1500000, 2500000})); + + std::fstream out("4x4_ac.png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst); + + SVG svg("4x4_ac.svg"); + svg.draw(extracted); + svg.Close(); + + REQUIRE(extracted.size() == 2); + } + + SECTION("Ambiguous case with 'bd' square") { + + // Draw a triangle from individual pixels + rst.draw(square(1., {3500000, 1500000})); + rst.draw(square(1., {3500000, 2500000})); + rst.draw(square(1., {2500000, 1500000})); + + rst.draw(square(1., {1500000, 2500000})); + rst.draw(square(1., {1500000, 3500000})); + rst.draw(square(1., {2500000, 3500000})); + + std::fstream out("4x4_bd.png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst); + + SVG svg("4x4_bd.svg"); + svg.draw(extracted); + svg.Close(); + + REQUIRE(extracted.size() == 2); + } +} + +TEST_CASE("Square with hole in the middle", "[MarchingSquares]") { + using namespace Slic3r; + + ExPolygons inp = {square_with_hole(50.)}; + + SECTION("Proportional raster, 1x1 mm pixel size, full accuracy") { + test_expolys(create_raster({100, 100}, 100., 100.), inp, W2x2, "square_with_hole_proportional_1x1_mm_px_full"); + } + + SECTION("Proportional raster, 1x1 mm pixel size, half accuracy") { + test_expolys(create_raster({100, 100}, 100., 100.), inp, W4x4, "square_with_hole_proportional_1x1_mm_px_half"); + } + + SECTION("Landscape raster, 1x1 mm pixel size, full accuracy") { + test_expolys(create_raster({150, 100}, 150., 100.), inp, W2x2, "square_with_hole_landsc_1x1_mm_px_full"); + } + + SECTION("Landscape raster, 1x1 mm pixel size, half accuracy") { + test_expolys(create_raster({150, 100}, 150., 100.), inp, W4x4, "square_with_hole_landsc_1x1_mm_px_half"); + } + + SECTION("Portrait raster, 1x1 mm pixel size, full accuracy") { + test_expolys(create_raster({100, 150}, 100., 150.), inp, W2x2, "square_with_hole_portrait_1x1_mm_px_full"); + } + + SECTION("Portrait raster, 1x1 mm pixel size, half accuracy") { + test_expolys(create_raster({100, 150}, 100., 150.), inp, W4x4, "square_with_hole_portrait_1x1_mm_px_half"); + } + + SECTION("Proportional raster, 2x2 mm pixel size, full accuracy") { + test_expolys(create_raster({200, 200}, 100., 100.), inp, W2x2, "square_with_hole_proportional_2x2_mm_px_full"); + } + + SECTION("Proportional raster, 2x2 mm pixel size, half accuracy") { + test_expolys(create_raster({200, 200}, 100., 100.), inp, W4x4, "square_with_hole_proportional_2x2_mm_px_half"); + } + + SECTION("Proportional raster, 0.5x0.5 mm pixel size, full accuracy") { + test_expolys(create_raster({50, 50}, 100., 100.), inp, W2x2, "square_with_hole_proportional_0.5x0.5_mm_px_full"); + } + + SECTION("Proportional raster, 0.5x0.5 mm pixel size, half accuracy") { + test_expolys(create_raster({50, 50}, 100., 100.), inp, W4x4, "square_with_hole_proportional_0.5x0.5_mm_px_half"); + } +} + +TEST_CASE("Circle with hole in the middle", "[MarchingSquares]") { + using namespace Slic3r; + + test_expolys(create_raster({1000, 1000}), circle_with_hole(25.), W2x2, "circle_with_hole"); +} + +static void recreate_object_from_rasters(const std::string &objname, float lh) { + TriangleMesh mesh = load_model(objname); + + auto bb = mesh.bounding_box(); + Vec3f tr = -bb.center().cast(); + mesh.translate(tr.x(), tr.y(), tr.z()); + bb = mesh.bounding_box(); + + std::vector layers; + slice_mesh(mesh, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh), layers, 0.f, []{}); + + sla::RasterBase::Resolution res{2560, 1440}; + double disp_w = 120.96; + double disp_h = 68.04; + + size_t cntr = 0; + for (ExPolygons &layer : layers) { + auto rst = create_raster(res, disp_w, disp_h); + + for (ExPolygon &island : layer) { + rst.draw(island); + } + + std::fstream out(objname + std::to_string(cntr) + ".png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons layer_ = sla::raster_to_polygons(rst); +// float delta = scaled(std::min(rst.pixel_dimensions().h_mm, +// rst.pixel_dimensions().w_mm)) / 2; + +// layer_ = expolygons_simplify(layer_, delta); + + SVG svg(objname + std::to_string(cntr) + ".svg", BoundingBox(Point{0, 0}, Point{scaled(disp_w), scaled(disp_h)})); + svg.draw(layer_); + svg.draw(layer, "green"); + svg.Close(); + + double layera = 0., layera_ = 0.; + for (auto &p : layer) layera += p.area(); + for (auto &p : layer_) layera_ += p.area(); + + std::cout << cntr++ << std::endl; + double diff = std::abs(layera_ - layera); + REQUIRE((diff <= 0.1 * layera || diff < scaled(1.) * scaled(1.))); + + layer = std::move(layer_); + } + + TriangleMesh out = slices_to_triangle_mesh(layers, bb.min.z(), double(lh), double(lh)); + + out.require_shared_vertices(); + out.WriteOBJFile("out_from_rasters.obj"); +} + +TEST_CASE("Recreate object from rasters", "[SL1Import]") { + recreate_object_from_rasters("frog_legs.obj", 0.05f); +} diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 10f5742d3..82df2c1a6 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -154,19 +154,12 @@ TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") { test_support_model_collision(fname, supportcfg); } -TEST_CASE("DefaultRasterShouldBeEmpty", "[SLARasterOutput]") { - sla::Raster raster; - REQUIRE(raster.empty()); -} - TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") { // Default Prusa SL1 display parameters - sla::Raster::Resolution res{2560, 1440}; - sla::Raster::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; + sla::RasterBase::Resolution res{2560, 1440}; + sla::RasterBase::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; - sla::Raster raster; - raster.reset(res, pixdim); - REQUIRE_FALSE(raster.empty()); + sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, 1.); REQUIRE(raster.resolution().width_px == res.width_px); REQUIRE(raster.resolution().height_px == res.height_px); REQUIRE(raster.pixel_dimensions().w_mm == Approx(pixdim.w_mm)); @@ -174,13 +167,14 @@ TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") { } TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { - sla::Raster::TMirroring mirrorings[] = {sla::Raster::NoMirror, - sla::Raster::MirrorX, - sla::Raster::MirrorY, - sla::Raster::MirrorXY}; + sla::RasterBase::TMirroring mirrorings[] = {sla::RasterBase::NoMirror, + sla::RasterBase::MirrorX, + sla::RasterBase::MirrorY, + sla::RasterBase::MirrorXY}; + + sla::RasterBase::Orientation orientations[] = + {sla::RasterBase::roLandscape, sla::RasterBase::roPortrait}; - sla::Raster::Orientation orientations[] = {sla::Raster::roLandscape, - sla::Raster::roPortrait}; for (auto orientation : orientations) for (auto &mirror : mirrorings) check_raster_transformations(orientation, mirror); @@ -189,10 +183,11 @@ TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") { double disp_w = 120., disp_h = 68.; - sla::Raster::Resolution res{2560, 1440}; - sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::RasterBase::Resolution res{2560, 1440}; + sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; - sla::Raster raster{res, pixdim}; + double gamma = 1.; + sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, gamma); auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); ExPolygon poly = square_with_hole(10.); @@ -215,6 +210,13 @@ TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") { diff = std::abs(a - ra); REQUIRE(diff <= predict_error(poly, pixdim)); + + sla::RasterGrayscaleAA raster0(res, pixdim, {}, [](double) { return 0.; }); + REQUIRE(raster_pxsum(raster0) == 0); + + raster0.draw(poly); + ra = raster_white_area(raster); + REQUIRE(raster_pxsum(raster0) == 0); } TEST_CASE("Triangle mesh conversions should be correct", "[SLAConversions]") diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index a844b2eae..883e4268a 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -1,4 +1,5 @@ #include "sla_test_utils.hpp" +#include "libslic3r/SLA/AGGRaster.hpp" void test_support_model_collision(const std::string &obj_filename, const sla::SupportConfig &input_supportcfg, @@ -293,18 +294,19 @@ void check_validity(const TriangleMesh &input_mesh, int flags) } } -void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirroring mirroring) +void check_raster_transformations(sla::RasterBase::Orientation o, sla::RasterBase::TMirroring mirroring) { double disp_w = 120., disp_h = 68.; - sla::Raster::Resolution res{2560, 1440}; - sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::RasterBase::Resolution res{2560, 1440}; + sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); - sla::Raster::Trafo trafo{o, mirroring}; - trafo.origin_x = bb.center().x(); - trafo.origin_y = bb.center().y(); + sla::RasterBase::Trafo trafo{o, mirroring}; + trafo.center_x = bb.center().x(); + trafo.center_y = bb.center().y(); + double gamma = 1.; - sla::Raster raster{res, pixdim, trafo}; + sla::RasterGrayscaleAAGammaPower raster{res, pixdim, trafo, gamma}; // create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors) coord_t pw = 32 * coord_t(std::ceil(scaled(pixdim.w_mm))); @@ -319,7 +321,7 @@ void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirr // Now calculate the position of the translated box according to output // trafo. - if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.); + if (o == sla::RasterBase::Orientation::roPortrait) expected_box.rotate(PI / 2.); if (mirroring[X]) for (auto &p : expected_box.contour.points) p.x() = -p.x(); @@ -340,10 +342,9 @@ void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirr auto px = raster.read_pixel(w, h); if (px != FullWhite) { - sla::PNGImage img; std::fstream outf("out.png", std::ios::out); - outf << img.serialize(raster); + outf << raster.encode(sla::PNGRasterEncoder()); } REQUIRE(px == FullWhite); @@ -361,9 +362,21 @@ ExPolygon square_with_hole(double v) return poly; } -double raster_white_area(const sla::Raster &raster) +long raster_pxsum(const sla::RasterGrayscaleAA &raster) { - if (raster.empty()) return std::nan(""); + auto res = raster.resolution(); + long a = 0; + + for (size_t x = 0; x < res.width_px; ++x) + for (size_t y = 0; y < res.height_px; ++y) + a += raster.read_pixel(x, y); + + return a; +} + +double raster_white_area(const sla::RasterGrayscaleAA &raster) +{ + if (raster.resolution().pixels() == 0) return std::nan(""); auto res = raster.resolution(); double a = 0; @@ -377,7 +390,7 @@ double raster_white_area(const sla::Raster &raster) return a; } -double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd) +double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd) { auto lines = p.lines(); double pix_err = pixel_area(FullWhite, pd) / 2.; diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index f3727bd39..3652b1f81 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -16,7 +16,7 @@ #include "libslic3r/SLA/SupportTreeBuilder.hpp" #include "libslic3r/SLA/SupportTreeBuildsteps.hpp" #include "libslic3r/SLA/SupportPointGenerator.hpp" -#include "libslic3r/SLA/Raster.hpp" +#include "libslic3r/SLA/AGGRaster.hpp" #include "libslic3r/SLA/ConcaveHull.hpp" #include "libslic3r/MTUtils.hpp" @@ -170,18 +170,19 @@ static constexpr const TPixel FullBlack = 0; template constexpr int arraysize(const A (&)[N]) { return N; } -void check_raster_transformations(sla::Raster::Orientation o, - sla::Raster::TMirroring mirroring); +void check_raster_transformations(sla::RasterBase::Orientation o, + sla::RasterBase::TMirroring mirroring); ExPolygon square_with_hole(double v); -inline double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim) +inline double pixel_area(TPixel px, const sla::RasterBase::PixelDim &pxdim) { return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack); } -double raster_white_area(const sla::Raster &raster); +double raster_white_area(const sla::RasterGrayscaleAA &raster); +long raster_pxsum(const sla::RasterGrayscaleAA &raster); -double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd); +double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd); #endif // SLA_TEST_UTILS_HPP diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 435dda5a2..63dc5b312 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -50,7 +50,7 @@ void erase(t_config_option_key opt_key); void normalize(); %name{setenv} void setenv_(); - double min_object_distance() %code{% PrintConfig cfg; cfg.apply(*THIS, true); RETVAL = cfg.min_object_distance(); %}; + double min_object_distance() %code{% RETVAL = Slic3r::min_object_distance(*THIS); %}; static DynamicPrintConfig* load(char *path) %code%{ auto config = new DynamicPrintConfig(); @@ -114,7 +114,7 @@ } %}; %name{setenv} void setenv_(); - double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; + double min_object_distance() %code{% RETVAL = Slic3r::min_object_distance(*THIS); %}; static StaticPrintConfig* load(char *path) %code%{ auto config = new FullPrintConfig(); diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 35b1c01ce..4fb35578d 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -3,6 +3,7 @@ %{ #include #include "libslic3r/Model.hpp" +#include "libslic3r/ModelArrange.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/Slicing.hpp" @@ -80,9 +81,9 @@ ModelObjectPtrs* objects() %code%{ RETVAL = &THIS->objects; %}; - bool arrange_objects(double dist, BoundingBoxf* bb = NULL); - void duplicate(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); - void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); + bool arrange_objects(double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) arrange_objects(*THIS, scaled(*bb), ap); else arrange_objects(*THIS, InfiniteBed{}, ap); %}; + void duplicate(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) duplicate(*THIS, copies_num, scaled(*bb), ap); else duplicate(*THIS, copies_num, InfiniteBed{}, ap); %}; + void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) duplicate_objects(*THIS, copies_num, scaled(*bb), ap); else duplicate_objects(*THIS, copies_num, InfiniteBed{}, ap); %}; void duplicate_objects_grid(unsigned int x, unsigned int y, double dist); bool looks_like_multipart_object() const;