Move SLA import to libslic3r with png reading using libpng
Also fix flipped object issue
This commit is contained in:
parent
2bcd36d155
commit
769ee15475
@ -8,8 +8,314 @@
|
|||||||
#include "libslic3r/Zipper.hpp"
|
#include "libslic3r/Zipper.hpp"
|
||||||
#include "libslic3r/SLAPrint.hpp"
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "libslic3r/SlicesToTriangleMesh.hpp"
|
||||||
|
#include "libslic3r/MarchingSquares.hpp"
|
||||||
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
|
#include "libslic3r/MTUtils.hpp"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
#include "libslic3r/SLA/RasterBase.hpp"
|
||||||
|
#include "libslic3r/miniz_extension.hpp"
|
||||||
|
#include "libslic3r/PNGRead.hpp"
|
||||||
|
|
||||||
|
#include <boost/property_tree/ini_parser.hpp>
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
|
namespace marchsq {
|
||||||
|
|
||||||
|
template<> struct _RasterTraits<Slic3r::png::ImageGreyscale> {
|
||||||
|
using Rst = Slic3r::png::ImageGreyscale;
|
||||||
|
|
||||||
|
// 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.get(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of rows and cols of the raster
|
||||||
|
static size_t rows(const Rst &rst) { return rst.rows; }
|
||||||
|
static size_t cols(const Rst &rst) { return rst.cols; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace marchsq
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct PNGBuffer { std::vector<uint8_t> buf; std::string fname; };
|
||||||
|
struct ArchiveData {
|
||||||
|
boost::property_tree::ptree profile, config;
|
||||||
|
std::vector<PNGBuffer> images;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const constexpr char *CONFIG_FNAME = "config.ini";
|
||||||
|
static const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
|
||||||
|
|
||||||
|
boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
|
||||||
|
MZ_Archive & zip)
|
||||||
|
{
|
||||||
|
std::string buf(size_t(entry.m_uncomp_size), '\0');
|
||||||
|
|
||||||
|
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
||||||
|
buf.data(), buf.size(), 0))
|
||||||
|
throw std::runtime_error(zip.get_errorstr());
|
||||||
|
|
||||||
|
boost::property_tree::ptree tree;
|
||||||
|
std::stringstream ss(buf);
|
||||||
|
boost::property_tree::read_ini(ss, tree);
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
PNGBuffer read_png(const mz_zip_archive_file_stat &entry,
|
||||||
|
MZ_Archive & zip,
|
||||||
|
const std::string & name)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> buf(entry.m_uncomp_size);
|
||||||
|
|
||||||
|
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
||||||
|
buf.data(), buf.size(), 0))
|
||||||
|
throw std::runtime_error(zip.get_errorstr());
|
||||||
|
|
||||||
|
return {std::move(buf), (name.empty() ? entry.m_filename : name)};
|
||||||
|
}
|
||||||
|
|
||||||
|
ArchiveData extract_sla_archive(const std::string &zipfname,
|
||||||
|
const std::string &exclude)
|
||||||
|
{
|
||||||
|
ArchiveData arch;
|
||||||
|
|
||||||
|
// Little RAII
|
||||||
|
struct Arch: public MZ_Archive {
|
||||||
|
Arch(const std::string &fname) {
|
||||||
|
if (!open_zip_reader(&arch, fname))
|
||||||
|
throw std::runtime_error(get_errorstr());
|
||||||
|
}
|
||||||
|
|
||||||
|
~Arch() { close_zip_reader(&arch); }
|
||||||
|
} zip (zipfname);
|
||||||
|
|
||||||
|
mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
|
||||||
|
|
||||||
|
for (mz_uint i = 0; i < num_entries; ++i)
|
||||||
|
{
|
||||||
|
mz_zip_archive_file_stat entry;
|
||||||
|
|
||||||
|
if (mz_zip_reader_file_stat(&zip.arch, i, &entry))
|
||||||
|
{
|
||||||
|
std::string name = entry.m_filename;
|
||||||
|
boost::algorithm::to_lower(name);
|
||||||
|
|
||||||
|
if (boost::algorithm::contains(name, exclude)) continue;
|
||||||
|
|
||||||
|
if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip);
|
||||||
|
if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip);
|
||||||
|
|
||||||
|
if (boost::filesystem::path(name).extension().string() == ".png") {
|
||||||
|
auto it = std::lower_bound(
|
||||||
|
arch.images.begin(), arch.images.end(), PNGBuffer{{}, name},
|
||||||
|
[](const PNGBuffer &r1, const PNGBuffer &r2) {
|
||||||
|
return std::less<std::string>()(r1.fname, r2.fname);
|
||||||
|
});
|
||||||
|
|
||||||
|
arch.images.insert(it, read_png(entry, zip, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arch;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
|
||||||
|
double px_w, double px_h)
|
||||||
|
{
|
||||||
|
ExPolygons polys; polys.reserve(rings.size());
|
||||||
|
|
||||||
|
for (const marchsq::Ring &ring : rings) {
|
||||||
|
Polygon poly; Points &pts = poly.points;
|
||||||
|
pts.reserve(ring.size());
|
||||||
|
|
||||||
|
for (const marchsq::Coord &crd : ring)
|
||||||
|
pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h));
|
||||||
|
|
||||||
|
polys.emplace_back(poly);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse the raster transformations
|
||||||
|
return union_ex(polys);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
|
||||||
|
{
|
||||||
|
for (auto &p : poly.contour.points) fn(p);
|
||||||
|
for (auto &h : poly.holes)
|
||||||
|
for (auto &p : h.points) fn(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void invert_raster_trafo(ExPolygons & expolys,
|
||||||
|
const sla::RasterBase::Trafo &trafo,
|
||||||
|
coord_t width,
|
||||||
|
coord_t height)
|
||||||
|
{
|
||||||
|
for (auto &expoly : expolys) {
|
||||||
|
if (trafo.mirror_y)
|
||||||
|
foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); });
|
||||||
|
|
||||||
|
if (trafo.mirror_x)
|
||||||
|
foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); });
|
||||||
|
|
||||||
|
expoly.translate(-trafo.center_x, -trafo.center_y);
|
||||||
|
|
||||||
|
if (trafo.flipXY)
|
||||||
|
foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); });
|
||||||
|
|
||||||
|
if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) {
|
||||||
|
expoly.contour.reverse();
|
||||||
|
for (auto &h : expoly.holes) h.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RasterParams {
|
||||||
|
sla::RasterBase::Trafo trafo; // Raster transformations
|
||||||
|
coord_t width, height; // scaled raster dimensions (not resolution)
|
||||||
|
double px_h, px_w; // pixel dimesions
|
||||||
|
marchsq::Coord win; // marching squares window size
|
||||||
|
};
|
||||||
|
|
||||||
|
RasterParams get_raster_params(const DynamicPrintConfig &cfg)
|
||||||
|
{
|
||||||
|
auto *opt_disp_cols = cfg.option<ConfigOptionInt>("display_pixels_x");
|
||||||
|
auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
|
||||||
|
auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
|
||||||
|
auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
|
||||||
|
auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
|
||||||
|
auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
|
||||||
|
auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("display_orientation");
|
||||||
|
|
||||||
|
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
|
||||||
|
!opt_mirror_x || !opt_mirror_y || !opt_orient)
|
||||||
|
throw std::runtime_error("Invalid SL1 file");
|
||||||
|
|
||||||
|
RasterParams rstp;
|
||||||
|
|
||||||
|
rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1);
|
||||||
|
rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1);
|
||||||
|
|
||||||
|
rstp.trafo = sla::RasterBase::Trafo{opt_orient->value == sladoLandscape ?
|
||||||
|
sla::RasterBase::roLandscape :
|
||||||
|
sla::RasterBase::roPortrait,
|
||||||
|
{opt_mirror_x->value, opt_mirror_y->value}};
|
||||||
|
|
||||||
|
rstp.height = scaled(opt_disp_h->value);
|
||||||
|
rstp.width = scaled(opt_disp_w->value);
|
||||||
|
|
||||||
|
return rstp;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SliceParams { double layerh = 0., initial_layerh = 0.; };
|
||||||
|
|
||||||
|
SliceParams get_slice_params(const DynamicPrintConfig &cfg)
|
||||||
|
{
|
||||||
|
auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
|
||||||
|
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
|
||||||
|
|
||||||
|
if (!opt_layerh || !opt_init_layerh)
|
||||||
|
throw std::runtime_error("Invalid SL1 file");
|
||||||
|
|
||||||
|
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ExPolygons> extract_slices_from_sla_archive(
|
||||||
|
ArchiveData & arch,
|
||||||
|
const RasterParams & rstp,
|
||||||
|
std::function<bool(int)> progr)
|
||||||
|
{
|
||||||
|
auto jobdir = arch.config.get<std::string>("jobDir");
|
||||||
|
for (auto &c : jobdir) c = std::tolower(c);
|
||||||
|
|
||||||
|
std::vector<ExPolygons> slices(arch.images.size());
|
||||||
|
|
||||||
|
struct Status
|
||||||
|
{
|
||||||
|
double incr, val, prev;
|
||||||
|
bool stop = false;
|
||||||
|
tbb::spin_mutex mutex;
|
||||||
|
} st {100. / slices.size(), 0., 0.};
|
||||||
|
|
||||||
|
tbb::parallel_for(size_t(0), arch.images.size(),
|
||||||
|
[&arch, &slices, &st, &rstp, progr](size_t i) {
|
||||||
|
// Status indication guarded with the spinlock
|
||||||
|
{
|
||||||
|
std::lock_guard<tbb::spin_mutex> lck(st.mutex);
|
||||||
|
if (st.stop) return;
|
||||||
|
|
||||||
|
st.val += st.incr;
|
||||||
|
double curr = std::round(st.val);
|
||||||
|
if (curr > st.prev) {
|
||||||
|
st.prev = curr;
|
||||||
|
st.stop = !progr(int(curr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
png::ImageGreyscale img;
|
||||||
|
png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()};
|
||||||
|
if (!png::decode_png(rb, img)) return;
|
||||||
|
|
||||||
|
auto rings = marchsq::execute(img, 128, rstp.win);
|
||||||
|
ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
|
||||||
|
|
||||||
|
// Invert the raster transformations indicated in
|
||||||
|
// the profile metadata
|
||||||
|
invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
|
||||||
|
|
||||||
|
slices[i] = std::move(expolys);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (st.stop) slices = {};
|
||||||
|
|
||||||
|
return slices;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
|
||||||
|
{
|
||||||
|
ArchiveData arch = extract_sla_archive(zipfname, "png");
|
||||||
|
out.load(arch.profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void import_sla_archive(
|
||||||
|
const std::string & zipfname,
|
||||||
|
Vec2i windowsize,
|
||||||
|
TriangleMesh & out,
|
||||||
|
DynamicPrintConfig & profile,
|
||||||
|
std::function<bool(int)> progr)
|
||||||
|
{
|
||||||
|
// Ensure minimum window size for marching squares
|
||||||
|
windowsize.x() = std::max(2, windowsize.x());
|
||||||
|
windowsize.y() = std::max(2, windowsize.y());
|
||||||
|
|
||||||
|
ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
|
||||||
|
profile.load(arch.profile);
|
||||||
|
|
||||||
|
RasterParams rstp = get_raster_params(profile);
|
||||||
|
rstp.win = {windowsize.y(), windowsize.x()};
|
||||||
|
|
||||||
|
SliceParams slicp = get_slice_params(profile);
|
||||||
|
|
||||||
|
std::vector<ExPolygons> slices =
|
||||||
|
extract_slices_from_sla_archive(arch, rstp, progr);
|
||||||
|
|
||||||
|
if (!slices.empty())
|
||||||
|
out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
|
||||||
|
}
|
||||||
|
|
||||||
using ConfMap = std::map<std::string, std::string>;
|
using ConfMap = std::map<std::string, std::string>;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -38,6 +38,24 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
|
||||||
|
|
||||||
|
void import_sla_archive(
|
||||||
|
const std::string & zipfname,
|
||||||
|
Vec2i windowsize,
|
||||||
|
TriangleMesh & out,
|
||||||
|
DynamicPrintConfig & profile,
|
||||||
|
std::function<bool(int)> progr = [](int) { return true; });
|
||||||
|
|
||||||
|
inline void import_sla_archive(
|
||||||
|
const std::string & zipfname,
|
||||||
|
Vec2i windowsize,
|
||||||
|
TriangleMesh & out,
|
||||||
|
std::function<bool(int)> progr = [](int) { return true; })
|
||||||
|
{
|
||||||
|
DynamicPrintConfig profile;
|
||||||
|
import_sla_archive(zipfname, windowsize, out, profile, progr);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Slic3r::sla
|
} // namespace Slic3r::sla
|
||||||
|
|
||||||
|
@ -7,11 +7,21 @@
|
|||||||
|
|
||||||
namespace Slic3r { namespace png {
|
namespace Slic3r { namespace png {
|
||||||
|
|
||||||
struct png_deleter { void operator()(png_struct *p) {
|
struct PNGDescr {
|
||||||
png_destroy_read_struct( &p, nullptr, nullptr); }
|
png_struct *png = nullptr; png_info *info = nullptr;
|
||||||
};
|
|
||||||
|
|
||||||
using png_ptr_t = std::unique_ptr<png_struct_def, png_deleter>;
|
PNGDescr() = default;
|
||||||
|
PNGDescr(const PNGDescr&) = delete;
|
||||||
|
PNGDescr(PNGDescr&&) = delete;
|
||||||
|
PNGDescr& operator=(const PNGDescr&) = delete;
|
||||||
|
PNGDescr& operator=(PNGDescr&&) = delete;
|
||||||
|
|
||||||
|
~PNGDescr()
|
||||||
|
{
|
||||||
|
if (png && info) png_destroy_info_struct(png, &info);
|
||||||
|
if (png) png_destroy_read_struct( &png, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
bool is_png(const ReadBuf &rb)
|
bool is_png(const ReadBuf &rb)
|
||||||
{
|
{
|
||||||
@ -25,22 +35,23 @@ bool decode_png(const ReadBuf &rb, ImageGreyscale &img)
|
|||||||
{
|
{
|
||||||
if (!is_png(rb)) return false;
|
if (!is_png(rb)) return false;
|
||||||
|
|
||||||
png_ptr_t png{png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr)};
|
PNGDescr dsc;
|
||||||
|
dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||||
|
|
||||||
if(!png) return false;
|
if(!dsc.png) return false;
|
||||||
|
|
||||||
png_infop info = png_create_info_struct(png.get());
|
dsc.info = png_create_info_struct(dsc.png);
|
||||||
if(!info) return {};
|
if(!dsc.info) return {};
|
||||||
|
|
||||||
FILE *io = ::fmemopen(const_cast<void *>(rb.buf), rb.sz, "rb");
|
FILE *io = ::fmemopen(const_cast<void *>(rb.buf), rb.sz, "rb");
|
||||||
png_init_io(png.get(), io);
|
png_init_io(dsc.png, io);
|
||||||
|
|
||||||
png_read_info(png.get(), info);
|
png_read_info(dsc.png, dsc.info);
|
||||||
|
|
||||||
img.cols = png_get_image_width(png.get(), info);
|
img.cols = png_get_image_width(dsc.png, dsc.info);
|
||||||
img.rows = png_get_image_height(png.get(), info);
|
img.rows = png_get_image_height(dsc.png, dsc.info);
|
||||||
size_t color_type = png_get_color_type(png.get(), info);
|
size_t color_type = png_get_color_type(dsc.png, dsc.info);
|
||||||
size_t bit_depth = png_get_bit_depth(png.get(), info);
|
size_t bit_depth = png_get_bit_depth(dsc.png, dsc.info);
|
||||||
|
|
||||||
if (color_type != PNG_COLOR_TYPE_GRAY || bit_depth != 8)
|
if (color_type != PNG_COLOR_TYPE_GRAY || bit_depth != 8)
|
||||||
return false;
|
return false;
|
||||||
@ -49,7 +60,7 @@ bool decode_png(const ReadBuf &rb, ImageGreyscale &img)
|
|||||||
|
|
||||||
auto readbuf = static_cast<png_bytep>(img.buf.data());
|
auto readbuf = static_cast<png_bytep>(img.buf.data());
|
||||||
for (size_t r = 0; r < img.rows; ++r)
|
for (size_t r = 0; r < img.rows; ++r)
|
||||||
png_read_row(png.get(), readbuf + r * img.cols, nullptr);
|
png_read_row(dsc.png, readbuf + r * img.cols, nullptr);
|
||||||
|
|
||||||
fclose(io);
|
fclose(io);
|
||||||
|
|
||||||
|
@ -19,12 +19,14 @@ struct RGB { uint8_t r, g, b; };
|
|||||||
using ImageRGB = Image<RGB>;
|
using ImageRGB = Image<RGB>;
|
||||||
using ImageGreyscale = Image<uint8_t>;
|
using ImageGreyscale = Image<uint8_t>;
|
||||||
|
|
||||||
|
bool is_png(const ReadBuf &pngbuf);
|
||||||
|
|
||||||
|
// Only decodes true 8 bit grayscale png images. Returns false for other formats
|
||||||
|
// TODO: implement transformation of rgb images into grayscale...
|
||||||
|
bool decode_png(const ReadBuf &pngbuf, ImageGreyscale &img);
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// bool decode_png(Buffer &&pngbuf, ImageRGB &img);
|
// bool decode_png(Buffer &&pngbuf, ImageRGB &img);
|
||||||
|
|
||||||
bool is_png(const ReadBuf &pngbuf);
|
|
||||||
|
|
||||||
bool decode_png(const ReadBuf &pngbuf, ImageGreyscale &img);
|
|
||||||
|
|
||||||
}}
|
}}
|
||||||
#endif // PNGREAD_HPP
|
#endif // PNGREAD_HPP
|
||||||
|
@ -187,8 +187,6 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
Utils/HexFile.cpp
|
Utils/HexFile.cpp
|
||||||
Utils/HexFile.hpp
|
Utils/HexFile.hpp
|
||||||
Utils/Thread.hpp
|
Utils/Thread.hpp
|
||||||
Utils/SLAImport.hpp
|
|
||||||
Utils/SLAImport.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
|
@ -294,7 +294,7 @@ public:
|
|||||||
void load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type);
|
void load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type);
|
||||||
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
|
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
|
||||||
void load_shape_object(const std::string &type_name);
|
void load_shape_object(const std::string &type_name);
|
||||||
void load_mesh_object(const TriangleMesh &mesh, const wxString &name);
|
void load_mesh_object(const TriangleMesh &mesh, const wxString &name);
|
||||||
void del_object(const int obj_idx);
|
void del_object(const int obj_idx);
|
||||||
void del_subobject_item(wxDataViewItem& item);
|
void del_subobject_item(wxDataViewItem& item);
|
||||||
void del_settings_from_config(const wxDataViewItem& parent_item);
|
void del_settings_from_config(const wxDataViewItem& parent_item);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
#include "SLAImportJob.hpp"
|
#include "SLAImportJob.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/Format/SL1.hpp"
|
||||||
|
|
||||||
#include "slic3r/GUI/GUI.hpp"
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
#include "slic3r/GUI/Plater.hpp"
|
#include "slic3r/GUI/Plater.hpp"
|
||||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||||
#include "slic3r/Utils/SLAImport.hpp"
|
|
||||||
|
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
|
@ -1,317 +0,0 @@
|
|||||||
#include "SLAImport.hpp"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "libslic3r/SlicesToTriangleMesh.hpp"
|
|
||||||
#include "libslic3r/MarchingSquares.hpp"
|
|
||||||
#include "libslic3r/ClipperUtils.hpp"
|
|
||||||
#include "libslic3r/MTUtils.hpp"
|
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
|
||||||
#include "libslic3r/SLA/RasterBase.hpp"
|
|
||||||
#include "libslic3r/miniz_extension.hpp"
|
|
||||||
#include "libslic3r/PNGRead.hpp"
|
|
||||||
|
|
||||||
#include <boost/property_tree/ini_parser.hpp>
|
|
||||||
#include <boost/filesystem/path.hpp>
|
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
|
|
||||||
#include <wx/mstream.h>
|
|
||||||
|
|
||||||
namespace marchsq {
|
|
||||||
|
|
||||||
template<> struct _RasterTraits<Slic3r::png::ImageGreyscale> {
|
|
||||||
using Rst = Slic3r::png::ImageGreyscale;
|
|
||||||
|
|
||||||
// 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.get(row, col);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of rows and cols of the raster
|
|
||||||
static size_t rows(const Rst &rst) { return rst.rows; }
|
|
||||||
static size_t cols(const Rst &rst) { return rst.cols; }
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace marchsq
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
struct PNGBuffer { std::vector<uint8_t> buf; std::string fname; };
|
|
||||||
struct ArchiveData {
|
|
||||||
boost::property_tree::ptree profile, config;
|
|
||||||
std::vector<PNGBuffer> images;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const constexpr char *CONFIG_FNAME = "config.ini";
|
|
||||||
static const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
|
|
||||||
|
|
||||||
boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
|
|
||||||
MZ_Archive & zip)
|
|
||||||
{
|
|
||||||
std::string buf(size_t(entry.m_uncomp_size), '\0');
|
|
||||||
|
|
||||||
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
|
||||||
buf.data(), buf.size(), 0))
|
|
||||||
throw std::runtime_error(zip.get_errorstr());
|
|
||||||
|
|
||||||
boost::property_tree::ptree tree;
|
|
||||||
std::stringstream ss(buf);
|
|
||||||
boost::property_tree::read_ini(ss, tree);
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
PNGBuffer read_png(const mz_zip_archive_file_stat &entry,
|
|
||||||
MZ_Archive & zip,
|
|
||||||
const std::string & name)
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> buf(entry.m_uncomp_size);
|
|
||||||
|
|
||||||
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
|
||||||
buf.data(), buf.size(), 0))
|
|
||||||
throw std::runtime_error(zip.get_errorstr());
|
|
||||||
|
|
||||||
return {std::move(buf), (name.empty() ? entry.m_filename : name)};
|
|
||||||
}
|
|
||||||
|
|
||||||
ArchiveData extract_sla_archive(const std::string &zipfname,
|
|
||||||
const std::string &exclude)
|
|
||||||
{
|
|
||||||
ArchiveData arch;
|
|
||||||
|
|
||||||
// Little RAII
|
|
||||||
struct Arch: public MZ_Archive {
|
|
||||||
Arch(const std::string &fname) {
|
|
||||||
if (!open_zip_reader(&arch, fname))
|
|
||||||
throw std::runtime_error(get_errorstr());
|
|
||||||
}
|
|
||||||
|
|
||||||
~Arch() { close_zip_reader(&arch); }
|
|
||||||
} zip (zipfname);
|
|
||||||
|
|
||||||
mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
|
|
||||||
|
|
||||||
for (mz_uint i = 0; i < num_entries; ++i)
|
|
||||||
{
|
|
||||||
mz_zip_archive_file_stat entry;
|
|
||||||
|
|
||||||
if (mz_zip_reader_file_stat(&zip.arch, i, &entry))
|
|
||||||
{
|
|
||||||
std::string name = entry.m_filename;
|
|
||||||
boost::algorithm::to_lower(name);
|
|
||||||
|
|
||||||
if (boost::algorithm::contains(name, exclude)) continue;
|
|
||||||
|
|
||||||
if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip);
|
|
||||||
if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip);
|
|
||||||
|
|
||||||
if (boost::filesystem::path(name).extension().string() == ".png") {
|
|
||||||
auto it = std::lower_bound(
|
|
||||||
arch.images.begin(), arch.images.end(), PNGBuffer{{}, name},
|
|
||||||
[](const PNGBuffer &r1, const PNGBuffer &r2) {
|
|
||||||
return std::less<std::string>()(r1.fname, r2.fname);
|
|
||||||
});
|
|
||||||
|
|
||||||
arch.images.insert(it, read_png(entry, zip, name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return arch;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
|
|
||||||
double px_w, double px_h)
|
|
||||||
{
|
|
||||||
ExPolygons polys; polys.reserve(rings.size());
|
|
||||||
|
|
||||||
for (const marchsq::Ring &ring : rings) {
|
|
||||||
Polygon poly; Points &pts = poly.points;
|
|
||||||
pts.reserve(ring.size());
|
|
||||||
|
|
||||||
for (const marchsq::Coord &crd : ring)
|
|
||||||
pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h));
|
|
||||||
|
|
||||||
polys.emplace_back(poly);
|
|
||||||
}
|
|
||||||
|
|
||||||
// reverse the raster transformations
|
|
||||||
return union_ex(polys);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
|
|
||||||
{
|
|
||||||
for (auto &p : poly.contour.points) fn(p);
|
|
||||||
for (auto &h : poly.holes)
|
|
||||||
for (auto &p : h.points) fn(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
void invert_raster_trafo(ExPolygons & expolys,
|
|
||||||
const sla::RasterBase::Trafo &trafo,
|
|
||||||
coord_t width,
|
|
||||||
coord_t height)
|
|
||||||
{
|
|
||||||
for (auto &expoly : expolys) {
|
|
||||||
if (trafo.mirror_y)
|
|
||||||
foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); });
|
|
||||||
|
|
||||||
if (trafo.mirror_x)
|
|
||||||
foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); });
|
|
||||||
|
|
||||||
expoly.translate(-trafo.center_x, -trafo.center_y);
|
|
||||||
|
|
||||||
if (trafo.flipXY)
|
|
||||||
foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); });
|
|
||||||
|
|
||||||
if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) {
|
|
||||||
expoly.contour.reverse();
|
|
||||||
for (auto &h : expoly.holes) h.reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RasterParams {
|
|
||||||
sla::RasterBase::Trafo trafo; // Raster transformations
|
|
||||||
coord_t width, height; // scaled raster dimensions (not resolution)
|
|
||||||
double px_h, px_w; // pixel dimesions
|
|
||||||
marchsq::Coord win; // marching squares window size
|
|
||||||
};
|
|
||||||
|
|
||||||
RasterParams get_raster_params(const DynamicPrintConfig &cfg)
|
|
||||||
{
|
|
||||||
auto *opt_disp_cols = cfg.option<ConfigOptionInt>("display_pixels_x");
|
|
||||||
auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
|
|
||||||
auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
|
|
||||||
auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
|
|
||||||
auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
|
|
||||||
auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
|
|
||||||
auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("display_orientation");
|
|
||||||
|
|
||||||
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
|
|
||||||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
|
|
||||||
throw std::runtime_error("Invalid SL1 file");
|
|
||||||
|
|
||||||
RasterParams rstp;
|
|
||||||
|
|
||||||
rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1);
|
|
||||||
rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1);
|
|
||||||
|
|
||||||
sla::RasterBase::Trafo trafo{opt_orient->value == sladoLandscape ?
|
|
||||||
sla::RasterBase::roLandscape :
|
|
||||||
sla::RasterBase::roPortrait,
|
|
||||||
{opt_mirror_x->value, opt_mirror_y->value}};
|
|
||||||
|
|
||||||
rstp.height = scaled(opt_disp_h->value);
|
|
||||||
rstp.width = scaled(opt_disp_w->value);
|
|
||||||
|
|
||||||
return rstp;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SliceParams { double layerh = 0., initial_layerh = 0.; };
|
|
||||||
|
|
||||||
SliceParams get_slice_params(const DynamicPrintConfig &cfg)
|
|
||||||
{
|
|
||||||
auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
|
|
||||||
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
|
|
||||||
|
|
||||||
if (!opt_layerh || !opt_init_layerh)
|
|
||||||
throw std::runtime_error("Invalid SL1 file");
|
|
||||||
|
|
||||||
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ExPolygons> extract_slices_from_sla_archive(
|
|
||||||
ArchiveData & arch,
|
|
||||||
const RasterParams & rstp,
|
|
||||||
std::function<bool(int)> progr)
|
|
||||||
{
|
|
||||||
auto jobdir = arch.config.get<std::string>("jobDir");
|
|
||||||
for (auto &c : jobdir) c = std::tolower(c);
|
|
||||||
|
|
||||||
std::vector<ExPolygons> slices(arch.images.size());
|
|
||||||
|
|
||||||
struct Status
|
|
||||||
{
|
|
||||||
double incr, val, prev;
|
|
||||||
bool stop = false;
|
|
||||||
tbb::spin_mutex mutex;
|
|
||||||
} st {100. / slices.size(), 0., 0.};
|
|
||||||
|
|
||||||
tbb::parallel_for(size_t(0), arch.images.size(),
|
|
||||||
[&arch, &slices, &st, &rstp, progr](size_t i) {
|
|
||||||
// Status indication guarded with the spinlock
|
|
||||||
{
|
|
||||||
std::lock_guard<tbb::spin_mutex> lck(st.mutex);
|
|
||||||
if (st.stop) return;
|
|
||||||
|
|
||||||
st.val += st.incr;
|
|
||||||
double curr = std::round(st.val);
|
|
||||||
if (curr > st.prev) {
|
|
||||||
st.prev = curr;
|
|
||||||
st.stop = !progr(int(curr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PNGBuffer &png = arch.images[i];
|
|
||||||
// wxMemoryInputStream stream{png.buf.data(), png.buf.size()};
|
|
||||||
// wxImage img{stream};
|
|
||||||
|
|
||||||
png::ImageGreyscale img;
|
|
||||||
png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()};
|
|
||||||
png::decode_png(rb, img);
|
|
||||||
|
|
||||||
auto rings = marchsq::execute(img, 128, rstp.win);
|
|
||||||
ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
|
|
||||||
|
|
||||||
// Invert the raster transformations indicated in
|
|
||||||
// the profile metadata
|
|
||||||
invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
|
|
||||||
|
|
||||||
slices[i] = std::move(expolys);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (st.stop) slices = {};
|
|
||||||
|
|
||||||
return slices;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
|
|
||||||
{
|
|
||||||
ArchiveData arch = extract_sla_archive(zipfname, "png");
|
|
||||||
out.load(arch.profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
void import_sla_archive(
|
|
||||||
const std::string & zipfname,
|
|
||||||
Vec2i windowsize,
|
|
||||||
TriangleMesh & out,
|
|
||||||
DynamicPrintConfig & profile,
|
|
||||||
std::function<bool(int)> progr)
|
|
||||||
{
|
|
||||||
// Ensure minimum window size for marching squares
|
|
||||||
windowsize.x() = std::max(2, windowsize.x());
|
|
||||||
windowsize.y() = std::max(2, windowsize.y());
|
|
||||||
|
|
||||||
ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
|
|
||||||
profile.load(arch.profile);
|
|
||||||
|
|
||||||
RasterParams rstp = get_raster_params(profile);
|
|
||||||
rstp.win = {windowsize.y(), windowsize.x()};
|
|
||||||
|
|
||||||
SliceParams slicp = get_slice_params(profile);
|
|
||||||
|
|
||||||
std::vector<ExPolygons> slices =
|
|
||||||
extract_slices_from_sla_archive(arch, rstp, progr);
|
|
||||||
|
|
||||||
if (!slices.empty())
|
|
||||||
out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Slic3r
|
|
@ -1,35 +0,0 @@
|
|||||||
#ifndef SLAIMPORT_HPP
|
|
||||||
#define SLAIMPORT_HPP
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include <libslic3r/Point.hpp>
|
|
||||||
#include <libslic3r/PrintConfig.hpp>
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
|
|
||||||
class TriangleMesh;
|
|
||||||
class DynamicPrintConfig;
|
|
||||||
|
|
||||||
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
|
|
||||||
|
|
||||||
void import_sla_archive(
|
|
||||||
const std::string & zipfname,
|
|
||||||
Vec2i windowsize,
|
|
||||||
TriangleMesh & out,
|
|
||||||
DynamicPrintConfig & profile,
|
|
||||||
std::function<bool(int)> progr = [](int) { return true; });
|
|
||||||
|
|
||||||
inline void import_sla_archive(
|
|
||||||
const std::string & zipfname,
|
|
||||||
Vec2i windowsize,
|
|
||||||
TriangleMesh & out,
|
|
||||||
std::function<bool(int)> progr = [](int) { return true; })
|
|
||||||
{
|
|
||||||
DynamicPrintConfig profile;
|
|
||||||
import_sla_archive(zipfname, windowsize, out, profile, progr);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // SLAIMPORT_HPP
|
|
Loading…
Reference in New Issue
Block a user