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

@ -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,44 +487,106 @@ void _arrange(
for (Item &itm : inp) itm.inflate(-infl);
}
// The final client function for arrangement. A progress indicator and
// a stop predicate can be also be passed to control the process.
void arrange(ArrangePolygons & arrangables,
const ArrangePolygons & excludes,
coord_t min_obj_dist,
const BedShapeHint & bedhint,
std::function<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)
{
double dx = p2.x() - p1.x();
double dy = p2.y() - p1.y();
return std::sqrt(dx*dx + dy*dy);
}
static CircleBed to_circle(const Point &center, const Points& points) {
std::vector<double> vertex_distances;
double avg_dist = 0;
for (auto pt : points)
{
double distance = distance_to(center, pt);
vertex_distances.push_back(distance);
avg_dist += distance;
}
avg_dist /= vertex_distances.size();
CircleBed ret(center, avg_dist);
for(auto el : vertex_distances)
{
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
ret = {};
break;
}
}
return ret;
}
// Create Item from Arrangeable
static void process_arrangeable(const ArrangePolygon &arrpoly,
std::vector<Item> & outp)
{
Polygon p = arrpoly.poly.contour;
const Vec2crd &offs = arrpoly.translation;
double rotation = arrpoly.rotation;
if (p.is_counter_clockwise()) p.reverse();
clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
if (!clpath.Contour.empty()) {
auto firstp = clpath.Contour.front();
clpath.Contour.emplace_back(firstp);
}
outp.emplace_back(std::move(clpath));
outp.back().rotation(rotation);
outp.back().translation({offs.x(), offs.y()});
outp.back().binId(arrpoly.bed_idx);
outp.back().priority(arrpoly.priority);
}
template<>
void arrange(ArrangePolygons & items,
const ArrangePolygons &excludes,
const Points & bed,
const ArrangeParams & params)
{
if (bed.empty())
arrange(items, excludes, InfiniteBed{}, params);
else if (bed.size() == 1)
arrange(items, excludes, InfiniteBed{bed.front()}, params);
else {
auto bb = BoundingBox(bed);
CircleBed circ = to_circle(bb.center(), bed);
auto parea = poly_area(bed);
if ((1.0 - parea / area(bb)) < 1e-3)
arrange(items, excludes, bb, params);
else if (!std::isnan(circ.radius()))
arrange(items, excludes, circ, params);
else
arrange(items, excludes, Polygon(bed), params);
}
}
template<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());
// Create Item from Arrangeable
auto process_arrangeable = [](const ArrangePolygon &arrpoly,
std::vector<Item> & outp)
{
Polygon p = arrpoly.poly.contour;
const Vec2crd &offs = arrpoly.translation;
double rotation = arrpoly.rotation;
if (p.is_counter_clockwise()) p.reverse();
clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
if (!clpath.Contour.empty()) {
auto firstp = clpath.Contour.front();
clpath.Contour.emplace_back(firstp);
}
outp.emplace_back(std::move(clpath));
outp.back().rotation(rotation);
outp.back().translation({offs.x(), offs.y()});
outp.back().binId(arrpoly.bed_idx);
outp.back().priority(arrpoly.priority);
};
for (ArrangePolygon &arrangeable : arrangables)
process_arrangeable(arrangeable, items);
@ -649,45 +595,10 @@ void arrange(ArrangePolygons & arrangables,
for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON));
auto &cfn = stopcondition;
auto &pri = progressind;
auto &cfn = params.stopcondition;
auto &pri = params.progressind;
switch (bedhint.get_type()) {
case bsBox: {
// Create the arranger for the box shaped bed
BoundingBox bbb = bedhint.get_box();
Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}};
_arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn);
break;
}
case bsCircle: {
auto cc = to_lnCircle(bedhint.get_circle());
_arrange(items, fixeditems, cc, min_obj_dist, pri, cfn);
break;
}
case bsIrregular: {
auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular());
auto irrbed = sl::create<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,11 +802,9 @@ public:
bool center_instances_around_point(const Vec2d &point);
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
TriangleMesh mesh() const;
bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL);
// Croaks if the duplicated objects do not fit the print bed.
void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
void duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
void duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
bool looks_like_multipart_object() const;
void convert_multipart_object(unsigned int max_extruders);
@ -822,6 +820,7 @@ public:
std::string propose_export_file_name_and_path(const std::string &new_extension) const;
private:
explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); };
void assign_new_unique_ids_recursive();
void update_links_bottom_up_recursive();
@ -831,7 +830,7 @@ private:
template<class Archive> void serialize(Archive &ar) {
Internal::StaticSerializationWrapper<ModelWipeTower> wipe_tower_wrapper(wipe_tower);
ar(materials, objects, wipe_tower_wrapper);
}
}
};
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE

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,12 +48,12 @@ int64_t Polygon::area2x() const
}
*/
double Polygon::area() const
double Polygon::area(const Points &points)
{
size_t n = points.size();
if (n < 3)
return 0.;
double a = 0.;
for (size_t i = 0, j = n - 1; i < n; ++i) {
a += ((double)points[j](0) + (double)points[i](0)) * ((double)points[i](1) - (double)points[j](1));
@ -62,6 +62,11 @@ double Polygon::area() const
return 0.5 * a;
}
double Polygon::area() const
{
return Polygon::area(points);
}
bool Polygon::is_counter_clockwise() const
{
return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this));

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) {}
@ -46,7 +47,8 @@ public:
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline split_at_first_point() const { return this->split_at_index(0); }
Points equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); }
static double area(const Points &pts);
double area() const;
bool is_counter_clockwise() const;
bool is_clockwise() const;

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,186 +29,27 @@ namespace Slic3r { namespace sla {
namespace {
/// This function will return a triangulation of a sheet connecting an upper
/// and a lower plate given as input polygons. It will not triangulate the
/// plates themselves only the sheet. The caller has to specify the lower and
/// upper z levels in world coordinates as well as the offset difference
/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the
/// offset difference is negative, the resulting triangle orientation will be
/// reversed.
///
/// IMPORTANT: This is not a universal triangulation algorithm. It assumes
/// that the lower and upper polygons are offsetted versions of the same
/// original polygon. In general, it assumes that one of the polygons is
/// completely inside the other. The offset difference is the reference
/// distance from the inner polygon's perimeter to the outer polygon's
/// perimeter. The real distance will be variable as the clipper offset has
/// different strategies (rounding, etc...). This algorithm should have
/// O(2n + 3m) complexity where n is the number of upper vertices and m is the
/// number of lower vertices.
Contour3D walls(
const Polygon &lower,
const Polygon &upper,
double lower_z_mm,
double upper_z_mm,
double offset_difference_mm,
ThrowOnCancel thr = [] {})
double upper_z_mm)
{
Wall w = triangulate_wall(lower, upper, lower_z_mm, upper_z_mm);
Contour3D ret;
if(upper.points.size() < 3 || lower.size() < 3) return ret;
// The concept of the algorithm is relatively simple. It will try to find
// the closest vertices from the upper and the lower polygon and use those
// as starting points. Then it will create the triangles sequentially using
// an edge from the upper polygon and a vertex from the lower or vice versa,
// depending on the resulting triangle's quality.
// The quality is measured by a scalar value. So far it looks like it is
// enough to derive it from the slope of the triangle's two edges connecting
// the upper and the lower part. A reference slope is calculated from the
// height and the offset difference.
// Offset in the index array for the ceiling
const auto offs = upper.points.size();
// Shorthand for the vertex arrays
auto& upts = upper.points, &lpts = lower.points;
auto& rpts = ret.points; auto& ind = ret.faces3;
// If the Z levels are flipped, or the offset difference is negative, we
// will interpret that as the triangles normals should be inverted.
bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0;
// Copy the points into the mesh, convert them from 2D to 3D
rpts.reserve(upts.size() + lpts.size());
ind.reserve(2 * upts.size() + 2 * lpts.size());
for (auto &p : upts)
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm);
for (auto &p : lpts)
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm);
// Create pointing indices into vertex arrays. u-upper, l-lower
size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1;
// Simple squared distance calculation.
auto distfn = [](const Vec3d& p1, const Vec3d& p2) {
auto p = p1 - p2; return p.transpose() * p;
};
// We need to find the closest point on lower polygon to the first point on
// the upper polygon. These will be our starting points.
double distmin = std::numeric_limits<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;
}
// Same as walls() but with identical higher and lower polygons.
Contour3D inline straight_walls(const Polygon &plate,
double lo_z,
double hi_z,
ThrowOnCancel thr)
double hi_z)
{
return walls(plate, plate, lo_z, hi_z, .0 /*offset_diff*/, thr);
return walls(plate, plate, lo_z, hi_z);
}
// Function to cut tiny connector cavities for a given polygon. The input poly
@ -534,10 +377,8 @@ bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg,
top_poly = pdiff.front();
double z_min = -cfg.wing_height, z_max = 0;
double offset_difference = -wing_distance;
pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max,
offset_difference, thr));
pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max));
thr();
pad.merge(triangulate_expolygon_3d(inner_base, z_min, NORMALS_UP));
return true;
@ -555,17 +396,17 @@ Contour3D create_outer_pad_geometry(const ExPolygons & skeleton,
offset_contour_only(pad_part, -scaled(cfg.bottom_offset()));
if (bottom_poly.empty()) continue;
thr();
double z_min = -cfg.height, z_max = 0;
ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min,
cfg.bottom_offset(), thr));
ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min));
if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr))
z_max = -cfg.wing_height;
for (auto &h : bottom_poly.holes)
ret.merge(straight_walls(h, z_max, z_min, thr));
ret.merge(straight_walls(h, z_max, z_min));
ret.merge(triangulate_expolygon_3d(bottom_poly, z_min, NORMALS_DOWN));
ret.merge(triangulate_expolygon_3d(top_poly, NORMALS_UP));
}
@ -581,11 +422,12 @@ Contour3D create_inner_pad_geometry(const ExPolygons & skeleton,
double z_max = 0., z_min = -cfg.height;
for (const ExPolygon &pad_part : skeleton) {
ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr));
thr();
ret.merge(straight_walls(pad_part.contour, z_max, z_min));
for (auto &h : pad_part.holes)
ret.merge(straight_walls(h, z_max, z_min, thr));
ret.merge(straight_walls(h, z_max, z_min));
ret.merge(triangulate_expolygon_3d(pad_part, z_min, NORMALS_DOWN));
ret.merge(triangulate_expolygon_3d(pad_part, z_max, NORMALS_UP));
}

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

@ -227,6 +227,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con
m_material_config.apply_only(config, material_diff, true);
// Handle changes to object config defaults
m_default_object_config.apply_only(config, object_diff, true);
if (m_printer) m_printer->apply(m_printer_config);
struct ModelObjectStatus {
enum Status {
@ -482,7 +484,6 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con
}
if(m_objects.empty()) {
m_printer.reset();
m_printer_input = {};
m_print_statistics = {};
}
@ -657,6 +658,12 @@ std::string SLAPrint::validate() const
return "";
}
void SLAPrint::set_printer(SLAPrinter *arch)
{
invalidate_step(slapsRasterize);
m_printer = arch;
}
bool SLAPrint::invalidate_step(SLAPrintStep step)
{
bool invalidated = Inherited::invalidate_step(step);
@ -676,7 +683,7 @@ void SLAPrint::process()
// Assumption: at this point the print objects should be populated only with
// the model objects we have to process and the instances are also filtered
Steps printsteps{this};
Steps printsteps(this);
// We want to first process all objects...
std::vector<SLAPrintObjectStep> level1_obj_steps = {
@ -729,7 +736,7 @@ void SLAPrint::process()
throw_if_canceled();
po->set_done(step);
}
incr = printsteps.progressrange(step);
}
}
@ -754,7 +761,7 @@ void SLAPrint::process()
throw_if_canceled();
set_done(currentstep);
}
st += printsteps.progressrange(currentstep);
}
@ -855,36 +862,6 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<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,14 +458,15 @@ 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);
}
friend class SLAPrint::Steps;
public:
explicit PrintLayer(coord_t lvl) : m_level(lvl) {}
// for being sorted in their container (see m_printer_input)
@ -474,8 +488,11 @@ public:
// The aggregated and leveled print records from various objects.
// TODO: use this structure for the preview in the future.
const std::vector<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);
@ -491,13 +508,13 @@ private:
std::vector<bool> m_stepmask;
// Ready-made data for rasterization.
std::vector<PrintLayer> m_printer_input;
// The printer itself
std::unique_ptr<sla::RasterWriter> m_printer;
std::vector<PrintLayer> m_printer_input;
// The archive object which collects the raster images after slicing
SLAPrinter *m_printer = nullptr;
// Estimated print time, material consumed.
SLAPrintStatistics m_print_statistics;
SLAPrintStatistics m_print_statistics;
class StatusReporter
{
@ -512,15 +529,6 @@ private:
double status() const { return m_st; }
} m_report_status;
sla::RasterWriter &init_printer();
inline sla::Raster::Orientation get_printer_orientation() const
{
auto ro = m_printer_config.display_orientation.getInt();
return ro == sla::Raster::roPortrait ? sla::Raster::roPortrait :
sla::Raster::roLandscape;
}
friend SLAPrintObject;
};

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