Fixed conflicts after merge with master

This commit is contained in:
enricoturri1966 2020-04-24 10:59:03 +02:00
commit e5c45405d4
80 changed files with 4288 additions and 2581 deletions

View File

@ -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<PrinterTechnology> *opt = config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology");
return (opt == nullptr) ? ptUnknown : opt->value;
}
int CLI::run(int argc, char **argv)
{
// Switch boost::filesystem to utf8.
@ -87,12 +83,14 @@ 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<std::string> &load_configs = m_config.option<ConfigOptionStrings>("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) {
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
model.duplicate(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb);
duplicate(model, size_t(dups), bed, arrange_cfg);
} else {
model.add_default_instances();
model.duplicate_objects(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb);
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<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&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<ConfigOptionPoints>("bed_shape")->values).center() :
m_config.option<ConfigOptionPoint>("center")->value);
if (user_center_specified) {
Vec2d c = m_config.option<ConfigOptionPoint>("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;
@ -614,6 +625,8 @@ bool CLI::setup(int argc, char **argv)
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 })
for (const std::pair<t_config_option_key, ConfigOptionDef> &optdef : *options)
@ -621,6 +634,11 @@ bool CLI::setup(int argc, char **argv)
set_data_dir(m_config.opt_string("datadir"));
if (!validity.empty()) {
boost::nowide::cerr << "error: " << validity << std::endl;
return false;
}
return true;
}

View File

@ -982,6 +982,9 @@ template<class S> inline double area(const S& poly, const PolygonTag& )
});
}
template<class RawShapes>
inline double area(const RawShapes& shapes, const MultiPolygonTag&);
template<class S> // Dispatching function
inline double area(const S& sh)
{

View File

@ -27,6 +27,7 @@ using Coord = TCoord<PointImpl>;
using Box = _Box<PointImpl>;
using Segment = _Segment<PointImpl>;
using Circle = _Circle<PointImpl>;
using MultiPolygon = TMultiShape<PolygonImpl>;
using Item = _Item<PolygonImpl>;
using Rectangle = _Rectangle<PolygonImpl>;

View File

@ -5,7 +5,7 @@
#include <fstream>
#include <string>
#include <libnest2d/libnest2d.hpp>
#include <libnest2d/nester.hpp>
namespace libnest2d { namespace svg {
@ -49,9 +49,8 @@ public:
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<Coord>(
std::round(conf_.height*conf_.mm_in_coord_units) );
@ -63,8 +62,14 @@ public:
for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d);
}
currentLayer() += shapelike::serialize<Formats::SVG>(tsh,
1.0/conf_.mm_in_coord_units) + "\n";
currentLayer() +=
shapelike::serialize<Formats::SVG>(tsh,
1.0 / conf_.mm_in_coord_units) +
"\n";
}
void writeItem(const Item& item) {
writeShape(item.transformedShape());
}
void writePackGroup(const PackGroup& result) {

View File

@ -1,7 +1,6 @@
#include "Arrange.hpp"
#include "Geometry.hpp"
//#include "Geometry.hpp"
#include "SVG.hpp"
#include "MTUtils.hpp"
#include <libnest2d/backends/clipper/geometries.hpp>
#include <libnest2d/optimizers/nlopt/subplex.hpp>
@ -83,7 +82,7 @@ const double BIG_ITEM_TRESHOLD = 0.02;
// Fill in the placer algorithm configuration with values carefully chosen for
// Slic3r.
template<class PConf>
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<double, Box>& result, const Box &binbb)
static double fixed_overfit(const std::tuple<double, Box>& 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<Item>& fixeditems) {
m_pconf.alignment = PConfig::Alignment::DONT_ALIGN;
auto bb = sl::boundingBox(m_bin);
@ -438,127 +440,6 @@ std::function<double(const Item &)> AutoArranger<clppr::Polygon>::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<double> vertex_distances;
double avg_dist = 0;
for (auto pt: polygon.points)
{
double distance = distance_to(center, pt);
vertex_distances.push_back(distance);
avg_dist += distance;
}
avg_dist /= vertex_distances.size();
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<class Bin> void remove_large_items(std::vector<Item> &items, Bin &&bin)
{
auto it = items.begin();
@ -572,12 +453,12 @@ void _arrange(
std::vector<Item> & shapes,
std::vector<Item> & excludes,
const BinT & bin,
coord_t minobjd,
const ArrangeParams & params,
std::function<void(unsigned)> progressfn,
std::function<bool()> 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<BinT> 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,24 +487,51 @@ 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<void(unsigned)> progressind,
std::function<bool()> 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<clppr::Polygon>(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)
{
namespace clppr = ClipperLib;
double dx = p2.x() - p1.x();
double dy = p2.y() - p1.y();
return std::sqrt(dx*dx + dy*dy);
}
std::vector<Item> items, fixeditems;
items.reserve(arrangables.size());
static CircleBed to_circle(const Point &center, const Points& points) {
std::vector<double> vertex_distances;
double avg_dist = 0;
// Create Item from Arrangeable
auto process_arrangeable = [](const ArrangePolygon &arrpoly,
std::vector<Item> & outp)
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<Item> & outp)
{
Polygon p = arrpoly.poly.contour;
const Vec2crd &offs = arrpoly.translation;
double rotation = arrpoly.rotation;
@ -639,7 +550,42 @@ void arrange(ArrangePolygons & arrangables,
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<class BedT>
void arrange(ArrangePolygons & arrangables,
const ArrangePolygons &excludes,
const BedT & bed,
const ArrangeParams & params)
{
namespace clppr = ClipperLib;
std::vector<Item> items, fixeditems;
items.reserve(arrangables.size());
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<clppr::Polygon>(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<void(unsigned)> prfn,
std::function<bool()> stopfn)
{
arrange(inp, {}, min_d, bedhint, prfn, stopfn);
}
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams &params);
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams &params);
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams &params);
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params);
} // namespace arr
} // namespace Slic3r

View File

@ -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<double> allowed_rotations = {0.};
/// Optional setter function which can store arbitrary data in its closure
std::function<void(const ArrangePolygon&)> setter = nullptr;
@ -140,6 +63,30 @@ struct ArrangePolygon {
using ArrangePolygons = std::vector<ArrangePolygon>;
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<void(unsigned)> progressind;
/// A predicate returning true if abort is needed.
std::function<bool(void)> 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<ArrangePolygon>;
* \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<void(unsigned)> progressind = nullptr,
std::function<bool(void)> stopcondition = nullptr);
template<class TBed> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams &params = {});
/// 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<void(unsigned)> progressind = nullptr,
std::function<bool(void)> 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 &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params);
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
}} // namespace Slic3r::arrangement
} // arr
} // Slic3r
#endif // MODELARRANGE_HPP

View File

@ -186,6 +186,11 @@ inline bool empty(const BoundingBox3Base<VT> &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

View File

@ -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

View File

@ -0,0 +1,171 @@
#include "SL1.hpp"
#include "GCode/ThumbnailData.hpp"
#include "libslic3r/Time.hpp"
#include <boost/log/trivial.hpp>
#include <boost/filesystem.hpp>
#include "libslic3r/Zipper.hpp"
#include "libslic3r/SLAPrint.hpp"
namespace Slic3r {
using ConfMap = std::map<std::string, std::string>;
namespace {
std::string to_ini(const ConfMap &m)
{
std::string ret;
for (auto &param : 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<sla::RasterBase> SL1Archive::create_raster() const
{
sla::RasterBase::Resolution res;
sla::RasterBase::PixelDim pxdim;
std::array<bool, 2> 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

View File

@ -0,0 +1,44 @@
#ifndef ARCHIVETRAITS_HPP
#define ARCHIVETRAITS_HPP
#include <string>
#include "libslic3r/Zipper.hpp"
#include "libslic3r/SLAPrint.hpp"
namespace Slic3r {
class SL1Archive: public SLAPrinter {
SLAPrinterConfig m_cfg;
protected:
uqptr<sla::RasterBase> 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

View File

@ -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 Vector, // The container type, must be random access...
class Value = typename Vector::value_type // The value type
>
class IndexBasedIterator
{
static const size_t NONE = size_t(-1);
std::reference_wrapper<Vector> 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 It> class Range
{
@ -252,97 +116,6 @@ template<class T> struct remove_cvref
template<class T> using remove_cvref_t = typename remove_cvref<T>::type;
// A shorter C++14 style form of the enable_if metafunction
template<bool B, class T>
using enable_if_t = typename std::enable_if<B, T>::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<class I> struct is_scaled_coord
{
static const SLIC3R_CONSTEXPR bool value =
std::is_integral<I>::value &&
std::numeric_limits<I>::digits >=
std::numeric_limits<coord_t>::digits;
};
// Meta predicates for floating, 'scaled coord' and generic arithmetic types
template<class T, class O = T>
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>;
template<class T, class O = T>
using ScaledCoordOnly = enable_if_t<is_scaled_coord<T>::value, O>;
template<class T, class O = T>
using IntegerOnly = enable_if_t<std::is_integral<T>::value, O>;
template<class T, class O = T>
using ArithmeticOnly = enable_if_t<std::is_arithmetic<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<class Tout,
class Tin,
class = FloatingOnly<Tin>>
inline constexpr FloatingOnly<Tout> 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<class Tout = coord_t, class Tin, class = FloatingOnly<Tin>>
inline constexpr ScaledCoordOnly<Tout> scaled(const Tin &v) noexcept
{
//return static_cast<Tout>(std::round(v / SCALING_FACTOR));
return Tout(v / Tin(SCALING_FACTOR));
}
// Conversion for Eigen vectors (N dimensional points)
template<class Tout = coord_t,
class Tin,
int N,
class = FloatingOnly<Tin>,
int...EigenArgs>
inline Eigen::Matrix<ArithmeticOnly<Tout>, N, EigenArgs...>
scaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v)
{
return (v / SCALING_FACTOR).template cast<Tout>();
}
// Conversion from arithmetic scaled type to floating point unscaled
template<class Tout = double,
class Tin,
class = ArithmeticOnly<Tin>,
class = FloatingOnly<Tout>>
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 Tout = double,
class Tin,
int N,
class = ArithmeticOnly<Tin>,
class = FloatingOnly<Tout>,
int...EigenArgs>
inline constexpr Eigen::Matrix<Tout, N, EigenArgs...>
unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept
{
return v.template cast<Tout>() * SCALING_FACTOR;
}
template<class T, class I, class... Args> // Arbitrary allocator can be used
inline IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity)
{
@ -353,10 +126,10 @@ inline IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity)
}
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
template<class T, class I>
template<class T, class I, class = IntegerOnly<I>>
inline std::vector<T> linspace_vector(const ArithmeticOnly<T> &start,
const T &stop,
const IntegerOnly<I> &n)
const I &n)
{
std::vector<T> vals(n, T());

View File

@ -0,0 +1,448 @@
#ifndef MARCHINGSQUARES_HPP
#define MARCHINGSQUARES_HPP
#include <type_traits>
#include <cstdint>
#include <vector>
#include <algorithm>
#include <cassert>
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<Coord>;
// Specialize this struct to register a raster type for the Marching squares alg
template<class T, class Enable = void> 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<class ExecutionPolicy, class Enable = void> struct _Loop {
template<class It, class Fn> 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<class T> using RasterTraits = _RasterTraits<std::decay_t<T>>;
template<class T> using TRasterValue = typename RasterTraits<T>::ValueType;
template<class T> size_t rows(const T &raster)
{
return RasterTraits<T>::rows(raster);
}
template<class T> size_t cols(const T &raster)
{
return RasterTraits<T>::cols(raster);
}
template<class T> TRasterValue<T> isoval(const T &rst, const Coord &crd)
{
return RasterTraits<T>::get(rst, crd.r, crd.c);
}
template<class ExecutionPolicy, class It, class Fn>
void for_each(ExecutionPolicy&& policy, It from, It to, Fn &&fn)
{
_Loop<ExecutionPolicy>::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<class E> constexpr std::underlying_type_t<E> _t(E e) noexcept
{
return static_cast<std::underlying_type_t<E>>(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 Rst> class Grid {
const Rst * m_rst = nullptr;
Coord m_cellsize, m_res_1, m_window, m_gridsize, m_grid_1;
std::vector<uint8_t> 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<Rst> 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<Rst> 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<Rst>;
using pointer = TRasterValue<Rst> *;
using reference = TRasterValue<Rst> &;
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<class ExecutionPolicy>
void tag_grid(ExecutionPolicy &&policy, TRasterValue<Rst> isoval)
{
// parallel for r
for_each (std::forward<ExecutionPolicy>(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<Ring> scan_rings()
{
std::vector<Ring> 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<class ExecutionPolicy>
void interpolate_rings(ExecutionPolicy && policy,
std::vector<Ring> &rings,
TRasterValue<Rst> isov)
{
for_each(std::forward<ExecutionPolicy>(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<class Raster, class ExecutionPolicy>
std::vector<marchsq::Ring> execute_with_policy(ExecutionPolicy && policy,
const Raster & raster,
TRasterValue<Raster> 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<Raster> grid{raster, windowsize, overlap};
grid.tag_grid(std::forward<ExecutionPolicy>(policy), isoval);
std::vector<marchsq::Ring> rings = grid.scan_rings();
grid.interpolate_rings(std::forward<ExecutionPolicy>(policy), rings, isoval);
return rings;
}
template<class Raster>
std::vector<marchsq::Ring> execute(const Raster &raster,
TRasterValue<Raster> 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

View File

@ -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<double>(),
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 */
}

View File

@ -802,10 +802,8 @@ 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);
bool looks_like_multipart_object() const;
@ -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();

View File

@ -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<double>(),
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

View File

@ -0,0 +1,68 @@
#ifndef MODELARRANGE_HPP
#define MODELARRANGE_HPP
#include <libslic3r/Model.hpp>
#include <libslic3r/Arrange.hpp>
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<void(arrangement::ArrangePolygon&)>;
[[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<class TBed>
bool arrange_objects(Model & model,
const TBed & bed,
const ArrangeParams &params,
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<class TBed>
void duplicate(Model & model,
size_t copies_num,
const TBed & bed,
const ArrangeParams &params,
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<class TBed>
void duplicate_objects(Model & model,
size_t copies_num,
const TBed & bed,
const ArrangeParams &params,
VirtualBedFn vfn = throw_if_out_of_bed)
{
duplicate_objects(model, copies_num);
arrange_objects(model, bed, params, vfn);
}
}
#endif // MODELARRANGE_HPP

View File

@ -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<class Tout,
class Tin,
class = FloatingOnly<Tin>>
inline constexpr FloatingOnly<Tout> 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<class Tout = coord_t, class Tin, class = FloatingOnly<Tin>>
inline constexpr ScaledCoordOnly<Tout> scaled(const Tin &v) noexcept
{
//return static_cast<Tout>(std::round(v / SCALING_FACTOR));
return Tout(v / Tin(SCALING_FACTOR));
}
// Conversion for Eigen vectors (N dimensional points)
template<class Tout = coord_t,
class Tin,
int N,
class = FloatingOnly<Tin>,
int...EigenArgs>
inline Eigen::Matrix<ArithmeticOnly<Tout>, N, EigenArgs...>
scaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v)
{
return (v / SCALING_FACTOR).template cast<Tout>();
}
// Conversion from arithmetic scaled type to floating point unscaled
template<class Tout = double,
class Tin,
class = ArithmeticOnly<Tin>,
class = FloatingOnly<Tout>>
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 Tout = double,
class Tin,
int N,
class = ArithmeticOnly<Tin>,
class = FloatingOnly<Tout>,
int...EigenArgs>
inline constexpr Eigen::Matrix<Tout, N, EigenArgs...>
unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept
{
return v.template cast<Tout>() * SCALING_FACTOR;
}
} // namespace Slic3r
// start Boost

View File

@ -48,7 +48,7 @@ int64_t Polygon::area2x() const
}
*/
double Polygon::area() const
double Polygon::area(const Points &points)
{
size_t n = points.size();
if (n < 3)
@ -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));

View File

@ -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<Point> points) : MultiPoint(points) {}
Polygon(const Polygon &other) : MultiPoint(other.points) {}
@ -47,6 +48,7 @@ public:
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;

View File

@ -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<ConfigOptionFloat>("extruder_clearance_radius");
auto dd_opt = cfg.option<ConfigOptionFloat>("duplicate_distance");
auto co_opt = cfg.option<ConfigOptionBool>("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<PrinterTechnology> *opt = cfg.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology");
if (opt) return opt->value;
const ConfigOptionBool *export_opt = cfg.option<ConfigOptionBool>("export_sla");
if (export_opt && export_opt->getBool()) return ptSLA;
export_opt = cfg.option<ConfigOptionBool>("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<const ConfigBase*>(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<Vec2d> &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<ConfigOptionPoints>("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<ConfigOptionPoint>("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/types/polymorphic.hpp>
CEREAL_REGISTER_TYPE(Slic3r::DynamicPrintConfig)
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig)

View File

@ -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

View File

@ -0,0 +1,222 @@
#ifndef AGGRASTER_HPP
#define AGGRASTER_HPP
#include <libslic3r/SLA/RasterBase.hpp>
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/MTUtils.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
// For rasterizing
#include <agg/agg_basics.h>
#include <agg/agg_rendering_buffer.h>
#include <agg/agg_pixfmt_gray.h>
#include <agg/agg_pixfmt_rgb.h>
#include <agg/agg_renderer_base.h>
#include <agg/agg_renderer_scanline.h>
#include <agg/agg_scanline_p.h>
#include <agg/agg_rasterizer_scanline_aa.h>
#include <agg/agg_path_storage.h>
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<class Color> struct Colors {
static const Color White;
static const Color Black;
};
template<class Color> const Color Colors<Color>::White = Color{255};
template<class Color> const Color Colors<Color>::Black = Color{0};
template<class PixelRenderer,
template<class /*agg::renderer_base<PixelRenderer>*/> 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<TPixel> m_buf;
agg::rendering_buffer m_rbuf;
PixelRenderer m_pixrenderer;
agg::renderer_base<PixelRenderer> m_raw_renderer;
Renderer<agg::renderer_base<PixelRenderer>> 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<class PointVec> 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<class PointVec> 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<class PointVec> 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<class P> 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<class GammaFn> 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<TValue *>(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<agg::pixfmt_gray8, agg::renderer_scanline_aa_solid>;
class RasterGrayscaleAA : public _RasterGrayscaleAA {
using Base = _RasterGrayscaleAA;
using typename Base::TColor;
using typename Base::TValue;
public:
template<class GammaFn>
RasterGrayscaleAA(const RasterBase::Resolution &res,
const RasterBase::PixelDim & pd,
const RasterBase::Trafo & trafo,
GammaFn && fn)
: Base(res, pd, trafo, Colors<TColor>::White, Colors<TColor>::Black,
std::forward<GammaFn>(fn))
{}
uint8_t read_pixel(size_t col, size_t row) const
{
static_assert(std::is_same<TValue, uint8_t>::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<TColor>::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

View File

@ -11,6 +11,8 @@
#include "Tesselate.hpp"
#include "MTUtils.hpp"
#include "TriangulateWall.hpp"
// For debugging:
// #include <fstream>
// #include <libnest2d/tools/benchmark.h>
@ -27,175 +29,17 @@ 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<double>::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;
}
@ -203,10 +47,9 @@ Contour3D walls(
// 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,16 +396,16 @@ 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,10 +422,11 @@ 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));

View File

@ -1,320 +0,0 @@
#ifndef SLARASTER_CPP
#define SLARASTER_CPP
#include <functional>
#include <libslic3r/SLA/Raster.hpp>
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/MTUtils.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
// For rasterizing
#include <agg/agg_basics.h>
#include <agg/agg_rendering_buffer.h>
#include <agg/agg_pixfmt_gray.h>
#include <agg/agg_pixfmt_rgb.h>
#include <agg/agg_renderer_base.h>
#include <agg/agg_renderer_scanline.h>
#include <agg/agg_scanline_p.h>
#include <agg/agg_rasterizer_scanline_aa.h>
#include <agg/agg_path_storage.h>
// Experimental minz image write:
#include <miniz.h>
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<TPixelRenderer>;
using TPixel = TPixelRenderer::color_type;
using TRawBuffer = agg::rendering_buffer;
using TBuffer = std::vector<TPixelRenderer::pixel_type>;
using TRendererAA = agg::renderer_scanline_aa_solid<TRawRenderer>;
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<double(double)> 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<TPixelRenderer::value_type *>(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<class P> 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<class PointVec> 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<class PointVec> 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<class PointVec> 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<std::uint8_t*>(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<const char *>(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<const std::uint8_t*>(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

View File

@ -1,157 +0,0 @@
#ifndef SLA_RASTER_HPP
#define SLA_RASTER_HPP
#include <ostream>
#include <memory>
#include <vector>
#include <array>
#include <utility>
#include <cstdint>
#include <libslic3r/ExPolygon.hpp>
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<Impl> m_impl;
public:
// Raw byte buffer paired with its size. Suitable for compressed image data.
class RawData
{
protected:
std::vector<std::uint8_t> m_buffer;
const Impl& get_internals(const Raster& raster);
public:
RawData() = default;
RawData(std::vector<std::uint8_t>&& 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<bool, 2>;
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

View File

@ -0,0 +1,89 @@
#ifndef SLARASTER_CPP
#define SLARASTER_CPP
#include <functional>
#include <libslic3r/SLA/RasterBase.hpp>
#include <libslic3r/SLA/AGGRaster.hpp>
// minz image write:
#include <miniz.h>
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<uint8_t> 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<std::uint8_t*>(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<const char *>(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<uint8_t> 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<const std::uint8_t*>(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<RasterBase> create_raster_grayscale_aa(
const RasterBase::Resolution &res,
const RasterBase::PixelDim & pxdim,
double gamma,
const RasterBase::Trafo & tr)
{
std::unique_ptr<RasterBase> rst;
if (gamma > 0)
rst = std::make_unique<RasterGrayscaleAAGammaPower>(res, pxdim, tr, gamma);
else
rst = std::make_unique<RasterGrayscaleAA>(res, pxdim, tr, agg::gamma_threshold(.5));
return rst;
}
} // namespace sla
} // namespace Slic3r
#endif // SLARASTER_CPP

View File

@ -0,0 +1,124 @@
#ifndef SLA_RASTERBASE_HPP
#define SLA_RASTERBASE_HPP
#include <ostream>
#include <memory>
#include <vector>
#include <array>
#include <utility>
#include <cstdint>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/SLA/Concurrency.hpp>
namespace ClipperLib { struct Polygon; }
namespace Slic3r {
template<class T> using uqptr = std::unique_ptr<T>;
template<class T> using shptr = std::shared_ptr<T>;
template<class T> using wkptr = std::weak_ptr<T>;
namespace sla {
// Raw byte buffer paired with its size. Suitable for compressed image data.
class EncodedRaster {
protected:
std::vector<uint8_t> m_buffer;
std::string m_ext;
public:
EncodedRaster() = default;
explicit EncodedRaster(std::vector<uint8_t> &&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<EncodedRaster(const void *ptr, size_t w, size_t h, size_t num_components)>;
class RasterBase {
public:
enum Orientation { roLandscape, roPortrait };
using TMirroring = std::array<bool, 2>;
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<RasterBase> 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

View File

@ -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<Slic3r::sla::RasterGrayscaleAA> {
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<class Fn> 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<marchsq::Ring> 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

View File

@ -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

View File

@ -1,151 +0,0 @@
#include <string_view>
#include <libslic3r/SLA/RasterWriter.hpp>
#include "libslic3r/PrintConfig.hpp"
#include <libslic3r/Zipper.hpp>
#include <libslic3r/Time.hpp>
#include "ExPolygon.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
#include <boost/log/trivial.hpp>
#include <boost/filesystem/path.hpp>
namespace Slic3r { namespace sla {
void RasterWriter::write_ini(const std::map<std::string, std::string> &m, std::string &ini)
{
for (auto &param : 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<std::string, std::string> &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

View File

@ -1,130 +0,0 @@
#ifndef SLA_RASTERWRITER_HPP
#define SLA_RASTERWRITER_HPP
// For png export of the sliced model
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <array>
#include <libslic3r/SLA/Raster.hpp>
#include <libslic3r/Zipper.hpp>
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<Layer> m_layers_rst;
Raster::Resolution m_res;
Raster::PixelDim m_pxdim;
Raster::Trafo m_trafo;
double m_gamma;
std::map<std::string, std::string> m_config;
std::map<std::string, std::string> m_slicer_config;
static void write_ini(const std::map<std::string, std::string> &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<class Poly> 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

View File

@ -228,6 +228,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con
// 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 {
Unknown,
@ -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<SLAPrintObjectStep> level1_obj_steps = {
@ -855,36 +862,6 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
return invalidated;
}
sla::RasterWriter & SLAPrint::init_printer()
{
sla::Raster::Resolution res;
sla::Raster::PixelDim pxdim;
std::array<bool, 2> 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
{

View File

@ -3,7 +3,7 @@
#include <mutex>
#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<sla::EncodedRaster> m_layers;
virtual uqptr<sla::RasterBase> 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<class Fn> 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,7 +458,8 @@ public:
std::vector<ClipperLib::Polygon> m_transformed_slices;
template<class Container> void transformed_slices(Container&& c) {
template<class Container> void transformed_slices(Container&& c)
{
m_transformed_slices = std::forward<Container>(c);
}
@ -475,7 +489,10 @@ public:
// TODO: use this structure for the preview in the future.
const std::vector<PrintLayer>& print_layers() const { return m_printer_input; }
void set_printer(SLAPrinter *archiver);
private:
// Implement same logic as in SLAPrintObject
bool invalidate_step(SLAPrintStep st);
@ -493,8 +510,8 @@ private:
// Ready-made data for rasterization.
std::vector<PrintLayer> m_printer_input;
// The printer itself
std::unique_ptr<sla::RasterWriter> m_printer;
// The archive object which collects the raster images after slicing
SLAPrinter *m_printer = nullptr;
// Estimated print time, material consumed.
SLAPrintStatistics m_print_statistics;
@ -513,15 +530,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;
};

View File

@ -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)

View File

@ -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);

View File

@ -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 <tbb/parallel_for.h>
#include <tbb/parallel_reduce.h>
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<ExPolygons> &slices,
double zmin,
const std::vector<float> & grid)
{
assert(slices.size() == grid.size());
using Layers = std::vector<sla::Contour3D>;
std::vector<sla::Contour3D> 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<Layers::iterator>& 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<ExPolygons> &slices,
double zmin,
double lh,
double ilh)
{
std::vector<sla::Contour3D> wall_meshes(slices.size());
std::vector<float> 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

View File

@ -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<ExPolygons> &slices,
double zmin,
double lh,
double ilh);
inline TriangleMesh slices_to_triangle_mesh(
const std::vector<ExPolygons> &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

View File

@ -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<size_t, size_t> 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<Vec3d> &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<Vec3d> *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<Vec3i> &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<Vec3i> &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<Vec3d> *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

View File

@ -0,0 +1,17 @@
#ifndef TRIANGULATEWALL_HPP
#define TRIANGULATEWALL_HPP
#include "libslic3r/Polygon.hpp"
namespace Slic3r {
using Wall = std::pair<std::vector<Vec3d>, std::vector<Vec3i>>;
Wall triangulate_wall(
const Polygon & lower,
const Polygon & upper,
double lower_z_mm,
double upper_z_mm);
}
#endif // TRIANGULATEWALL_HPP

View File

@ -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;

View File

@ -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.

View File

@ -17,6 +17,7 @@
#include <vector>
#include <cassert>
#include <cmath>
#include <type_traits>
#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<class I> struct is_scaled_coord
{
static const constexpr bool value =
std::is_integral<I>::value &&
std::numeric_limits<I>::digits >=
std::numeric_limits<coord_t>::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<class T> FloatingOnly<T, bool> 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<class T, class O = T>
using FloatingOnly = std::enable_if_t<std::is_floating_point<T>::value, O>;
template<class T, class O = T>
using ScaledCoordOnly = std::enable_if_t<is_scaled_coord<T>::value, O>;
template<class T, class O = T>
using IntegerOnly = std::enable_if_t<std::is_integral<T>::value, O>;
template<class T, class O = T>
using ArithmeticOnly = std::enable_if_t<std::is_arithmetic<T>::value, O>;
} // namespace Slic3r
#endif

View File

@ -1,9 +1,17 @@
#include <exception>
#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

View File

@ -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

View File

@ -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)

View File

@ -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 <cassert>
@ -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)
{
@ -493,7 +494,7 @@ void BackgroundSlicingProcess::prepare_upload()
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;

View File

@ -10,6 +10,7 @@
#include <wx/event.h>
#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
@ -158,6 +160,7 @@ private:
GCodePreviewData *m_gcode_preview_data = nullptr;
// Callback function, used to write thumbnails into gcode.
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
SL1Archive m_sla_archive;
#if ENABLE_GCODE_VIEWER
GCodeProcessor::Result* m_gcode_result = nullptr;
#endif // ENABLE_GCODE_VIEWER

View File

@ -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

View File

@ -160,7 +160,7 @@ private:
std::vector<unsigned char> 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

View File

@ -24,6 +24,7 @@
#include <wx/filefn.h>
#include <wx/sysopt.h>
#include <wx/msgdlg.h>
#include <wx/richmsgdlg.h>
#include <wx/log.h>
#include <wx/intl.h>
@ -321,11 +322,6 @@ 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();
@ -333,9 +329,35 @@ bool GUI_App::on_init_inner()
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();
#endif // __WXMSW__

View File

@ -2071,10 +2071,13 @@ 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);

View File

@ -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<std::pair<wxString, bool>> &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);

View File

@ -1,155 +0,0 @@
#ifndef JOB_HPP
#define JOB_HPP
#include <atomic>
#include <slic3r/Utils/Thread.hpp>
#include <slic3r/GUI/I18N.hpp>
#include <slic3r/GUI/ProgressIndicator.hpp>
#include <wx/event.h>
#include <boost/thread.hpp>
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<bool> m_running{false}, m_canceled{false};
bool m_finalized = false;
std::shared_ptr<ProgressIndicator> 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<ProgressIndicator> 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

View File

@ -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<double>((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<const Selection::InstanceIdxsList *>
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<bool> 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<double>(), ap.rotation);
}
}} // namespace Slic3r::GUI

View File

@ -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<class T> 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<double>();
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<ProgressIndicator> 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

121
src/slic3r/GUI/Jobs/Job.cpp Normal file
View File

@ -0,0 +1,121 @@
#include <algorithm>
#include "Job.hpp"
#include <boost/log/trivial.hpp>
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<ProgressIndicator> 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<bool> 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<GUI::Job> &j) {
return j->is_running();
});
}
}

110
src/slic3r/GUI/Jobs/Job.hpp Normal file
View File

@ -0,0 +1,110 @@
#ifndef JOB_HPP
#define JOB_HPP
#include <atomic>
#include <slic3r/Utils/Thread.hpp>
#include <slic3r/GUI/I18N.hpp>
#include "ProgressIndicator.hpp"
#include <wx/event.h>
#include <boost/thread.hpp>
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<bool> m_running{false}, m_canceled{false};
bool m_finalized = false;
std::shared_ptr<ProgressIndicator> 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<ProgressIndicator> 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<std::unique_ptr<GUI::Job>> m_jobs;
protected:
virtual void before_start() {}
public:
virtual ~ExclusiveJobGroup() = default;
size_t add_job(std::unique_ptr<GUI::Job> &&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

View File

@ -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();
}
}}

View File

@ -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<ProgressIndicator> pri, Plater *plater)
: Job{std::move(pri)}, m_plater{plater}
{}
void process() override;
void finalize() override;
};
}} // namespace Slic3r::GUI
#endif // ROTOPTIMIZEJOB_HPP

View File

@ -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 <wx/dialog.h>
#include <wx/stattext.h>
#include <wx/combobox.h>
#include <wx/filename.h>
#include <wx/filepicker.h>
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<wxString> 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<wxString> 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<ProgressIndicator> pri, Plater *plater)
: Job{std::move(pri)}, p{std::make_unique<priv>(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();
}
}}

View File

@ -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<priv> p;
public:
SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater);
~SLAImportJob();
void process() override;
void reset();
protected:
void prepare() override;
void finalize() override;
};
}} // namespace Slic3r::GUI
#endif // SLAIMPORTJOB_HPP

View File

@ -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,

View File

@ -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<double>((1. + LOGICAL_BED_GAP) * bedwidth);
}
// Set up arrange polygon for a ModelInstance and Wipe tower
template<class T> 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<double>();
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<const Selection::InstanceIdxsList *>
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<bool> 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 {
// 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;
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<std::reference_wrapper<Job>> m_jobs{arrange_job,
rotoptimize_job};
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<bool> 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<ArrangeJob>(m->statusbar(), m->q));
m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q));
m_sla_import_id = add_job(std::make_unique<SLAImportJob>(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;
@ -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<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
std::vector<size_t> 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<int> &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<ConfigOptionPoints>("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();
@ -2868,10 +2565,11 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
movable.emplace_back(std::move(arrpoly));
}
if (updated_wipe_tower())
fixed.emplace_back(wipetower.get_arrange_polygon());
if (p->view3D->get_canvas3d()->get_wipe_tower_info())
fixed.emplace_back(get_wipe_tower_arrangepoly(*this));
arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint());
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;

View File

@ -9,8 +9,10 @@
#include <wx/bmpcbox.h>
#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<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, bool load_model = true, bool load_config = true);
@ -253,12 +256,16 @@ public:
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
void unbind_canvas_event_handlers();
@ -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

View File

@ -6,7 +6,7 @@
#include <functional>
#include <string>
#include "ProgressIndicator.hpp"
#include "Jobs/ProgressIndicator.hpp"
class wxTimer;
class wxGauge;

View File

@ -18,6 +18,8 @@
#include <openssl/x509.h>
#endif
#define L(s) s
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
@ -32,6 +34,7 @@ namespace Slic3r {
struct CurlGlobalInit
{
static std::unique_ptr<CurlGlobalInit> instance;
std::string message;
CurlGlobalInit()
{
@ -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);
}
::curl_global_init(CURL_GLOBAL_DEFAULT);
#endif // OPENSSL_CERT_OVERRIDE
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<CurlGlobalInit>();
Http::tls_global_init();
if (curl == nullptr) {
throw std::runtime_error(std::string("Could not construct Curl object"));
@ -497,6 +517,25 @@ bool Http::ca_file_supported()
return res;
}
std::string Http::tls_global_init()
{
if (!CurlGlobalInit::instance)
CurlGlobalInit::instance = std::make_unique<CurlGlobalInit>();
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)
{
::CURL *curl = ::curl_easy_init();

View File

@ -101,6 +101,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);
private:

View File

@ -0,0 +1,314 @@
#include "SLAImport.hpp"
#include <sstream>
#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 <boost/property_tree/ini_parser.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/algorithm/string.hpp>
#include <wx/image.h>
#include <wx/mstream.h>
namespace marchsq {
// Specialize this struct to register a raster type for the Marching squares alg
template<> struct _RasterTraits<wxImage> {
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<sla::EncodedRaster> 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<uint8_t> 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<std::string>()(r1.extension(), r2.extension());
});
arch.images.insert(it, read_png(entry, zip, name));
}
}
}
return arch;
}
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &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<class Fn> 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<ConfigOptionInt>("display_pixels_x");
auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("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<ConfigOptionFloat>("layer_height");
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("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<ExPolygons> extract_slices_from_sla_archive(
ArchiveData & arch,
const RasterParams & rstp,
std::function<bool(int)> progr)
{
auto jobdir = arch.config.get<std::string>("jobDir");
for (auto &c : jobdir) c = std::tolower(c);
std::vector<ExPolygons> 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<tbb::spin_mutex> 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<bool(int)> 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<ExPolygons> 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

View File

@ -0,0 +1,36 @@
#ifndef SLAIMPORT_HPP
#define SLAIMPORT_HPP
#include <functional>
#include <libslic3r/Point.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/PrintConfig.hpp>
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<bool(int)> progr = [](int) { return true; });
inline void import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
TriangleMesh & out,
std::function<bool(int)> progr = [](int) { return true; })
{
DynamicPrintConfig profile;
import_sla_archive(zipfname, windowsize, out, profile, progr);
}
}
#endif // SLAIMPORT_HPP

View File

@ -12,6 +12,7 @@
#include <boost/nowide/cstdio.hpp>
#include <boost/filesystem.hpp>
#include <libslic3r/ModelArrange.hpp>
using namespace std;
@ -167,8 +168,7 @@ void init_print(std::vector<TriangleMesh> &&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);

View File

@ -2,6 +2,7 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/Model.hpp"
#include "libslic3r/ModelArrange.hpp"
#include <boost/nowide/cstdio.hpp>
#include <boost/filesystem.hpp>
@ -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?") {

View File

@ -472,32 +472,30 @@ TEST_CASE("ArrangeRectanglesLoose", "[Nesting]")
namespace {
using namespace libnest2d;
template<long long SCALE = 1, class Bin>
void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) {
std::string loc = "out";
template<long long SCALE = 1, class It>
void exportSVG(const char *loc, It from, It to) {
static std::string svg_header =
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
static const char* svg_header =
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
)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<Formats::SVG>(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<Formats::SVG>(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);
@ -513,6 +511,12 @@ void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin
// i++;
// }
}
template<long long SCALE = 1>
void exportSVG(std::vector<std::reference_wrapper<Item>>& 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<SCALE>(result, i);
}
REQUIRE(valid);
} else {
@ -894,7 +898,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
int TEST_CASEcase = 0;
auto& exportfun = exportSVG<SCALE, Box>;
auto& exportfun = exportSVG<SCALE>;
auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){
TEST_CASEcase++;
@ -941,7 +945,7 @@ void testNfp(const std::vector<ItemPair>& 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<class It> 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<RectangleItem> input(9, {W, W});
auto bin = Box::infinite();
NfpPlacer::Config pconfig;
pconfig.object_function = [](const Item &item) -> double {
return pl::magnsq<PointImpl, double>(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<RectangleItem> 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<PolygonImpl> &/*packed_items*/,
const _ItemGroup<PolygonImpl> &/*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<double>() / (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);
}

View File

@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests
test_stl.cpp
test_meshsimplify.cpp
test_meshboolean.cpp
test_marchingsquares.cpp
test_timeutils.cpp
)

View File

@ -0,0 +1,371 @@
#define NOMINMAX
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <fstream>
#include <libslic3r/MarchingSquares.hpp>
#include <libslic3r/SLA/RasterToPolygons.hpp>
#include <libslic3r/SLA/AGGRaster.hpp>
#include <libslic3r/MTUtils.hpp>
#include <libslic3r/SVG.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/TriangulateWall.hpp>
#include <libslic3r/Tesselate.hpp>
#include <libslic3r/SlicesToTriangleMesh.hpp>
#include <libslic3r/SLA/Contour3D.hpp>
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<double> 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<class Rst>
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<double>(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<float>();
mesh.translate(tr.x(), tr.y(), tr.z());
bb = mesh.bounding_box();
std::vector<ExPolygons> 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<double>(1.) * scaled<double>(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);
}

View File

@ -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]")

View File

@ -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<double>(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.;

View File

@ -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 <class A, int N> 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

View File

@ -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();

View File

@ -3,6 +3,7 @@
%{
#include <xsinit.h>
#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;