Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbubnikv <bubnikv@gmail.com>2020-04-28 20:31:18 +0300
committerbubnikv <bubnikv@gmail.com>2020-04-28 20:31:18 +0300
commit4de4d765ee582879ba0c310b4fa89c260a5d14ad (patch)
treef0d8612410a7bada8fcfb0537872296141e7c369 /src
parentef89c73fd557ea2f7b67f62f00360b76de62f370 (diff)
parentdb49a4516e73bd0b4f18dcbbf573e2ff923d69ff (diff)
Merge branch 'master' of https://github.com/Prusa3d/PrusaSlicer
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/PrusaSlicer.cpp92
-rw-r--r--src/libnest2d/include/libnest2d/geometry_traits.hpp3
-rw-r--r--src/libnest2d/include/libnest2d/libnest2d.hpp1
-rw-r--r--src/libnest2d/tools/svgtools.hpp25
-rw-r--r--src/libslic3r/Arrange.cpp318
-rw-r--r--src/libslic3r/Arrange.hpp171
-rw-r--r--src/libslic3r/BoundingBox.hpp5
-rw-r--r--src/libslic3r/CMakeLists.txt21
-rw-r--r--src/libslic3r/Format/SL1.cpp171
-rw-r--r--src/libslic3r/Format/SL1.hpp44
-rw-r--r--src/libslic3r/MTUtils.hpp233
-rw-r--r--src/libslic3r/MarchingSquares.hpp448
-rw-r--r--src/libslic3r/Model.cpp159
-rw-r--r--src/libslic3r/Model.hpp58
-rw-r--r--src/libslic3r/ModelArrange.cpp83
-rw-r--r--src/libslic3r/ModelArrange.hpp68
-rw-r--r--src/libslic3r/Point.hpp67
-rw-r--r--src/libslic3r/Polygon.cpp9
-rw-r--r--src/libslic3r/Polygon.hpp4
-rw-r--r--src/libslic3r/Print.cpp11
-rw-r--r--src/libslic3r/Print.hpp5
-rw-r--r--src/libslic3r/PrintConfig.cpp83
-rw-r--r--src/libslic3r/PrintConfig.hpp9
-rw-r--r--src/libslic3r/PrintObject.cpp164
-rw-r--r--src/libslic3r/SLA/AGGRaster.hpp222
-rw-r--r--src/libslic3r/SLA/Common.cpp8
-rw-r--r--src/libslic3r/SLA/EigenMesh3D.hpp2
-rw-r--r--src/libslic3r/SLA/Pad.cpp200
-rw-r--r--src/libslic3r/SLA/Raster.cpp320
-rw-r--r--src/libslic3r/SLA/Raster.hpp157
-rw-r--r--src/libslic3r/SLA/RasterBase.cpp89
-rw-r--r--src/libslic3r/SLA/RasterBase.hpp124
-rw-r--r--src/libslic3r/SLA/RasterToPolygons.cpp91
-rw-r--r--src/libslic3r/SLA/RasterToPolygons.hpp15
-rw-r--r--src/libslic3r/SLA/RasterWriter.cpp151
-rw-r--r--src/libslic3r/SLA/RasterWriter.hpp130
-rw-r--r--src/libslic3r/SLAPrint.cpp45
-rw-r--r--src/libslic3r/SLAPrint.hpp70
-rw-r--r--src/libslic3r/SLAPrintSteps.cpp47
-rw-r--r--src/libslic3r/SLAPrintSteps.hpp2
-rw-r--r--src/libslic3r/SlicesToTriangleMesh.cpp128
-rw-r--r--src/libslic3r/SlicesToTriangleMesh.hpp24
-rw-r--r--src/libslic3r/SupportMaterial.cpp27
-rw-r--r--src/libslic3r/TriangulateWall.cpp133
-rw-r--r--src/libslic3r/TriangulateWall.hpp17
-rw-r--r--src/libslic3r/Zipper.cpp82
-rw-r--r--src/libslic3r/Zipper.hpp4
-rw-r--r--src/libslic3r/libslic3r.h32
-rw-r--r--src/libslic3r/miniz_extension.cpp88
-rw-r--r--src/libslic3r/miniz_extension.hpp21
-rw-r--r--src/slic3r/CMakeLists.txt24
-rw-r--r--src/slic3r/GUI/3DBed.cpp17
-rw-r--r--src/slic3r/GUI/3DBed.hpp12
-rw-r--r--src/slic3r/GUI/AppConfig.hpp2
-rw-r--r--src/slic3r/GUI/BackgroundSlicingProcess.cpp7
-rw-r--r--src/slic3r/GUI/BackgroundSlicingProcess.hpp5
-rw-r--r--src/slic3r/GUI/ConfigWizard.cpp2
-rw-r--r--src/slic3r/GUI/Field.cpp12
-rw-r--r--src/slic3r/GUI/GLCanvas3D.cpp29
-rw-r--r--src/slic3r/GUI/GLCanvas3D.hpp2
-rw-r--r--src/slic3r/GUI/GUI_App.cpp37
-rw-r--r--src/slic3r/GUI/GUI_ObjectList.cpp31
-rw-r--r--src/slic3r/GUI/GUI_ObjectList.hpp2
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoBase.hpp6
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp673
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp97
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp48
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp4
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp383
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp23
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp359
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp35
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmos.hpp1
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp473
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp309
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmosManager.cpp237
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmosManager.hpp70
-rw-r--r--src/slic3r/GUI/Job.hpp155
-rw-r--r--src/slic3r/GUI/Jobs/ArrangeJob.cpp223
-rw-r--r--src/slic3r/GUI/Jobs/ArrangeJob.hpp77
-rw-r--r--src/slic3r/GUI/Jobs/Job.cpp121
-rw-r--r--src/slic3r/GUI/Jobs/Job.hpp110
-rw-r--r--src/slic3r/GUI/Jobs/ProgressIndicator.hpp (renamed from src/slic3r/GUI/ProgressIndicator.hpp)0
-rw-r--r--src/slic3r/GUI/Jobs/RotoptimizeJob.cpp68
-rw-r--r--src/slic3r/GUI/Jobs/RotoptimizeJob.hpp24
-rw-r--r--src/slic3r/GUI/Jobs/SLAImportJob.cpp226
-rw-r--r--src/slic3r/GUI/Jobs/SLAImportJob.hpp31
-rw-r--r--src/slic3r/GUI/MainFrame.cpp5
-rw-r--r--src/slic3r/GUI/MeshUtils.cpp40
-rw-r--r--src/slic3r/GUI/MeshUtils.hpp16
-rw-r--r--src/slic3r/GUI/Plater.cpp518
-rw-r--r--src/slic3r/GUI/Plater.hpp13
-rw-r--r--src/slic3r/GUI/ProgressStatusBar.hpp2
-rw-r--r--src/slic3r/GUI/wxExtensions.cpp3
-rw-r--r--src/slic3r/Utils/Http.cpp67
-rw-r--r--src/slic3r/Utils/Http.hpp4
-rw-r--r--src/slic3r/Utils/SLAImport.cpp314
-rw-r--r--src/slic3r/Utils/SLAImport.hpp36
99 files changed, 6071 insertions, 3367 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b0eab9bcc..cdc8e0a13 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -59,6 +59,10 @@ if (SLIC3R_GUI)
include(${wxWidgets_USE_FILE})
+ list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX png)
+ list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX expat)
+ list(APPEND wxWidgets_LIBRARIES ${PNG_LIBRARIES} ${EXPAT_LIBRARIES})
+
# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc)
message(STATUS "wx libs: ${wxWidgets_LIBRARIES}")
diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp
index 9270d3887..9efecb50c 100644
--- a/src/PrusaSlicer.cpp
+++ b/src/PrusaSlicer.cpp
@@ -34,6 +34,7 @@
#include "libslic3r/Config.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/Model.hpp"
+#include "libslic3r/ModelArrange.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/TriangleMesh.hpp"
@@ -41,6 +42,7 @@
#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/OBJ.hpp"
+#include "libslic3r/Format/SL1.hpp"
#include "libslic3r/Utils.hpp"
#include "PrusaSlicer.hpp"
@@ -53,14 +55,16 @@
using namespace Slic3r;
-PrinterTechnology get_printer_technology(const DynamicConfig &config)
-{
- const ConfigOptionEnum<PrinterTechnology> *opt = config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology");
- return (opt == nullptr) ? ptUnknown : opt->value;
-}
-
int CLI::run(int argc, char **argv)
{
+
+#ifdef __WXGTK__
+ // On Linux, wxGTK has no support for Wayland, and the app crashes on
+ // startup if gtk3 is used. This env var has to be set explicitly to
+ // instruct the window manager to fall back to X server mode.
+ ::setenv("GDK_BACKEND", "x11", /* replace */ true);
+#endif
+
// Switch boost::filesystem to utf8.
try {
boost::nowide::nowide_filesystem();
@@ -86,13 +90,15 @@ int CLI::run(int argc, char **argv)
m_extra_config.apply(m_config, true);
m_extra_config.normalize();
+
+ PrinterTechnology printer_technology = Slic3r::printer_technology(m_config);
bool start_gui = m_actions.empty() &&
// cutting transformations are setting an "export" action.
std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() &&
std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() &&
std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end();
- PrinterTechnology printer_technology = get_printer_technology(m_extra_config);
+
const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
// load config files supplied via --load
@@ -113,7 +119,7 @@ int CLI::run(int argc, char **argv)
return 1;
}
config.normalize();
- PrinterTechnology other_printer_technology = get_printer_technology(config);
+ PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
if (printer_technology == ptUnknown) {
printer_technology = other_printer_technology;
} else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
@@ -134,7 +140,7 @@ int CLI::run(int argc, char **argv)
// When loading an AMF or 3MF, config is imported as well, including the printer technology.
DynamicPrintConfig config;
model = Model::read_from_file(file, &config, true);
- PrinterTechnology other_printer_technology = get_printer_technology(config);
+ PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
if (printer_technology == ptUnknown) {
printer_technology = other_printer_technology;
} else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
@@ -161,9 +167,6 @@ int CLI::run(int argc, char **argv)
// Normalizing after importing the 3MFs / AMFs
m_print_config.normalize();
- if (printer_technology == ptUnknown)
- printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA;
-
// Initialize full print configs for both the FFF and SLA technologies.
FullPrintConfig fff_print_config;
SLAFullPrintConfig sla_print_config;
@@ -174,6 +177,7 @@ int CLI::run(int argc, char **argv)
m_print_config.apply(fff_print_config, true);
} else if (printer_technology == ptSLA) {
// The default value has to be different from the one in fff mode.
+ sla_print_config.printer_technology.value = ptSLA;
sla_print_config.output_filename_format.value = "[input_filename_base].sl1";
// The default bed shape should reflect the default display parameters
@@ -186,8 +190,18 @@ int CLI::run(int argc, char **argv)
m_print_config.apply(sla_print_config, true);
}
+ std::string validity = m_print_config.validate();
+ if (!validity.empty()) {
+ boost::nowide::cerr << "error: " << validity << std::endl;
+ return 1;
+ }
+
// Loop through transform options.
bool user_center_specified = false;
+ Points bed = get_bed_shape(m_print_config);
+ ArrangeParams arrange_cfg;
+ arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config));
+
for (auto const &opt_key : m_transforms) {
if (opt_key == "merge") {
Model m;
@@ -197,29 +211,33 @@ int CLI::run(int argc, char **argv)
// Rearrange instances unless --dont-arrange is supplied
if (! m_config.opt_bool("dont_arrange")) {
m.add_default_instances();
- const BoundingBoxf &bb = fff_print_config.bed_shape.values;
- m.arrange_objects(
- fff_print_config.min_object_distance(),
- // If we are going to use the merged model for printing, honor
- // the configured print bed for arranging, otherwise do it freely.
- this->has_print_action() ? &bb : nullptr
- );
+ if (this->has_print_action())
+ arrange_objects(m, bed, arrange_cfg);
+ else
+ arrange_objects(m, InfiniteBed{}, arrange_cfg);
}
m_models.clear();
m_models.emplace_back(std::move(m));
} else if (opt_key == "duplicate") {
- const BoundingBoxf &bb = fff_print_config.bed_shape.values;
for (auto &model : m_models) {
const bool all_objects_have_instances = std::none_of(
model.objects.begin(), model.objects.end(),
[](ModelObject* o){ return o->instances.empty(); }
);
- if (all_objects_have_instances) {
- // if all input objects have defined position(s) apply duplication to the whole model
- model.duplicate(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb);
- } else {
- model.add_default_instances();
- model.duplicate_objects(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb);
+
+ int dups = m_config.opt_int("duplicate");
+ if (!all_objects_have_instances) model.add_default_instances();
+
+ try {
+ if (dups > 1) {
+ // if all input objects have defined position(s) apply duplication to the whole model
+ duplicate(model, size_t(dups), bed, arrange_cfg);
+ } else {
+ arrange_objects(model, bed, arrange_cfg);
+ }
+ } catch (std::exception &ex) {
+ boost::nowide::cerr << "error: " << ex.what() << std::endl;
+ return 1;
}
}
} else if (opt_key == "duplicate_grid") {
@@ -413,7 +431,8 @@ int CLI::run(int argc, char **argv)
std::string outfile = m_config.opt_string("output");
Print fff_print;
SLAPrint sla_print;
-
+ SL1Archive sla_archive(sla_print.printer_config());
+ sla_print.set_printer(&sla_archive);
sla_print.set_status_callback(
[](const PrintBase::SlicingStatus& s)
{
@@ -423,11 +442,11 @@ int CLI::run(int argc, char **argv)
PrintBase *print = (printer_technology == ptFFF) ? static_cast<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&sla_print);
if (! m_config.opt_bool("dont_arrange")) {
- //FIXME make the min_object_distance configurable.
- model.arrange_objects(fff_print.config().min_object_distance());
- model.center_instances_around_point((! user_center_specified && m_print_config.has("bed_shape")) ?
- BoundingBoxf(m_print_config.opt<ConfigOptionPoints>("bed_shape")->values).center() :
- m_config.option<ConfigOptionPoint>("center")->value);
+ if (user_center_specified) {
+ Vec2d c = m_config.option<ConfigOptionPoint>("center")->value;
+ arrange_objects(model, InfiniteBed{scaled(c)}, arrange_cfg);
+ } else
+ arrange_objects(model, bed, arrange_cfg);
}
if (printer_technology == ptFFF) {
for (auto* mo : model.objects)
@@ -453,7 +472,7 @@ int CLI::run(int argc, char **argv)
outfile = sla_print.output_filepath(outfile);
// We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata
outfile_final = sla_print.print_statistics().finalize_output_path(outfile);
- sla_print.export_raster(outfile_final);
+ sla_archive.export_print(outfile_final, sla_print);
}
if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final)) {
boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl;
@@ -609,6 +628,8 @@ bool CLI::setup(int argc, char **argv)
if (opt_loglevel != 0)
set_logging_level(opt_loglevel->value);
}
+
+ std::string validity = m_config.validate();
// Initialize with defaults.
for (const t_optiondef_map *options : { &cli_actions_config_def.options, &cli_transform_config_def.options, &cli_misc_config_def.options })
@@ -616,6 +637,11 @@ bool CLI::setup(int argc, char **argv)
m_config.option(optdef.first, true);
set_data_dir(m_config.opt_string("datadir"));
+
+ if (!validity.empty()) {
+ boost::nowide::cerr << "error: " << validity << std::endl;
+ return false;
+ }
return true;
}
diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp
index 72e239a70..c447cfcd9 100644
--- a/src/libnest2d/include/libnest2d/geometry_traits.hpp
+++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp
@@ -982,6 +982,9 @@ template<class S> inline double area(const S& poly, const PolygonTag& )
});
}
+template<class RawShapes>
+inline double area(const RawShapes& shapes, const MultiPolygonTag&);
+
template<class S> // Dispatching function
inline double area(const S& sh)
{
diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp
index b6d7fcdcf..5eef28c40 100644
--- a/src/libnest2d/include/libnest2d/libnest2d.hpp
+++ b/src/libnest2d/include/libnest2d/libnest2d.hpp
@@ -27,6 +27,7 @@ using Coord = TCoord<PointImpl>;
using Box = _Box<PointImpl>;
using Segment = _Segment<PointImpl>;
using Circle = _Circle<PointImpl>;
+using MultiPolygon = TMultiShape<PolygonImpl>;
using Item = _Item<PolygonImpl>;
using Rectangle = _Rectangle<PolygonImpl>;
diff --git a/src/libnest2d/tools/svgtools.hpp b/src/libnest2d/tools/svgtools.hpp
index e1ed1ad05..2a05b551d 100644
--- a/src/libnest2d/tools/svgtools.hpp
+++ b/src/libnest2d/tools/svgtools.hpp
@@ -5,7 +5,7 @@
#include <fstream>
#include <string>
-#include <libnest2d/libnest2d.hpp>
+#include <libnest2d/nester.hpp>
namespace libnest2d { namespace svg {
@@ -48,23 +48,28 @@ public:
conf_.width = static_cast<double>(box.width()) /
conf_.mm_in_coord_units;
}
-
- void writeItem(const Item& item) {
+
+ void writeShape(RawShape tsh) {
if(svg_layers_.empty()) addLayer();
- auto tsh = item.transformedShape();
if(conf_.origo_location == BOTTOMLEFT) {
auto d = static_cast<Coord>(
- std::round(conf_.height*conf_.mm_in_coord_units) );
-
+ std::round(conf_.height*conf_.mm_in_coord_units) );
+
auto& contour = shapelike::contour(tsh);
for(auto& v : contour) setY(v, -getY(v) + d);
-
+
auto& holes = shapelike::holes(tsh);
for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d);
-
+
}
- currentLayer() += shapelike::serialize<Formats::SVG>(tsh,
- 1.0/conf_.mm_in_coord_units) + "\n";
+ currentLayer() +=
+ shapelike::serialize<Formats::SVG>(tsh,
+ 1.0 / conf_.mm_in_coord_units) +
+ "\n";
+ }
+
+ void writeItem(const Item& item) {
+ writeShape(item.transformedShape());
}
void writePackGroup(const PackGroup& result) {
diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp
index b8ef0bcdc..5b048b0ff 100644
--- a/src/libslic3r/Arrange.cpp
+++ b/src/libslic3r/Arrange.cpp
@@ -1,7 +1,6 @@
#include "Arrange.hpp"
-#include "Geometry.hpp"
+//#include "Geometry.hpp"
#include "SVG.hpp"
-#include "MTUtils.hpp"
#include <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)
{
- namespace clppr = ClipperLib;
+ 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;
- std::vector<Item> items, fixeditems;
- items.reserve(arrangables.size());
+ for (auto pt : points)
+ {
+ double distance = distance_to(center, pt);
+ vertex_distances.push_back(distance);
+ avg_dist += distance;
+ }
- // Create Item from Arrangeable
- auto process_arrangeable = [](const ArrangePolygon &arrpoly,
- std::vector<Item> & outp)
+ avg_dist /= vertex_distances.size();
+
+ CircleBed ret(center, avg_dist);
+ for(auto el : vertex_distances)
{
- Polygon p = arrpoly.poly.contour;
- const Vec2crd &offs = arrpoly.translation;
- double rotation = arrpoly.rotation;
+ if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
+ ret = {};
+ break;
+ }
+ }
+
+ return ret;
+}
- if (p.is_counter_clockwise()) p.reverse();
+// 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;
- clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
-
- if (!clpath.Contour.empty()) {
- auto firstp = clpath.Contour.front();
- clpath.Contour.emplace_back(firstp);
- }
+ if (p.is_counter_clockwise()) p.reverse();
- 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);
- };
+ 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());
+
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
diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp
index 1cfe1c907..352e9e1cf 100644
--- a/src/libslic3r/Arrange.hpp
+++ b/src/libslic3r/Arrange.hpp
@@ -1,12 +1,10 @@
-#ifndef MODELARRANGE_HPP
-#define MODELARRANGE_HPP
+#ifndef ARRANGE_HPP
+#define ARRANGE_HPP
#include "ExPolygon.hpp"
#include "BoundingBox.hpp"
-namespace Slic3r {
-
-namespace arrangement {
+namespace Slic3r { namespace arrangement {
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
class CircleBed {
@@ -15,96 +13,16 @@ class CircleBed {
public:
inline CircleBed(): center_(0, 0), radius_(std::nan("")) {}
- inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
+ explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
inline double radius() const { return radius_; }
inline const Point& center() const { return center_; }
- inline operator bool() { return !std::isnan(radius_); }
};
/// Representing an unbounded bed.
-struct InfiniteBed { Point center; };
-
-/// Types of print bed shapes.
-enum BedShapes {
- bsBox,
- bsCircle,
- bsIrregular,
- bsInfinite,
- bsUnknown
-};
-
-/// Info about the print bed for the arrange() function. This is a variant
-/// holding one of the four shapes a bed can be.
-class BedShapeHint {
- BedShapes m_type = BedShapes::bsInfinite;
-
- // The union neither calls constructors nor destructors of its members.
- // The only member with non-trivial constructor / destructor is the polygon,
- // a placement new / delete needs to be called over it.
- union BedShape_u { // TODO: use variant from cpp17?
- CircleBed circ;
- BoundingBox box;
- Polyline polygon;
- InfiniteBed infbed{};
- ~BedShape_u() {}
- BedShape_u() {}
- } m_bed;
-
- // Reset the type, allocate m_bed properly
- void reset(BedShapes type);
-
-public:
-
- BedShapeHint(){}
-
- /// Get a bed shape hint for arrange() from a naked Polyline.
- explicit BedShapeHint(const Polyline &polyl);
- explicit BedShapeHint(const BoundingBox &bb)
- {
- m_type = bsBox; m_bed.box = bb;
- }
-
- explicit BedShapeHint(const CircleBed &c)
- {
- m_type = bsCircle; m_bed.circ = c;
- }
-
- explicit BedShapeHint(const InfiniteBed &ibed)
- {
- m_type = bsInfinite; m_bed.infbed = ibed;
- }
-
- ~BedShapeHint()
- {
- if (m_type == BedShapes::bsIrregular)
- m_bed.polygon.Slic3r::Polyline::~Polyline();
- }
-
- BedShapeHint(const BedShapeHint &cpy) { *this = cpy; }
- BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); }
-
- BedShapeHint &operator=(const BedShapeHint &cpy);
- BedShapeHint& operator=(BedShapeHint &&cpy);
-
- BedShapes get_type() const { return m_type; }
-
- const BoundingBox &get_box() const
- {
- assert(m_type == bsBox); return m_bed.box;
- }
- const CircleBed &get_circle() const
- {
- assert(m_type == bsCircle); return m_bed.circ;
- }
- const Polyline &get_irregular() const
- {
- assert(m_type == bsIrregular); return m_bed.polygon;
- }
- const InfiniteBed &get_infinite() const
- {
- assert(m_type == bsInfinite); return m_bed.infbed;
- }
+struct InfiniteBed {
+ Point center;
+ explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
};
/// A logical bed representing an object not being arranged. Either the arrange
@@ -125,9 +43,14 @@ struct ArrangePolygon {
ExPolygon poly; /// The 2D silhouette to be arranged
Vec2crd translation{0, 0}; /// The translation of the poly
double rotation{0.0}; /// The rotation of the poly in radians
+ coord_t inflation = 0; /// Arrange with inflated polygon
int bed_idx{UNARRANGED}; /// To which logical bed does poly belong...
int priority{0};
+ // If empty, any rotation is allowed (currently unsupported)
+ // If only a zero is there, no rotation is allowed
+ std::vector<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);
-
-/// 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);
-
-} // arr
-} // Slic3r
+template<class TBed> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams &params = {});
+
+// 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
+
#endif // MODELARRANGE_HPP
diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp
index 4fbe72163..2216d7888 100644
--- a/src/libslic3r/BoundingBox.hpp
+++ b/src/libslic3r/BoundingBox.hpp
@@ -186,6 +186,11 @@ inline bool empty(const BoundingBox3Base<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
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index b37e09ad0..30614100f 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -77,6 +77,8 @@ add_library(libslic3r STATIC
Format/PRUS.hpp
Format/STL.cpp
Format/STL.hpp
+ Format/SL1.hpp
+ Format/SL1.cpp
GCode/Analyzer.cpp
GCode/Analyzer.hpp
GCode/ThumbnailData.cpp
@@ -120,6 +122,8 @@ add_library(libslic3r STATIC
Line.hpp
Model.cpp
Model.hpp
+ ModelArrange.hpp
+ ModelArrange.cpp
CustomGCode.cpp
CustomGCode.hpp
Arrange.hpp
@@ -160,6 +164,8 @@ add_library(libslic3r STATIC
SLAPrint.hpp
Slicing.cpp
Slicing.hpp
+ SlicesToTriangleMesh.hpp
+ SlicesToTriangleMesh.cpp
SlicingAdaptive.cpp
SlicingAdaptive.hpp
SupportMaterial.cpp
@@ -175,6 +181,8 @@ add_library(libslic3r STATIC
Tesselate.hpp
TriangleMesh.cpp
TriangleMesh.hpp
+ TriangulateWall.hpp
+ TriangulateWall.cpp
utils.cpp
Utils.hpp
Time.cpp
@@ -189,6 +197,7 @@ add_library(libslic3r STATIC
SimplifyMesh.hpp
SimplifyMeshImpl.hpp
SimplifyMesh.cpp
+ MarchingSquares.hpp
${OpenVDBUtils_SOURCES}
SLA/Common.hpp
SLA/Common.cpp
@@ -206,10 +215,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
@@ -262,7 +272,8 @@ endif ()
encoding_check(libslic3r)
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
-target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
+target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
+target_include_directories(libslic3r PUBLIC ${EXPAT_INCLUDE_DIRS})
target_link_libraries(libslic3r
libnest2d
admesh
diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp
new file mode 100644
index 000000000..ba5e89330
--- /dev/null
+++ b/src/libslic3r/Format/SL1.cpp
@@ -0,0 +1,171 @@
+#include "SL1.hpp"
+#include "GCode/ThumbnailData.hpp"
+#include "libslic3r/Time.hpp"
+
+#include <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
diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp
new file mode 100644
index 000000000..1b9e95392
--- /dev/null
+++ b/src/libslic3r/Format/SL1.hpp
@@ -0,0 +1,44 @@
+#ifndef ARCHIVETRAITS_HPP
+#define ARCHIVETRAITS_HPP
+
+#include <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
diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp
index 3402d2c85..294f9ae6f 100644
--- a/src/libslic3r/MTUtils.hpp
+++ b/src/libslic3r/MTUtils.hpp
@@ -11,6 +11,7 @@
#include "libslic3r.h"
#include "Point.hpp"
+#include "BoundingBox.hpp"
namespace Slic3r {
@@ -75,143 +76,6 @@ public:
}
};
-/// An std compatible random access iterator which uses indices to the
-/// source vector thus resistant to invalidation caused by relocations. It
-/// also "knows" its container. No comparison is neccesary to the container
-/// "end()" iterator. The template can be instantiated with a different
-/// value type than that of the container's but the types must be
-/// compatible. E.g. a base class of the contained objects is compatible.
-///
-/// For a constant iterator, one can instantiate this template with a value
-/// type preceded with 'const'.
-template<class 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());
diff --git a/src/libslic3r/MarchingSquares.hpp b/src/libslic3r/MarchingSquares.hpp
new file mode 100644
index 000000000..d5f07fbde
--- /dev/null
+++ b/src/libslic3r/MarchingSquares.hpp
@@ -0,0 +1,448 @@
+#ifndef MARCHINGSQUARES_HPP
+#define MARCHINGSQUARES_HPP
+
+#include <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
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 59df8eb61..98f595d91 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -1,4 +1,5 @@
#include "Model.hpp"
+#include "ModelArrange.hpp"
#include "Geometry.hpp"
#include "MTUtils.hpp"
@@ -355,116 +356,6 @@ TriangleMesh Model::mesh() const
return mesh;
}
-static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out)
-{
- if (sizes.empty())
- // return if the list is empty or the following call to BoundingBoxf constructor will lead to a crash
- return true;
-
- // we supply unscaled data to arrange()
- bool result = Slic3r::Geometry::arrange(
- sizes.size(), // number of parts
- BoundingBoxf(sizes).max, // width and height of a single cell
- dist, // distance between cells
- bb, // bounding box of the area to fill
- out // output positions
- );
-
- if (!result && bb != nullptr) {
- // Try to arrange again ignoring bb
- result = Slic3r::Geometry::arrange(
- sizes.size(), // number of parts
- BoundingBoxf(sizes).max, // width and height of a single cell
- dist, // distance between cells
- nullptr, // bounding box of the area to fill
- out // output positions
- );
- }
-
- return result;
-}
-
-/* arrange objects preserving their instance count
- but altering their instance positions */
-bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
-{
- size_t count = 0;
- for (auto obj : objects) count += obj->instances.size();
-
- arrangement::ArrangePolygons input;
- ModelInstancePtrs instances;
- input.reserve(count);
- instances.reserve(count);
- for (ModelObject *mo : objects)
- for (ModelInstance *minst : mo->instances) {
- input.emplace_back(minst->get_arrange_polygon());
- instances.emplace_back(minst);
- }
-
- arrangement::BedShapeHint bedhint;
- coord_t bedwidth = 0;
-
- if (bb) {
- bedwidth = scaled(bb->size().x());
- bedhint = arrangement::BedShapeHint(
- BoundingBox(scaled(bb->min), scaled(bb->max)));
- }
-
- arrangement::arrange(input, scaled(dist), bedhint);
-
- bool ret = true;
- coord_t stride = bedwidth + bedwidth / 5;
-
- for(size_t i = 0; i < input.size(); ++i) {
- if (input[i].bed_idx != 0) ret = false;
- if (input[i].bed_idx >= 0) {
- input[i].translation += Vec2crd{input[i].bed_idx * stride, 0};
- instances[i]->apply_arrange_result(input[i].translation.cast<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";
@@ -1149,6 +1040,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
for (ModelVolume *volume : volumes) {
const auto volume_matrix = volume->get_matrix();
+ volume->m_supported_facets.clear();
+
if (! volume->is_model_part()) {
// Modifiers are not cut, but we still need to add the instance transformation
// to the modifier volume transformation to preserve their shape properly.
@@ -1848,6 +1741,41 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
return ret;
}
+
+std::vector<int> FacetsAnnotation::get_facets(FacetSupportType type) const
+{
+ std::vector<int> out;
+ for (auto& [facet_idx, this_type] : m_data)
+ if (this_type == type)
+ out.push_back(facet_idx);
+ return out;
+}
+
+
+
+void FacetsAnnotation::set_facet(int idx, FacetSupportType type)
+{
+ bool changed = true;
+
+ if (type == FacetSupportType::NONE)
+ changed = m_data.erase(idx) != 0;
+ else
+ m_data[idx] = type;
+
+ if (changed)
+ update_timestamp();
+}
+
+
+
+void FacetsAnnotation::clear()
+{
+ m_data.clear();
+ update_timestamp();
+}
+
+
+
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
// ordered in the same order. In that case it is not necessary to kill the background processing.
bool model_object_list_equal(const Model &model_old, const Model &model_new)
@@ -1911,6 +1839,16 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO
return false;
}
+bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new) {
+ assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
+ assert(mo.volumes.size() == mo_new.volumes.size());
+ for (size_t i=0; i<mo.volumes.size(); ++i) {
+ if (! mo_new.volumes[i]->m_supported_facets.is_same_as(mo.volumes[i]->m_supported_facets))
+ return true;
+ }
+ return false;
+};
+
extern bool model_has_multi_part_objects(const Model &model)
{
for (const ModelObject *model_object : model.objects)
@@ -1991,6 +1929,7 @@ void check_model_ids_equal(const Model &model1, const Model &model2)
}
}
}
+
#endif /* NDEBUG */
}
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 2ddad9e59..cd2c4957d 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -19,6 +19,7 @@
#include <string>
#include <utility>
#include <vector>
+#include <chrono>
namespace cereal {
class BinaryInputArchive;
@@ -214,8 +215,8 @@ public:
when user expects that. */
Vec3d origin_translation;
- Model* get_model() { return m_model; };
- const Model* get_model() const { return m_model; };
+ Model* get_model() { return m_model; }
+ const Model* get_model() const { return m_model; }
ModelVolume* add_volume(const TriangleMesh &mesh);
ModelVolume* add_volume(TriangleMesh &&mesh);
@@ -391,6 +392,34 @@ enum class ModelVolumeType : int {
SUPPORT_BLOCKER,
};
+enum class FacetSupportType : int8_t {
+ NONE = 0,
+ ENFORCER = 1,
+ BLOCKER = 2
+};
+
+class FacetsAnnotation {
+public:
+ using ClockType = std::chrono::steady_clock;
+
+ std::vector<int> get_facets(FacetSupportType type) const;
+ void set_facet(int idx, FacetSupportType type);
+ void clear();
+
+ ClockType::time_point get_timestamp() const { return timestamp; }
+ bool is_same_as(const FacetsAnnotation& other) const {
+ return timestamp == other.get_timestamp();
+ }
+
+private:
+ std::map<int, FacetSupportType> m_data;
+
+ ClockType::time_point timestamp;
+ void update_timestamp() {
+ timestamp = ClockType::now();
+ }
+};
+
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
// ModelVolume instances are owned by a ModelObject.
class ModelVolume final : public ObjectBase
@@ -421,8 +450,11 @@ public:
// overriding the global Slic3r settings and the ModelObject settings.
ModelConfig config;
+ // List of mesh facets to be supported/unsupported.
+ FacetsAnnotation m_supported_facets;
+
// A parent object owning this modifier volume.
- ModelObject* get_object() const { return this->object; };
+ ModelObject* get_object() const { return this->object; }
ModelVolumeType type() const { return m_type; }
void set_type(const ModelVolumeType t) { m_type = t; }
bool is_model_part() const { return m_type == ModelVolumeType::MODEL_PART; }
@@ -548,7 +580,9 @@ private:
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other) :
ObjectBase(other),
- name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
+ name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
+ config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
+ m_supported_facets(other.m_supported_facets)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == other.id() && this->config.id() == other.config.id());
@@ -565,6 +599,8 @@ private:
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id());
+
+ m_supported_facets.clear();
}
ModelVolume& operator=(ModelVolume &rhs) = delete;
@@ -802,11 +838,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,7 +856,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()); };
+ explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }
void assign_new_unique_ids_recursive();
void update_links_bottom_up_recursive();
@@ -831,7 +865,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
@@ -849,6 +883,10 @@ extern bool model_object_list_extended(const Model &model_old, const Model &mode
// than the old ModelObject.
extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type);
+// Test whether the now ModelObject has newer custom supports data than the old one.
+// The function assumes that volumes list is synchronized.
+extern bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new);
+
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
// Either the model cannot be loaded, or a SLA printer has to be activated.
extern bool model_has_multi_part_objects(const Model &model);
diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp
new file mode 100644
index 000000000..85aa25a5f
--- /dev/null
+++ b/src/libslic3r/ModelArrange.cpp
@@ -0,0 +1,83 @@
+#include "ModelArrange.hpp"
+#include "MTUtils.hpp"
+
+namespace Slic3r {
+
+arrangement::ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances)
+{
+ size_t count = 0;
+ for (auto obj : model.objects) count += obj->instances.size();
+
+ ArrangePolygons input;
+ input.reserve(count);
+ instances.clear(); instances.reserve(count);
+ for (ModelObject *mo : model.objects)
+ for (ModelInstance *minst : mo->instances) {
+ input.emplace_back(minst->get_arrange_polygon());
+ instances.emplace_back(minst);
+ }
+
+ return input;
+}
+
+bool apply_arrange_polys(ArrangePolygons &input, ModelInstancePtrs &instances, VirtualBedFn vfn)
+{
+ bool ret = true;
+
+ for(size_t i = 0; i < input.size(); ++i) {
+ if (input[i].bed_idx != 0) { ret = false; if (vfn) vfn(input[i]); }
+ if (input[i].bed_idx >= 0)
+ instances[i]->apply_arrange_result(input[i].translation.cast<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
diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp
new file mode 100644
index 000000000..d65b0fd6d
--- /dev/null
+++ b/src/libslic3r/ModelArrange.hpp
@@ -0,0 +1,68 @@
+#ifndef MODELARRANGE_HPP
+#define MODELARRANGE_HPP
+
+#include <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
diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp
index e095f1c75..e511a6316 100644
--- a/src/libslic3r/Point.hpp
+++ b/src/libslic3r/Point.hpp
@@ -114,6 +114,7 @@ public:
Point& operator+=(const Point& rhs) { (*this)(0) += rhs(0); (*this)(1) += rhs(1); return *this; }
Point& operator-=(const Point& rhs) { (*this)(0) -= rhs(0); (*this)(1) -= rhs(1); return *this; }
Point& operator*=(const double &rhs) { (*this)(0) = coord_t((*this)(0) * rhs); (*this)(1) = coord_t((*this)(1) * rhs); return *this; }
+ Point operator*(const double &rhs) { return Point((*this)(0) * rhs, (*this)(1) * rhs); }
void rotate(double angle);
void rotate(double angle, const Point &center);
@@ -288,6 +289,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
diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp
index e1e299144..48e63dab3 100644
--- a/src/libslic3r/Polygon.cpp
+++ b/src/libslic3r/Polygon.cpp
@@ -48,12 +48,12 @@ int64_t Polygon::area2x() const
}
*/
-double Polygon::area() const
+double Polygon::area(const Points &points)
{
size_t n = points.size();
if (n < 3)
return 0.;
-
+
double a = 0.;
for (size_t i = 0, j = n - 1; i < n; ++i) {
a += ((double)points[j](0) + (double)points[i](0)) * ((double)points[i](1) - (double)points[j](1));
@@ -62,6 +62,11 @@ double Polygon::area() const
return 0.5 * a;
}
+double Polygon::area() const
+{
+ return Polygon::area(points);
+}
+
bool Polygon::is_counter_clockwise() const
{
return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this));
diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp
index 0f8457ebd..c6678e2d8 100644
--- a/src/libslic3r/Polygon.hpp
+++ b/src/libslic3r/Polygon.hpp
@@ -22,6 +22,7 @@ public:
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
Polygon() {}
+ virtual ~Polygon() = default;
explicit Polygon(const Points &points) : MultiPoint(points) {}
Polygon(std::initializer_list<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;
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 986b7fa09..dad41a642 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -404,6 +404,7 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
// Copy the ModelVolume data.
mv_dst.name = mv_src.name;
static_cast<DynamicPrintConfig&>(mv_dst.config) = static_cast<const DynamicPrintConfig&>(mv_src.config);
+ mv_dst.m_supported_facets = mv_src.m_supported_facets;
//FIXME what to do with the materials?
// mv_dst.m_material_id = mv_src.m_material_id;
++ i_src;
@@ -854,7 +855,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
}
// Copy content of the ModelObject including its ID, do not change the parent.
model_object.assign_copy(model_object_new);
- } else if (support_blockers_differ || support_enforcers_differ) {
+ } else if (support_blockers_differ || support_enforcers_differ || model_custom_supports_data_changed(model_object, model_object_new)) {
// First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list.
this->call_cancel_callback();
update_apply_status(false);
@@ -862,8 +863,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id()));
for (auto it = range.first; it != range.second; ++ it)
update_apply_status(it->print_object->invalidate_step(posSupportMaterial));
- // Copy just the support volumes.
- model_volume_list_update_supports(model_object, model_object_new);
+ if (support_enforcers_differ || support_blockers_differ) {
+ // Copy just the support volumes.
+ model_volume_list_update_supports(model_object, model_object_new);
+ }
}
if (! model_parts_differ && ! modifiers_differ) {
// Synchronize Object's config.
@@ -881,7 +884,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
}
}
}
- // Synchronize (just copy) the remaining data of ModelVolumes (name, config).
+ // Synchronize (just copy) the remaining data of ModelVolumes (name, config, custom supports data).
//FIXME What to do with m_material_id?
model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART);
model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER);
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index d4d3a1ddc..54ebceeb6 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -192,6 +192,11 @@ public:
std::vector<ExPolygons> slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); }
std::vector<ExPolygons> slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); }
+ // Helpers to project custom supports on slices
+ void project_and_append_custom_supports(FacetSupportType type, std::vector<ExPolygons>& expolys) const;
+ void project_and_append_custom_enforcers(std::vector<ExPolygons>& enforcers) const { project_and_append_custom_supports(FacetSupportType::ENFORCER, enforcers); }
+ void project_and_append_custom_blockers(std::vector<ExPolygons>& blockers) const { project_and_append_custom_supports(FacetSupportType::BLOCKER, blockers); }
+
private:
// to be called from Print only.
friend class Print;
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index a9175ab0c..a155dff01 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -3109,6 +3109,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")) {
@@ -3179,22 +3215,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()
{
@@ -3604,8 +3624,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)
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index b3dc3f154..10ff8086c 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -212,6 +212,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,
@@ -778,8 +781,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;
@@ -1334,6 +1335,10 @@ private:
static PrintAndCLIConfigDef s_def;
};
+Points get_bed_shape(const DynamicPrintConfig &cfg);
+Points get_bed_shape(const PrintConfig &cfg);
+Points get_bed_shape(const SLAPrinterConfig &cfg);
+
} // namespace Slic3r
// Serialization through the Cereal library
diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp
index 5d8d6a756..5174fd6e9 100644
--- a/src/libslic3r/PrintObject.cpp
+++ b/src/libslic3r/PrintObject.cpp
@@ -2665,4 +2665,168 @@ void PrintObject::_generate_support_material()
support_material.generate(*this);
}
+
+void PrintObject::project_and_append_custom_supports(
+ FacetSupportType type, std::vector<ExPolygons>& expolys) const
+{
+ for (const ModelVolume* mv : this->model_object()->volumes) {
+ const std::vector<int> custom_facets = mv->m_supported_facets.get_facets(type);
+ if (custom_facets.empty())
+ continue;
+
+ const TriangleMesh& mesh = mv->mesh();
+ const Transform3f& tr1 = mv->get_matrix().cast<float>();
+ const Transform3f& tr2 = this->trafo().cast<float>();
+ const Transform3f tr = tr2 * tr1;
+
+
+ // The projection will be at most a pentagon. Let's minimize heap
+ // reallocations by saving in in the following struct.
+ // Points are used so that scaling can be done in parallel
+ // and they can be moved from to create an ExPolygon later.
+ struct LightPolygon {
+ LightPolygon() { pts.reserve(5); }
+ Points pts;
+
+ void add(const Vec2f& pt) {
+ pts.emplace_back(scale_(pt.x()), scale_(pt.y()));
+ assert(pts.size() <= 5);
+ }
+ };
+
+ // Structure to collect projected polygons. One element for each triangle.
+ // Saves vector of polygons and layer_id of the first one.
+ struct TriangleProjections {
+ size_t first_layer_id;
+ std::vector<LightPolygon> polygons;
+ };
+
+ // Vector to collect resulting projections from each triangle.
+ std::vector<TriangleProjections> projections_of_triangles(custom_facets.size());
+
+ // Iterate over all triangles.
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, custom_facets.size()),
+ [&](const tbb::blocked_range<size_t>& range) {
+ for (size_t idx = range.begin(); idx < range.end(); ++ idx) {
+
+ std::array<Vec3f, 3> facet;
+
+ // Transform the triangle into worlds coords.
+ for (int i=0; i<3; ++i)
+ facet[i] = tr * mesh.its.vertices[mesh.its.indices[custom_facets[idx]](i)];
+
+ // Ignore triangles with upward-pointing normal.
+ if ((facet[1]-facet[0]).cross(facet[2]-facet[0]).z() > 0.)
+ continue;
+
+ // Sort the three vertices according to z-coordinate.
+ std::sort(facet.begin(), facet.end(),
+ [](const Vec3f& pt1, const Vec3f&pt2) {
+ return pt1.z() < pt2.z();
+ });
+
+ std::array<Vec2f, 3> trianglef;
+ for (int i=0; i<3; ++i) {
+ trianglef[i] = Vec2f(facet[i].x(), facet[i].y());
+ trianglef[i] += Vec2f(unscale<float>(this->center_offset().x()),
+ unscale<float>(this->center_offset().y()));
+ }
+
+ // Find lowest slice not below the triangle.
+ auto it = std::lower_bound(layers().begin(), layers().end(), facet[0].z()+EPSILON,
+ [](const Layer* l1, float z) {
+ return l1->slice_z < z;
+ });
+
+ // Count how many projections will be generated for this triangle
+ // and allocate respective amount in projections_of_triangles.
+ projections_of_triangles[idx].first_layer_id = it-layers().begin();
+ size_t last_layer_id = projections_of_triangles[idx].first_layer_id;
+ // The cast in the condition below is important. The comparison must
+ // be an exact opposite of the one lower in the code where
+ // the polygons are appended. And that one is on floats.
+ while (last_layer_id + 1 < layers().size()
+ && float(layers()[last_layer_id]->slice_z) <= facet[2].z())
+ ++last_layer_id;
+ projections_of_triangles[idx].polygons.resize(
+ last_layer_id - projections_of_triangles[idx].first_layer_id + 1);
+
+ // Calculate how to move points on triangle sides per unit z increment.
+ Vec2f ta(trianglef[1] - trianglef[0]);
+ Vec2f tb(trianglef[2] - trianglef[0]);
+ ta *= 1./(facet[1].z() - facet[0].z());
+ tb *= 1./(facet[2].z() - facet[0].z());
+
+ // Projection on current slice will be build directly in place.
+ LightPolygon* proj = &projections_of_triangles[idx].polygons[0];
+ proj->add(trianglef[0]);
+
+ bool passed_first = false;
+ bool stop = false;
+
+ // Project a sub-polygon on all slices intersecting the triangle.
+ while (it != layers().end()) {
+ const float z = (*it)->slice_z;
+
+ // Projections of triangle sides intersections with slices.
+ // a moves along one side, b tracks the other.
+ Vec2f a;
+ Vec2f b;
+
+ // If the middle vertex was already passed, append the vertex
+ // and use ta for tracking the remaining side.
+ if (z > facet[1].z() && ! passed_first) {
+ proj->add(trianglef[1]);
+ ta = trianglef[2]-trianglef[1];
+ ta *= 1./(facet[2].z() - facet[1].z());
+ passed_first = true;
+ }
+
+ // This slice is above the triangle already.
+ if (z > facet[2].z() || it+1 == layers().end()) {
+ proj->add(trianglef[2]);
+ stop = true;
+ }
+ else {
+ // Move a, b along the side it currently tracks to get
+ // projected intersection with current slice.
+ a = passed_first ? (trianglef[1]+ta*(z-facet[1].z()))
+ : (trianglef[0]+ta*(z-facet[0].z()));
+ b = trianglef[0]+tb*(z-facet[0].z());
+ proj->add(a);
+ proj->add(b);
+ }
+
+ if (stop)
+ break;
+
+ // Advance to the next layer.
+ ++it;
+ ++proj;
+ assert(proj <= &projections_of_triangles[idx].polygons.back() );
+
+ // a, b are first two points of the polygon for the next layer.
+ proj->add(b);
+ proj->add(a);
+ }
+ }
+ }); // end of parallel_for
+
+ // Make sure that the output vector can be used.
+ expolys.resize(layers().size());
+
+ // Now append the collected polygons to respective layers.
+ for (auto& trg : projections_of_triangles) {
+ int layer_id = trg.first_layer_id;
+
+ for (const LightPolygon& poly : trg.polygons) {
+ expolys[layer_id].emplace_back(std::move(poly.pts));
+ ++layer_id;
+ }
+ }
+
+ } // loop over ModelVolumes
+}
+
} // namespace Slic3r
diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp
new file mode 100644
index 000000000..37baed9e8
--- /dev/null
+++ b/src/libslic3r/SLA/AGGRaster.hpp
@@ -0,0 +1,222 @@
+#ifndef AGGRASTER_HPP
+#define AGGRASTER_HPP
+
+#include <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
diff --git a/src/libslic3r/SLA/Common.cpp b/src/libslic3r/SLA/Common.cpp
index 9945ed7fa..b57039ad1 100644
--- a/src/libslic3r/SLA/Common.cpp
+++ b/src/libslic3r/SLA/Common.cpp
@@ -303,8 +303,10 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
ret.m_t = double(hit.t);
ret.m_dir = dir;
ret.m_source = s;
- if(!std::isinf(hit.t) && !std::isnan(hit.t))
+ if(!std::isinf(hit.t) && !std::isnan(hit.t)) {
ret.m_normal = this->normal_by_face_id(hit.id);
+ ret.m_face_id = hit.id;
+ }
return ret;
}
@@ -340,8 +342,10 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
outs.back().m_t = double(hit.t);
outs.back().m_dir = dir;
outs.back().m_source = s;
- if(!std::isinf(hit.t) && !std::isnan(hit.t))
+ if(!std::isinf(hit.t) && !std::isnan(hit.t)) {
outs.back().m_normal = this->normal_by_face_id(hit.id);
+ outs.back().m_face_id = hit.id;
+ }
}
return outs;
diff --git a/src/libslic3r/SLA/EigenMesh3D.hpp b/src/libslic3r/SLA/EigenMesh3D.hpp
index bcf94ec16..014a57e82 100644
--- a/src/libslic3r/SLA/EigenMesh3D.hpp
+++ b/src/libslic3r/SLA/EigenMesh3D.hpp
@@ -55,6 +55,7 @@ public:
class hit_result {
// m_t holds a distance from m_source to the intersection.
double m_t = infty();
+ int m_face_id = -1;
const EigenMesh3D *m_mesh = nullptr;
Vec3d m_dir;
Vec3d m_source;
@@ -74,6 +75,7 @@ public:
inline const Vec3d& direction() const { return m_dir; }
inline const Vec3d& source() const { return m_source; }
inline Vec3d position() const { return m_source + m_dir * m_t; }
+ inline int face() const { return m_face_id; }
inline bool is_valid() const { return m_mesh != nullptr; }
inline bool is_hit() const { return !std::isinf(m_t); }
diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp
index cf1786758..d933ef5ed 100644
--- a/src/libslic3r/SLA/Pad.cpp
+++ b/src/libslic3r/SLA/Pad.cpp
@@ -11,6 +11,8 @@
#include "Tesselate.hpp"
#include "MTUtils.hpp"
+#include "TriangulateWall.hpp"
+
// For debugging:
// #include <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)
{
- 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);
+ Wall w = triangulate_wall(lower, upper, lower_z_mm, upper_z_mm);
+ Contour3D ret;
+ ret.points = std::move(w.first);
+ ret.faces3 = std::move(w.second);
+
return ret;
}
// Same as walls() but with identical higher and lower polygons.
Contour3D inline straight_walls(const Polygon &plate,
double lo_z,
- double hi_z,
- ThrowOnCancel thr)
+ double hi_z)
{
- return walls(plate, plate, lo_z, hi_z, .0 /*offset_diff*/, thr);
+ return walls(plate, plate, lo_z, hi_z);
}
// Function to cut tiny connector cavities for a given polygon. The input poly
@@ -534,10 +377,8 @@ bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg,
top_poly = pdiff.front();
double z_min = -cfg.wing_height, z_max = 0;
- double offset_difference = -wing_distance;
- pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max,
- offset_difference, thr));
-
+ pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max));
+ thr();
pad.merge(triangulate_expolygon_3d(inner_base, z_min, NORMALS_UP));
return true;
@@ -555,17 +396,17 @@ Contour3D create_outer_pad_geometry(const ExPolygons & skeleton,
offset_contour_only(pad_part, -scaled(cfg.bottom_offset()));
if (bottom_poly.empty()) continue;
-
+ thr();
+
double z_min = -cfg.height, z_max = 0;
- ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min,
- cfg.bottom_offset(), thr));
+ ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min));
if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr))
z_max = -cfg.wing_height;
for (auto &h : bottom_poly.holes)
- ret.merge(straight_walls(h, z_max, z_min, thr));
-
+ ret.merge(straight_walls(h, z_max, z_min));
+
ret.merge(triangulate_expolygon_3d(bottom_poly, z_min, NORMALS_DOWN));
ret.merge(triangulate_expolygon_3d(top_poly, NORMALS_UP));
}
@@ -581,11 +422,12 @@ Contour3D create_inner_pad_geometry(const ExPolygons & skeleton,
double z_max = 0., z_min = -cfg.height;
for (const ExPolygon &pad_part : skeleton) {
- ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr));
+ thr();
+ ret.merge(straight_walls(pad_part.contour, z_max, z_min));
for (auto &h : pad_part.holes)
- ret.merge(straight_walls(h, z_max, z_min, thr));
-
+ ret.merge(straight_walls(h, z_max, z_min));
+
ret.merge(triangulate_expolygon_3d(pad_part, z_min, NORMALS_DOWN));
ret.merge(triangulate_expolygon_3d(pad_part, z_max, NORMALS_UP));
}
diff --git a/src/libslic3r/SLA/Raster.cpp b/src/libslic3r/SLA/Raster.cpp
deleted file mode 100644
index 9b7f1db7d..000000000
--- a/src/libslic3r/SLA/Raster.cpp
+++ /dev/null
@@ -1,320 +0,0 @@
-#ifndef SLARASTER_CPP
-#define SLARASTER_CPP
-
-#include <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
diff --git a/src/libslic3r/SLA/Raster.hpp b/src/libslic3r/SLA/Raster.hpp
deleted file mode 100644
index 4d76b3290..000000000
--- a/src/libslic3r/SLA/Raster.hpp
+++ /dev/null
@@ -1,157 +0,0 @@
-#ifndef SLA_RASTER_HPP
-#define SLA_RASTER_HPP
-
-#include <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
diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp
new file mode 100644
index 000000000..581e84880
--- /dev/null
+++ b/src/libslic3r/SLA/RasterBase.cpp
@@ -0,0 +1,89 @@
+#ifndef SLARASTER_CPP
+#define SLARASTER_CPP
+
+#include <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
diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp
new file mode 100644
index 000000000..431c731a6
--- /dev/null
+++ b/src/libslic3r/SLA/RasterBase.hpp
@@ -0,0 +1,124 @@
+#ifndef SLA_RASTERBASE_HPP
+#define SLA_RASTERBASE_HPP
+
+#include <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
diff --git a/src/libslic3r/SLA/RasterToPolygons.cpp b/src/libslic3r/SLA/RasterToPolygons.cpp
new file mode 100644
index 000000000..cd84a3cb4
--- /dev/null
+++ b/src/libslic3r/SLA/RasterToPolygons.cpp
@@ -0,0 +1,91 @@
+#include "RasterToPolygons.hpp"
+
+#include "AGGRaster.hpp"
+#include "libslic3r/MarchingSquares.hpp"
+#include "MTUtils.hpp"
+#include "ClipperUtils.hpp"
+
+namespace marchsq {
+
+// Specialize this struct to register a raster type for the Marching squares alg
+template<> struct _RasterTraits<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
diff --git a/src/libslic3r/SLA/RasterToPolygons.hpp b/src/libslic3r/SLA/RasterToPolygons.hpp
new file mode 100644
index 000000000..c0e1f4114
--- /dev/null
+++ b/src/libslic3r/SLA/RasterToPolygons.hpp
@@ -0,0 +1,15 @@
+#ifndef RASTERTOPOLYGONS_HPP
+#define RASTERTOPOLYGONS_HPP
+
+#include "libslic3r/ExPolygon.hpp"
+
+namespace Slic3r {
+namespace sla {
+
+class RasterGrayscaleAA;
+
+ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, Vec2i windowsize = {2, 2});
+
+}} // namespace Slic3r::sla
+
+#endif // RASTERTOPOLYGONS_HPP
diff --git a/src/libslic3r/SLA/RasterWriter.cpp b/src/libslic3r/SLA/RasterWriter.cpp
deleted file mode 100644
index 13aef7d8a..000000000
--- a/src/libslic3r/SLA/RasterWriter.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-#include <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
diff --git a/src/libslic3r/SLA/RasterWriter.hpp b/src/libslic3r/SLA/RasterWriter.hpp
deleted file mode 100644
index 75162893d..000000000
--- a/src/libslic3r/SLA/RasterWriter.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-#ifndef SLA_RASTERWRITER_HPP
-#define SLA_RASTERWRITER_HPP
-
-// For png export of the sliced model
-#include <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
diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp
index 4ec5aae29..2402207a8 100644
--- a/src/libslic3r/SLAPrint.cpp
+++ b/src/libslic3r/SLAPrint.cpp
@@ -227,6 +227,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con
m_material_config.apply_only(config, material_diff, true);
// Handle changes to object config defaults
m_default_object_config.apply_only(config, object_diff, true);
+
+ if (m_printer) m_printer->apply(m_printer_config);
struct ModelObjectStatus {
enum Status {
@@ -482,7 +484,6 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con
}
if(m_objects.empty()) {
- m_printer.reset();
m_printer_input = {};
m_print_statistics = {};
}
@@ -657,6 +658,12 @@ std::string SLAPrint::validate() const
return "";
}
+void SLAPrint::set_printer(SLAPrinter *arch)
+{
+ invalidate_step(slapsRasterize);
+ m_printer = arch;
+}
+
bool SLAPrint::invalidate_step(SLAPrintStep step)
{
bool invalidated = Inherited::invalidate_step(step);
@@ -676,7 +683,7 @@ void SLAPrint::process()
// Assumption: at this point the print objects should be populated only with
// the model objects we have to process and the instances are also filtered
- Steps printsteps{this};
+ Steps printsteps(this);
// We want to first process all objects...
std::vector<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
{
diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp
index 70f773f6b..a27207565 100644
--- a/src/libslic3r/SLAPrint.hpp
+++ b/src/libslic3r/SLAPrint.hpp
@@ -3,7 +3,7 @@
#include <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;
};
diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp
index 01220a633..e421e9c1d 100644
--- a/src/libslic3r/SLAPrintSteps.cpp
+++ b/src/libslic3r/SLAPrintSteps.cpp
@@ -816,16 +816,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
// Rasterizing the model objects, and their supports
void SLAPrint::Steps::rasterize()
{
- if(canceled()) return;
-
- auto &print_statistics = m_print->m_print_statistics;
- auto &printer_input = m_print->m_printer_input;
-
- // Set up the printer, allocate space for all the layers
- sla::RasterWriter &printer = m_print->init_printer();
-
- auto lvlcnt = unsigned(printer_input.size());
- printer.layers(lvlcnt);
+ if(canceled() || !m_print->m_printer) return;
// coefficient to map the rasterization state (0-99) to the allocated
// portion (slot) of the process state
@@ -837,7 +828,7 @@ void SLAPrint::Steps::rasterize()
// pst: previous state
double pst = current_status();
- double increment = (slot * sd) / printer_input.size();
+ double increment = (slot * sd) / m_print->m_printer_input.size();
double dstatus = current_status();
sla::ccr::SpinningMutex slck;
@@ -845,20 +836,14 @@ void SLAPrint::Steps::rasterize()
// procedure to process one height level. This will run in parallel
auto lvlfn =
- [this, &slck, &printer, increment, &dstatus, &pst]
- (PrintLayer& printlayer, size_t idx)
+ [this, &slck, increment, &dstatus, &pst]
+ (sla::RasterBase& raster, size_t idx)
{
+ PrintLayer& printlayer = m_print->m_printer_input[idx];
if(canceled()) return;
- auto level_id = unsigned(idx);
- // Switch to the appropriate layer in the printer
- printer.begin_layer(level_id);
-
- for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
- printer.draw_polygon(poly, level_id);
-
- // Finish the layer for later saving it.
- printer.finish_layer(level_id);
+ for (const ClipperLib::Polygon& poly : printlayer.transformed_slices())
+ raster.draw(poly);
// Status indication guarded with the spinlock
{
@@ -875,24 +860,8 @@ void SLAPrint::Steps::rasterize()
// last minute escape
if(canceled()) return;
- // Sequential version (for testing)
- // for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l);
-
// Print all the layers in parallel
- sla::ccr::enumerate(printer_input.begin(), printer_input.end(), lvlfn);
-
- // Set statistics values to the printer
- sla::RasterWriter::PrintStatistics stats;
- stats.used_material = (print_statistics.objects_used_material +
- print_statistics.support_used_material) / 1000;
-
- int num_fade = m_print->m_default_object_config.faded_layers.getInt();
- stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0);
- stats.num_fast = print_statistics.fast_layers_count;
- stats.num_slow = print_statistics.slow_layers_count;
- stats.estimated_print_time_s = print_statistics.estimated_print_time;
-
- printer.set_statistics(stats);
+ m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn);
}
std::string SLAPrint::Steps::label(SLAPrintObjectStep step)
diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp
index d3341bc14..19b64d4a9 100644
--- a/src/libslic3r/SLAPrintSteps.hpp
+++ b/src/libslic3r/SLAPrintSteps.hpp
@@ -46,7 +46,7 @@ private:
void apply_printer_corrections(SLAPrintObject &po, SliceOrigin o);
public:
- Steps(SLAPrint *print);
+ explicit Steps(SLAPrint *print);
void hollow_model(SLAPrintObject &po);
void drill_holes (SLAPrintObject &po);
diff --git a/src/libslic3r/SlicesToTriangleMesh.cpp b/src/libslic3r/SlicesToTriangleMesh.cpp
new file mode 100644
index 000000000..d6a546961
--- /dev/null
+++ b/src/libslic3r/SlicesToTriangleMesh.cpp
@@ -0,0 +1,128 @@
+
+#include "SlicesToTriangleMesh.hpp"
+
+#include "libslic3r/MTUtils.hpp"
+#include "libslic3r/SLA/Contour3D.hpp"
+#include "libslic3r/ClipperUtils.hpp"
+#include "libslic3r/Tesselate.hpp"
+
+#include <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
diff --git a/src/libslic3r/SlicesToTriangleMesh.hpp b/src/libslic3r/SlicesToTriangleMesh.hpp
new file mode 100644
index 000000000..133312d56
--- /dev/null
+++ b/src/libslic3r/SlicesToTriangleMesh.hpp
@@ -0,0 +1,24 @@
+#ifndef SLICESTOTRIANGLEMESH_HPP
+#define SLICESTOTRIANGLEMESH_HPP
+
+#include "libslic3r/TriangleMesh.hpp"
+#include "libslic3r/ExPolygon.hpp"
+
+namespace Slic3r {
+
+void slices_to_triangle_mesh(TriangleMesh & mesh,
+ const std::vector<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
diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp
index 72b4f465b..43f582d5f 100644
--- a/src/libslic3r/SupportMaterial.cpp
+++ b/src/libslic3r/SupportMaterial.cpp
@@ -971,6 +971,10 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
std::vector<ExPolygons> enforcers = object.slice_support_enforcers();
std::vector<ExPolygons> blockers = object.slice_support_blockers();
+ // Append custom supports.
+ object.project_and_append_custom_enforcers(enforcers);
+ object.project_and_append_custom_blockers(blockers);
+
// Output layers, sorted by top Z.
MyLayersPtr contact_out;
@@ -1097,10 +1101,10 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
if (! enforcers.empty()) {
// Apply the "support enforcers".
//FIXME add the "enforcers" to the sparse support regions only.
- const ExPolygons &enforcer = enforcers[layer_id - 1];
+ const ExPolygons &enforcer = enforcers[layer_id];
if (! enforcer.empty()) {
// Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes.
- Polygons new_contacts = diff(intersection(layerm_polygons, to_polygons(enforcer)),
+ Polygons new_contacts = diff(intersection(layerm_polygons, to_polygons(std::move(enforcer))),
offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS));
if (! new_contacts.empty()) {
if (diff_polygons.empty())
@@ -1111,19 +1115,26 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
}
}
}
- // Apply the "support blockers".
- if (! diff_polygons.empty() && ! blockers.empty() && ! blockers[layer_id].empty()) {
- // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes.
- diff_polygons = diff(diff_polygons, to_polygons(blockers[layer_id]));
- }
+
if (diff_polygons.empty())
continue;
+ // Apply the "support blockers".
+ if (! blockers.empty() && ! blockers[layer_id].empty()) {
+ // Expand the blocker a bit. Custom blockers produce strips
+ // spanning just the projection between the two slices.
+ // Subtracting them as they are may leave unwanted narrow
+ // residues of diff_polygons that would then be supported.
+ diff_polygons = diff(diff_polygons,
+ offset(union_(to_polygons(std::move(blockers[layer_id]))),
+ 1000.*SCALED_EPSILON));
+ }
+
#ifdef SLIC3R_DEBUG
{
::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg",
iRun, layer_id,
- std::find_if(layer.regions.begin(), layer.regions.end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions.begin()),
+ std::find_if(layer.regions.begin(), layer.regions.end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions.begin()),
get_extents(diff_polygons));
Slic3r::ExPolygons expolys = union_ex(diff_polygons, false);
svg.draw(expolys);
diff --git a/src/libslic3r/TriangulateWall.cpp b/src/libslic3r/TriangulateWall.cpp
new file mode 100644
index 000000000..ec2945b10
--- /dev/null
+++ b/src/libslic3r/TriangulateWall.cpp
@@ -0,0 +1,133 @@
+#include "TriangulateWall.hpp"
+#include "MTUtils.hpp"
+
+namespace Slic3r {
+
+class Ring {
+ size_t idx = 0, nextidx = 1, startidx = 0, begin = 0, end = 0;
+
+public:
+ explicit Ring(size_t from, size_t to) : begin(from), end(to) { init(begin); }
+
+ size_t size() const { return end - begin; }
+ std::pair<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
diff --git a/src/libslic3r/TriangulateWall.hpp b/src/libslic3r/TriangulateWall.hpp
new file mode 100644
index 000000000..68bf4b0ac
--- /dev/null
+++ b/src/libslic3r/TriangulateWall.hpp
@@ -0,0 +1,17 @@
+#ifndef TRIANGULATEWALL_HPP
+#define TRIANGULATEWALL_HPP
+
+#include "libslic3r/Polygon.hpp"
+
+namespace Slic3r {
+
+using Wall = std::pair<std::vector<Vec3d>, std::vector<Vec3i>>;
+
+Wall triangulate_wall(
+ const Polygon & lower,
+ const Polygon & upper,
+ double lower_z_mm,
+ double upper_z_mm);
+}
+
+#endif // TRIANGULATEWALL_HPP
diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp
index a5b53584d..02f022083 100644
--- a/src/libslic3r/Zipper.cpp
+++ b/src/libslic3r/Zipper.cpp
@@ -17,90 +17,14 @@
namespace Slic3r {
-class Zipper::Impl {
+class Zipper::Impl: public MZ_Archive {
public:
- mz_zip_archive arch;
std::string m_zipname;
- static std::string get_errorstr(mz_zip_error mz_err)
- {
- switch (mz_err)
- {
- case MZ_ZIP_NO_ERROR:
- return "no error";
- case MZ_ZIP_UNDEFINED_ERROR:
- return L("undefined error");
- case MZ_ZIP_TOO_MANY_FILES:
- return L("too many files");
- case MZ_ZIP_FILE_TOO_LARGE:
- return L("file too large");
- case MZ_ZIP_UNSUPPORTED_METHOD:
- return L("unsupported method");
- case MZ_ZIP_UNSUPPORTED_ENCRYPTION:
- return L("unsupported encryption");
- case MZ_ZIP_UNSUPPORTED_FEATURE:
- return L("unsupported feature");
- case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR:
- return L("failed finding central directory");
- case MZ_ZIP_NOT_AN_ARCHIVE:
- return L("not a ZIP archive");
- case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED:
- return L("invalid header or archive is corrupted");
- case MZ_ZIP_UNSUPPORTED_MULTIDISK:
- return L("unsupported multidisk archive");
- case MZ_ZIP_DECOMPRESSION_FAILED:
- return L("decompression failed or archive is corrupted");
- case MZ_ZIP_COMPRESSION_FAILED:
- return L("compression failed");
- case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE:
- return L("unexpected decompressed size");
- case MZ_ZIP_CRC_CHECK_FAILED:
- return L("CRC-32 check failed");
- case MZ_ZIP_UNSUPPORTED_CDIR_SIZE:
- return L("unsupported central directory size");
- case MZ_ZIP_ALLOC_FAILED:
- return L("allocation failed");
- case MZ_ZIP_FILE_OPEN_FAILED:
- return L("file open failed");
- case MZ_ZIP_FILE_CREATE_FAILED:
- return L("file create failed");
- case MZ_ZIP_FILE_WRITE_FAILED:
- return L("file write failed");
- case MZ_ZIP_FILE_READ_FAILED:
- return L("file read failed");
- case MZ_ZIP_FILE_CLOSE_FAILED:
- return L("file close failed");
- case MZ_ZIP_FILE_SEEK_FAILED:
- return L("file seek failed");
- case MZ_ZIP_FILE_STAT_FAILED:
- return L("file stat failed");
- case MZ_ZIP_INVALID_PARAMETER:
- return L("invalid parameter");
- case MZ_ZIP_INVALID_FILENAME:
- return L("invalid filename");
- case MZ_ZIP_BUF_TOO_SMALL:
- return L("buffer too small");
- case MZ_ZIP_INTERNAL_ERROR:
- return L("internal error");
- case MZ_ZIP_FILE_NOT_FOUND:
- return L("file not found");
- case MZ_ZIP_ARCHIVE_TOO_LARGE:
- return L("archive is too large");
- case MZ_ZIP_VALIDATION_FAILED:
- return L("validation failed");
- case MZ_ZIP_WRITE_CALLBACK_FAILED:
- return L("write calledback failed");
- default:
- break;
- }
-
- return "unknown error";
- }
-
std::string formatted_errorstr() const
{
return L("Error with zip archive") + " " + m_zipname + ": " +
- get_errorstr(arch.m_last_error) + "!";
+ get_errorstr() + "!";
}
SLIC3R_NORETURN void blow_up() const
@@ -167,7 +91,7 @@ void Zipper::add_entry(const std::string &name)
m_entry = name;
}
-void Zipper::add_entry(const std::string &name, const uint8_t *data, size_t l)
+void Zipper::add_entry(const std::string &name, const void *data, size_t l)
{
if(!m_impl->is_alive()) return;
diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp
index be1e69b5c..bbaf2f05e 100644
--- a/src/libslic3r/Zipper.hpp
+++ b/src/libslic3r/Zipper.hpp
@@ -28,7 +28,7 @@ public:
// Will blow up in a runtime exception if the file cannot be created.
explicit Zipper(const std::string& zipfname,
- e_compression level = NO_COMPRESSION);
+ e_compression level = FAST_COMPRESSION);
~Zipper();
// No copies allwed, this is a file resource...
@@ -49,7 +49,7 @@ public:
/// Add a new binary file entry with an instantly given byte buffer.
/// This method throws exactly like finish_entry() does.
- void add_entry(const std::string& name, const std::uint8_t* data, size_t l);
+ void add_entry(const std::string& name, const void* data, size_t bytes);
// Writing data to the archive works like with standard streams. The target
// within the zip file is the entry created with the add_entry method.
diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h
index f6fbba994..814ee0807 100644
--- a/src/libslic3r/libslic3r.h
+++ b/src/libslic3r/libslic3r.h
@@ -17,6 +17,7 @@
#include <vector>
#include <cassert>
#include <cmath>
+#include <type_traits>
#include "Technologies.hpp"
#include "Semver.hpp"
@@ -248,6 +249,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
diff --git a/src/libslic3r/miniz_extension.cpp b/src/libslic3r/miniz_extension.cpp
index 17cc136fc..76b4cb4e5 100644
--- a/src/libslic3r/miniz_extension.cpp
+++ b/src/libslic3r/miniz_extension.cpp
@@ -1,9 +1,17 @@
+#include <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
diff --git a/src/libslic3r/miniz_extension.hpp b/src/libslic3r/miniz_extension.hpp
index 8d0967cbc..006226bf2 100644
--- a/src/libslic3r/miniz_extension.hpp
+++ b/src/libslic3r/miniz_extension.hpp
@@ -11,6 +11,25 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8);
bool close_zip_reader(mz_zip_archive *zip);
bool close_zip_writer(mz_zip_archive *zip);
-}
+class MZ_Archive {
+public:
+ mz_zip_archive arch;
+
+ MZ_Archive();
+
+ static std::string get_errorstr(mz_zip_error mz_err);
+
+ std::string get_errorstr() const
+ {
+ return get_errorstr(arch.m_last_error) + "!";
+ }
+
+ bool is_alive() const
+ {
+ return arch.m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED;
+ }
+};
+
+} // namespace Slic3r
#endif // MINIZ_EXTENSION_HPP
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index c8f7e9f1c..3711b12a1 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -31,9 +31,10 @@ set(SLIC3R_GUI_SOURCES
GUI/GLCanvas3DManager.cpp
GUI/Selection.hpp
GUI/Selection.cpp
- GUI/Gizmos/GLGizmos.hpp
GUI/Gizmos/GLGizmosManager.cpp
GUI/Gizmos/GLGizmosManager.hpp
+ GUI/Gizmos/GLGizmosCommon.cpp
+ GUI/Gizmos/GLGizmosCommon.hpp
GUI/Gizmos/GLGizmoBase.cpp
GUI/Gizmos/GLGizmoBase.hpp
GUI/Gizmos/GLGizmoMove.cpp
@@ -44,6 +45,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoScale.hpp
GUI/Gizmos/GLGizmoSlaSupports.cpp
GUI/Gizmos/GLGizmoSlaSupports.hpp
+ GUI/Gizmos/GLGizmoFdmSupports.cpp
+ GUI/Gizmos/GLGizmoFdmSupports.hpp
GUI/Gizmos/GLGizmoFlatten.cpp
GUI/Gizmos/GLGizmoFlatten.hpp
GUI/Gizmos/GLGizmoCut.cpp
@@ -139,12 +142,19 @@ set(SLIC3R_GUI_SOURCES
GUI/UpdateDialogs.hpp
GUI/FirmwareDialog.cpp
GUI/FirmwareDialog.hpp
- GUI/ProgressIndicator.hpp
- GUI/ProgressStatusBar.hpp
- GUI/ProgressStatusBar.cpp
GUI/PrintHostDialogs.cpp
GUI/PrintHostDialogs.hpp
- GUI/Job.hpp
+ GUI/Jobs/Job.hpp
+ GUI/Jobs/Job.cpp
+ GUI/Jobs/ArrangeJob.hpp
+ GUI/Jobs/ArrangeJob.cpp
+ GUI/Jobs/RotoptimizeJob.hpp
+ GUI/Jobs/RotoptimizeJob.cpp
+ GUI/Jobs/SLAImportJob.hpp
+ GUI/Jobs/SLAImportJob.cpp
+ GUI/Jobs/ProgressIndicator.hpp
+ GUI/ProgressStatusBar.hpp
+ GUI/ProgressStatusBar.cpp
GUI/Mouse3DController.cpp
GUI/Mouse3DController.hpp
GUI/DoubleSlider.cpp
@@ -174,6 +184,8 @@ set(SLIC3R_GUI_SOURCES
Utils/HexFile.cpp
Utils/HexFile.hpp
Utils/Thread.hpp
+ Utils/SLAImport.hpp
+ Utils/SLAImport.cpp
)
if (APPLE)
@@ -192,7 +204,7 @@ add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES})
encoding_check(libslic3r_gui)
-target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL OpenGL::GLU hidapi libcurl ${wxWidgets_LIBRARIES})
+target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL OpenGL::GLU hidapi libcurl ${wxWidgets_LIBRARIES} rt)
if(APPLE)
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY})
diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp
index 742941b84..2cc9de38c 100644
--- a/src/slic3r/GUI/3DBed.cpp
+++ b/src/slic3r/GUI/3DBed.cpp
@@ -259,7 +259,8 @@ Point Bed3D::point_projection(const Point& point) const
return m_polygon.point_projection(point);
}
-void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes) const
+void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor,
+ bool show_axes, bool show_texture) const
{
m_scale_factor = scale_factor;
@@ -270,9 +271,9 @@ void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool sho
switch (m_type)
{
- case System: { render_system(canvas, bottom); break; }
+ case System: { render_system(canvas, bottom, show_texture); break; }
default:
- case Custom: { render_custom(canvas, bottom); break; }
+ case Custom: { render_custom(canvas, bottom, show_texture); break; }
}
glsafe(::glDisable(GL_DEPTH_TEST));
@@ -384,12 +385,13 @@ void Bed3D::render_axes() const
m_axes.render();
}
-void Bed3D::render_system(GLCanvas3D& canvas, bool bottom) const
+void Bed3D::render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) const
{
if (!bottom)
render_model();
- render_texture(bottom, canvas);
+ if (show_texture)
+ render_texture(bottom, canvas);
}
void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
@@ -564,7 +566,7 @@ void Bed3D::render_model() const
}
}
-void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const
+void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture) const
{
if (m_texture_filename.empty() && m_model_filename.empty())
{
@@ -575,7 +577,8 @@ void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const
if (!bottom)
render_model();
- render_texture(bottom, canvas);
+ if (show_texture)
+ render_texture(bottom, canvas);
}
void Bed3D::render_default(bool bottom) const
diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp
index 3a5f95978..abdfca1fe 100644
--- a/src/slic3r/GUI/3DBed.hpp
+++ b/src/slic3r/GUI/3DBed.hpp
@@ -103,11 +103,15 @@ public:
// Return true if the bed shape changed, so the calee will update the UI.
bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model);
- const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; }
+ const BoundingBoxf3& get_bounding_box(bool extended) const {
+ return extended ? m_extended_bounding_box : m_bounding_box;
+ }
+
bool contains(const Point& point) const;
Point point_projection(const Point& point) const;
- void render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes) const;
+ void render(GLCanvas3D& canvas, bool bottom, float scale_factor,
+ bool show_axes, bool show_texture) const;
private:
void calc_bounding_boxes() const;
@@ -115,10 +119,10 @@ private:
void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox);
std::tuple<EType, std::string, std::string> detect_type(const Pointfs& shape) const;
void render_axes() const;
- void render_system(GLCanvas3D& canvas, bool bottom) const;
+ void render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) const;
void render_texture(bool bottom, GLCanvas3D& canvas) const;
void render_model() const;
- void render_custom(GLCanvas3D& canvas, bool bottom) const;
+ void render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture) const;
void render_default(bool bottom) const;
void reset();
};
diff --git a/src/slic3r/GUI/AppConfig.hpp b/src/slic3r/GUI/AppConfig.hpp
index 346d42b21..1e90d32e0 100644
--- a/src/slic3r/GUI/AppConfig.hpp
+++ b/src/slic3r/GUI/AppConfig.hpp
@@ -60,7 +60,7 @@ public:
boost::trim_all(key_trimmed);
assert(key_trimmed == key);
assert(! key_trimmed.empty());
-#endif _NDEBUG
+#endif // _NDEBUG
std::string &old = m_storage[section][key];
if (old != value) {
old = value;
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
index 7ce942855..cfd38354b 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
@@ -19,6 +19,7 @@
#include "libslic3r/Utils.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/GCode/PreviewData.hpp"
+#include "libslic3r/Format/SL1.hpp"
#include "libslic3r/libslic3r.h"
#include <cassert>
@@ -149,7 +150,7 @@ void BackgroundSlicingProcess::process_sla()
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
Zipper zipper(export_path);
- m_sla_print->export_raster(zipper);
+ m_sla_archive.export_print(zipper, *m_sla_print);
if (m_thumbnail_cb != nullptr)
{
@@ -473,9 +474,9 @@ void BackgroundSlicingProcess::prepare_upload()
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
} else {
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
-
+
Zipper zipper{source_path.string()};
- m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string());
+ m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string());
if (m_thumbnail_cb != nullptr)
{
ThumbnailsList thumbnails;
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
index b868a233f..efaea1d11 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
@@ -10,6 +10,7 @@
#include <wx/event.h>
#include "libslic3r/Print.hpp"
+#include "libslic3r/Format/SL1.hpp"
#include "slic3r/Utils/PrintHost.hpp"
@@ -19,6 +20,7 @@ class DynamicPrintConfig;
class GCodePreviewData;
class Model;
class SLAPrint;
+class SL1Archive;
class SlicingStatusEvent : public wxEvent
{
@@ -47,7 +49,7 @@ public:
~BackgroundSlicingProcess();
void set_fff_print(Print *print) { m_fff_print = print; }
- void set_sla_print(SLAPrint *print) { m_sla_print = print; }
+ void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); }
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
@@ -155,6 +157,7 @@ private:
GCodePreviewData *m_gcode_preview_data = nullptr;
// Callback function, used to write thumbnails into gcode.
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
+ SL1Archive m_sla_archive;
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
std::string m_temp_output_path;
// Output path provided by the user. The output path may be set even if the slicing is running,
diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp
index d0c768de5..e242033d9 100644
--- a/src/slic3r/GUI/ConfigWizard.cpp
+++ b/src/slic3r/GUI/ConfigWizard.cpp
@@ -2096,7 +2096,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
_(L("Filament Profiles Selection")), _(L("Filaments")), _(L("Type:")) ));
p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
- _(L("SLA Material Profiles Selection")) + " ", _(L("SLA Materials")), _(L("Layer height:")) ));
+ _(L("SLA Material Profiles Selection")) + " ", _(L("SLA Materials")), _(L("Type:")) ));
p->add_page(p->page_update = new PageUpdate(this));
diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp
index 186444f66..68193f84d 100644
--- a/src/slic3r/GUI/Field.cpp
+++ b/src/slic3r/GUI/Field.cpp
@@ -1291,9 +1291,13 @@ void PointCtrl::set_value(const boost::any& value, bool change_event)
boost::any& PointCtrl::get_value()
{
double x, y;
- x_textctrl->GetValue().ToDouble(&x);
- y_textctrl->GetValue().ToDouble(&y);
-
+ if (!x_textctrl->GetValue().ToDouble(&x) ||
+ !y_textctrl->GetValue().ToDouble(&y))
+ {
+ set_value(m_value.empty() ? Vec2d(0.0, 0.0) : m_value, true);
+ show_error(m_parent, _L("Invalid numeric input."));
+ }
+ else
if (m_opt.min > x || x > m_opt.max ||
m_opt.min > y || y > m_opt.max)
{
@@ -1303,7 +1307,7 @@ boost::any& PointCtrl::get_value()
if (y > m_opt.max) y = m_opt.max;
set_value(Vec2d(x, y), true);
- show_error(m_parent, _(L("Input value is out of range")));
+ show_error(m_parent, _L("Input value is out of range"));
}
return m_value = Vec2d(x, y);
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 74abbebdd..40568f8de 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -1,5 +1,4 @@
#include "libslic3r/libslic3r.h"
-#include "slic3r/GUI/Gizmos/GLGizmos.hpp"
#include "GLCanvas3D.hpp"
#include "admesh/stl.h"
@@ -1744,6 +1743,8 @@ void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObje
m_render_sla_auxiliaries = visible;
for (GLVolume* vol : m_volumes.volumes) {
+ if (vol->composite_id.object_id == 1000)
+ continue; // the wipe tower
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
&& vol->composite_id.volume_id < 0)
@@ -1754,10 +1755,15 @@ void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObje
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx)
{
for (GLVolume* vol : m_volumes.volumes) {
- if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
- && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)) {
- vol->is_active = visible;
- vol->force_native_color = (instance_idx != -1);
+ if (vol->composite_id.object_id == 1000) { // wipe tower
+ vol->is_active = (visible && mo == nullptr);
+ }
+ else {
+ if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
+ && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)) {
+ vol->is_active = visible;
+ vol->force_native_color = (instance_idx != -1);
+ }
}
}
if (visible && !mo)
@@ -5186,7 +5192,7 @@ void GLCanvas3D::_picking_pass() const
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
- m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane();
+ m_camera_clipping_plane = m_gizmos.get_clipping_plane();
if (m_camera_clipping_plane.is_active()) {
::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data());
::glEnable(GL_CLIP_PLANE0);
@@ -5335,14 +5341,19 @@ void GLCanvas3D::_render_background() const
glsafe(::glPopMatrix());
}
-void GLCanvas3D::_render_bed(float theta, bool show_axes) const
+void GLCanvas3D::_render_bed(bool bottom, bool show_axes) const
{
float scale_factor = 1.0;
#if ENABLE_RETINA_GL
scale_factor = m_retina_helper->get_scale_factor();
#endif // ENABLE_RETINA_GL
+
+ bool show_texture = ! bottom ||
+ (m_gizmos.get_current_type() != GLGizmosManager::FdmSupports
+ && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports);
+
#if ENABLE_NON_STATIC_CANVAS_MANAGER
- wxGetApp().plater()->get_bed().render(const_cast<GLCanvas3D&>(*this), theta, scale_factor, show_axes);
+ wxGetApp().plater()->get_bed().render(const_cast<GLCanvas3D&>(*this), bottom, scale_factor, show_axes, show_texture);
#else
m_bed.render(const_cast<GLCanvas3D&>(*this), theta, scale_factor, show_axes);
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
@@ -5355,7 +5366,7 @@ void GLCanvas3D::_render_objects() const
glsafe(::glEnable(GL_DEPTH_TEST));
- m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane();
+ m_camera_clipping_plane = m_gizmos.get_clipping_plane();
if (m_picking_enabled)
{
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 07fa00b9d..c7d4f4a06 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -755,7 +755,7 @@ private:
void _picking_pass() const;
void _rectangular_selection_picking_pass() const;
void _render_background() const;
- void _render_bed(float theta, bool show_axes) const;
+ void _render_bed(bool bottom, bool show_axes) const;
void _render_objects() const;
void _render_selection() const;
#if ENABLE_RENDER_SELECTION_CENTER
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 234847bc4..aee71f16e 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -24,6 +24,7 @@
#include <wx/filefn.h>
#include <wx/sysopt.h>
#include <wx/msgdlg.h>
+#include <wx/richmsgdlg.h>
#include <wx/log.h>
#include <wx/intl.h>
@@ -321,20 +322,41 @@ bool GUI_App::on_init_inner()
set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
app_config = new AppConfig();
- preset_bundle = new PresetBundle();
-
- // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory
- // supplied as argument to --datadir; in that case we should still run the wizard
- preset_bundle->setup_directories();
// load settings
app_conf_exists = app_config->exists();
if (app_conf_exists) {
app_config->load();
}
-
+
+ std::string msg = Http::tls_global_init();
+ wxRichMessageDialog
+ dlg(nullptr,
+ wxString::Format(_(L("%s\nDo you want to continue?")), _(msg)),
+ "PrusaSlicer", wxICON_QUESTION | wxYES_NO);
+
+ bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes";
+ std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location");
+ ssl_accept = ssl_accept && ssl_cert_store == Http::tls_system_cert_store();
+
+ dlg.ShowCheckBox(_(L("Remember my choice")));
+ if (!msg.empty() && !ssl_accept) {
+ if (dlg.ShowModal() != wxID_YES) return false;
+
+ app_config->set("tls_cert_store_accepted",
+ dlg.IsCheckBoxChecked() ? "yes" : "no");
+ app_config->set("tls_accepted_cert_store_location",
+ dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : "");
+ }
+
app_config->set("version", SLIC3R_VERSION);
app_config->save();
+
+ preset_bundle = new PresetBundle();
+
+ // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory
+ // supplied as argument to --datadir; in that case we should still run the wizard
+ preset_bundle->setup_directories();
#ifdef __WXMSW__
associate_3mf_files();
@@ -925,7 +947,8 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
local_menu->AppendSeparator();
auto mode_menu = new wxMenu();
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("Simple")), _(L("Simple View Mode")));
- mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _(L("Advanced")), _(L("Advanced View Mode")));
+// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _(L("Advanced")), _(L("Advanced View Mode")));
+ mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Advanced View Mode"));
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("Expert")), _(L("Expert View Mode")));
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if(get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple);
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if(get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced);
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 5d06edad9..1f2ce0221 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -1209,6 +1209,13 @@ static bool improper_category(const std::string& category, const int extruders_c
(!is_object_settings && category == "Support material");
}
+static bool is_object_item(ItemType item_type)
+{
+ return item_type & itObject || item_type & itInstance ||
+ // multi-selection in ObjectList, but full_object in Selection
+ (item_type == itUndef && scene_selection().is_single_full_object());
+}
+
void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part)
{
auto options = get_options(is_part);
@@ -1579,9 +1586,7 @@ wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_)
const ItemType item_type = m_objects_model->GetItemType(GetSelection());
if (item_type == itUndef && !selection.is_single_full_object())
return nullptr;
- const bool is_object_settings = item_type & itObject || item_type & itInstance ||
- // multi-selection in ObjectList, but full_object in Selection
- (item_type == itUndef && selection.is_single_full_object());
+ const bool is_object_settings = is_object_item(item_type);
create_freq_settings_popupmenu(menu, is_object_settings);
if (mode == comAdvanced)
@@ -1821,8 +1826,7 @@ wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
const wxDataViewItem selected_item = GetSelection();
wxDataViewItem item = m_objects_model->GetItemType(selected_item) & itSettings ? m_objects_model->GetParent(selected_item) : selected_item;
- const bool is_part = !(m_objects_model->GetItemType(item) == itObject || scene_selection().is_single_full_object());
- get_options_menu(settings_menu, is_part);
+ get_options_menu(settings_menu, !is_object_item(m_objects_model->GetItemType(item)));
for (auto cat : settings_menu) {
append_menu_item(menu, wxID_ANY, _(cat.first), "",
@@ -2067,37 +2071,40 @@ void ObjectList::load_shape_object(const std::string& type_name)
// Create mesh
BoundingBoxf3 bb;
TriangleMesh mesh = create_mesh(type_name, bb);
+ load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name));
+}
+void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name)
+{
// Add mesh to model as a new object
Model& model = wxGetApp().plater()->model();
- const wxString name = _(L("Shape")) + "-" + _(type_name);
#ifdef _DEBUG
check_model_ids_validity(model);
#endif /* _DEBUG */
-
+
std::vector<size_t> object_idxs;
ModelObject* new_object = model.add_object();
new_object->name = into_u8(name);
new_object->add_instance(); // each object should have at list one instance
-
+
ModelVolume* new_volume = new_object->add_volume(mesh);
new_volume->name = into_u8(name);
// set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
new_object->invalidate_bounding_box();
-
+
new_object->center_around_origin();
new_object->ensure_on_bed();
-
+
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
-
+
object_idxs.push_back(model.objects.size() - 1);
#ifdef _DEBUG
check_model_ids_validity(model);
#endif /* _DEBUG */
-
+
paste_objects_into_list(object_idxs);
#ifdef _DEBUG
diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp
index 609411cd5..72e130737 100644
--- a/src/slic3r/GUI/GUI_ObjectList.hpp
+++ b/src/slic3r/GUI/GUI_ObjectList.hpp
@@ -24,6 +24,7 @@ class ConfigOptionsGroup;
class DynamicPrintConfig;
class ModelObject;
class ModelVolume;
+class TriangleMesh;
enum class ModelVolumeType : int;
// FIXME: broken build on mac os because of this is missing:
@@ -265,6 +266,7 @@ public:
void load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type);
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
void load_shape_object(const std::string &type_name);
+ void load_mesh_object(const TriangleMesh &mesh, const wxString &name);
void del_object(const int obj_idx);
void del_subobject_item(wxDataViewItem& item);
void del_settings_from_config(const wxDataViewItem& parent_item);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
index 0d19a86af..8a2b71976 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
@@ -32,6 +32,8 @@ static const float CONSTRAINED_COLOR[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
class ImGuiWrapper;
class GLCanvas3D;
class ClippingPlane;
+enum class CommonGizmosDataID;
+class CommonGizmosDataPool;
class GLGizmoBase
{
@@ -101,6 +103,7 @@ protected:
ImGuiWrapper* m_imgui;
bool m_first_input_window_render;
mutable std::string m_tooltip;
+ CommonGizmosDataPool* m_c;
public:
GLGizmoBase(GLCanvas3D& parent,
@@ -128,6 +131,8 @@ public:
bool is_activable() const { return on_is_activable(); }
bool is_selectable() const { return on_is_selectable(); }
+ CommonGizmosDataID get_requirements() const { return on_get_requirements(); }
+ void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; }
unsigned int get_sprite_id() const { return m_sprite_id; }
@@ -161,6 +166,7 @@ protected:
virtual void on_set_hover_id() {}
virtual bool on_is_activable() const { return true; }
virtual bool on_is_selectable() const { return true; }
+ virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); }
virtual void on_enable_grabber(unsigned int id) {}
virtual void on_disable_grabber(unsigned int id) {}
virtual void on_start_dragging() {}
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
new file mode 100644
index 000000000..8094d10ad
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
@@ -0,0 +1,673 @@
+// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
+#include "GLGizmoFdmSupports.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
+
+#include <GL/glew.h>
+
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/PresetBundle.hpp"
+#include "slic3r/GUI/Camera.hpp"
+#include "libslic3r/Model.hpp"
+
+
+
+namespace Slic3r {
+namespace GUI {
+
+GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
+ : GLGizmoBase(parent, icon_filename, sprite_id)
+ , m_quadric(nullptr)
+{
+ m_clipping_plane.reset(new ClippingPlane());
+ m_quadric = ::gluNewQuadric();
+ if (m_quadric != nullptr)
+ // using GLU_FILL does not work when the instance's transformation
+ // contains mirroring (normals are reverted)
+ ::gluQuadricDrawStyle(m_quadric, GLU_FILL);
+}
+
+GLGizmoFdmSupports::~GLGizmoFdmSupports()
+{
+ if (m_quadric != nullptr)
+ ::gluDeleteQuadric(m_quadric);
+}
+
+bool GLGizmoFdmSupports::on_init()
+{
+ m_shortcut_key = WXK_CONTROL_L;
+
+ m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
+ m_desc["reset_direction"] = _L("Reset direction");
+ m_desc["cursor_size"] = _L("Cursor size") + ": ";
+ m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
+ m_desc["enforce"] = _L("Enforce supports");
+ m_desc["block_caption"] = _L("Right mouse button") + " ";
+ m_desc["block"] = _L("Block supports");
+ m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
+ m_desc["remove"] = _L("Remove selection");
+ m_desc["remove_all"] = _L("Remove all");
+
+ return true;
+}
+
+void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const Selection& selection)
+{
+ const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr;
+ if (! mo)
+ return;
+
+ if (mo && selection.is_from_single_instance()
+ && (mo != m_old_mo || mo->volumes.size() != m_old_volumes_size))
+ {
+ update_mesh();
+ m_old_mo = mo;
+ m_old_volumes_size = mo->volumes.size();
+ }
+}
+
+
+
+void GLGizmoFdmSupports::on_render() const
+{
+ const Selection& selection = m_parent.get_selection();
+
+ glsafe(::glEnable(GL_BLEND));
+ glsafe(::glEnable(GL_DEPTH_TEST));
+
+ render_triangles(selection);
+ m_c->object_clipper()->render_cut();
+ render_cursor_circle();
+
+ glsafe(::glDisable(GL_BLEND));
+}
+
+void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
+{
+ const ModelObject* mo = m_c->selection_info()->model_object();
+
+ glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
+ ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); } );
+ glsafe(::glPolygonOffset(-1.0, 1.0));
+
+ int mesh_id = -1;
+ for (const ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
+ continue;
+
+ ++mesh_id;
+
+ const Transform3d trafo_matrix =
+ mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() *
+ mv->get_matrix();
+
+ glsafe(::glPushMatrix());
+ glsafe(::glMultMatrixd(trafo_matrix.data()));
+
+ // Now render both enforcers and blockers.
+ for (int i=0; i<2; ++i) {
+ if (m_ivas[mesh_id][i].has_VBOs()) {
+ glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f));
+ m_ivas[mesh_id][i].render();
+ }
+ }
+ glsafe(::glPopMatrix());
+ }
+}
+
+
+void GLGizmoFdmSupports::render_cursor_circle() const
+{
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ float zoom = (float)camera.get_zoom();
+ float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+
+ Size cnv_size = m_parent.get_canvas_size();
+ float cnv_half_width = 0.5f * (float)cnv_size.get_width();
+ float cnv_half_height = 0.5f * (float)cnv_size.get_height();
+ if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f))
+ return;
+ Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1));
+ Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1));
+ center = center * inv_zoom;
+
+ glsafe(::glLineWidth(1.5f));
+ float color[3];
+ color[0] = 0.f;
+ color[1] = 1.f;
+ color[2] = 0.3f;
+ glsafe(::glColor3fv(color));
+ glsafe(::glDisable(GL_DEPTH_TEST));
+
+ glsafe(::glPushMatrix());
+ glsafe(::glLoadIdentity());
+ // ensure that the circle is renderered inside the frustrum
+ glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5)));
+ // ensure that the overlay fits the frustrum near z plane
+ double gui_scale = camera.get_gui_scale();
+ glsafe(::glScaled(gui_scale, gui_scale, 1.0));
+
+ glsafe(::glPushAttrib(GL_ENABLE_BIT));
+ glsafe(::glLineStipple(4, 0xAAAA));
+ glsafe(::glEnable(GL_LINE_STIPPLE));
+
+ ::glBegin(GL_LINE_LOOP);
+ for (double angle=0; angle<2*M_PI; angle+=M_PI/20.)
+ ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle)));
+ glsafe(::glEnd());
+
+ glsafe(::glPopAttrib());
+ glsafe(::glPopMatrix());
+}
+
+
+void GLGizmoFdmSupports::on_render_for_picking() const
+{
+
+}
+
+
+
+void GLGizmoFdmSupports::update_mesh()
+{
+ wxBusyCursor wait;
+
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ size_t num_of_volumes = 0;
+ for (const ModelVolume* mv : mo->volumes)
+ if (mv->is_model_part())
+ ++num_of_volumes;
+
+ m_selected_facets.resize(num_of_volumes);
+ m_neighbors.resize(num_of_volumes);
+ m_ivas.clear();
+ m_ivas.resize(num_of_volumes);
+
+ int volume_id = -1;
+ for (const ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
+ continue;
+
+ ++volume_id;
+
+ // This mesh does not account for the possible Z up SLA offset.
+ const TriangleMesh* mesh = &mv->mesh();
+
+ m_selected_facets[volume_id].assign(mesh->its.indices.size(), FacetSupportType::NONE);
+
+ // Load current state from ModelVolume.
+ for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) {
+ const std::vector<int>& list = mv->m_supported_facets.get_facets(type);
+ for (int i : list)
+ m_selected_facets[volume_id][i] = type;
+ }
+ update_vertex_buffers(mv, volume_id, true, true);
+
+ m_neighbors[volume_id].resize(3 * mesh->its.indices.size());
+
+ // Prepare vector of vertex_index - facet_index pairs to quickly find adjacent facets
+ for (size_t i=0; i<mesh->its.indices.size(); ++i) {
+ const stl_triangle_vertex_indices& ind = mesh->its.indices[i];
+ m_neighbors[volume_id][3*i] = std::make_pair(ind(0), i);
+ m_neighbors[volume_id][3*i+1] = std::make_pair(ind(1), i);
+ m_neighbors[volume_id][3*i+2] = std::make_pair(ind(2), i);
+ }
+ std::sort(m_neighbors[volume_id].begin(), m_neighbors[volume_id].end());
+ }
+}
+
+
+
+
+bool operator<(const GLGizmoFdmSupports::NeighborData& a, const GLGizmoFdmSupports::NeighborData& b) {
+ return a.first < b.first;
+}
+
+
+// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
+// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
+// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
+// concludes that the event was not intended for it, it should return false.
+bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
+{
+ if (action == SLAGizmoEventType::MouseWheelUp
+ || action == SLAGizmoEventType::MouseWheelDown) {
+ if (control_down) {
+ double pos = m_c->object_clipper()->get_position();
+ pos = action == SLAGizmoEventType::MouseWheelDown
+ ? std::max(0., pos - 0.01)
+ : std::min(1., pos + 0.01);
+ m_c->object_clipper()->set_position(pos, true);
+ return true;
+ }
+ else if (alt_down) {
+ m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown
+ ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
+ : std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
+ m_parent.set_as_dirty();
+ return true;
+ }
+ }
+
+ if (action == SLAGizmoEventType::ResetClippingPlane) {
+ m_c->object_clipper()->set_position(-1., false);
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::LeftDown
+ || action == SLAGizmoEventType::RightDown
+ || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) {
+
+ FacetSupportType new_state = FacetSupportType::NONE;
+ if (! shift_down) {
+ if (action == SLAGizmoEventType::Dragging)
+ new_state = m_button_down == Button::Left
+ ? FacetSupportType::ENFORCER
+ : FacetSupportType::BLOCKER;
+ else
+ new_state = action == SLAGizmoEventType::LeftDown
+ ? FacetSupportType::ENFORCER
+ : FacetSupportType::BLOCKER;
+ }
+
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ const Selection& selection = m_parent.get_selection();
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
+ const Transform3d& instance_trafo = mi->get_transformation().get_matrix();
+
+ std::vector<std::vector<std::pair<Vec3f, size_t>>> hit_positions_and_facet_ids;
+ bool some_mesh_was_hit = false;
+
+ Vec3f normal = Vec3f::Zero();
+ Vec3f hit = Vec3f::Zero();
+ size_t facet = 0;
+ Vec3f closest_hit = Vec3f::Zero();
+ double closest_hit_squared_distance = std::numeric_limits<double>::max();
+ size_t closest_facet = 0;
+ size_t closest_hit_mesh_id = size_t(-1);
+
+ // Transformations of individual meshes
+ std::vector<Transform3d> trafo_matrices;
+
+ int mesh_id = -1;
+ // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
+ for (const ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
+ continue;
+
+ ++mesh_id;
+
+ trafo_matrices.push_back(instance_trafo * mv->get_matrix());
+ hit_positions_and_facet_ids.push_back(std::vector<std::pair<Vec3f, size_t>>());
+
+ if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
+ mouse_position,
+ trafo_matrices[mesh_id],
+ camera,
+ hit,
+ normal,
+ m_clipping_plane.get(),
+ &facet))
+ {
+ // Is this hit the closest to the camera so far?
+ double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm();
+ if (hit_squared_distance < closest_hit_squared_distance) {
+ closest_hit_squared_distance = hit_squared_distance;
+ closest_facet = facet;
+ closest_hit_mesh_id = mesh_id;
+ closest_hit = hit;
+ }
+ }
+ }
+ // We now know where the ray hit, let's save it and cast another ray
+ if (closest_hit_mesh_id != size_t(-1)) // only if there is at least one hit
+ hit_positions_and_facet_ids[closest_hit_mesh_id].emplace_back(closest_hit, closest_facet);
+
+
+ // Now propagate the hits
+ mesh_id = -1;
+ for (const ModelVolume* mv : mo->volumes) {
+
+ if (! mv->is_model_part())
+ continue;
+
+ ++mesh_id;
+ bool update_both = false;
+
+ const Transform3d& trafo_matrix = trafo_matrices[mesh_id];
+
+ // Calculate how far can a point be from the line (in mesh coords).
+ // FIXME: The scaling of the mesh can be non-uniform.
+ const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor();
+ const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.;
+ const float limit = pow(m_cursor_radius/avg_scaling , 2.f);
+
+ // For all hits on this mesh...
+ for (const std::pair<Vec3f, size_t>& hit_and_facet : hit_positions_and_facet_ids[mesh_id]) {
+ some_mesh_was_hit = true;
+ const TriangleMesh* mesh = &mv->mesh();
+ std::vector<NeighborData>& neighbors = m_neighbors[mesh_id];
+
+ // Calculate direction from camera to the hit (in mesh coords):
+ Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast<float>() - hit_and_facet.first).normalized();
+
+ // A lambda to calculate distance from the centerline:
+ auto squared_distance_from_line = [&hit_and_facet, &dir](const Vec3f point) -> float {
+ Vec3f diff = hit_and_facet.first - point;
+ return (diff - diff.dot(dir) * dir).squaredNorm();
+ };
+
+ // A lambda to determine whether this facet is potentionally visible (still can be obscured)
+ auto faces_camera = [&dir](const ModelVolume* mv, const size_t& facet) -> bool {
+ return (mv->mesh().stl.facet_start[facet].normal.dot(dir) > 0.);
+ };
+ // Now start with the facet the pointer points to and check all adjacent facets. neighbors vector stores
+ // pairs of vertex_idx - facet_idx and is sorted with respect to the former. Neighboring facet index can be
+ // quickly found by finding a vertex in the list and read the respective facet ids.
+ std::vector<size_t> facets_to_select{hit_and_facet.second};
+ NeighborData vertex = std::make_pair(0, 0);
+ std::vector<bool> visited(m_selected_facets[mesh_id].size(), false); // keep track of facets we already processed
+ size_t facet_idx = 0; // index into facets_to_select
+ auto it = neighbors.end();
+ while (facet_idx < facets_to_select.size()) {
+ size_t facet = facets_to_select[facet_idx];
+ if (! visited[facet]) {
+ // check all three vertices and in case they're close enough, find the remaining facets
+ // and add them to the list to be proccessed later
+ for (size_t i=0; i<3; ++i) {
+ vertex.first = mesh->its.indices[facet](i); // vertex index
+ float dist = squared_distance_from_line(mesh->its.vertices[vertex.first]);
+ if (dist < limit) {
+ it = std::lower_bound(neighbors.begin(), neighbors.end(), vertex);
+ while (it != neighbors.end() && it->first == vertex.first) {
+ if (it->second != facet && faces_camera(mv, it->second))
+ facets_to_select.push_back(it->second);
+ ++it;
+ }
+ }
+ }
+ visited[facet] = true;
+ }
+ ++facet_idx;
+ }
+
+ // Now just select all facets that passed.
+ for (size_t next_facet : facets_to_select) {
+ FacetSupportType& facet = m_selected_facets[mesh_id][next_facet];
+
+ if (facet != new_state && facet != FacetSupportType::NONE) {
+ // this triangle is currently in the other VBA.
+ // Both VBAs need to be refreshed.
+ update_both = true;
+ }
+ facet = new_state;
+ }
+ }
+
+ update_vertex_buffers(mv, mesh_id,
+ new_state == FacetSupportType::ENFORCER || update_both,
+ new_state == FacetSupportType::BLOCKER || update_both
+ );
+ }
+
+ if (some_mesh_was_hit)
+ {
+ if (m_button_down == Button::None)
+ m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
+ // Force rendering. In case the user is dragging, the queue can be
+ // flooded by wxEVT_MOVING event and rendering would be skipped.
+ m_parent.render();
+ return true;
+ }
+ if (action == SLAGizmoEventType::Dragging && m_button_down != Button::None) {
+ // Same as above. We don't want the cursor to freeze when we
+ // leave the mesh while painting.
+ m_parent.render();
+ return true;
+ }
+ }
+
+ if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
+ && m_button_down != Button::None) {
+ m_button_down = Button::None;
+
+ // Synchronize gizmo with ModelVolume data.
+ ModelObject* mo = m_c->selection_info()->model_object();
+ int idx = -1;
+ for (ModelVolume* mv : mo->volumes) {
+ ++idx;
+ if (! mv->is_model_part())
+ continue;
+ for (int i=0; i<int(m_selected_facets[idx].size()); ++i)
+ mv->m_supported_facets.set_facet(i, m_selected_facets[idx][i]);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+
+void GLGizmoFdmSupports::update_vertex_buffers(const ModelVolume* mv,
+ int mesh_id,
+ bool update_enforcers,
+ bool update_blockers)
+{
+ const TriangleMesh* mesh = &mv->mesh();
+
+ for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) {
+ if ((type == FacetSupportType::ENFORCER && ! update_enforcers)
+ || (type == FacetSupportType::BLOCKER && ! update_blockers))
+ continue;
+
+ GLIndexedVertexArray& iva = m_ivas[mesh_id][type==FacetSupportType::ENFORCER ? 0 : 1];
+ iva.release_geometry();
+ size_t triangle_cnt=0;
+ for (size_t facet_idx=0; facet_idx<m_selected_facets[mesh_id].size(); ++facet_idx) {
+ FacetSupportType status = m_selected_facets[mesh_id][facet_idx];
+ if (status != type)
+ continue;
+ for (int i=0; i<3; ++i)
+ iva.push_geometry(mesh->its.vertices[mesh->its.indices[facet_idx](i)].cast<double>(),
+ MeshRaycaster::get_triangle_normal(mesh->its, facet_idx).cast<double>());
+ iva.push_triangle(3*triangle_cnt, 3*triangle_cnt+1, 3*triangle_cnt+2);
+ ++triangle_cnt;
+ }
+ if (! m_selected_facets[mesh_id].empty())
+ iva.finalize_geometry(true);
+ }
+}
+
+
+void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit)
+{
+ if (! m_c->selection_info()->model_object())
+ return;
+
+ const float approx_height = m_imgui->scaled(18.0f);
+ y = std::min(y, bottom_limit - approx_height);
+ m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
+ m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
+
+ // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
+ const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
+ const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
+ const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
+ const float minimal_slider_width = m_imgui->scaled(4.f);
+
+ float caption_max = 0.f;
+ float total_text_max = 0.;
+ for (const std::string& t : {"enforce", "block", "remove"}) {
+ caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t+"_caption")).x);
+ total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x);
+ }
+ caption_max += m_imgui->scaled(1.f);
+ total_text_max += m_imgui->scaled(1.f);
+
+ float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left);
+ window_width = std::max(window_width, total_text_max);
+ window_width = std::max(window_width, button_width);
+
+ auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
+ static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
+ ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
+ m_imgui->text(caption);
+ ImGui::PopStyleColor();
+ ImGui::SameLine(caption_max);
+ m_imgui->text(text);
+ };
+
+ for (const std::string& t : {"enforce", "block", "remove"})
+ draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
+
+ m_imgui->text("");
+
+ if (m_imgui->button(m_desc.at("remove_all"))) {
+ ModelObject* mo = m_c->selection_info()->model_object();
+ int idx = -1;
+ for (ModelVolume* mv : mo->volumes) {
+ ++idx;
+ if (mv->is_model_part()) {
+ m_selected_facets[idx].assign(m_selected_facets[idx].size(), FacetSupportType::NONE);
+ mv->m_supported_facets.clear();
+ update_vertex_buffers(mv, idx, true, true);
+ m_parent.set_as_dirty();
+ }
+ }
+ }
+
+ const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
+
+ m_imgui->text(m_desc.at("cursor_size"));
+ ImGui::SameLine(clipping_slider_left);
+ ImGui::PushItemWidth(window_width - clipping_slider_left);
+ ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
+ if (ImGui::IsItemHovered()) {
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(max_tooltip_width);
+ ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
+ ImGui::Separator();
+ if (m_c->object_clipper()->get_position() == 0.f)
+ m_imgui->text(m_desc.at("clipping_of_view"));
+ else {
+ if (m_imgui->button(m_desc.at("reset_direction"))) {
+ wxGetApp().CallAfter([this](){
+ m_c->object_clipper()->set_position(-1., false);
+ });
+ }
+ }
+
+ ImGui::SameLine(clipping_slider_left);
+ ImGui::PushItemWidth(window_width - clipping_slider_left);
+ float clp_dist = m_c->object_clipper()->get_position();
+ if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f"))
+ m_c->object_clipper()->set_position(clp_dist, true);
+ if (ImGui::IsItemHovered()) {
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(max_tooltip_width);
+ ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
+
+
+ m_imgui->end();
+}
+
+bool GLGizmoFdmSupports::on_is_activable() const
+{
+ const Selection& selection = m_parent.get_selection();
+
+ if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF
+ || !selection.is_single_full_instance())
+ return false;
+
+ // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
+ const Selection::IndicesList& list = selection.get_volume_idxs();
+ for (const auto& idx : list)
+ if (selection.get_volume(idx)->is_outside)
+ return false;
+
+ return true;
+}
+
+bool GLGizmoFdmSupports::on_is_selectable() const
+{
+ return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF );
+}
+
+std::string GLGizmoFdmSupports::on_get_name() const
+{
+ return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data();
+}
+
+
+CommonGizmosDataID GLGizmoFdmSupports::on_get_requirements() const
+{
+ return CommonGizmosDataID(
+ int(CommonGizmosDataID::SelectionInfo)
+ | int(CommonGizmosDataID::InstancesHider)
+ | int(CommonGizmosDataID::Raycaster)
+ | int(CommonGizmosDataID::HollowedMesh)
+ | int(CommonGizmosDataID::ObjectClipper)
+ | int(CommonGizmosDataID::SupportsClipper));
+}
+
+
+void GLGizmoFdmSupports::on_set_state()
+{
+ if (m_state == m_old_state)
+ return;
+
+ if (m_state == On && m_old_state != On) { // the gizmo was just turned on
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("FDM gizmo turned on")));
+ }
+ if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
+ // we are actually shutting down
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("FDM gizmo turned off")));
+ m_old_mo = nullptr;
+ m_ivas.clear();
+ m_neighbors.clear();
+ m_selected_facets.clear();
+ }
+ m_old_state = m_state;
+}
+
+
+
+void GLGizmoFdmSupports::on_start_dragging()
+{
+
+}
+
+
+void GLGizmoFdmSupports::on_stop_dragging()
+{
+
+}
+
+
+
+void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive& ar)
+{
+
+}
+
+
+
+void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive& ar) const
+{
+
+}
+
+
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
new file mode 100644
index 000000000..994218f69
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
@@ -0,0 +1,97 @@
+#ifndef slic3r_GLGizmoFdmSupports_hpp_
+#define slic3r_GLGizmoFdmSupports_hpp_
+
+#include "GLGizmoBase.hpp"
+
+#include "slic3r/GUI/3DScene.hpp"
+
+#include <cereal/types/vector.hpp>
+
+
+namespace Slic3r {
+
+enum class FacetSupportType : int8_t;
+
+namespace GUI {
+
+enum class SLAGizmoEventType : unsigned char;
+
+class GLGizmoFdmSupports : public GLGizmoBase
+{
+private:
+ const ModelObject* m_old_mo = nullptr;
+ size_t m_old_volumes_size = 0;
+
+ GLUquadricObj* m_quadric;
+
+ float m_cursor_radius = 2.f;
+ static constexpr float CursorRadiusMin = 0.f;
+ static constexpr float CursorRadiusMax = 8.f;
+ static constexpr float CursorRadiusStep = 0.2f;
+
+ // For each model-part volume, store a list of statuses of
+ // individual facets (one of the enum values above).
+ std::vector<std::vector<FacetSupportType>> m_selected_facets;
+
+ // Store two vertex buffer arrays (for enforcers/blockers)
+ // for each model-part volume.
+ std::vector<std::array<GLIndexedVertexArray, 2>> m_ivas;
+
+ void update_vertex_buffers(const ModelVolume* mv,
+ int mesh_id,
+ bool update_enforcers,
+ bool update_blockers);
+
+public:
+ GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
+ ~GLGizmoFdmSupports() override;
+ void set_fdm_support_data(ModelObject* model_object, const Selection& selection);
+ bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
+ using NeighborData = std::pair<size_t, size_t>;
+
+
+private:
+ bool on_init() override;
+ void on_render() const override;
+ void on_render_for_picking() const override;
+
+ void render_triangles(const Selection& selection) const;
+ void render_cursor_circle() const;
+ void update_mesh();
+
+ float m_clipping_plane_distance = 0.f;
+ std::unique_ptr<ClippingPlane> m_clipping_plane;
+
+ // This map holds all translated description texts, so they can be easily referenced during layout calculations
+ // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
+ std::map<std::string, wxString> m_desc;
+
+ enum class Button {
+ None,
+ Left,
+ Right
+ };
+
+ Button m_button_down = Button::None;
+ EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
+
+ std::vector<std::vector<NeighborData>> m_neighbors; // pairs of vertex_index - facet_index for each mesh
+
+protected:
+ void on_set_state() override;
+ void on_start_dragging() override;
+ void on_stop_dragging() override;
+ void on_render_input_window(float x, float y, float bottom_limit) override;
+ std::string on_get_name() const override;
+ bool on_is_activable() const override;
+ bool on_is_selectable() const override;
+ void on_load(cereal::BinaryInputArchive& ar) override;
+ void on_save(cereal::BinaryOutputArchive& ar) const override;
+ CommonGizmosDataID on_get_requirements() const override;
+};
+
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLGizmoFdmSupports_hpp_
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
index 9fae8893a..7f33916cd 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
@@ -2,6 +2,7 @@
#include "GLGizmoFlatten.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include <numeric>
@@ -26,20 +27,15 @@ bool GLGizmoFlatten::on_init()
void GLGizmoFlatten::on_set_state()
{
- // m_model_object pointer can be invalid (for instance because of undo/redo action),
- // we should recover it from the object id
- m_model_object = nullptr;
- for (const auto mo : wxGetApp().model().objects) {
- if (mo->id() == m_model_object_id) {
- m_model_object = mo;
- break;
- }
- }
-
if (m_state == On && is_plane_update_necessary())
update_planes();
}
+CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const
+{
+ return CommonGizmosDataID::SelectionInfo;
+}
+
std::string GLGizmoFlatten::on_get_name() const
{
return (_(L("Place on face")) + " [F]").ToUTF8().data();
@@ -132,18 +128,17 @@ void GLGizmoFlatten::on_render_for_picking() const
void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object)
{
m_starting_center = Vec3d::Zero();
- if (m_model_object != model_object) {
+ if (model_object != m_old_model_object) {
m_planes.clear();
m_planes_valid = false;
}
- m_model_object = model_object;
- m_model_object_id = model_object ? model_object->id() : 0;
}
void GLGizmoFlatten::update_planes()
{
+ const ModelObject* mo = m_c->selection_info()->model_object();
TriangleMesh ch;
- for (const ModelVolume* vol : m_model_object->volumes)
+ for (const ModelVolume* vol : mo->volumes)
{
if (vol->type() != ModelVolumeType::MODEL_PART)
continue;
@@ -153,7 +148,7 @@ void GLGizmoFlatten::update_planes()
}
ch = ch.convex_hull_3d();
m_planes.clear();
- const Transform3d& inst_matrix = m_model_object->instances.front()->get_matrix(true);
+ const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true);
// Following constants are used for discarding too small polygons.
const float minimal_area = 5.f; // in square mm (world coordinates)
@@ -331,12 +326,13 @@ void GLGizmoFlatten::update_planes()
// Planes are finished - let's save what we calculated it from:
m_volumes_matrices.clear();
m_volumes_types.clear();
- for (const ModelVolume* vol : m_model_object->volumes) {
+ for (const ModelVolume* vol : mo->volumes) {
m_volumes_matrices.push_back(vol->get_matrix());
m_volumes_types.push_back(vol->type());
}
- m_first_instance_scale = m_model_object->instances.front()->get_scaling_factor();
- m_first_instance_mirror = m_model_object->instances.front()->get_mirror();
+ m_first_instance_scale = mo->instances.front()->get_scaling_factor();
+ m_first_instance_mirror = mo->instances.front()->get_mirror();
+ m_old_model_object = mo;
m_planes_valid = true;
}
@@ -344,20 +340,22 @@ void GLGizmoFlatten::update_planes()
bool GLGizmoFlatten::is_plane_update_necessary() const
{
- if (m_state != On || !m_model_object || m_model_object->instances.empty())
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ if (m_state != On || ! mo || mo->instances.empty())
return false;
- if (! m_planes_valid || m_model_object->volumes.size() != m_volumes_matrices.size())
+ if (! m_planes_valid || mo != m_old_model_object
+ || mo->volumes.size() != m_volumes_matrices.size())
return true;
// We want to recalculate when the scale changes - some planes could (dis)appear.
- if (! m_model_object->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale)
- || ! m_model_object->instances.front()->get_mirror().isApprox(m_first_instance_mirror))
+ if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale)
+ || ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror))
return true;
- for (unsigned int i=0; i < m_model_object->volumes.size(); ++i)
- if (! m_model_object->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i])
- || m_model_object->volumes[i]->type() != m_volumes_types[i])
+ for (unsigned int i=0; i < mo->volumes.size(); ++i)
+ if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i])
+ || mo->volumes[i]->type() != m_volumes_types[i])
return true;
return false;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp
index 9cd2ab6bb..05b4ae4cd 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp
@@ -30,8 +30,7 @@ private:
std::vector<PlaneData> m_planes;
bool m_planes_valid = false;
mutable Vec3d m_starting_center;
- const ModelObject* m_model_object = nullptr;
- ObjectID m_model_object_id = 0;
+ const ModelObject* m_old_model_object = nullptr;
std::vector<const Transform3d*> instances_matrices;
void update_planes();
@@ -51,6 +50,7 @@ protected:
virtual void on_render() const override;
virtual void on_render_for_picking() const override;
virtual void on_set_state() override;
+ virtual CommonGizmosDataID on_get_requirements() const override;
};
} // namespace GUI
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
index 51e6d7458..d2f668dab 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
@@ -1,21 +1,15 @@
#include "GLGizmoHollow.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Camera.hpp"
-#include "slic3r/GUI/Gizmos/GLGizmos.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include <GL/glew.h>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectSettings.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
-#include "slic3r/GUI/MeshUtils.hpp"
#include "slic3r/GUI/Plater.hpp"
-#if ENABLE_NON_STATIC_CANVAS_MANAGER
-#include "slic3r/GUI/Camera.hpp"
-#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
#include "slic3r/GUI/PresetBundle.hpp"
-#include "libslic3r/SLAPrint.hpp"
-#include "libslic3r/TriangleMesh.hpp"
namespace Slic3r {
@@ -59,32 +53,14 @@ bool GLGizmoHollow::on_init()
void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&)
{
- if (m_c->recent_update) {
-
- if (m_state == On)
- m_c->build_AABB_if_needed();
-
- update_clipping_plane(m_c->m_clipping_plane_was_moved);
-
- // This is a temporary and not very nice hack, to make sure that
- // if the cp was moved by the data returned by backend, it will
- // remember its direction. FIXME: Refactor this mess and make
- // the clipping plane itself part of the shared data.
- if (! m_c->m_clipping_plane_was_moved && m_c->m_clipping_plane_distance == 0.25f)
- m_c->m_clipping_plane_was_moved = true;
-
-
- if (m_c->m_model_object) {
- reload_cache();
- if (m_c->has_drilled_mesh())
- m_holes_in_drilled_mesh = m_c->m_model_object->sla_drain_holes;
- }
- }
+ if (! m_c->selection_info())
+ return;
- if (m_state == On) {
- m_parent.toggle_model_objects_visibility(false);
- m_parent.toggle_model_objects_visibility(true, m_c->m_model_object, m_c->m_active_instance);
- m_parent.toggle_sla_auxiliaries_visibility(m_show_supports, m_c->m_model_object, m_c->m_active_instance);
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ if (mo) {
+ reload_cache();
+ if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh())
+ m_holes_in_drilled_mesh = mo->sla_drain_holes;
}
}
@@ -93,12 +69,12 @@ void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&)
void GLGizmoHollow::on_render() const
{
const Selection& selection = m_parent.get_selection();
+ const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info();
// If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off
if (m_state == On
- && (m_c->m_model_object != selection.get_model()->objects[selection.get_object_idx()]
- || m_c->m_active_instance != selection.get_instance_idx()
- || m_c->m_model_object_id != m_c->m_model_object->id())) {
+ && (sel_info->model_object() != selection.get_model()->objects[selection.get_object_idx()]
+ || sel_info->get_active_instance() != selection.get_instance_idx())) {
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
return;
}
@@ -106,92 +82,17 @@ void GLGizmoHollow::on_render() const
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
- m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
-
if (m_quadric != nullptr && selection.is_from_single_instance())
render_points(selection, false);
m_selection_rectangle.render(m_parent);
- render_clipping_plane(selection);
+ m_c->object_clipper()->render_cut();
+ m_c->supports_clipper()->render_cut();
glsafe(::glDisable(GL_BLEND));
}
-
-void GLGizmoHollow::render_clipping_plane(const Selection& selection) const
-{
- if (m_c->m_clipping_plane_distance == 0.f)
- return;
-
- // Get transformation of the instance
- const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
- Geometry::Transformation trafo = vol->get_instance_transformation();
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
-
- // Get transformation of supports
- Geometry::Transformation supports_trafo;
- supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), vol->get_sla_shift_z()));
- supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2)));
- // I don't know why, but following seems to be correct.
- supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2),
- 1,
- 1.));
-
- // Now initialize the TMS for the object, perform the cut and save the result.
- if (! m_c->m_object_clipper) {
- m_c->m_object_clipper.reset(new MeshClipper);
- m_c->m_object_clipper->set_mesh(*m_c->mesh());
- }
- m_c->m_object_clipper->set_plane(*m_c->m_clipping_plane);
- m_c->m_object_clipper->set_transformation(trafo);
-
- if (m_c->m_print_object_idx >= 0) {
- const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_c->m_print_object_idx];
-
- if (print_object->is_step_done(slaposSupportTree) && !print_object->get_mesh(slaposSupportTree).empty()) {
- // If the supports are already calculated, save the timestamp of the respective step
- // so we can later tell they were recalculated.
- size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
-
- if (! m_c->m_supports_clipper || (int)timestamp != m_c->m_old_timestamp) {
- // The timestamp has changed.
- m_c->m_supports_clipper.reset(new MeshClipper);
- // The mesh should already have the shared vertices calculated.
- m_c->m_supports_clipper->set_mesh(print_object->support_mesh());
- m_c->m_old_timestamp = timestamp;
- }
- m_c->m_supports_clipper->set_plane(*m_c->m_clipping_plane);
- m_c->m_supports_clipper->set_transformation(supports_trafo);
- }
- else
- // The supports are not valid. We better dump the cached data.
- m_c->m_supports_clipper.reset();
- }
-
- // At this point we have the triangulated cuts for both the object and supports - let's render.
- if (! m_c->m_object_clipper->get_triangles().empty()) {
- ::glPushMatrix();
- ::glColor3f(1.0f, 0.37f, 0.0f);
- ::glBegin(GL_TRIANGLES);
- for (const Vec3f& point : m_c->m_object_clipper->get_triangles())
- ::glVertex3f(point(0), point(1), point(2));
- ::glEnd();
- ::glPopMatrix();
- }
-
- if (m_show_supports && m_c->m_supports_clipper && ! m_c->m_supports_clipper->get_triangles().empty()) {
- ::glPushMatrix();
- ::glColor3f(1.0f, 0.f, 0.37f);
- ::glBegin(GL_TRIANGLES);
- for (const Vec3f& point : m_c->m_supports_clipper->get_triangles())
- ::glVertex3f(point(0), point(1), point(2));
- ::glEnd();
- ::glPopMatrix();
- }
-}
-
-
void GLGizmoHollow::on_render_for_picking() const
{
const Selection& selection = m_parent.get_selection();
@@ -213,17 +114,18 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons
const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix();
glsafe(::glPushMatrix());
- glsafe(::glTranslated(0.0, 0.0, m_z_shift));
+ glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift()));
glsafe(::glMultMatrixd(instance_matrix.data()));
float render_color[4];
- size_t cache_size = m_c->m_model_object->sla_drain_holes.size();
+ const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
+ size_t cache_size = drain_holes.size();
for (size_t i = 0; i < cache_size; ++i)
{
- const sla::DrainHole& drain_hole = m_c->m_model_object->sla_drain_holes[i];
+ const sla::DrainHole& drain_hole = drain_holes[i];
const bool& point_selected = m_selected[i];
- if (is_mesh_point_clipped((drain_hole.pos+m_c->HoleStickOutLength*drain_hole.normal).cast<double>()))
+ if (is_mesh_point_clipped((drain_hole.pos+HoleStickOutLength*drain_hole.normal).cast<double>()))
continue;
// First decide about the color of the point.
@@ -261,7 +163,6 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons
glFrontFace(GL_CW);
// Matrices set, we can render the point mark now.
-
Eigen::Quaterniond q;
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast<double>());
Eigen::AngleAxisd aa(q);
@@ -297,12 +198,17 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons
bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const
{
- if (m_c->m_clipping_plane_distance == 0.f)
+ if (m_c->object_clipper()->get_position() == 0.)
return false;
- Vec3d transformed_point = m_c->m_model_object->instances[m_c->m_active_instance]->get_transformation().get_matrix() * point;
- transformed_point(2) += m_z_shift;
- return m_c->m_clipping_plane->is_point_clipped(transformed_point);
+ auto sel_info = m_c->selection_info();
+ int active_inst = m_c->selection_info()->get_active_instance();
+ const ModelInstance* mi = sel_info->model_object()->instances[active_inst];
+ const Transform3d& trafo = mi->get_transformation().get_matrix();
+
+ Vec3d transformed_point = trafo * point;
+ transformed_point(2) += sel_info->get_sla_shift();
+ return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
}
@@ -311,7 +217,7 @@ bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const
// Return false if no intersection was found, true otherwise.
bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
{
- if (! m_c->m_mesh_raycaster)
+ if (! m_c->raycaster()->raycaster())
return false;
#if ENABLE_NON_STATIC_CANVAS_MANAGER
@@ -322,20 +228,23 @@ bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, V
const Selection& selection = m_parent.get_selection();
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
Geometry::Transformation trafo = volume->get_instance_transformation();
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
+
+ double clp_dist = m_c->object_clipper()->get_position();
+ const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane();
// The raycaster query
Vec3f hit;
Vec3f normal;
- if (m_c->m_mesh_raycaster->unproject_on_mesh(
+ if (m_c->raycaster()->raycaster()->unproject_on_mesh(
mouse_pos,
trafo.get_matrix(),
camera,
hit,
normal,
- m_c->m_clipping_plane_distance != 0.f ? m_c->m_clipping_plane.get() : nullptr))
+ clp_dist != 0. ? clp : nullptr))
{
- if (m_c->has_drilled_mesh()) {
+ if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) {
// in this case the raycaster sees the hollowed and drilled mesh.
// if the point lies on the surface created by the hole, we want
// to ignore it.
@@ -362,6 +271,10 @@ bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, V
// concludes that the event was not intended for it, it should return false.
bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
{
+ ModelObject* mo = m_c->selection_info()->model_object();
+ int active_inst = m_c->selection_info()->get_active_instance();
+
+
// left down with shift - show the selection rectangle:
if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) {
if (m_hover_id == -1) {
@@ -393,15 +306,15 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos
if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole")));
- Vec3d scaling = m_c->m_model_object->instances[m_c->m_active_instance]->get_scaling_factor();
+ Vec3d scaling = mo->instances[active_inst]->get_scaling_factor();
Vec3f normal_transformed(pos_and_normal.second(0)/scaling(0),
pos_and_normal.second(1)/scaling(1),
pos_and_normal.second(2)/scaling(2));
- m_c->m_model_object->sla_drain_holes.emplace_back(pos_and_normal.first + m_c->HoleStickOutLength * pos_and_normal.second/* normal_transformed.normalized()*/,
+ mo->sla_drain_holes.emplace_back(pos_and_normal.first + HoleStickOutLength * pos_and_normal.second/* normal_transformed.normalized()*/,
-pos_and_normal.second, m_new_hole_radius, m_new_hole_height);
m_selected.push_back(false);
- assert(m_selected.size() == m_c->m_model_object->sla_drain_holes.size());
+ assert(m_selected.size() == mo->sla_drain_holes.size());
m_parent.set_as_dirty();
m_wait_for_up_event = true;
}
@@ -420,11 +333,11 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos
GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
// First collect positions of all the points in world coordinates.
- Geometry::Transformation trafo = m_c->m_model_object->instances[m_c->m_active_instance]->get_transformation();
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
+ Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation();
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
std::vector<Vec3d> points;
- for (unsigned int i=0; i<m_c->m_model_object->sla_drain_holes.size(); ++i)
- points.push_back(trafo.get_matrix() * m_c->m_model_object->sla_drain_holes[i].pos.cast<double>());
+ for (unsigned int i=0; i<mo->sla_drain_holes.size(); ++i)
+ points.push_back(trafo.get_matrix() * mo->sla_drain_holes[i].pos.cast<double>());
// Now ask the rectangle which of the points are inside.
std::vector<Vec3f> points_inside;
@@ -433,11 +346,9 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos
points_inside.push_back(points[idx].cast<float>());
// Only select/deselect points that are actually visible
-#if ENABLE_NON_STATIC_CANVAS_MANAGER
- for (size_t idx : m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, wxGetApp().plater()->get_camera(), points_inside, m_c->m_clipping_plane.get()))
-#else
- for (size_t idx : m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_c->m_clipping_plane.get()))
-#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+ for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs(
+ trafo, wxGetApp().plater()->get_camera(), points_inside,
+ m_c->object_clipper()->get_clipping_plane()))
{
if (rectangle_status == GLSelectionRectangle::Deselect)
unselect_point(points_idxs[idx]);
@@ -491,20 +402,21 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos
}
if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
- m_c->m_clipping_plane_distance = std::min(1.f, m_c->m_clipping_plane_distance + 0.01f);
- update_clipping_plane(m_c->m_clipping_plane_was_moved);
- m_c->m_clipping_plane_was_moved = true;
+ double pos = m_c->object_clipper()->get_position();
+ pos = std::min(1., pos + 0.01);
+ m_c->object_clipper()->set_position(pos, true);
return true;
}
if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
- m_c->m_clipping_plane_distance = std::max(0.f, m_c->m_clipping_plane_distance - 0.01f);
- update_clipping_plane(true);
+ double pos = m_c->object_clipper()->get_position();
+ pos = std::max(0., pos - 0.01);
+ m_c->object_clipper()->set_position(pos, true);
return true;
}
if (action == SLAGizmoEventType::ResetClippingPlane) {
- update_clipping_plane();
+ m_c->object_clipper()->set_position(-1., false);
return true;
}
@@ -514,11 +426,12 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos
void GLGizmoHollow::delete_selected_points()
{
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete drainage hole")));
+ sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
- for (unsigned int idx=0; idx<m_c->m_model_object->sla_drain_holes.size(); ++idx) {
+ for (unsigned int idx=0; idx<drain_holes.size(); ++idx) {
if (m_selected[idx]) {
m_selected.erase(m_selected.begin()+idx);
- m_c->m_model_object->sla_drain_holes.erase(m_c->m_model_object->sla_drain_holes.begin() + (idx--));
+ drain_holes.erase(drain_holes.begin() + (idx--));
}
}
@@ -527,12 +440,14 @@ void GLGizmoHollow::delete_selected_points()
void GLGizmoHollow::on_update(const UpdateData& data)
{
+ sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
+
if (m_hover_id != -1) {
std::pair<Vec3f, Vec3f> pos_and_normal;
if (! unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
return;
- m_c->m_model_object->sla_drain_holes[m_hover_id].pos = pos_and_normal.first + m_c->HoleStickOutLength * pos_and_normal.second;
- m_c->m_model_object->sla_drain_holes[m_hover_id].normal = -pos_and_normal.second;
+ drain_holes[m_hover_id].pos = pos_and_normal.first + HoleStickOutLength * pos_and_normal.second;
+ drain_holes[m_hover_id].normal = -pos_and_normal.second;
}
}
@@ -540,19 +455,22 @@ void GLGizmoHollow::on_update(const UpdateData& data)
void GLGizmoHollow::hollow_mesh(bool postpone_error_messages)
{
wxGetApp().CallAfter([this, postpone_error_messages]() {
- wxGetApp().plater()->reslice_SLA_hollowing(*m_c->m_model_object, postpone_error_messages);
+ wxGetApp().plater()->reslice_SLA_hollowing(
+ *m_c->selection_info()->model_object(), postpone_error_messages);
});
}
-std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>> GLGizmoHollow::get_config_options(const std::vector<std::string>& keys) const
+std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>>
+GLGizmoHollow::get_config_options(const std::vector<std::string>& keys) const
{
std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>> out;
+ const ModelObject* mo = m_c->selection_info()->model_object();
- if (!m_c->m_model_object)
+ if (! mo)
return out;
- const DynamicPrintConfig& object_cfg = m_c->m_model_object->config;
+ const DynamicPrintConfig& object_cfg = mo->config;
const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
@@ -573,18 +491,10 @@ std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>> GLGizmoHollo
}
-ClippingPlane GLGizmoHollow::get_sla_clipping_plane() const
-{
- if (!m_c->m_model_object || m_state == Off || m_c->m_clipping_plane_distance == 0.f)
- return ClippingPlane::ClipsNothing();
- else
- return ClippingPlane(-m_c->m_clipping_plane->get_normal(), m_c->m_clipping_plane->get_data()[3]);
-}
-
-
void GLGizmoHollow::on_render_input_window(float x, float y, float bottom_limit)
{
- if (! m_c->m_model_object)
+ ModelObject* mo = m_c->selection_info()->model_object();
+ if (! mo)
return;
bool first_run = true; // This is a hack to redraw the button when all points are removed,
@@ -650,7 +560,7 @@ RENDER_AGAIN:
auto opts = get_config_options({"hollowing_enable"});
m_enable_hollowing = static_cast<const ConfigOptionBool*>(opts[0].first)->value;
if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) {
- m_c->m_model_object->config.opt<ConfigOptionBool>("hollowing_enable", true)->value = m_enable_hollowing;
+ mo->config.opt<ConfigOptionBool>("hollowing_enable", true)->value = m_enable_hollowing;
wxGetApp().obj_list()->update_and_show_object_settings_item();
config_changed = true;
}
@@ -712,14 +622,14 @@ RENDER_AGAIN:
}
if (slider_edited || slider_released) {
if (slider_released) {
- m_c->m_model_object->config.opt<ConfigOptionFloat>("hollowing_min_thickness", true)->value = m_offset_stash;
- m_c->m_model_object->config.opt<ConfigOptionFloat>("hollowing_quality", true)->value = m_quality_stash;
- m_c->m_model_object->config.opt<ConfigOptionFloat>("hollowing_closing_distance", true)->value = m_closing_d_stash;
+ mo->config.opt<ConfigOptionFloat>("hollowing_min_thickness", true)->value = m_offset_stash;
+ mo->config.opt<ConfigOptionFloat>("hollowing_quality", true)->value = m_quality_stash;
+ mo->config.opt<ConfigOptionFloat>("hollowing_closing_distance", true)->value = m_closing_d_stash;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change")));
}
- m_c->m_model_object->config.opt<ConfigOptionFloat>("hollowing_min_thickness", true)->value = offset;
- m_c->m_model_object->config.opt<ConfigOptionFloat>("hollowing_quality", true)->value = quality;
- m_c->m_model_object->config.opt<ConfigOptionFloat>("hollowing_closing_distance", true)->value = closing_d;
+ mo->config.opt<ConfigOptionFloat>("hollowing_min_thickness", true)->value = offset;
+ mo->config.opt<ConfigOptionFloat>("hollowing_quality", true)->value = quality;
+ mo->config.opt<ConfigOptionFloat>("hollowing_closing_distance", true)->value = closing_d;
if (slider_released) {
wxGetApp().obj_list()->update_and_show_object_settings_item();
config_changed = true;
@@ -750,9 +660,9 @@ RENDER_AGAIN:
m_imgui->text(m_desc["hole_depth"]);
ImGui::SameLine(diameter_slider_left);
- m_new_hole_height -= m_c->HoleStickOutLength;
+ m_new_hole_height -= HoleStickOutLength;
ImGui::SliderFloat(" ", &m_new_hole_height, 0.f, 10.f, "%.1f mm");
- m_new_hole_height += m_c->HoleStickOutLength;
+ m_new_hole_height += HoleStickOutLength;
clicked |= ImGui::IsItemClicked();
edited |= ImGui::IsItemEdited();
@@ -764,19 +674,19 @@ RENDER_AGAIN:
// - take correct undo/redo snapshot after the user is done with moving the slider
if (! m_selection_empty) {
if (clicked) {
- m_holes_stash = m_c->m_model_object->sla_drain_holes;
+ m_holes_stash = mo->sla_drain_holes;
}
if (edited) {
for (size_t idx=0; idx<m_selected.size(); ++idx)
if (m_selected[idx]) {
- m_c->m_model_object->sla_drain_holes[idx].radius = m_new_hole_radius;
- m_c->m_model_object->sla_drain_holes[idx].height = m_new_hole_height;
+ mo->sla_drain_holes[idx].radius = m_new_hole_radius;
+ mo->sla_drain_holes[idx].height = m_new_hole_height;
}
}
if (deactivated) {
// momentarily restore the old value to take snapshot
- sla::DrainHoles new_holes = m_c->m_model_object->sla_drain_holes;
- m_c->m_model_object->sla_drain_holes = m_holes_stash;
+ sla::DrainHoles new_holes = mo->sla_drain_holes;
+ mo->sla_drain_holes = m_holes_stash;
float backup_rad = m_new_hole_radius;
float backup_hei = m_new_hole_height;
for (size_t i=0; i<m_holes_stash.size(); ++i) {
@@ -789,7 +699,7 @@ RENDER_AGAIN:
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Change drainage hole diameter")));
m_new_hole_radius = backup_rad;
m_new_hole_height = backup_hei;
- m_c->m_model_object->sla_drain_holes = new_holes;
+ mo->sla_drain_holes = new_holes;
}
}
@@ -797,33 +707,33 @@ RENDER_AGAIN:
remove_selected = m_imgui->button(m_desc.at("remove_selected"));
m_imgui->disabled_end();
- m_imgui->disabled_begin(m_c->m_model_object->sla_drain_holes.empty());
+ m_imgui->disabled_begin(mo->sla_drain_holes.empty());
remove_all = m_imgui->button(m_desc.at("remove_all"));
m_imgui->disabled_end();
// Following is rendered in both editing and non-editing mode:
// m_imgui->text("");
ImGui::Separator();
- if (m_c->m_clipping_plane_distance == 0.f)
+ if (m_c->object_clipper()->get_position() == 0.f)
m_imgui->text(m_desc.at("clipping_of_view"));
else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){
- update_clipping_plane();
+ m_c->object_clipper()->set_position(-1., false);
});
}
}
ImGui::SameLine(clipping_slider_left);
ImGui::PushItemWidth(window_width - clipping_slider_left);
- if (ImGui::SliderFloat(" ", &m_c->m_clipping_plane_distance, 0.f, 1.f, "%.2f")) {
- update_clipping_plane(m_c->m_clipping_plane_was_moved);
- m_c->m_clipping_plane_was_moved = true;
- }
+ float clp_dist = m_c->object_clipper()->get_position();
+ if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f"))
+ m_c->object_clipper()->set_position(clp_dist, true);
// make sure supports are shown/hidden as appropriate
- if (m_imgui->checkbox(m_desc["show_supports"], m_show_supports)) {
- m_parent.toggle_sla_auxiliaries_visibility(m_show_supports, m_c->m_model_object, m_c->m_active_instance);
+ bool show_sups = m_c->instances_hider()->are_supports_shown();
+ if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) {
+ m_c->instances_hider()->show_supports(show_sups);
force_refresh = true;
}
@@ -882,54 +792,30 @@ std::string GLGizmoHollow::on_get_name() const
}
+CommonGizmosDataID GLGizmoHollow::on_get_requirements() const
+{
+ return CommonGizmosDataID(
+ int(CommonGizmosDataID::SelectionInfo)
+ | int(CommonGizmosDataID::InstancesHider)
+ | int(CommonGizmosDataID::Raycaster)
+ | int(CommonGizmosDataID::HollowedMesh)
+ | int(CommonGizmosDataID::ObjectClipper)
+ | int(CommonGizmosDataID::SupportsClipper));
+}
+
void GLGizmoHollow::on_set_state()
{
- // m_c->m_model_object pointer can be invalid (for instance because of undo/redo action),
- // we should recover it from the object id
- m_c->m_model_object = nullptr;
- for (const auto mo : wxGetApp().model().objects) {
- if (mo->id() == m_c->m_model_object_id) {
- m_c->m_model_object = mo;
- break;
- }
- }
-
if (m_state == m_old_state)
return;
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
- //Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on")));
- //m_c->update_from_backend(m_parent, m_c->m_model_object);
- m_c->unstash_clipping_plane();
- update_clipping_plane(m_c->m_clipping_plane_was_moved);
-
- m_c->build_AABB_if_needed();
-
// we'll now reload support points:
- if (m_c->m_model_object)
+ if (m_c->selection_info()->model_object())
reload_cache();
-
- m_parent.toggle_model_objects_visibility(false);
- if (m_c->m_model_object) {
- m_parent.toggle_model_objects_visibility(true, m_c->m_model_object, m_c->m_active_instance);
- m_parent.toggle_sla_auxiliaries_visibility(m_show_supports, m_c->m_model_object, m_c->m_active_instance);
- }
}
- if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
- //Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off")));
+ if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));
- m_parent.toggle_model_objects_visibility(true);
- m_c->stash_clipping_plane();
- m_c->m_clipping_plane_distance = 0.f;
- update_clipping_plane(true);
- // Release clippers and the AABB raycaster.
- m_c->m_object_clipper.reset();
- m_c->m_supports_clipper.reset();
- //m_c->m_mesh_raycaster.reset();
- //m_c->m_cavity_mesh.reset();
- //m_c->m_volume_with_cavity.reset();
- }
m_old_state = m_state;
}
@@ -940,7 +826,7 @@ void GLGizmoHollow::on_start_dragging()
if (m_hover_id != -1) {
select_point(NoPoints);
select_point(m_hover_id);
- m_hole_before_drag = m_c->m_model_object->sla_drain_holes[m_hover_id].pos;
+ m_hole_before_drag = m_c->selection_info()->model_object()->sla_drain_holes[m_hover_id].pos;
}
else
m_hole_before_drag = Vec3f::Zero();
@@ -949,15 +835,16 @@ void GLGizmoHollow::on_start_dragging()
void GLGizmoHollow::on_stop_dragging()
{
+ sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
if (m_hover_id != -1) {
- Vec3f backup = m_c->m_model_object->sla_drain_holes[m_hover_id].pos;
+ Vec3f backup = drain_holes[m_hover_id].pos;
if (m_hole_before_drag != Vec3f::Zero() // some point was touched
&& backup != m_hole_before_drag) // and it was moved, not just selected
{
- m_c->m_model_object->sla_drain_holes[m_hover_id].pos = m_hole_before_drag;
+ drain_holes[m_hover_id].pos = m_hole_before_drag;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move drainage hole")));
- m_c->m_model_object->sla_drain_holes[m_hover_id].pos = backup;
+ drain_holes[m_hover_id].pos = backup;
}
}
m_hole_before_drag = Vec3f::Zero();
@@ -967,10 +854,7 @@ void GLGizmoHollow::on_stop_dragging()
void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar)
{
- ar(m_c->m_clipping_plane_distance,
- *m_c->m_clipping_plane,
- m_c->m_model_object_id,
- m_new_hole_radius,
+ ar(m_new_hole_radius,
m_new_hole_height,
m_selected,
m_selection_empty
@@ -981,10 +865,7 @@ void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar)
void GLGizmoHollow::on_save(cereal::BinaryOutputArchive& ar) const
{
- ar(m_c->m_clipping_plane_distance,
- *m_c->m_clipping_plane,
- m_c->m_model_object_id,
- m_new_hole_radius,
+ ar(m_new_hole_radius,
m_new_hole_height,
m_selected,
m_selection_empty
@@ -995,13 +876,15 @@ void GLGizmoHollow::on_save(cereal::BinaryOutputArchive& ar) const
void GLGizmoHollow::select_point(int i)
{
+ const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
+
if (i == AllPoints || i == NoPoints) {
m_selected.assign(m_selected.size(), i == AllPoints);
m_selection_empty = (i == NoPoints);
if (i == AllPoints) {
- m_new_hole_radius = m_c->m_model_object->sla_drain_holes[0].radius;
- m_new_hole_height = m_c->m_model_object->sla_drain_holes[0].height;
+ m_new_hole_radius = drain_holes[0].radius;
+ m_new_hole_height = drain_holes[0].height;
}
}
else {
@@ -1009,8 +892,8 @@ void GLGizmoHollow::select_point(int i)
m_selected.push_back(false);
m_selected[i] = true;
m_selection_empty = false;
- m_new_hole_radius = m_c->m_model_object->sla_drain_holes[i].radius;
- m_new_hole_height = m_c->m_model_object->sla_drain_holes[i].height;
+ m_new_hole_radius = drain_holes[i].radius;
+ m_new_hole_height = drain_holes[i].height;
}
}
@@ -1030,31 +913,13 @@ void GLGizmoHollow::unselect_point(int i)
void GLGizmoHollow::reload_cache()
{
m_selected.clear();
- m_selected.assign(m_c->m_model_object->sla_drain_holes.size(), false);
-}
-
-void GLGizmoHollow::update_clipping_plane(bool keep_normal) const
-{
- if (! m_c->m_model_object)
- return;
-#if ENABLE_NON_STATIC_CANVAS_MANAGER
- Vec3d normal = (keep_normal && m_c->m_clipping_plane->get_normal() != Vec3d::Zero() ?
- m_c->m_clipping_plane->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward());
-#else
- Vec3d normal = (keep_normal && m_c->m_clipping_plane->get_normal() != Vec3d::Zero() ?
- m_c->m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward());
-#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
-
- const Vec3d& center = m_c->m_model_object->instances[m_c->m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
- float dist = normal.dot(center);
- *m_c->m_clipping_plane = ClippingPlane(normal, (dist - (-m_c->m_active_instance_bb_radius) - m_c->m_clipping_plane_distance * 2*m_c->m_active_instance_bb_radius));
- m_parent.set_as_dirty();
+ m_selected.assign(m_c->selection_info()->model_object()->sla_drain_holes.size(), false);
}
void GLGizmoHollow::on_set_hover_id()
{
- if (int(m_c->m_model_object->sla_drain_holes.size()) <= m_hover_id)
+ if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id)
m_hover_id = -1;
}
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp
index 3aafcbf55..3ee83c345 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp
@@ -13,16 +13,11 @@
namespace Slic3r {
namespace GUI {
-class ClippingPlane;
-class MeshClipper;
-class MeshRaycaster;
-class CommonGizmosData;
enum class SLAGizmoEventType : unsigned char;
class GLGizmoHollow : public GLGizmoBase
{
private:
- mutable double m_z_shift = 0.;
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
GLUquadricObj* m_quadric;
@@ -33,12 +28,10 @@ public:
~GLGizmoHollow() override;
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
- void delete_selected_points();
- ClippingPlane get_sla_clipping_plane() const;
-
- bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); }
- void update_clipping_plane(bool keep_normal = false) const;
- void set_common_data_ptr(CommonGizmosData* ptr) { m_c = ptr; }
+ void delete_selected_points();
+ bool is_selection_rectangle_dragging() const {
+ return m_selection_rectangle.is_dragging();
+ }
private:
bool on_init() override;
@@ -47,11 +40,10 @@ private:
void on_render_for_picking() const override;
void render_points(const Selection& selection, bool picking = false) const;
- void render_clipping_plane(const Selection& selection) const;
void hollow_mesh(bool postpone_error_messages = false);
bool unsaved_changes() const;
- bool m_show_supports = true;
+ // bool m_show_supports = true;
float m_new_hole_radius = 2.f; // Size of a new hole.
float m_new_hole_height = 6.f;
mutable std::vector<bool> m_selected; // which holes are currently selected
@@ -67,10 +59,6 @@ private:
sla::DrainHoles m_holes_in_drilled_mesh;
sla::DrainHoles m_holes_stash;
-
- CommonGizmosData* m_c = nullptr;
-
- //std::unique_ptr<ClippingPlane> m_clipping_plane;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
@@ -101,6 +89,7 @@ protected:
void on_start_dragging() override;
void on_stop_dragging() override;
void on_render_input_window(float x, float y, float bottom_limit) override;
+ virtual CommonGizmosDataID on_get_requirements() const override;
std::string on_get_name() const override;
bool on_is_activable() const override;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
index 064302c02..496568d51 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
@@ -2,7 +2,7 @@
#include "GLGizmoSlaSupports.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Camera.hpp"
-#include "slic3r/GUI/Gizmos/GLGizmos.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include <GL/glew.h>
@@ -14,10 +14,6 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_ObjectSettings.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
-#if ENABLE_NON_STATIC_CANVAS_MANAGER
-#include "slic3r/GUI/Camera.hpp"
-#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
-#include "slic3r/GUI/MeshUtils.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include "libslic3r/SLAPrint.hpp"
@@ -29,7 +25,6 @@ namespace GUI {
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_quadric(nullptr)
- , m_its(nullptr)
{
m_quadric = ::gluNewQuadric();
if (m_quadric != nullptr)
@@ -66,26 +61,21 @@ bool GLGizmoSlaSupports::on_init()
void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection)
{
- if (m_c->recent_update) {
- if (m_state == On)
- m_c->build_AABB_if_needed();
+ if (! m_c->selection_info())
+ return;
- update_clipping_plane(m_c->m_clipping_plane_was_moved);
+ ModelObject* mo = m_c->selection_info()->model_object();
+ if (mo != m_old_mo) {
disable_editing_mode();
- if (m_c->m_model_object)
+ if (mo)
reload_cache();
- }
-
- if (m_state == On) {
- m_parent.toggle_model_objects_visibility(false);
- m_parent.toggle_model_objects_visibility(true, m_c->m_model_object, m_c->m_active_instance);
- m_parent.toggle_sla_auxiliaries_visibility(! m_editing_mode, m_c->m_model_object, m_c->m_active_instance);
+ m_old_mo = mo;
}
// If we triggered autogeneration before, check backend and fetch results if they are there
- if (m_c->m_model_object) {
- if (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating)
+ if (mo) {
+ if (mo->sla_points_status == sla::PointsStatus::Generating)
get_data_from_backend();
}
}
@@ -94,13 +84,13 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S
void GLGizmoSlaSupports::on_render() const
{
+ ModelObject* mo = m_c->selection_info()->model_object();
const Selection& selection = m_parent.get_selection();
// If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off
if (m_state == On
- && (m_c->m_model_object != selection.get_model()->objects[selection.get_object_idx()]
- || m_c->m_active_instance != selection.get_instance_idx()
- || m_c->m_model_object_id != m_c->m_model_object->id())) {
+ && (mo != selection.get_model()->objects[selection.get_object_idx()]
+ || m_c->selection_info()->get_active_instance() != selection.get_instance_idx())) {
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
return;
}
@@ -108,113 +98,20 @@ void GLGizmoSlaSupports::on_render() const
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
- m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
-
if (m_quadric != nullptr && selection.is_from_single_instance())
render_points(selection, false);
m_selection_rectangle.render(m_parent);
- render_clipping_plane(selection);
+ m_c->object_clipper()->render_cut();
+ m_c->supports_clipper()->render_cut();
glsafe(::glDisable(GL_BLEND));
}
-
-void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
-{
- if (m_c->m_clipping_plane_distance == 0.f || m_c->m_mesh->empty())
- return;
-
- // Get transformation of the instance
- const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
- Geometry::Transformation trafo = vol->get_instance_transformation();
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
-
- // Get transformation of supports
- Geometry::Transformation supports_trafo;
- supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), vol->get_sla_shift_z()));
- supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2)));
- // I don't know why, but following seems to be correct.
- supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2),
- 1,
- 1.));
-
- // Now initialize the TMS for the object, perform the cut and save the result.
- if (! m_c->m_object_clipper) {
- m_c->m_object_clipper.reset(new MeshClipper);
- m_c->m_object_clipper->set_mesh(*m_c->mesh());
- }
- m_c->m_object_clipper->set_plane(*m_c->m_clipping_plane);
- m_c->m_object_clipper->set_transformation(trafo);
-
-
- // Next, ask the backend if supports are already calculated. If so, we are gonna cut them too.
- // First we need a pointer to the respective SLAPrintObject. The index into objects vector is
- // cached so we don't have todo it on each render. We only search for the po if needed:
- if (m_c->m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_c->m_print_objects_count) {
- m_c->m_print_objects_count = m_parent.sla_print()->objects().size();
- m_c->m_print_object_idx = -1;
- for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
- ++m_c->m_print_object_idx;
- if (po->model_object()->id() == m_c->m_model_object->id())
- break;
- }
- }
- if (m_c->m_print_object_idx >= 0) {
- const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_c->m_print_object_idx];
-
- if (print_object->is_step_done(slaposSupportTree) && !print_object->get_mesh(slaposSupportTree).empty()) {
- // If the supports are already calculated, save the timestamp of the respective step
- // so we can later tell they were recalculated.
- size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
-
- if (! m_c->m_supports_clipper || (int)timestamp != m_c->m_old_timestamp) {
- // The timestamp has changed.
- m_c->m_supports_clipper.reset(new MeshClipper);
- // The mesh should already have the shared vertices calculated.
- m_c->m_supports_clipper->set_mesh(print_object->support_mesh());
- m_c->m_old_timestamp = timestamp;
- }
- m_c->m_supports_clipper->set_plane(*m_c->m_clipping_plane);
- m_c->m_supports_clipper->set_transformation(supports_trafo);
- }
- else
- // The supports are not valid. We better dump the cached data.
- m_c->m_supports_clipper.reset();
- }
-
- // At this point we have the triangulated cuts for both the object and supports - let's render.
- if (! m_c->m_object_clipper->get_triangles().empty()) {
- ::glPushMatrix();
- ::glColor3f(1.0f, 0.37f, 0.0f);
- ::glBegin(GL_TRIANGLES);
- for (const Vec3f& point : m_c->m_object_clipper->get_triangles())
- ::glVertex3f(point(0), point(1), point(2));
- ::glEnd();
- ::glPopMatrix();
- }
-
- if (m_c->m_supports_clipper && ! m_c->m_supports_clipper->get_triangles().empty() && !m_editing_mode) {
- // The supports are hidden in the editing mode, so it makes no sense to render the cuts.
- ::glPushMatrix();
- ::glColor3f(1.0f, 0.f, 0.37f);
- ::glBegin(GL_TRIANGLES);
- for (const Vec3f& point : m_c->m_supports_clipper->get_triangles())
- ::glVertex3f(point(0), point(1), point(2));
- ::glEnd();
- ::glPopMatrix();
- }
-}
-
-
void GLGizmoSlaSupports::on_render_for_picking() const
{
const Selection& selection = m_parent.get_selection();
-#if ENABLE_RENDER_PICKING_PASS
- m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
-#endif
-
glsafe(::glEnable(GL_DEPTH_TEST));
render_points(selection, true);
}
@@ -227,9 +124,10 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse();
const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix();
+ float z_shift = m_c->selection_info()->get_sla_shift();
glsafe(::glPushMatrix());
- glsafe(::glTranslated(0.0, 0.0, m_z_shift));
+ glsafe(::glTranslated(0.0, 0.0, z_shift));
glsafe(::glMultMatrixd(instance_matrix.data()));
float render_color[4];
@@ -285,7 +183,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
if (m_editing_mode) {
// in case the normal is not yet cached, find and cache it
if (m_editing_cache[i].normal == Vec3f::Zero())
- m_c->m_mesh_raycaster->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal);
+ m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal);
Eigen::Quaterniond q;
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast<double>());
@@ -315,14 +213,15 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
}
// Now render the drain holes:
- if (! m_c->has_drilled_mesh()) {
+ //if (! m_c->has_drilled_mesh()) {
+ if (! m_c->hollowed_mesh()->get_hollowed_mesh()) {
render_color[0] = 0.7f;
render_color[1] = 0.7f;
render_color[2] = 0.7f;
render_color[3] = 0.7f;
glsafe(::glColor4fv(render_color));
- for (const sla::DrainHole& drain_hole : m_c->m_model_object->sla_drain_holes) {
- if (is_mesh_point_clipped((drain_hole.pos+m_c->HoleStickOutLength*drain_hole.normal).cast<double>()))
+ for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) {
+ if (is_mesh_point_clipped((drain_hole.pos+HoleStickOutLength*drain_hole.normal).cast<double>()))
continue;
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
@@ -365,12 +264,17 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const
{
- if (m_c->m_clipping_plane_distance == 0.f)
+ if (m_c->object_clipper()->get_position() == 0.)
return false;
- Vec3d transformed_point = m_c->m_model_object->instances[m_c->m_active_instance]->get_transformation().get_matrix() * point;
- transformed_point(2) += m_z_shift;
- return m_c->m_clipping_plane->is_point_clipped(transformed_point);
+ auto sel_info = m_c->selection_info();
+ int active_inst = m_c->selection_info()->get_active_instance();
+ const ModelInstance* mi = sel_info->model_object()->instances[active_inst];
+ const Transform3d& trafo = mi->get_transformation().get_matrix();
+
+ Vec3d transformed_point = trafo * point;
+ transformed_point(2) += sel_info->get_sla_shift();
+ return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
}
@@ -379,37 +283,37 @@ bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const
// Return false if no intersection was found, true otherwise.
bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
{
- if (! m_c->m_mesh_raycaster)
+ if (! m_c->raycaster()->raycaster())
return false;
-#if ENABLE_NON_STATIC_CANVAS_MANAGER
const Camera& camera = wxGetApp().plater()->get_camera();
-#else
- const Camera& camera = m_parent.get_camera();
-#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
const Selection& selection = m_parent.get_selection();
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
Geometry::Transformation trafo = volume->get_instance_transformation();
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
+
+ double clp_dist = m_c->object_clipper()->get_position();
+ const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane();
// The raycaster query
Vec3f hit;
Vec3f normal;
- if (m_c->m_mesh_raycaster->unproject_on_mesh(
+ if (m_c->raycaster()->raycaster()->unproject_on_mesh(
mouse_pos,
trafo.get_matrix(),
camera,
hit,
normal,
- m_c->m_clipping_plane_distance != 0.f ? m_c->m_clipping_plane.get() : nullptr))
+ clp_dist != 0. ? clp : nullptr))
{
// Check whether the hit is in a hole
bool in_hole = false;
// In case the hollowed and drilled mesh is available, we can allow
// placing points in holes, because they should never end up
// on surface that's been drilled away.
- if (! m_c->has_drilled_mesh()) {
- for (const sla::DrainHole& hole : m_c->m_model_object->sla_drain_holes) {
+ if (! m_c->hollowed_mesh()->get_hollowed_mesh()) {
+ sla::DrainHoles drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
+ for (const sla::DrainHole& hole : drain_holes) {
if (hole.is_inside(hit)) {
in_hole = true;
break;
@@ -432,6 +336,9 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec
// concludes that the event was not intended for it, it should return false.
bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
{
+ ModelObject* mo = m_c->selection_info()->model_object();
+ int active_inst = m_c->selection_info()->get_active_instance();
+
if (m_editing_mode) {
// left down with shift - show the selection rectangle:
@@ -483,8 +390,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
// First collect positions of all the points in world coordinates.
- Geometry::Transformation trafo = m_c->m_model_object->instances[m_c->m_active_instance]->get_transformation();
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
+ Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation();
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
std::vector<Vec3d> points;
for (unsigned int i=0; i<m_editing_cache.size(); ++i)
points.push_back(trafo.get_matrix() * m_editing_cache[i].support_point.pos.cast<double>());
@@ -496,11 +403,9 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
points_inside.push_back(points[idx].cast<float>());
// Only select/deselect points that are actually visible
-#if ENABLE_NON_STATIC_CANVAS_MANAGER
- for (size_t idx : m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, wxGetApp().plater()->get_camera(), points_inside, m_c->m_clipping_plane.get()))
-#else
- for (size_t idx : m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_c->m_clipping_plane.get()))
-#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
+ for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs(
+ trafo, wxGetApp().plater()->get_camera(), points_inside,
+ m_c->object_clipper()->get_clipping_plane()))
{
if (rectangle_status == GLSelectionRectangle::Deselect)
unselect_point(points_idxs[idx]);
@@ -577,20 +482,21 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
}
if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
- m_c->m_clipping_plane_distance = std::min(1.f, m_c->m_clipping_plane_distance + 0.01f);
- update_clipping_plane(m_c->m_clipping_plane_was_moved);
- m_c->m_clipping_plane_was_moved = true;
+ double pos = m_c->object_clipper()->get_position();
+ pos = std::min(1., pos + 0.01);
+ m_c->object_clipper()->set_position(pos, true);
return true;
}
if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
- m_c->m_clipping_plane_distance = std::max(0.f, m_c->m_clipping_plane_distance - 0.01f);
- update_clipping_plane(true);
+ double pos = m_c->object_clipper()->get_position();
+ pos = std::max(0., pos - 0.01);
+ m_c->object_clipper()->set_position(pos, true);
return true;
}
if (action == SLAGizmoEventType::ResetClippingPlane) {
- update_clipping_plane();
+ m_c->object_clipper()->set_position(-1., false);
return true;
}
@@ -634,11 +540,12 @@ void GLGizmoSlaSupports::on_update(const UpdateData& data)
std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vector<std::string>& keys) const
{
std::vector<const ConfigOption*> out;
+ const ModelObject* mo = m_c->selection_info()->model_object();
- if (!m_c->m_model_object)
+ if (! mo)
return out;
- const DynamicPrintConfig& object_cfg = m_c->m_model_object->config;
+ const DynamicPrintConfig& object_cfg = mo->config;
const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
@@ -659,14 +566,6 @@ std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const st
}
-ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const
-{
- if (!m_c->m_model_object || m_state == Off || m_c->m_clipping_plane_distance == 0.f)
- return ClippingPlane::ClipsNothing();
- else
- return ClippingPlane(-m_c->m_clipping_plane->get_normal(), m_c->m_clipping_plane->get_data()[3]);
-}
-
/*
void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& idxs) const
@@ -714,7 +613,9 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_l
static float last_y = 0.0f;
static float last_h = 0.0f;
- if (! m_c->m_model_object)
+ ModelObject* mo = m_c->selection_info()->model_object();
+
+ if (! mo)
return;
bool first_run = true; // This is a hack to redraw the button when all points are removed,
@@ -851,15 +752,15 @@ RENDER_AGAIN:
m_density_stash = density;
}
if (slider_edited) {
- m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
- m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
+ mo->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
+ mo->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
}
if (slider_released) {
- m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash;
- m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)m_density_stash;
+ mo->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash;
+ mo->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)m_density_stash;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change")));
- m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
- m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
+ mo->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
+ mo->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
wxGetApp().obj_list()->update_and_show_object_settings_item();
}
@@ -886,7 +787,7 @@ RENDER_AGAIN:
// Following is rendered in both editing and non-editing mode:
ImGui::Separator();
- if (m_c->m_clipping_plane_distance == 0.f)
+ if (m_c->object_clipper()->get_position() == 0.f)
{
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
@@ -894,17 +795,16 @@ RENDER_AGAIN:
else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){
- update_clipping_plane();
+ m_c->object_clipper()->set_position(-1., false);
});
}
}
ImGui::SameLine(clipping_slider_left);
ImGui::PushItemWidth(window_width - clipping_slider_left);
- if (ImGui::SliderFloat(" ", &m_c->m_clipping_plane_distance, 0.f, 1.f, "%.2f")) {
- update_clipping_plane(m_c->m_clipping_plane_was_moved);
- m_c->m_clipping_plane_was_moved = true;
- }
+ float clp_dist = m_c->object_clipper()->get_position();
+ if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f"))
+ m_c->object_clipper()->set_position(clp_dist, true);
if (m_imgui->button("?")) {
@@ -969,18 +869,22 @@ std::string GLGizmoSlaSupports::on_get_name() const
}
+CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const
+{
+ return CommonGizmosDataID(
+ int(CommonGizmosDataID::SelectionInfo)
+ | int(CommonGizmosDataID::InstancesHider)
+ | int(CommonGizmosDataID::Raycaster)
+ | int(CommonGizmosDataID::HollowedMesh)
+ | int(CommonGizmosDataID::ObjectClipper)
+ | int(CommonGizmosDataID::SupportsClipper));
+}
+
+
void GLGizmoSlaSupports::on_set_state()
{
- // m_c->m_model_object pointer can be invalid (for instance because of undo/redo action),
- // we should recover it from the object id
- m_c->m_model_object = nullptr;
- for (const auto mo : wxGetApp().model().objects) {
- if (mo->id() == m_c->m_model_object_id) {
- m_c->m_model_object = mo;
- break;
- }
- }
+ const ModelObject* mo = m_c->selection_info()->model_object();
if (m_state == m_old_state)
return;
@@ -988,28 +892,16 @@ void GLGizmoSlaSupports::on_set_state()
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on")));
- m_c->unstash_clipping_plane();
- update_clipping_plane(m_c->m_clipping_plane_was_moved);
-
- m_c->build_AABB_if_needed();
-
-
// we'll now reload support points:
- if (m_c->m_model_object)
+ if (mo)
reload_cache();
- m_parent.toggle_model_objects_visibility(false);
- if (m_c->m_model_object) {
- m_parent.toggle_model_objects_visibility(true, m_c->m_model_object, m_c->m_active_instance);
- m_parent.toggle_sla_auxiliaries_visibility(! m_editing_mode, m_c->m_model_object, m_c->m_active_instance);
- }
-
// Set default head diameter from config.
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
- bool will_ask = m_c->m_model_object && m_editing_mode && unsaved_changes();
+ bool will_ask = mo && m_editing_mode && unsaved_changes();
if (will_ask) {
wxGetApp().CallAfter([this]() {
// Following is called through CallAfter, because otherwise there was a problem
@@ -1028,16 +920,8 @@ void GLGizmoSlaSupports::on_set_state()
// we are actually shutting down
disable_editing_mode(); // so it is not active next time the gizmo opens
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off")));
- m_parent.toggle_model_objects_visibility(true);
m_normal_cache.clear();
- m_c->stash_clipping_plane();
- m_c->m_clipping_plane_distance = 0.f;
- update_clipping_plane(true);
- // Release clippers and the AABB raycaster.
- m_its = nullptr;
- m_c->m_object_clipper.reset();
- m_c->m_supports_clipper.reset();
- //m_c->m_mesh_raycaster.reset();
+
}
}
m_old_state = m_state;
@@ -1077,10 +961,7 @@ void GLGizmoSlaSupports::on_stop_dragging()
void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
{
- ar(m_c->m_clipping_plane_distance,
- *m_c->m_clipping_plane,
- m_c->m_model_object_id,
- m_new_point_head_diameter,
+ ar(m_new_point_head_diameter,
m_normal_cache,
m_editing_cache,
m_selection_empty
@@ -1091,10 +972,7 @@ void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const
{
- ar(m_c->m_clipping_plane_distance,
- *m_c->m_clipping_plane,
- m_c->m_model_object_id,
- m_new_point_head_diameter,
+ ar(m_new_point_head_diameter,
m_normal_cache,
m_editing_cache,
m_selection_empty
@@ -1171,9 +1049,10 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
for (const CacheEntry& ce : m_editing_cache)
m_normal_cache.push_back(ce.support_point);
- m_c->m_model_object->sla_points_status = sla::PointsStatus::UserModified;
- m_c->m_model_object->sla_support_points.clear();
- m_c->m_model_object->sla_support_points = m_normal_cache;
+ ModelObject* mo = m_c->selection_info()->model_object();
+ mo->sla_points_status = sla::PointsStatus::UserModified;
+ mo->sla_support_points.clear();
+ mo->sla_support_points = m_normal_cache;
reslice_SLA_supports();
}
@@ -1183,23 +1062,25 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
void GLGizmoSlaSupports::reload_cache()
{
+ const ModelObject* mo = m_c->selection_info()->model_object();
m_normal_cache.clear();
- if (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated || m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating)
+ if (mo->sla_points_status == sla::PointsStatus::AutoGenerated || mo->sla_points_status == sla::PointsStatus::Generating)
get_data_from_backend();
else
- for (const sla::SupportPoint& point : m_c->m_model_object->sla_support_points)
+ for (const sla::SupportPoint& point : mo->sla_support_points)
m_normal_cache.emplace_back(point);
}
bool GLGizmoSlaSupports::has_backend_supports() const
{
- if (! m_c->m_model_object)
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ if (! mo)
return false;
// find SlaPrintObject with this ID
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
- if (po->model_object()->id() == m_c->m_model_object->id())
+ if (po->model_object()->id() == mo->id())
return po->is_step_done(slaposSupportPoints);
}
return false;
@@ -1207,24 +1088,28 @@ bool GLGizmoSlaSupports::has_backend_supports() const
void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const
{
- wxGetApp().CallAfter([this, postpone_error_messages]() { wxGetApp().plater()->reslice_SLA_supports(*m_c->m_model_object, postpone_error_messages); });
+ wxGetApp().CallAfter([this, postpone_error_messages]() {
+ wxGetApp().plater()->reslice_SLA_supports(
+ *m_c->selection_info()->model_object(), postpone_error_messages);
+ });
}
void GLGizmoSlaSupports::get_data_from_backend()
{
if (! has_backend_supports())
return;
+ ModelObject* mo = m_c->selection_info()->model_object();
// find the respective SLAPrintObject, we need a pointer to it
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
- if (po->model_object()->id() == m_c->m_model_object->id()) {
+ if (po->model_object()->id() == mo->id()) {
m_normal_cache.clear();
const std::vector<sla::SupportPoint>& points = po->get_support_points();
auto mat = po->trafo().inverse().cast<float>();
for (unsigned int i=0; i<points.size();++i)
m_normal_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island));
- m_c->m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated;
+ mo->sla_points_status = sla::PointsStatus::AutoGenerated;
break;
}
}
@@ -1241,10 +1126,12 @@ void GLGizmoSlaSupports::auto_generate()
_(L("Are you sure you want to do it?")) + "\n",
_(L("Warning")), wxICON_WARNING | wxYES | wxNO);
- if (m_c->m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
+ ModelObject* mo = m_c->selection_info()->model_object();
+
+ if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points")));
wxGetApp().CallAfter([this]() { reslice_SLA_supports(); });
- m_c->m_model_object->sla_points_status = sla::PointsStatus::Generating;
+ mo->sla_points_status = sla::PointsStatus::Generating;
}
}
@@ -1259,7 +1146,7 @@ void GLGizmoSlaSupports::switch_to_editing_mode()
m_editing_cache.emplace_back(sp);
select_point(NoPoints);
- m_parent.toggle_sla_auxiliaries_visibility(false, m_c->m_model_object, m_c->m_active_instance);
+ m_c->instances_hider()->show_supports(false);
m_parent.set_as_dirty();
}
@@ -1269,7 +1156,7 @@ void GLGizmoSlaSupports::disable_editing_mode()
if (m_editing_mode) {
m_editing_mode = false;
wxGetApp().plater()->leave_gizmos_stack();
- m_parent.toggle_sla_auxiliaries_visibility(true, m_c->m_model_object, m_c->m_active_instance);
+ m_c->instances_hider()->show_supports(true);
m_parent.set_as_dirty();
}
}
@@ -1288,26 +1175,6 @@ bool GLGizmoSlaSupports::unsaved_changes() const
return false;
}
-
-void GLGizmoSlaSupports::update_clipping_plane(bool keep_normal) const
-{
- if (! m_c->m_model_object)
- return;
-
-#if ENABLE_NON_STATIC_CANVAS_MANAGER
- Vec3d normal = (keep_normal && m_c->m_clipping_plane->get_normal() != Vec3d::Zero() ?
- m_c->m_clipping_plane->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward());
-#else
- Vec3d normal = (keep_normal && m_c->m_clipping_plane->get_normal() != Vec3d::Zero() ?
- m_c->m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward());
-#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
-
- const Vec3d& center = m_c->m_model_object->instances[m_c->m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
- float dist = normal.dot(center);
- *m_c->m_clipping_plane = ClippingPlane(normal, (dist - (-m_c->m_active_instance_bb_radius) - m_c->m_clipping_plane_distance * 2*m_c->m_active_instance_bb_radius));
- m_parent.set_as_dirty();
-}
-
SlaGizmoHelpDialog::SlaGizmoHelpDialog()
: wxDialog(nullptr, wxID_ANY, _(L("SLA gizmo keyboard shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
{
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
index bd48ecd65..917d22a98 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
@@ -13,34 +13,17 @@
namespace Slic3r {
namespace GUI {
-class ClippingPlane;
-class MeshClipper;
-class MeshRaycaster;
-class CommonGizmosData;
enum class SLAGizmoEventType : unsigned char;
class GLGizmoSlaSupports : public GLGizmoBase
{
private:
- //ModelObject* m_model_object = nullptr;
- //ObjectID m_model_object_id = 0;
- //int m_active_instance = -1;
- //float m_active_instance_bb_radius; // to cache the bb
- mutable double m_z_shift = 0.f;
+
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
const float RenderPointScale = 1.f;
GLUquadricObj* m_quadric;
- typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
- typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
-
- //std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
- //const TriangleMesh* m_mesh;
- const indexed_triangle_set* m_its;
- //mutable int m_old_timestamp = -1;
- //mutable int m_print_object_idx = -1;
- //mutable int m_print_objects_count = -1;
class CacheEntry {
public:
@@ -75,14 +58,12 @@ public:
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
void delete_selected_points(bool force = false);
- ClippingPlane get_sla_clipping_plane() const;
+ //ClippingPlane get_sla_clipping_plane() const;
bool is_in_editing_mode() const { return m_editing_mode; }
bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); }
bool has_backend_supports() const;
void reslice_SLA_supports(bool postpone_error_messages = false) const;
- void update_clipping_plane(bool keep_normal = false) const;
- void set_common_data_ptr(CommonGizmosData* ptr) { m_c = ptr; }
private:
bool on_init() override;
@@ -90,9 +71,7 @@ private:
void on_render() const override;
void on_render_for_picking() const override;
- //void render_selection_rectangle() const;
void render_points(const Selection& selection, bool picking = false) const;
- void render_clipping_plane(const Selection& selection) const;
bool unsaved_changes() const;
bool m_lock_unique_islands = false;
@@ -104,8 +83,7 @@ private:
float m_density_stash = 0.f; // and again
mutable std::vector<CacheEntry> m_editing_cache; // a support point and whether it is currently selected
std::vector<sla::SupportPoint> m_normal_cache; // to restore after discarding changes or undo/redo
-
- //std::unique_ptr<ClippingPlane> m_clipping_plane;
+ const ModelObject* m_old_mo = nullptr;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
@@ -117,11 +95,6 @@ private:
bool m_selection_empty = true;
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
- CommonGizmosData* m_c = nullptr;
-
- //mutable std::unique_ptr<MeshClipper> m_object_clipper;
- //mutable std::unique_ptr<MeshClipper> m_supports_clipper;
-
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
bool is_mesh_point_clipped(const Vec3d& point) const;
bool is_point_in_hole(const Vec3f& pt) const;
@@ -142,7 +115,6 @@ private:
void auto_generate();
void switch_to_editing_mode();
void disable_editing_mode();
- void reset_clipping_plane_normal() const;
protected:
void on_set_state() override;
@@ -159,6 +131,7 @@ protected:
std::string on_get_name() const override;
bool on_is_activable() const override;
bool on_is_selectable() const override;
+ virtual CommonGizmosDataID on_get_requirements() const override;
void on_load(cereal::BinaryInputArchive& ar) override;
void on_save(cereal::BinaryOutputArchive& ar) const override;
};
diff --git a/src/slic3r/GUI/Gizmos/GLGizmos.hpp b/src/slic3r/GUI/Gizmos/GLGizmos.hpp
index 9f97c42b4..e8e73959c 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmos.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmos.hpp
@@ -31,6 +31,7 @@ enum class SLAGizmoEventType : unsigned char {
#include "slic3r/GUI/Gizmos/GLGizmoRotate.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp
new file mode 100644
index 000000000..32a6de42b
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp
@@ -0,0 +1,473 @@
+#include "GLGizmosCommon.hpp"
+
+#include <cassert>
+
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "libslic3r/SLAPrint.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/Camera.hpp"
+
+#include <GL/glew.h>
+
+namespace Slic3r {
+namespace GUI {
+
+using namespace CommonGizmosDataObjects;
+
+CommonGizmosDataPool::CommonGizmosDataPool(GLCanvas3D* canvas)
+ : m_canvas(canvas)
+{
+ using c = CommonGizmosDataID;
+ m_data[c::SelectionInfo].reset( new SelectionInfo(this));
+ m_data[c::InstancesHider].reset( new InstancesHider(this));
+ m_data[c::HollowedMesh].reset( new HollowedMesh(this));
+ m_data[c::Raycaster].reset( new Raycaster(this));
+ m_data[c::ObjectClipper].reset( new ObjectClipper(this));
+ m_data[c::SupportsClipper].reset( new SupportsClipper(this));
+
+}
+
+void CommonGizmosDataPool::update(CommonGizmosDataID required)
+{
+ assert(check_dependencies(required));
+ for (auto& [id, data] : m_data) {
+ if (int(required) & int(CommonGizmosDataID(id)))
+ data->update();
+ else
+ if (data->is_valid())
+ data->release();
+
+ }
+}
+
+
+SelectionInfo* CommonGizmosDataPool::selection_info() const
+{
+ SelectionInfo* sel_info = dynamic_cast<SelectionInfo*>(m_data.at(CommonGizmosDataID::SelectionInfo).get());
+ assert(sel_info);
+ return sel_info->is_valid() ? sel_info : nullptr;
+}
+
+
+InstancesHider* CommonGizmosDataPool::instances_hider() const
+{
+ InstancesHider* inst_hider = dynamic_cast<InstancesHider*>(m_data.at(CommonGizmosDataID::InstancesHider).get());
+ assert(inst_hider);
+ return inst_hider->is_valid() ? inst_hider : nullptr;
+}
+
+HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const
+{
+ HollowedMesh* hol_mesh = dynamic_cast<HollowedMesh*>(m_data.at(CommonGizmosDataID::HollowedMesh).get());
+ assert(hol_mesh);
+ return hol_mesh->is_valid() ? hol_mesh : nullptr;
+}
+
+Raycaster* CommonGizmosDataPool::raycaster() const
+{
+ Raycaster* rc = dynamic_cast<Raycaster*>(m_data.at(CommonGizmosDataID::Raycaster).get());
+ assert(rc);
+ return rc->is_valid() ? rc : nullptr;
+}
+
+ObjectClipper* CommonGizmosDataPool::object_clipper() const
+{
+ ObjectClipper* oc = dynamic_cast<ObjectClipper*>(m_data.at(CommonGizmosDataID::ObjectClipper).get());
+ // ObjectClipper is used from outside the gizmos to report current clipping plane.
+ // This function can be called when oc is nullptr.
+ return (oc && oc->is_valid()) ? oc : nullptr;
+}
+
+SupportsClipper* CommonGizmosDataPool::supports_clipper() const
+{
+ SupportsClipper* sc = dynamic_cast<SupportsClipper*>(m_data.at(CommonGizmosDataID::SupportsClipper).get());
+ assert(sc);
+ return sc->is_valid() ? sc : nullptr;
+}
+
+#ifndef NDEBUG
+// Check the required resources one by one and return true if all
+// dependencies are met.
+bool CommonGizmosDataPool::check_dependencies(CommonGizmosDataID required) const
+{
+ // This should iterate over currently required data. Each of them should
+ // be asked about its dependencies and it must check that all dependencies
+ // are also in required and before the current one.
+ for (auto& [id, data] : m_data) {
+ // in case we don't use this, the deps are irrelevant
+ if (! (int(required) & int(CommonGizmosDataID(id))))
+ continue;
+
+
+ CommonGizmosDataID deps = data->get_dependencies();
+ assert(int(deps) == (int(deps) & int(required)));
+ }
+
+
+ return true;
+}
+#endif // NDEBUG
+
+
+
+
+void SelectionInfo::on_update()
+{
+ const Selection& selection = get_pool()->get_canvas()->get_selection();
+ if (selection.is_single_full_instance()) {
+ m_model_object = selection.get_model()->objects[selection.get_object_idx()];
+ m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
+ }
+ else
+ m_model_object = nullptr;
+}
+
+void SelectionInfo::on_release()
+{
+ m_model_object = nullptr;
+}
+
+int SelectionInfo::get_active_instance() const
+{
+ const Selection& selection = get_pool()->get_canvas()->get_selection();
+ return selection.get_instance_idx();
+}
+
+
+
+
+
+void InstancesHider::on_update()
+{
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+ int active_inst = get_pool()->selection_info()->get_active_instance();
+ GLCanvas3D* canvas = get_pool()->get_canvas();
+
+ if (mo && active_inst != -1) {
+ canvas->toggle_model_objects_visibility(false);
+ canvas->toggle_model_objects_visibility(true, mo, active_inst);
+ canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst);
+ }
+ else
+ canvas->toggle_model_objects_visibility(true);
+}
+
+void InstancesHider::on_release()
+{
+ get_pool()->get_canvas()->toggle_model_objects_visibility(true);
+}
+
+void InstancesHider::show_supports(bool show) {
+ if (m_show_supports != show) {
+ m_show_supports = show;
+ on_update();
+ }
+}
+
+
+
+void HollowedMesh::on_update()
+{
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+ if (! mo)
+ return;
+
+ const GLCanvas3D* canvas = get_pool()->get_canvas();
+ const PrintObjects& print_objects = canvas->sla_print()->objects();
+ const SLAPrintObject* print_object = m_print_object_idx != -1
+ ? print_objects[m_print_object_idx]
+ : nullptr;
+
+ // Find the respective SLAPrintObject.
+ if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) {
+ m_print_objects_count = print_objects.size();
+ m_print_object_idx = -1;
+ for (const SLAPrintObject* po : print_objects) {
+ ++m_print_object_idx;
+ if (po->model_object()->id() == mo->id()) {
+ print_object = po;
+ break;
+ }
+ }
+ }
+
+ // If there is a valid SLAPrintObject, check state of Hollowing step.
+ if (print_object) {
+ if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) {
+ size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp;
+ if (timestamp > m_old_hollowing_timestamp) {
+ const TriangleMesh& backend_mesh = print_object->get_mesh_to_print();
+ if (! backend_mesh.empty()) {
+ m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh));
+ Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse();
+ m_hollowed_mesh_transformed->transform(trafo_inv);
+ m_old_hollowing_timestamp = timestamp;
+ }
+ else
+ m_hollowed_mesh_transformed.reset(nullptr);
+ }
+ }
+ else
+ m_hollowed_mesh_transformed.reset(nullptr);
+ }
+}
+
+
+void HollowedMesh::on_release()
+{
+ m_hollowed_mesh_transformed.reset();
+ m_old_hollowing_timestamp = 0;
+ m_print_object_idx = -1;
+}
+
+
+const TriangleMesh* HollowedMesh::get_hollowed_mesh() const
+{
+ return m_hollowed_mesh_transformed.get();
+}
+
+
+
+
+
+void Raycaster::on_update()
+{
+ wxBusyCursor wait;
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+
+ if (! mo)
+ return;
+
+ std::vector<const TriangleMesh*> meshes;
+ const std::vector<ModelVolume*>& mvs = mo->volumes;
+ if (mvs.size() == 1) {
+ assert(mvs.front()->is_model_part());
+ const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh();
+ if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh())
+ meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh());
+ }
+ if (meshes.empty()) {
+ for (const ModelVolume* mv : mvs) {
+ if (mv->is_model_part())
+ meshes.push_back(&mv->mesh());
+ }
+ }
+
+ if (meshes != m_old_meshes) {
+ m_raycasters.clear();
+ for (const TriangleMesh* mesh : meshes)
+ m_raycasters.emplace_back(new MeshRaycaster(*mesh));
+ m_old_meshes = meshes;
+ }
+}
+
+void Raycaster::on_release()
+{
+ m_raycasters.clear();
+ m_old_meshes.clear();
+}
+
+std::vector<const MeshRaycaster*> Raycaster::raycasters() const
+{
+ std::vector<const MeshRaycaster*> mrcs;
+ for (const auto& raycaster_unique_ptr : m_raycasters)
+ mrcs.push_back(raycaster_unique_ptr.get());
+ return mrcs;
+}
+
+
+
+
+
+void ObjectClipper::on_update()
+{
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+ if (! mo)
+ return;
+
+ // which mesh should be cut?
+ std::vector<const TriangleMesh*> meshes;
+ bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh();
+ if (has_hollowed)
+ meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh());
+
+ if (meshes.empty()) {
+ for (const ModelVolume* mv : mo->volumes)
+ if (mv->is_model_part())
+ meshes.push_back(&mv->mesh());
+ }
+
+ if (meshes != m_old_meshes) {
+ m_clippers.clear();
+ for (const TriangleMesh* mesh : meshes) {
+ m_clippers.emplace_back(new MeshClipper);
+ m_clippers.back()->set_mesh(*mesh);
+ }
+ m_old_meshes = meshes;
+ m_active_inst_bb_radius =
+ mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius();
+ //if (has_hollowed && m_clp_ratio != 0.)
+ // m_clp_ratio = 0.25;
+ }
+}
+
+
+void ObjectClipper::on_release()
+{
+ m_clippers.clear();
+ m_old_meshes.clear();
+ m_clp.reset();
+ m_clp_ratio = 0.;
+
+}
+
+void ObjectClipper::render_cut() const
+{
+ if (m_clp_ratio == 0.)
+ return;
+ const SelectionInfo* sel_info = get_pool()->selection_info();
+ const ModelObject* mo = sel_info->model_object();
+ Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
+
+ size_t clipper_id = 0;
+ for (const ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
+ continue;
+
+ Geometry::Transformation vol_trafo = mv->get_transformation();
+ Geometry::Transformation trafo = inst_trafo * vol_trafo;
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
+
+ auto& clipper = m_clippers[clipper_id];
+ clipper->set_plane(*m_clp);
+ clipper->set_transformation(trafo);
+
+ if (! clipper->get_triangles().empty()) {
+ ::glPushMatrix();
+ ::glColor3f(1.0f, 0.37f, 0.0f);
+ ::glBegin(GL_TRIANGLES);
+ for (const Vec3f& point : clipper->get_triangles())
+ ::glVertex3f(point(0), point(1), point(2));
+ ::glEnd();
+ ::glPopMatrix();
+ }
+ ++clipper_id;
+ }
+}
+
+
+void ObjectClipper::set_position(double pos, bool keep_normal)
+{
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+ int active_inst = get_pool()->selection_info()->get_active_instance();
+ double z_shift = get_pool()->selection_info()->get_sla_shift();
+
+ Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward();
+ const Vec3d& center = mo->instances[active_inst]->get_offset() + Vec3d(0., 0., z_shift);
+ float dist = normal.dot(center);
+
+ if (pos < 0.)
+ pos = m_clp_ratio;
+
+ m_clp_ratio = pos;
+ m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2*m_active_inst_bb_radius)));
+ get_pool()->get_canvas()->set_as_dirty();
+}
+
+
+
+void SupportsClipper::on_update()
+{
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+ if (! mo)
+ return;
+
+ const GLCanvas3D* canvas = get_pool()->get_canvas();
+ const PrintObjects& print_objects = canvas->sla_print()->objects();
+ const SLAPrintObject* print_object = m_print_object_idx != -1
+ ? print_objects[m_print_object_idx]
+ : nullptr;
+
+ // Find the respective SLAPrintObject.
+ if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) {
+ m_print_objects_count = print_objects.size();
+ m_print_object_idx = -1;
+ for (const SLAPrintObject* po : print_objects) {
+ ++m_print_object_idx;
+ if (po->model_object()->id() == mo->id()) {
+ print_object = po;
+ break;
+ }
+ }
+ }
+
+ if (print_object
+ && print_object->is_step_done(slaposSupportTree)
+ && ! print_object->support_mesh().empty())
+ {
+ // If the supports are already calculated, save the timestamp of the respective step
+ // so we can later tell they were recalculated.
+ size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
+ if (! m_clipper || timestamp != m_old_timestamp) {
+ // The timestamp has changed.
+ m_clipper.reset(new MeshClipper);
+ // The mesh should already have the shared vertices calculated.
+ m_clipper->set_mesh(print_object->support_mesh());
+ m_old_timestamp = timestamp;
+ }
+ }
+ else
+ // The supports are not valid. We better dump the cached data.
+ m_clipper.reset();
+}
+
+
+void SupportsClipper::on_release()
+{
+ m_clipper.reset();
+ m_old_timestamp = 0;
+ m_print_object_idx = -1;
+}
+
+void SupportsClipper::render_cut() const
+{
+ const CommonGizmosDataObjects::ObjectClipper* ocl = get_pool()->object_clipper();
+ if (ocl->get_position() == 0.
+ || ! get_pool()->instances_hider()->are_supports_shown()
+ || ! m_clipper)
+ return;
+
+ const SelectionInfo* sel_info = get_pool()->selection_info();
+ const ModelObject* mo = sel_info->model_object();
+ Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
+ //Geometry::Transformation vol_trafo = mo->volumes.front()->get_transformation();
+ Geometry::Transformation trafo = inst_trafo;// * vol_trafo;
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
+
+
+ // Get transformation of supports
+ Geometry::Transformation supports_trafo = trafo;
+ supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift()));
+ supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2)));
+ // I don't know why, but following seems to be correct.
+ supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2),
+ 1,
+ 1.));
+
+ m_clipper->set_plane(*ocl->get_clipping_plane());
+ m_clipper->set_transformation(supports_trafo);
+
+ if (! m_clipper->get_triangles().empty()) {
+ ::glPushMatrix();
+ ::glColor3f(1.0f, 0.f, 0.37f);
+ ::glBegin(GL_TRIANGLES);
+ for (const Vec3f& point : m_clipper->get_triangles())
+ ::glVertex3f(point(0), point(1), point(2));
+ ::glEnd();
+ ::glPopMatrix();
+ }
+}
+
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp
new file mode 100644
index 000000000..31c473bac
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp
@@ -0,0 +1,309 @@
+#ifndef slic3r_GUI_GLGizmosCommon_hpp_
+#define slic3r_GUI_GLGizmosCommon_hpp_
+
+#include <memory>
+#include <map>
+
+#include "slic3r/GUI/MeshUtils.hpp"
+
+namespace Slic3r {
+
+class ModelObject;
+
+
+namespace GUI {
+
+class GLCanvas3D;
+
+static constexpr float HoleStickOutLength = 1.f;
+
+enum class SLAGizmoEventType : unsigned char {
+ LeftDown = 1,
+ LeftUp,
+ RightDown,
+ RightUp,
+ Dragging,
+ Delete,
+ SelectAll,
+ ShiftUp,
+ AltUp,
+ ApplyChanges,
+ DiscardChanges,
+ AutomaticGeneration,
+ ManualEditing,
+ MouseWheelUp,
+ MouseWheelDown,
+ ResetClippingPlane
+};
+
+
+
+class CommonGizmosDataBase;
+namespace CommonGizmosDataObjects {
+ class SelectionInfo;
+ class InstancesHider;
+ class HollowedMesh;
+ class Raycaster;
+ class ObjectClipper;
+ class SupportsClipper;
+}
+
+// Some of the gizmos use the same data that need to be updated ocassionally.
+// It is also desirable that the data are not recalculated when the gizmos
+// are just switched, but on the other hand, they should be released when
+// they are not in use by any gizmo anymore.
+
+// Enumeration of various data types that the data pool can contain.
+// Each gizmo can tell which of the data it wants to use through
+// on_get_requirements() method.
+enum class CommonGizmosDataID {
+ None = 0,
+ SelectionInfo = 1 << 0,
+ InstancesHider = 1 << 1,
+ HollowedMesh = 1 << 2,
+ Raycaster = 1 << 3,
+ ObjectClipper = 1 << 4,
+ SupportsClipper = 1 << 5,
+
+};
+
+
+// Following class holds pointers to the common data objects and triggers
+// their updating/releasing. There is just one object of this type (managed
+// by GLGizmoManager, the gizmos keep a pointer to it.
+class CommonGizmosDataPool {
+public:
+ CommonGizmosDataPool(GLCanvas3D* canvas);
+
+ // Update all resources and release what is not used.
+ // Accepts a bitmask of currently required resources.
+ void update(CommonGizmosDataID required);
+
+ // Getters for the data that need to be accessed from the gizmos directly.
+ CommonGizmosDataObjects::SelectionInfo* selection_info() const;
+ CommonGizmosDataObjects::InstancesHider* instances_hider() const;
+ CommonGizmosDataObjects::HollowedMesh* hollowed_mesh() const;
+ CommonGizmosDataObjects::Raycaster* raycaster() const;
+ CommonGizmosDataObjects::ObjectClipper* object_clipper() const;
+ CommonGizmosDataObjects::SupportsClipper* supports_clipper() const;
+
+
+ GLCanvas3D* get_canvas() const { return m_canvas; }
+
+private:
+ std::map<CommonGizmosDataID, std::unique_ptr<CommonGizmosDataBase>> m_data;
+ GLCanvas3D* m_canvas;
+
+#ifndef NDEBUG
+ bool check_dependencies(CommonGizmosDataID required) const;
+#endif
+};
+
+
+
+
+
+// Base class for a wrapper object managing a single resource.
+// Each of the enum values above (safe None) will have an object of this kind.
+class CommonGizmosDataBase {
+public:
+ // Pass a backpointer to the pool, so the individual
+ // objects can communicate with one another.
+ explicit CommonGizmosDataBase(CommonGizmosDataPool* cgdp)
+ : m_common{cgdp} {}
+ virtual ~CommonGizmosDataBase() {}
+
+ // Update the resource.
+ void update() { on_update(); m_is_valid = true; }
+
+ // Release any data that are stored internally.
+ void release() { on_release(); m_is_valid = false; }
+
+ // Returns whether the resource is currently maintained.
+ bool is_valid() const { return m_is_valid; }
+
+#ifndef NDEBUG
+ // Return a bitmask of all resources that this one relies on.
+ // The dependent resource must have higher ID than the one
+ // it depends on.
+ virtual CommonGizmosDataID get_dependencies() const { return CommonGizmosDataID::None; }
+#endif // NDEBUG
+
+protected:
+ virtual void on_release() = 0;
+ virtual void on_update() = 0;
+ CommonGizmosDataPool* get_pool() const { return m_common; }
+
+
+private:
+ bool m_is_valid = false;
+ CommonGizmosDataPool* m_common = nullptr;
+};
+
+
+
+// The specializations of the CommonGizmosDataBase class live in this
+// namespace to avoid clashes in GUI namespace.
+namespace CommonGizmosDataObjects
+{
+
+class SelectionInfo : public CommonGizmosDataBase
+{
+public:
+ explicit SelectionInfo(CommonGizmosDataPool* cgdp)
+ : CommonGizmosDataBase(cgdp) {}
+
+ ModelObject* model_object() const { return m_model_object; }
+ int get_active_instance() const;
+ float get_sla_shift() const { return m_z_shift; }
+
+protected:
+ void on_update() override;
+ void on_release() override;
+
+private:
+ ModelObject* m_model_object = nullptr;
+ int m_active_inst = -1;
+ float m_z_shift = 0.f;
+};
+
+
+
+class InstancesHider : public CommonGizmosDataBase
+{
+public:
+ explicit InstancesHider(CommonGizmosDataPool* cgdp)
+ : CommonGizmosDataBase(cgdp) {}
+#ifndef NDEBUG
+ CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
+#endif // NDEBUG
+
+ void show_supports(bool show);
+ bool are_supports_shown() const { return m_show_supports; }
+
+protected:
+ void on_update() override;
+ void on_release() override;
+
+private:
+ bool m_show_supports = false;
+};
+
+
+
+class HollowedMesh : public CommonGizmosDataBase
+{
+public:
+ explicit HollowedMesh(CommonGizmosDataPool* cgdp)
+ : CommonGizmosDataBase(cgdp) {}
+#ifndef NDEBUG
+ CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
+#endif // NDEBUG
+
+ const TriangleMesh* get_hollowed_mesh() const;
+
+protected:
+ void on_update() override;
+ void on_release() override;
+
+private:
+ std::unique_ptr<TriangleMesh> m_hollowed_mesh_transformed;
+ size_t m_old_hollowing_timestamp = 0;
+ int m_print_object_idx = -1;
+ int m_print_objects_count = 0;
+};
+
+
+
+class Raycaster : public CommonGizmosDataBase
+{
+public:
+ explicit Raycaster(CommonGizmosDataPool* cgdp)
+ : CommonGizmosDataBase(cgdp) {}
+#ifndef NDEBUG
+ CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
+#endif // NDEBUG
+
+ const MeshRaycaster* raycaster() const { assert(m_raycasters.size() == 1); return m_raycasters.front().get(); }
+ std::vector<const MeshRaycaster*> raycasters() const;
+
+protected:
+ void on_update() override;
+ void on_release() override;
+
+private:
+ std::vector<std::unique_ptr<MeshRaycaster>> m_raycasters;
+ std::vector<const TriangleMesh*> m_old_meshes;
+};
+
+
+
+class ObjectClipper : public CommonGizmosDataBase
+{
+public:
+ explicit ObjectClipper(CommonGizmosDataPool* cgdp)
+ : CommonGizmosDataBase(cgdp) {}
+#ifndef NDEBUG
+ CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
+#endif // NDEBUG
+
+ void set_position(double pos, bool keep_normal);
+ double get_position() const { return m_clp_ratio; }
+ ClippingPlane* get_clipping_plane() const { return m_clp.get(); }
+ void render_cut() const;
+
+
+protected:
+ void on_update() override;
+ void on_release() override;
+
+private:
+ std::vector<const TriangleMesh*> m_old_meshes;
+ std::vector<std::unique_ptr<MeshClipper>> m_clippers;
+ std::unique_ptr<ClippingPlane> m_clp;
+ double m_clp_ratio = 0.;
+ double m_active_inst_bb_radius = 0.;
+};
+
+
+
+class SupportsClipper : public CommonGizmosDataBase
+{
+public:
+ explicit SupportsClipper(CommonGizmosDataPool* cgdp)
+ : CommonGizmosDataBase(cgdp) {}
+#ifndef NDEBUG
+ CommonGizmosDataID get_dependencies() const override {
+ return CommonGizmosDataID(
+ int(CommonGizmosDataID::SelectionInfo)
+ | int(CommonGizmosDataID::ObjectClipper)
+ );
+ }
+#endif // NDEBUG
+
+ void render_cut() const;
+
+
+protected:
+ void on_update() override;
+ void on_release() override;
+
+private:
+ size_t m_old_timestamp = 0;
+ int m_print_object_idx = -1;
+ int m_print_objects_count = 0;
+ std::unique_ptr<MeshClipper> m_clipper;
+};
+
+} // namespace CommonGizmosDataObjects
+
+
+
+
+
+
+} // namespace GUI
+} // namespace Slic3r
+
+
+#endif // slic3r_GUI_GLGizmosCommon_hpp_
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index 379bd48d1..685e49d36 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -9,10 +9,15 @@
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
-#include "libslic3r/SLAPrint.hpp"
-#include "slic3r/GUI/MeshUtils.hpp"
-#include "slic3r/GUI/Gizmos/GLGizmos.hpp"
-#include "slic3r/GUI/Camera.hpp"
+
+#include "slic3r/GUI/Gizmos/GLGizmoMove.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoScale.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoRotate.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
#include <wx/glcanvas.h>
@@ -97,16 +102,16 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoCut(m_parent, "cut.svg", 4));
m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", 5));
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6));
+ m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "sla_supports.svg", 7));
- m_common_gizmos_data.reset(new CommonGizmosData());
- dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->set_common_data_ptr(m_common_gizmos_data.get());
- dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->set_common_data_ptr(m_common_gizmos_data.get());
+ m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
for (auto& gizmo : m_gizmos) {
if (! gizmo->init()) {
m_gizmos.clear();
return false;
}
+ gizmo->set_common_data_pool(m_common_gizmos_data.get());
}
m_current = Undefined;
@@ -198,6 +203,10 @@ void GLGizmosManager::update_data()
enable_grabber(Scale, i, enable_scale_xyz);
}
+ m_common_gizmos_data->update(get_current()
+ ? get_current()->get_requirements()
+ : CommonGizmosDataID(0));
+
if (selection.is_single_full_instance())
{
// all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first
@@ -207,6 +216,7 @@ void GLGizmosManager::update_data()
ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()];
set_flattening_data(model_object);
set_sla_support_data(model_object);
+ set_fdm_support_data(model_object);
}
else if (selection.is_single_volume() || selection.is_single_modifier())
{
@@ -215,6 +225,7 @@ void GLGizmosManager::update_data()
set_rotation(Vec3d::Zero());
set_flattening_data(nullptr);
set_sla_support_data(nullptr);
+ set_fdm_support_data(nullptr);
}
else if (is_wipe_tower)
{
@@ -223,6 +234,7 @@ void GLGizmosManager::update_data()
set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast<const ConfigOptionFloat*>(config.option("wipe_tower_rotation_angle"))->value));
set_flattening_data(nullptr);
set_sla_support_data(nullptr);
+ set_fdm_support_data(nullptr);
}
else
{
@@ -230,6 +242,7 @@ void GLGizmosManager::update_data()
set_rotation(Vec3d::Zero());
set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
+ set_fdm_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
}
}
@@ -358,15 +371,27 @@ void GLGizmosManager::set_sla_support_data(ModelObject* model_object)
|| wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA)
return;
- m_common_gizmos_data->update_from_backend(m_parent, model_object);
+ /*m_common_gizmos_data->update_from_backend(m_parent, model_object);
auto* gizmo_supports = dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get());
- auto* gizmo_hollow = dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get());
+
// note: sla support gizmo takes care of updating the common data.
// following lines are thus dependent
- gizmo_supports->set_sla_support_data(model_object, m_parent.get_selection());
+ //gizmo_supports->set_sla_support_data(model_object, m_parent.get_selection());
+ */
+ auto* gizmo_hollow = dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get());
+ auto* gizmo_supports = dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get());
gizmo_hollow->set_sla_support_data(model_object, m_parent.get_selection());
+ gizmo_supports->set_sla_support_data(model_object, m_parent.get_selection());
+}
+
+void GLGizmosManager::set_fdm_support_data(ModelObject* model_object)
+{
+ if (!m_enabled || m_gizmos.empty())
+ return;
+
+ dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->set_fdm_support_data(model_object, m_parent.get_selection());
}
// Returns true if the gizmo used the event to do something, false otherwise.
@@ -377,20 +402,24 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
if (m_current == SlaSupports)
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
- if (m_current == Hollow)
+ else if (m_current == Hollow)
return dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
- return false;
+ else if (m_current == FdmSupports)
+ return dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
+ else
+ return false;
}
-ClippingPlane GLGizmosManager::get_sla_clipping_plane() const
+ClippingPlane GLGizmosManager::get_clipping_plane() const
{
- if (!m_enabled || (m_current != SlaSupports && m_current != Hollow) || m_gizmos.empty())
+ if (! m_common_gizmos_data
+ || ! m_common_gizmos_data->object_clipper()
+ || m_common_gizmos_data->object_clipper()->get_position() == 0.)
return ClippingPlane::ClipsNothing();
-
- if (m_current == SlaSupports)
- return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->get_sla_clipping_plane();
- else
- return dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->get_sla_clipping_plane();
+ else {
+ const ClippingPlane& clp = *m_common_gizmos_data->object_clipper()->get_clipping_plane();
+ return ClippingPlane(-clp.get_normal(), clp.get_data()[3]);
+ }
}
bool GLGizmosManager::wants_reslice_supports_on_undo() const
@@ -410,6 +439,7 @@ void GLGizmosManager::render_current_gizmo() const
void GLGizmosManager::render_current_gizmo_for_picking_pass() const
{
if (! m_enabled || m_current == Undefined)
+
return;
m_gizmos[m_current]->render_for_picking();
@@ -439,7 +469,7 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
{
bool processed = false;
- if (m_current == SlaSupports || m_current == Hollow) {
+ if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) {
float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
processed = true;
@@ -529,8 +559,8 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
processed = true;
m_mouse_capture.right = false;
}
- else
- return false;
+// else
+// return false;
}
#if ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX
else if (evt.Dragging() && !is_dragging())
@@ -618,7 +648,8 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
if (evt.LeftDown())
{
- if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
+ if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports)
+ && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
// the gizmo got the event and took some action, there is no need to do anything more
processed = true;
else if (!selection.is_empty() && grabber_contains_mouse()) {
@@ -636,17 +667,25 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
processed = true;
}
}
- else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::RightDown))
+ else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports || m_current == Hollow)
+ && gizmo_event(SLAGizmoEventType::RightDown, mouse_pos))
{
// we need to set the following right up as processed to avoid showing the context menu if the user release the mouse over the object
pending_right_up = true;
// event was taken care of by the SlaSupports gizmo
processed = true;
}
+ else if (evt.RightDown() && (selected_object_idx != -1) && m_current == FdmSupports
+ && gizmo_event(SLAGizmoEventType::RightDown, mouse_pos))
+ {
+ // event was taken care of by the FdmSupports gizmo
+ processed = true;
+ }
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow))
// don't allow dragging objects with the Sla gizmo on
processed = true;
- else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
+ else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports )
+ && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
{
// the gizmo got the event and took some action, no need to do anything more here
m_parent.set_as_dirty();
@@ -723,9 +762,9 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
processed = true;
}
#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI
- else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow) && !m_parent.is_mouse_dragging())
+ else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) && !m_parent.is_mouse_dragging())
{
- // in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither
+ // in case SLA/FDM gizmo is selected, we just pass the LeftUp event and stop processing - neither
// object moving or selecting is suppressed in that case
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown());
processed = true;
@@ -735,6 +774,11 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
// to avoid to loose the selection when user clicks an the white faces of a different object while the Flatten gizmo is active
processed = true;
}
+ else if (evt.RightUp() && m_current == FdmSupports && !m_parent.is_mouse_dragging())
+ {
+ gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown());
+ processed = true;
+ }
}
else
{
@@ -824,7 +868,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
case 'r' :
case 'R' :
{
- if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
+ if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
processed = true;
break;
@@ -1188,10 +1232,13 @@ void GLGizmosManager::activate_gizmo(EType type)
return; // gizmo refused to be turned off, do nothing.
}
+ m_current = type;
+ m_common_gizmos_data->update(get_current()
+ ? get_current()->get_requirements()
+ : CommonGizmosDataID(0));
+
if (type != Undefined)
m_gizmos[type]->set_state(GLGizmoBase::On);
-
- m_current = type;
}
@@ -1204,135 +1251,5 @@ bool GLGizmosManager::grabber_contains_mouse() const
return (curr != nullptr) ? (curr->get_hover_id() != -1) : false;
}
-
-
-CommonGizmosData::CommonGizmosData()
-{
- m_clipping_plane.reset(new ClippingPlane(Vec3d::Zero(), 0.));
-}
-
-
-
-bool CommonGizmosData::update_from_backend(GLCanvas3D& canvas, ModelObject* model_object)
-{
- recent_update = false;
- bool object_changed = false;
-
- if (m_model_object != model_object
- || (model_object && m_model_object_id != model_object->id())) {
- m_model_object = model_object;
- m_print_object_idx = -1;
- m_mesh_raycaster.reset();
- m_object_clipper.reset();
- m_supports_clipper.reset();
- m_old_mesh = nullptr;
- m_mesh = nullptr;
- m_backend_mesh_transformed.clear();
-
- object_changed = true;
- recent_update = true;
- }
-
- if (m_model_object) {
- int active_inst = canvas.get_selection().get_instance_idx();
- if (m_active_instance != active_inst) {
- m_active_instance = active_inst;
- m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius();
- recent_update = true;
- }
- }
-
-
- if (! m_model_object || ! canvas.get_selection().is_from_single_instance())
- return false;
-
- int old_po_idx = m_print_object_idx;
-
- // First we need a pointer to the respective SLAPrintObject. The index into objects vector is
- // cached so we don't have todo it on each render. We only search for the po if needed:
- if (m_print_object_idx < 0 || (int)canvas.sla_print()->objects().size() != m_print_objects_count) {
- m_print_objects_count = canvas.sla_print()->objects().size();
- m_print_object_idx = -1;
- for (const SLAPrintObject* po : canvas.sla_print()->objects()) {
- ++m_print_object_idx;
- if (po->model_object()->id() == m_model_object->id())
- break;
- }
- }
-
- bool mesh_exchanged = false;
- m_mesh = nullptr;
- // Load either the model_object mesh, or one provided by the backend
- // This mesh does not account for the possible Z up SLA offset.
- // The backend mesh needs to be transformed and because a pointer to it is
- // saved, a copy is stored as a member (FIXME)
- if (m_print_object_idx >=0) {
- const SLAPrintObject* po = canvas.sla_print()->objects()[m_print_object_idx];
- if (po->is_step_done(slaposDrillHoles)) {
- m_backend_mesh_transformed = po->get_mesh_to_print();
- m_backend_mesh_transformed.transform(canvas.sla_print()->sla_trafo(*m_model_object).inverse());
- m_mesh = &m_backend_mesh_transformed;
- m_has_drilled_mesh = true;
- mesh_exchanged = true;
- }
- }
-
- if (! m_mesh) {
- m_mesh = &m_model_object->volumes.front()->mesh();
- m_backend_mesh_transformed.clear();
- m_has_drilled_mesh = false;
- }
-
- m_model_object_id = m_model_object->id();
-
- if (m_mesh != m_old_mesh) {
- // Update clipping plane position.
- float new_clp_pos = m_clipping_plane_distance;
- if (object_changed) {
- new_clp_pos = 0.f;
- m_clipping_plane_was_moved = false;
- } else {
- // After we got a drilled mesh, move the cp to 25%. This only applies when
- // the hollowing gizmo is active and hollowing is enabled
- if (m_clipping_plane_distance == 0.f && mesh_exchanged && m_has_drilled_mesh) {
- const DynamicPrintConfig& cfg =
- (m_model_object && m_model_object->config.has("hollowing_enable"))
- ? m_model_object->config
- : wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
-
- if (cfg.has("hollowing_enable") && cfg.opt_bool("hollowing_enable")
- && canvas.get_gizmos_manager().get_current_type() == GLGizmosManager::Hollow) {
- new_clp_pos = 0.25f;
- m_clipping_plane_was_moved = false; // so it uses current camera direction
- }
- }
- }
- m_clipping_plane_distance = new_clp_pos;
- m_clipping_plane_distance_stash = new_clp_pos;
-
- m_schedule_aabb_calculation = true;
- recent_update = true;
- return true;
- }
- if (! recent_update)
- recent_update = m_print_object_idx < 0 && old_po_idx >= 0;
-
- return recent_update;
-}
-
-
-void CommonGizmosData::build_AABB_if_needed()
-{
- if (! m_schedule_aabb_calculation)
- return;
-
- wxBusyCursor wait;
- m_mesh_raycaster.reset(new MeshRaycaster(*m_mesh));
- m_object_clipper.reset();
- m_supports_clipper.reset();
- m_old_mesh = m_mesh;
- m_schedule_aabb_calculation = false;
-}
-
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
index 7ae1fa661..478774718 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
@@ -3,8 +3,8 @@
#include "slic3r/GUI/GLTexture.hpp"
#include "slic3r/GUI/GLToolbar.hpp"
-#include "libslic3r/ObjectID.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoBase.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include <map>
@@ -19,7 +19,7 @@ namespace GUI {
class GLCanvas3D;
class ClippingPlane;
enum class SLAGizmoEventType : unsigned char;
-class CommonGizmosData;
+class CommonGizmosDataPool;
class Rect
{
@@ -64,6 +64,7 @@ public:
Cut,
Hollow,
SlaSupports,
+ FdmSupports,
Undefined
};
@@ -115,7 +116,8 @@ private:
MouseCapture m_mouse_capture;
std::string m_tooltip;
bool m_serializing;
- std::unique_ptr<CommonGizmosData> m_common_gizmos_data;
+ //std::unique_ptr<CommonGizmosData> m_common_gizmos_data;
+ std::unique_ptr<CommonGizmosDataPool> m_common_gizmos_data;
public:
explicit GLGizmosManager(GLCanvas3D& parent);
@@ -197,8 +199,11 @@ public:
void set_flattening_data(const ModelObject* model_object);
void set_sla_support_data(ModelObject* model_object);
+
+ void set_fdm_support_data(ModelObject* model_object);
+
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false);
- ClippingPlane get_sla_clipping_plane() const;
+ ClippingPlane get_clipping_plane() const;
bool wants_reslice_supports_on_undo() const;
void render_current_gizmo() const;
@@ -231,63 +236,6 @@ private:
-class MeshRaycaster;
-class MeshClipper;
-
-// This class is only for sharing SLA related data between SLA gizmos
-// and its synchronization with backend data. It should not be misused
-// for anything else.
-class CommonGizmosData {
-public:
- CommonGizmosData();
- const TriangleMesh* mesh() const {
- return (! m_mesh ? nullptr : m_mesh); //(m_cavity_mesh ? m_cavity_mesh.get() : m_mesh));
- }
-
- bool update_from_backend(GLCanvas3D& canvas, ModelObject* model_object);
- bool recent_update = false;
- static constexpr float HoleStickOutLength = 1.f;
-
- ModelObject* m_model_object = nullptr;
- const TriangleMesh* m_mesh;
- std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
- std::unique_ptr<MeshClipper> m_object_clipper;
- std::unique_ptr<MeshClipper> m_supports_clipper;
-
- //std::unique_ptr<TriangleMesh> m_cavity_mesh;
- //std::unique_ptr<GLVolume> m_volume_with_cavity;
-
- int m_active_instance = -1;
- float m_active_instance_bb_radius = 0;
- ObjectID m_model_object_id = 0;
- int m_print_object_idx = -1;
- int m_print_objects_count = -1;
- int m_old_timestamp = -1;
-
- float m_clipping_plane_distance = 0.f;
- std::unique_ptr<ClippingPlane> m_clipping_plane;
- bool m_clipping_plane_was_moved = false;
-
- void stash_clipping_plane() {
- m_clipping_plane_distance_stash = m_clipping_plane_distance;
- }
-
- void unstash_clipping_plane() {
- m_clipping_plane_distance = m_clipping_plane_distance_stash;
- }
-
- bool has_drilled_mesh() const { return m_has_drilled_mesh; }
-
- void build_AABB_if_needed();
-
-private:
- const TriangleMesh* m_old_mesh;
- TriangleMesh m_backend_mesh_transformed;
- float m_clipping_plane_distance_stash = 0.f;
- bool m_has_drilled_mesh = false;
- bool m_schedule_aabb_calculation = false;
-};
-
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Job.hpp b/src/slic3r/GUI/Job.hpp
deleted file mode 100644
index ac31b9bdb..000000000
--- a/src/slic3r/GUI/Job.hpp
+++ /dev/null
@@ -1,155 +0,0 @@
-#ifndef JOB_HPP
-#define JOB_HPP
-
-#include <atomic>
-
-#include <slic3r/Utils/Thread.hpp>
-#include <slic3r/GUI/I18N.hpp>
-#include <slic3r/GUI/ProgressIndicator.hpp>
-
-#include <wx/event.h>
-
-#include <boost/thread.hpp>
-
-namespace Slic3r { namespace GUI {
-
-// A class to handle UI jobs like arranging and optimizing rotation.
-// These are not instant jobs, the user has to be informed about their
-// state in the status progress indicator. On the other hand they are
-// separated from the background slicing process. Ideally, these jobs should
-// run when the background process is not running.
-//
-// TODO: A mechanism would be useful for blocking the plater interactions:
-// objects would be frozen for the user. In case of arrange, an animation
-// could be shown, or with the optimize orientations, partial results
-// could be displayed.
-class Job : public wxEvtHandler
-{
- int m_range = 100;
- boost::thread m_thread;
- std::atomic<bool> m_running{false}, m_canceled{false};
- bool m_finalized = false;
- std::shared_ptr<ProgressIndicator> m_progress;
-
- void run()
- {
- m_running.store(true);
- process();
- m_running.store(false);
-
- // ensure to call the last status to finalize the job
- update_status(status_range(), "");
- }
-
-protected:
- // status range for a particular job
- virtual int status_range() const { return 100; }
-
- // status update, to be used from the work thread (process() method)
- void update_status(int st, const wxString &msg = "")
- {
- auto evt = new wxThreadEvent();
- evt->SetInt(st);
- evt->SetString(msg);
- wxQueueEvent(this, evt);
- }
-
- bool was_canceled() const { return m_canceled.load(); }
-
- // Launched just before start(), a job can use it to prepare internals
- virtual void prepare() {}
-
- // Launched when the job is finished. It refreshes the 3Dscene by def.
- virtual void finalize() { m_finalized = true; }
-
-
-public:
- Job(std::shared_ptr<ProgressIndicator> pri) : m_progress(pri)
- {
- Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
- auto msg = evt.GetString();
- if (!msg.empty())
- m_progress->set_status_text(msg.ToUTF8().data());
-
- if (m_finalized) return;
-
- m_progress->set_progress(evt.GetInt());
- if (evt.GetInt() == status_range()) {
- // set back the original range and cancel callback
- m_progress->set_range(m_range);
- m_progress->set_cancel_callback();
- wxEndBusyCursor();
-
- finalize();
-
- // dont do finalization again for the same process
- m_finalized = true;
- }
- });
- }
-
- bool is_finalized() const { return m_finalized; }
-
- Job(const Job &) = delete;
- Job(Job &&) = delete;
- Job &operator=(const Job &) = delete;
- Job &operator=(Job &&) = delete;
-
- virtual void process() = 0;
-
- void start()
- { // Start the job. No effect if the job is already running
- if (!m_running.load()) {
- prepare();
-
- // Save the current status indicatior range and push the new one
- m_range = m_progress->get_range();
- m_progress->set_range(status_range());
-
- // init cancellation flag and set the cancel callback
- m_canceled.store(false);
- m_progress->set_cancel_callback(
- [this]() { m_canceled.store(true); });
-
- m_finalized = false;
-
- // Changing cursor to busy
- wxBeginBusyCursor();
-
- try { // Execute the job
- m_thread = create_thread([this] { this->run(); });
- } catch (std::exception &) {
- update_status(status_range(),
- _(L("ERROR: not enough resources to "
- "execute a new job.")));
- }
-
- // The state changes will be undone when the process hits the
- // last status value, in the status update handler (see ctor)
- }
- }
-
- // To wait for the running job and join the threads. False is
- // returned if the timeout has been reached and the job is still
- // running. Call cancel() before this fn if you want to explicitly
- // end the job.
- bool join(int timeout_ms = 0)
- {
- if (!m_thread.joinable()) return true;
-
- if (timeout_ms <= 0)
- m_thread.join();
- else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
- return false;
-
- return true;
- }
-
- bool is_running() const { return m_running.load(); }
- void cancel() { m_canceled.store(true); }
-};
-
-}
-}
-
-#endif // JOB_HPP
diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp
new file mode 100644
index 000000000..00b9fb654
--- /dev/null
+++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp
@@ -0,0 +1,223 @@
+#include "ArrangeJob.hpp"
+
+#include "libslic3r/MTUtils.hpp"
+
+#include "slic3r/GUI/Plater.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/GUI.hpp"
+
+namespace Slic3r { namespace GUI {
+
+// Cache the wti info
+class WipeTower: public GLCanvas3D::WipeTowerInfo {
+ using ArrangePolygon = arrangement::ArrangePolygon;
+public:
+ explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti)
+ : GLCanvas3D::WipeTowerInfo(wti)
+ {}
+
+ explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti)
+ : GLCanvas3D::WipeTowerInfo(std::move(wti))
+ {}
+
+ void apply_arrange_result(const Vec2d& tr, double rotation)
+ {
+ m_pos = unscaled(tr); m_rotation = rotation;
+ apply_wipe_tower();
+ }
+
+ ArrangePolygon get_arrange_polygon() const
+ {
+ Polygon ap({
+ {coord_t(0), coord_t(0)},
+ {scaled(m_bb_size(X)), coord_t(0)},
+ {scaled(m_bb_size)},
+ {coord_t(0), scaled(m_bb_size(Y))},
+ {coord_t(0), coord_t(0)},
+ });
+
+ ArrangePolygon ret;
+ ret.poly.contour = std::move(ap);
+ ret.translation = scaled(m_pos);
+ ret.rotation = m_rotation;
+ ret.priority++;
+ return ret;
+ }
+};
+
+static WipeTower get_wipe_tower(Plater &plater)
+{
+ return WipeTower{plater.canvas3D()->get_wipe_tower_info()};
+}
+
+void ArrangeJob::clear_input()
+{
+ const Model &model = m_plater->model();
+
+ size_t count = 0, cunprint = 0; // To know how much space to reserve
+ for (auto obj : model.objects)
+ for (auto mi : obj->instances)
+ mi->printable ? count++ : cunprint++;
+
+ m_selected.clear();
+ m_unselected.clear();
+ m_unprintable.clear();
+ m_selected.reserve(count + 1 /* for optional wti */);
+ m_unselected.reserve(count + 1 /* for optional wti */);
+ m_unprintable.reserve(cunprint /* for optional wti */);
+}
+
+double ArrangeJob::bed_stride() const {
+ double bedwidth = m_plater->bed_shape_bb().size().x();
+ return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
+}
+
+void ArrangeJob::prepare_all() {
+ clear_input();
+
+ for (ModelObject *obj: m_plater->model().objects)
+ for (ModelInstance *mi : obj->instances) {
+ ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
+ cont.emplace_back(get_arrange_poly(mi));
+ }
+
+ if (auto wti = get_wipe_tower(*m_plater))
+ m_selected.emplace_back(wti.get_arrange_polygon());
+}
+
+void ArrangeJob::prepare_selected() {
+ clear_input();
+
+ Model &model = m_plater->model();
+ double stride = bed_stride();
+
+ std::vector<const Selection::InstanceIdxsList *>
+ obj_sel(model.objects.size(), nullptr);
+
+ for (auto &s : m_plater->get_selection().get_content())
+ if (s.first < int(obj_sel.size()))
+ obj_sel[size_t(s.first)] = &s.second;
+
+ // Go through the objects and check if inside the selection
+ for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
+ const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
+ ModelObject *mo = model.objects[oidx];
+
+ std::vector<bool> inst_sel(mo->instances.size(), false);
+
+ if (instlist)
+ for (auto inst_id : *instlist)
+ inst_sel[size_t(inst_id)] = true;
+
+ for (size_t i = 0; i < inst_sel.size(); ++i) {
+ ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
+
+ ArrangePolygons &cont = mo->instances[i]->printable ?
+ (inst_sel[i] ? m_selected :
+ m_unselected) :
+ m_unprintable;
+
+ cont.emplace_back(std::move(ap));
+ }
+ }
+
+ if (auto wti = get_wipe_tower(*m_plater)) {
+ ArrangePolygon &&ap = get_arrange_poly(&wti);
+
+ m_plater->get_selection().is_wipe_tower() ?
+ m_selected.emplace_back(std::move(ap)) :
+ m_unselected.emplace_back(std::move(ap));
+ }
+
+ // If the selection was empty arrange everything
+ if (m_selected.empty()) m_selected.swap(m_unselected);
+
+ // The strides have to be removed from the fixed items. For the
+ // arrangeable (selected) items bed_idx is ignored and the
+ // translation is irrelevant.
+ for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
+}
+
+void ArrangeJob::prepare()
+{
+ wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
+}
+
+void ArrangeJob::process()
+{
+ static const auto arrangestr = _(L("Arranging"));
+
+ double dist = min_object_distance(*m_plater->config());
+
+ arrangement::ArrangeParams params;
+ params.min_obj_distance = scaled(dist);
+
+ auto count = unsigned(m_selected.size() + m_unprintable.size());
+ Points bedpts = get_bed_shape(*m_plater->config());
+
+ params.stopcondition = [this]() { return was_canceled(); };
+
+ try {
+ params.progressind = [this, count](unsigned st) {
+ st += m_unprintable.size();
+ if (st > 0) update_status(int(count - st), arrangestr);
+ };
+
+ arrangement::arrange(m_selected, m_unselected, bedpts, params);
+
+ params.progressind = [this, count](unsigned st) {
+ if (st > 0) update_status(int(count - st), arrangestr);
+ };
+
+ arrangement::arrange(m_unprintable, {}, bedpts, params);
+ } catch (std::exception & /*e*/) {
+ GUI::show_error(m_plater,
+ _(L("Could not arrange model objects! "
+ "Some geometries may be invalid.")));
+ }
+
+ // finalize just here.
+ update_status(int(count),
+ was_canceled() ? _(L("Arranging canceled."))
+ : _(L("Arranging done.")));
+}
+
+void ArrangeJob::finalize() {
+ // Ignore the arrange result if aborted.
+ if (was_canceled()) return;
+
+ // Unprintable items go to the last virtual bed
+ int beds = 0;
+
+ // Apply the arrange result to all selected objects
+ for (ArrangePolygon &ap : m_selected) {
+ beds = std::max(ap.bed_idx, beds);
+ ap.apply();
+ }
+
+ // Get the virtual beds from the unselected items
+ for (ArrangePolygon &ap : m_unselected)
+ beds = std::max(ap.bed_idx, beds);
+
+ // Move the unprintable items to the last virtual bed.
+ for (ArrangePolygon &ap : m_unprintable) {
+ ap.bed_idx += beds + 1;
+ ap.apply();
+ }
+
+ m_plater->update();
+
+ Job::finalize();
+}
+
+arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &plater)
+{
+ return WipeTower{plater.canvas3D()->get_wipe_tower_info()}.get_arrange_polygon();
+}
+
+void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap)
+{
+ WipeTower{plater.canvas3D()->get_wipe_tower_info()}.apply_arrange_result(ap.translation.cast<double>(), ap.rotation);
+}
+
+}} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp
new file mode 100644
index 000000000..bd097af6b
--- /dev/null
+++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp
@@ -0,0 +1,77 @@
+#ifndef ARRANGEJOB_HPP
+#define ARRANGEJOB_HPP
+
+#include "Job.hpp"
+#include "libslic3r/Arrange.hpp"
+
+namespace Slic3r { namespace GUI {
+
+class Plater;
+
+class ArrangeJob : public Job
+{
+ Plater *m_plater;
+
+ using ArrangePolygon = arrangement::ArrangePolygon;
+ using ArrangePolygons = arrangement::ArrangePolygons;
+
+ // The gap between logical beds in the x axis expressed in ratio of
+ // the current bed width.
+ static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
+
+ ArrangePolygons m_selected, m_unselected, m_unprintable;
+
+ // clear m_selected and m_unselected, reserve space for next usage
+ void clear_input();
+
+ // Stride between logical beds
+ double bed_stride() const;
+
+ // Set up arrange polygon for a ModelInstance and Wipe tower
+ template<class T> ArrangePolygon get_arrange_poly(T *obj) const
+ {
+ ArrangePolygon ap = obj->get_arrange_polygon();
+ ap.priority = 0;
+ ap.bed_idx = ap.translation.x() / bed_stride();
+ ap.setter = [obj, this](const ArrangePolygon &p) {
+ if (p.is_arranged()) {
+ Vec2d t = p.translation.cast<double>();
+ t.x() += p.bed_idx * bed_stride();
+ obj->apply_arrange_result(t, p.rotation);
+ }
+ };
+ return ap;
+ }
+
+ // Prepare all objects on the bed regardless of the selection
+ void prepare_all();
+
+ // Prepare the selected and unselected items separately. If nothing is
+ // selected, behaves as if everything would be selected.
+ void prepare_selected();
+
+protected:
+
+ void prepare() override;
+
+public:
+ ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
+ : Job{std::move(pri)}, m_plater{plater}
+ {}
+
+ int status_range() const override
+ {
+ return int(m_selected.size() + m_unprintable.size());
+ }
+
+ void process() override;
+
+ void finalize() override;
+};
+
+arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &);
+void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap);
+
+}} // namespace Slic3r::GUI
+
+#endif // ARRANGEJOB_HPP
diff --git a/src/slic3r/GUI/Jobs/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp
new file mode 100644
index 000000000..cc2cb75f1
--- /dev/null
+++ b/src/slic3r/GUI/Jobs/Job.cpp
@@ -0,0 +1,121 @@
+#include <algorithm>
+
+#include "Job.hpp"
+#include <boost/log/trivial.hpp>
+
+namespace Slic3r {
+
+void GUI::Job::run()
+{
+ m_running.store(true);
+ process();
+ m_running.store(false);
+
+ // ensure to call the last status to finalize the job
+ update_status(status_range(), "");
+}
+
+void GUI::Job::update_status(int st, const wxString &msg)
+{
+ auto evt = new wxThreadEvent();
+ evt->SetInt(st);
+ evt->SetString(msg);
+ wxQueueEvent(this, evt);
+}
+
+GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
+ : m_progress(std::move(pri))
+{
+ Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
+ auto msg = evt.GetString();
+ if (!msg.empty())
+ m_progress->set_status_text(msg.ToUTF8().data());
+
+ if (m_finalized) return;
+
+ m_progress->set_progress(evt.GetInt());
+ if (evt.GetInt() == status_range()) {
+ // set back the original range and cancel callback
+ m_progress->set_range(m_range);
+ m_progress->set_cancel_callback();
+ wxEndBusyCursor();
+
+ finalize();
+
+ // dont do finalization again for the same process
+ m_finalized = true;
+ }
+ });
+}
+
+void GUI::Job::start()
+{ // Start the job. No effect if the job is already running
+ if (!m_running.load()) {
+ prepare();
+
+ // Save the current status indicatior range and push the new one
+ m_range = m_progress->get_range();
+ m_progress->set_range(status_range());
+
+ // init cancellation flag and set the cancel callback
+ m_canceled.store(false);
+ m_progress->set_cancel_callback(
+ [this]() { m_canceled.store(true); });
+
+ m_finalized = false;
+
+ // Changing cursor to busy
+ wxBeginBusyCursor();
+
+ try { // Execute the job
+ m_thread = create_thread([this] { this->run(); });
+ } catch (std::exception &) {
+ update_status(status_range(),
+ _(L("ERROR: not enough resources to "
+ "execute a new job.")));
+ }
+
+ // The state changes will be undone when the process hits the
+ // last status value, in the status update handler (see ctor)
+ }
+}
+
+bool GUI::Job::join(int timeout_ms)
+{
+ if (!m_thread.joinable()) return true;
+
+ if (timeout_ms <= 0)
+ m_thread.join();
+ else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
+ return false;
+
+ return true;
+}
+
+void GUI::ExclusiveJobGroup::start(size_t jid) {
+ assert(jid < m_jobs.size());
+ stop_all();
+ m_jobs[jid]->start();
+}
+
+void GUI::ExclusiveJobGroup::join_all(int wait_ms)
+{
+ std::vector<bool> aborted(m_jobs.size(), false);
+
+ for (size_t jid = 0; jid < m_jobs.size(); ++jid)
+ aborted[jid] = m_jobs[jid]->join(wait_ms);
+
+ if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; }))
+ BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
+}
+
+bool GUI::ExclusiveJobGroup::is_any_running() const
+{
+ return std::any_of(m_jobs.begin(), m_jobs.end(),
+ [](const std::unique_ptr<GUI::Job> &j) {
+ return j->is_running();
+ });
+}
+
+}
+
diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp
new file mode 100644
index 000000000..130ca2ed9
--- /dev/null
+++ b/src/slic3r/GUI/Jobs/Job.hpp
@@ -0,0 +1,110 @@
+#ifndef JOB_HPP
+#define JOB_HPP
+
+#include <atomic>
+
+#include <slic3r/Utils/Thread.hpp>
+#include <slic3r/GUI/I18N.hpp>
+
+#include "ProgressIndicator.hpp"
+
+#include <wx/event.h>
+
+#include <boost/thread.hpp>
+
+namespace Slic3r { namespace GUI {
+
+// A class to handle UI jobs like arranging and optimizing rotation.
+// These are not instant jobs, the user has to be informed about their
+// state in the status progress indicator. On the other hand they are
+// separated from the background slicing process. Ideally, these jobs should
+// run when the background process is not running.
+//
+// TODO: A mechanism would be useful for blocking the plater interactions:
+// objects would be frozen for the user. In case of arrange, an animation
+// could be shown, or with the optimize orientations, partial results
+// could be displayed.
+class Job : public wxEvtHandler
+{
+ int m_range = 100;
+ boost::thread m_thread;
+ std::atomic<bool> m_running{false}, m_canceled{false};
+ bool m_finalized = false;
+ std::shared_ptr<ProgressIndicator> m_progress;
+
+ void run();
+
+protected:
+ // status range for a particular job
+ virtual int status_range() const { return 100; }
+
+ // status update, to be used from the work thread (process() method)
+ void update_status(int st, const wxString &msg = "");
+
+ bool was_canceled() const { return m_canceled.load(); }
+
+ // Launched just before start(), a job can use it to prepare internals
+ virtual void prepare() {}
+
+ // Launched when the job is finished. It refreshes the 3Dscene by def.
+ virtual void finalize() { m_finalized = true; }
+
+public:
+ Job(std::shared_ptr<ProgressIndicator> pri);
+
+ bool is_finalized() const { return m_finalized; }
+
+ Job(const Job &) = delete;
+ Job(Job &&) = delete;
+ Job &operator=(const Job &) = delete;
+ Job &operator=(Job &&) = delete;
+
+ virtual void process() = 0;
+
+ void start();
+
+ // To wait for the running job and join the threads. False is
+ // returned if the timeout has been reached and the job is still
+ // running. Call cancel() before this fn if you want to explicitly
+ // end the job.
+ bool join(int timeout_ms = 0);
+
+ bool is_running() const { return m_running.load(); }
+ void cancel() { m_canceled.store(true); }
+};
+
+// Jobs defined inside the group class will be managed so that only one can
+// run at a time. Also, the background process will be stopped if a job is
+// started.
+class ExclusiveJobGroup
+{
+ static const int ABORT_WAIT_MAX_MS = 10000;
+
+ std::vector<std::unique_ptr<GUI::Job>> m_jobs;
+
+protected:
+ virtual void before_start() {}
+
+public:
+ virtual ~ExclusiveJobGroup() = default;
+
+ size_t add_job(std::unique_ptr<GUI::Job> &&job)
+ {
+ m_jobs.emplace_back(std::move(job));
+ return m_jobs.size() - 1;
+ }
+
+ void start(size_t jid);
+
+ void cancel_all() { for (auto& j : m_jobs) j->cancel(); }
+
+ void join_all(int wait_ms = 0);
+
+ void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
+
+ bool is_any_running() const;
+};
+
+}} // namespace Slic3r::GUI
+
+#endif // JOB_HPP
diff --git a/src/slic3r/GUI/ProgressIndicator.hpp b/src/slic3r/GUI/Jobs/ProgressIndicator.hpp
index 674a81ba2..674a81ba2 100644
--- a/src/slic3r/GUI/ProgressIndicator.hpp
+++ b/src/slic3r/GUI/Jobs/ProgressIndicator.hpp
diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp
new file mode 100644
index 000000000..4cc34e71c
--- /dev/null
+++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp
@@ -0,0 +1,68 @@
+#include "RotoptimizeJob.hpp"
+
+#include "libslic3r/MTUtils.hpp"
+#include "libslic3r/SLA/Rotfinder.hpp"
+#include "libslic3r/MinAreaBoundingBox.hpp"
+
+#include "slic3r/GUI/Plater.hpp"
+
+namespace Slic3r { namespace GUI {
+
+void RotoptimizeJob::process()
+{
+ int obj_idx = m_plater->get_selected_object_idx();
+ if (obj_idx < 0) { return; }
+
+ ModelObject *o = m_plater->model().objects[size_t(obj_idx)];
+
+ auto r = sla::find_best_rotation(
+ *o,
+ .005f,
+ [this](unsigned s) {
+ if (s < 100)
+ update_status(int(s),
+ _(L("Searching for optimal orientation")));
+ },
+ [this]() { return was_canceled(); });
+
+
+ double mindist = 6.0; // FIXME
+
+ if (!was_canceled()) {
+ for(ModelInstance * oi : o->instances) {
+ oi->set_rotation({r[X], r[Y], r[Z]});
+
+ auto trmatrix = oi->get_transformation().get_matrix();
+ Polygon trchull = o->convex_hull_2d(trmatrix);
+
+ MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
+ double phi = rotbb.angle_to_X();
+
+ // The box should be landscape
+ if(rotbb.width() < rotbb.height()) phi += PI / 2;
+
+ Vec3d rt = oi->get_rotation(); rt(Z) += phi;
+
+ oi->set_rotation(rt);
+ }
+
+ m_plater->find_new_position(o->instances, scaled(mindist));
+
+ // Correct the z offset of the object which was corrupted be
+ // the rotation
+ o->ensure_on_bed();
+ }
+
+ update_status(100, was_canceled() ? _(L("Orientation search canceled.")) :
+ _(L("Orientation found.")));
+}
+
+void RotoptimizeJob::finalize()
+{
+ if (!was_canceled())
+ m_plater->update();
+
+ Job::finalize();
+}
+
+}}
diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp
new file mode 100644
index 000000000..983c43c68
--- /dev/null
+++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp
@@ -0,0 +1,24 @@
+#ifndef ROTOPTIMIZEJOB_HPP
+#define ROTOPTIMIZEJOB_HPP
+
+#include "Job.hpp"
+
+namespace Slic3r { namespace GUI {
+
+class Plater;
+
+class RotoptimizeJob : public Job
+{
+ Plater *m_plater;
+public:
+ RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
+ : Job{std::move(pri)}, m_plater{plater}
+ {}
+
+ void process() override;
+ void finalize() override;
+};
+
+}} // namespace Slic3r::GUI
+
+#endif // ROTOPTIMIZEJOB_HPP
diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp
new file mode 100644
index 000000000..e791bf94e
--- /dev/null
+++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp
@@ -0,0 +1,226 @@
+#include "SLAImportJob.hpp"
+
+#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/AppConfig.hpp"
+#include "slic3r/GUI/Plater.hpp"
+#include "slic3r/GUI/PresetBundle.hpp"
+#include "slic3r/GUI/GUI_ObjectList.hpp"
+#include "slic3r/Utils/SLAImport.hpp"
+
+#include <wx/dialog.h>
+#include <wx/stattext.h>
+#include <wx/combobox.h>
+#include <wx/filename.h>
+#include <wx/filepicker.h>
+
+namespace Slic3r { namespace GUI {
+
+enum class Sel { modelAndProfile, profileOnly, modelOnly};
+
+class ImportDlg: public wxDialog {
+ wxFilePickerCtrl *m_filepicker;
+ wxComboBox *m_import_dropdown, *m_quality_dropdown;
+
+public:
+ ImportDlg(Plater *plater)
+ : wxDialog{plater, wxID_ANY, "Import SLA archive"}
+ {
+ auto szvert = new wxBoxSizer{wxVERTICAL};
+ auto szfilepck = new wxBoxSizer{wxHORIZONTAL};
+
+ m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
+ from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
+ "SL1 archive files (*.sl1, *.zip)|*.sl1;*.SL1;*.zip;*.ZIP",
+ wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+
+ szfilepck->Add(new wxStaticText(this, wxID_ANY, _(L("Import file: "))), 0, wxALIGN_CENTER);
+ szfilepck->Add(m_filepicker, 1);
+ szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5);
+
+ auto szchoices = new wxBoxSizer{wxHORIZONTAL};
+
+ static const std::vector<wxString> inp_choices = {
+ _(L("Import model and profile")),
+ _(L("Import profile only")),
+ _(L("Import model only"))
+ };
+
+ m_import_dropdown = new wxComboBox(
+ this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize,
+ inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
+
+ szchoices->Add(m_import_dropdown);
+ szchoices->Add(new wxStaticText(this, wxID_ANY, _(L("Quality: "))), 0, wxALIGN_CENTER | wxALL, 5);
+
+ static const std::vector<wxString> qual_choices = {
+ _(L("Accurate")),
+ _(L("Balanced")),
+ _(L("Quick"))
+ };
+
+ m_quality_dropdown = new wxComboBox(
+ this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize,
+ qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
+ szchoices->Add(m_quality_dropdown);
+
+ m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
+ if (get_selection() == Sel::profileOnly)
+ m_quality_dropdown->Disable();
+ else m_quality_dropdown->Enable();
+ });
+
+ szvert->Add(szchoices, 0, wxALL, 5);
+ szvert->AddStretchSpacer(1);
+ auto szbtn = new wxBoxSizer(wxHORIZONTAL);
+ szbtn->Add(new wxButton{this, wxID_CANCEL});
+ szbtn->Add(new wxButton{this, wxID_OK});
+ szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5);
+
+ SetSizerAndFit(szvert);
+ }
+
+ Sel get_selection() const
+ {
+ int sel = m_import_dropdown->GetSelection();
+ return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
+ }
+
+ Vec2i get_marchsq_windowsize() const
+ {
+ enum { Accurate, Balanced, Fast};
+
+ switch(m_quality_dropdown->GetSelection())
+ {
+ case Fast: return {8, 8};
+ case Balanced: return {4, 4};
+ default:
+ case Accurate:
+ return {2, 2};
+ }
+ }
+
+ wxString get_path() const
+ {
+ return m_filepicker->GetPath();
+ }
+};
+
+class SLAImportJob::priv {
+public:
+ Plater *plater;
+
+ Sel sel = Sel::modelAndProfile;
+
+ TriangleMesh mesh;
+ DynamicPrintConfig profile;
+ wxString path;
+ Vec2i win = {2, 2};
+ std::string err;
+
+ priv(Plater *plt): plater{plt} {}
+};
+
+SLAImportJob::SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
+ : Job{std::move(pri)}, p{std::make_unique<priv>(plater)}
+{}
+
+SLAImportJob::~SLAImportJob() = default;
+
+void SLAImportJob::process()
+{
+ auto progr = [this](int s) {
+ if (s < 100) update_status(int(s), _(L("Importing SLA archive")));
+ return !was_canceled();
+ };
+
+ if (p->path.empty()) return;
+
+ std::string path = p->path.ToUTF8().data();
+ try {
+ switch (p->sel) {
+ case Sel::modelAndProfile:
+ import_sla_archive(path, p->win, p->mesh, p->profile, progr);
+ break;
+ case Sel::modelOnly:
+ import_sla_archive(path, p->win, p->mesh, progr);
+ break;
+ case Sel::profileOnly:
+ import_sla_archive(path, p->profile);
+ break;
+ }
+
+ } catch (std::exception &ex) {
+ p->err = ex.what();
+ }
+
+ update_status(100, was_canceled() ? _(L("Importing canceled.")) :
+ _(L("Importing done.")));
+}
+
+void SLAImportJob::reset()
+{
+ p->sel = Sel::modelAndProfile;
+ p->mesh = {};
+ p->profile = {};
+ p->win = {2, 2};
+ p->path.Clear();
+}
+
+void SLAImportJob::prepare()
+{
+ reset();
+
+ ImportDlg dlg{p->plater};
+
+ if (dlg.ShowModal() == wxID_OK) {
+ auto path = dlg.get_path();
+ auto nm = wxFileName(path);
+ p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : path.ToUTF8();
+ p->sel = dlg.get_selection();
+ p->win = dlg.get_marchsq_windowsize();
+ } else {
+ p->path = "";
+ }
+}
+
+void SLAImportJob::finalize()
+{
+ // Ignore the arrange result if aborted.
+ if (was_canceled()) return;
+
+ if (!p->err.empty()) {
+ show_error(p->plater, p->err);
+ p->err = "";
+ return;
+ }
+
+ std::string name = wxFileName(p->path).GetName().ToUTF8().data();
+
+ if (!p->profile.empty()) {
+ const ModelObjectPtrs& objects = p->plater->model().objects;
+ for (auto object : objects)
+ if (object->volumes.size() > 1)
+ {
+ Slic3r::GUI::show_info(nullptr,
+ _(L("You cannot load SLA project with a multi-part object on the bed")) + "\n\n" +
+ _(L("Please check your object list before preset changing.")),
+ _(L("Attention!")) );
+ return;
+ }
+
+ DynamicPrintConfig config = {};
+ config.apply(SLAFullPrintConfig::defaults());
+ config += std::move(p->profile);
+
+ wxGetApp().preset_bundle->load_config_model(name, std::move(config));
+ wxGetApp().load_current_presets();
+ }
+
+ if (!p->mesh.empty())
+ p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name);
+
+ reset();
+}
+
+}}
diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.hpp b/src/slic3r/GUI/Jobs/SLAImportJob.hpp
new file mode 100644
index 000000000..cff6cc899
--- /dev/null
+++ b/src/slic3r/GUI/Jobs/SLAImportJob.hpp
@@ -0,0 +1,31 @@
+#ifndef SLAIMPORTJOB_HPP
+#define SLAIMPORTJOB_HPP
+
+#include "Job.hpp"
+
+namespace Slic3r { namespace GUI {
+
+class Plater;
+
+class SLAImportJob : public Job {
+ class priv;
+
+ std::unique_ptr<priv> p;
+
+public:
+ SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater);
+ ~SLAImportJob();
+
+ void process() override;
+
+ void reset();
+
+protected:
+ void prepare() override;
+
+ void finalize() override;
+};
+
+}} // namespace Slic3r::GUI
+
+#endif // SLAIMPORTJOB_HPP
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 6a1964506..f7d7a6cac 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -589,6 +589,11 @@ void MainFrame::init_menubar()
append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")),
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
+
+ append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")),
+ [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
+ [this](){return m_plater != nullptr; }, this);
+
import_menu->AppendSeparator();
append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")),
[this](wxCommandEvent&) { load_config_file(); }, "import_config", nullptr,
diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp
index 37b6efd87..6c4f5e49d 100644
--- a/src/slic3r/GUI/MeshUtils.cpp
+++ b/src/slic3r/GUI/MeshUtils.cpp
@@ -85,19 +85,25 @@ void MeshClipper::recalculate_triangles()
tr = m_trafo.get_matrix().cast<float>() * tr;
m_triangles3d.clear();
- m_triangles3d.reserve(m_triangles2d.size());
- for (const Vec2f& pt : m_triangles2d) {
- m_triangles3d.push_back(Vec3f(pt(0), pt(1), height_mesh+0.001f));
- m_triangles3d.back() = tr * m_triangles3d.back();
- }
+ m_triangles3d.reserve(m_triangles2d.size());
+ for (const Vec2f& pt : m_triangles2d) {
+ m_triangles3d.push_back(Vec3f(pt(0), pt(1), height_mesh+0.001f));
+ m_triangles3d.back() = tr * m_triangles3d.back();
+ }
m_triangles_valid = true;
}
+Vec3f MeshRaycaster::get_triangle_normal(const indexed_triangle_set& its, size_t facet_idx)
+{
+ Vec3f a(its.vertices[its.indices[facet_idx](1)] - its.vertices[its.indices[facet_idx](0)]);
+ Vec3f b(its.vertices[its.indices[facet_idx](2)] - its.vertices[its.indices[facet_idx](0)]);
+ return Vec3f(a.cross(b)).normalized();
+}
-bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
- Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane) const
+void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
+ Vec3d& point, Vec3d& direction) const
{
const std::array<int, 4>& viewport = camera.get_viewport();
const Transform3d& model_mat = camera.get_view_matrix();
@@ -112,7 +118,21 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
pt1 = inv * pt1;
pt2 = inv * pt2;
- std::vector<sla::EigenMesh3D::hit_result> hits = m_emesh.query_ray_hits(pt1, pt2-pt1);
+ point = pt1;
+ direction = pt2-pt1;
+}
+
+
+bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
+ Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane,
+ size_t* facet_idx) const
+{
+ Vec3d point;
+ Vec3d direction;
+ line_from_mouse_pos(mouse_pos, trafo, camera, point, direction);
+
+ std::vector<sla::EigenMesh3D::hit_result> hits = m_emesh.query_ray_hits(point, direction);
+
if (hits.empty())
return false; // no intersection found
@@ -134,6 +154,10 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
// Now stuff the points in the provided vector and calculate normals if asked about them:
position = hits[i].position().cast<float>();
normal = hits[i].normal().cast<float>();
+
+ if (facet_idx)
+ *facet_idx = hits[i].face();
+
return true;
}
diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp
index b4ad03011..92f444f55 100644
--- a/src/slic3r/GUI/MeshUtils.hpp
+++ b/src/slic3r/GUI/MeshUtils.hpp
@@ -4,6 +4,8 @@
#include "libslic3r/Point.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/SLA/EigenMesh3D.hpp"
+#include "admesh/stl.h"
+
#include <cfloat>
@@ -26,10 +28,7 @@ class ClippingPlane
public:
ClippingPlane()
{
- m_data[0] = 0.0;
- m_data[1] = 0.0;
- m_data[2] = 1.0;
- m_data[3] = 0.0;
+ *this = ClipsNothing();
}
ClippingPlane(const Vec3d& direction, double offset)
@@ -111,6 +110,9 @@ public:
: m_emesh(mesh)
{}
+ void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
+ Vec3d& point, Vec3d& direction) const;
+
// Given a mouse position, this returns true in case it is on the mesh.
bool unproject_on_mesh(
const Vec2d& mouse_pos,
@@ -118,7 +120,8 @@ public:
const Camera& camera, // current camera position
Vec3f& position, // where to save the positibon of the hit (mesh coords)
Vec3f& normal, // normal of the triangle that was hit
- const ClippingPlane* clipping_plane = nullptr // clipping plane (if active)
+ const ClippingPlane* clipping_plane = nullptr, // clipping plane (if active)
+ size_t* facet_idx = nullptr // index of the facet hit
) const;
// Given a vector of points in woorld coordinates, this returns vector
@@ -134,8 +137,11 @@ public:
// Given a point in world coords, the method returns closest point on the mesh.
// The output is in mesh coords.
// normal* can be used to also get normal of the respective triangle.
+
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
+ static Vec3f get_triangle_normal(const indexed_triangle_set& its, size_t facet_idx);
+
private:
sla::EigenMesh3D m_emesh;
};
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index c7c5798ce..db9e7e59a 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -36,7 +36,6 @@
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
-#include "libslic3r/SLA/Rotfinder.hpp"
#include "libslic3r/SLA/SupportPoint.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Print.hpp"
@@ -44,13 +43,6 @@
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Utils.hpp"
-//#include "libslic3r/ClipperUtils.hpp"
-
-// #include "libnest2d/optimizers/nlopt/genetic.hpp"
-// #include "libnest2d/backends/clipper/geometries.hpp"
-// #include "libnest2d/utils/rotcalipers.hpp"
-#include "libslic3r/MinAreaBoundingBox.hpp"
-
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
@@ -69,7 +61,9 @@
#include "Camera.hpp"
#include "Mouse3DController.hpp"
#include "Tab.hpp"
-#include "Job.hpp"
+#include "Jobs/ArrangeJob.hpp"
+#include "Jobs/RotoptimizeJob.hpp"
+#include "Jobs/SLAImportJob.hpp"
#include "PresetBundle.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "ProgressStatusBar.hpp"
@@ -1485,311 +1479,44 @@ struct Plater::priv
BackgroundSlicingProcess background_process;
bool suppressed_backround_processing_update { false };
- // Cache the wti info
- class WipeTower: public GLCanvas3D::WipeTowerInfo {
- using ArrangePolygon = arrangement::ArrangePolygon;
- friend priv;
- public:
-
- void apply_arrange_result(const Vec2d& tr, double rotation)
- {
- m_pos = unscaled(tr); m_rotation = rotation;
- apply_wipe_tower();
- }
-
- ArrangePolygon get_arrange_polygon() const
- {
- Polygon p({
- {coord_t(0), coord_t(0)},
- {scaled(m_bb_size(X)), coord_t(0)},
- {scaled(m_bb_size)},
- {coord_t(0), scaled(m_bb_size(Y))},
- {coord_t(0), coord_t(0)},
- });
-
- ArrangePolygon ret;
- ret.poly.contour = std::move(p);
- ret.translation = scaled(m_pos);
- ret.rotation = m_rotation;
- ret.priority++;
- return ret;
- }
- } wipetower;
-
- WipeTower& updated_wipe_tower() {
- auto wti = view3D->get_canvas3d()->get_wipe_tower_info();
- wipetower.m_pos = wti.pos();
- wipetower.m_rotation = wti.rotation();
- wipetower.m_bb_size = wti.bb_size();
- return wipetower;
- }
-
- // A class to handle UI jobs like arranging and optimizing rotation.
- // These are not instant jobs, the user has to be informed about their
- // state in the status progress indicator. On the other hand they are
- // separated from the background slicing process. Ideally, these jobs should
- // run when the background process is not running.
- //
- // TODO: A mechanism would be useful for blocking the plater interactions:
- // objects would be frozen for the user. In case of arrange, an animation
- // could be shown, or with the optimize orientations, partial results
- // could be displayed.
- class PlaterJob: public Job
+ // Jobs defined inside the group class will be managed so that only one can
+ // run at a time. Also, the background process will be stopped if a job is
+ // started. It is up the the plater to ensure that the background slicing
+ // can't be restarted while a ui job is still running.
+ class Jobs: public ExclusiveJobGroup
{
- priv *m_plater;
- protected:
-
- priv & plater() { return *m_plater; }
- const priv &plater() const { return *m_plater; }
-
- // Launched when the job is finished. It refreshes the 3Dscene by def.
- void finalize() override
- {
- // Do a full refresh of scene tree, including regenerating
- // all the GLVolumes. FIXME The update function shall just
- // reload the modified matrices.
- if (!Job::was_canceled())
- plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
-
- Job::finalize();
- }
-
+ priv *m;
+ size_t m_arrange_id, m_rotoptimize_id, m_sla_import_id;
+
+ void before_start() override { m->background_process.stop(); }
+
public:
- PlaterJob(priv *_plater)
- : Job(_plater->statusbar()), m_plater(_plater)
- {}
- };
-
- enum class Jobs : size_t {
- Arrange,
- Rotoptimize
- };
-
- class ArrangeJob : public PlaterJob
- {
- using ArrangePolygon = arrangement::ArrangePolygon;
- using ArrangePolygons = arrangement::ArrangePolygons;
-
- // The gap between logical beds in the x axis expressed in ratio of
- // the current bed width.
- static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
-
- ArrangePolygons m_selected, m_unselected, m_unprintable;
-
- // clear m_selected and m_unselected, reserve space for next usage
- void clear_input() {
- const Model &model = plater().model;
-
- size_t count = 0, cunprint = 0; // To know how much space to reserve
- for (auto obj : model.objects)
- for (auto mi : obj->instances)
- mi->printable ? count++ : cunprint++;
-
- m_selected.clear();
- m_unselected.clear();
- m_unprintable.clear();
- m_selected.reserve(count + 1 /* for optional wti */);
- m_unselected.reserve(count + 1 /* for optional wti */);
- m_unprintable.reserve(cunprint /* for optional wti */);
- }
-
- // Stride between logical beds
- double bed_stride() const {
- double bedwidth = plater().bed_shape_bb().size().x();
- return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
- }
-
- // Set up arrange polygon for a ModelInstance and Wipe tower
- template<class T> ArrangePolygon get_arrange_poly(T *obj) const {
- ArrangePolygon ap = obj->get_arrange_polygon();
- ap.priority = 0;
- ap.bed_idx = ap.translation.x() / bed_stride();
- ap.setter = [obj, this](const ArrangePolygon &p) {
- if (p.is_arranged()) {
- Vec2d t = p.translation.cast<double>();
- t.x() += p.bed_idx * bed_stride();
- obj->apply_arrange_result(t, p.rotation);
- }
- };
- return ap;
- }
-
- // Prepare all objects on the bed regardless of the selection
- void prepare_all() {
- clear_input();
-
- for (ModelObject *obj: plater().model.objects)
- for (ModelInstance *mi : obj->instances) {
- ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
- cont.emplace_back(get_arrange_poly(mi));
- }
-
- auto& wti = plater().updated_wipe_tower();
- if (wti) m_selected.emplace_back(get_arrange_poly(&wti));
- }
-
- // Prepare the selected and unselected items separately. If nothing is
- // selected, behaves as if everything would be selected.
- void prepare_selected() {
- clear_input();
-
- Model &model = plater().model;
- coord_t stride = bed_stride();
-
- std::vector<const Selection::InstanceIdxsList *>
- obj_sel(model.objects.size(), nullptr);
-
- for (auto &s : plater().get_selection().get_content())
- if (s.first < int(obj_sel.size()))
- obj_sel[size_t(s.first)] = &s.second;
-
- // Go through the objects and check if inside the selection
- for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
- const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
- ModelObject *mo = model.objects[oidx];
-
- std::vector<bool> inst_sel(mo->instances.size(), false);
-
- if (instlist)
- for (auto inst_id : *instlist)
- inst_sel[size_t(inst_id)] = true;
-
- for (size_t i = 0; i < inst_sel.size(); ++i) {
- ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
-
- ArrangePolygons &cont = mo->instances[i]->printable ?
- (inst_sel[i] ? m_selected :
- m_unselected) :
- m_unprintable;
-
- cont.emplace_back(std::move(ap));
- }
- }
-
- auto& wti = plater().updated_wipe_tower();
- if (wti) {
- ArrangePolygon &&ap = get_arrange_poly(&wti);
-
- plater().get_selection().is_wipe_tower() ?
- m_selected.emplace_back(std::move(ap)) :
- m_unselected.emplace_back(std::move(ap));
- }
-
- // If the selection was empty arrange everything
- if (m_selected.empty()) m_selected.swap(m_unselected);
-
- // The strides have to be removed from the fixed items. For the
- // arrangeable (selected) items bed_idx is ignored and the
- // translation is irrelevant.
- for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
- }
-
- protected:
-
- void prepare() override
+ Jobs(priv *_m) : m(_m)
{
- wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
+ m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q));
+ m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q));
+ m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->statusbar(), m->q));
}
-
- public:
- using PlaterJob::PlaterJob;
-
- int status_range() const override
+
+ void arrange()
{
- return int(m_selected.size() + m_unprintable.size());
- }
-
- void process() override;
-
- void finalize() override {
- // Ignore the arrange result if aborted.
- if (was_canceled()) return;
-
- // Unprintable items go to the last virtual bed
- int beds = 0;
-
- // Apply the arrange result to all selected objects
- for (ArrangePolygon &ap : m_selected) {
- beds = std::max(ap.bed_idx, beds);
- ap.apply();
- }
-
- // Get the virtual beds from the unselected items
- for (ArrangePolygon &ap : m_unselected)
- beds = std::max(ap.bed_idx, beds);
-
- // Move the unprintable items to the last virtual bed.
- for (ArrangePolygon &ap : m_unprintable) {
- ap.bed_idx += beds + 1;
- ap.apply();
- }
-
- plater().update();
- }
- };
-
- class RotoptimizeJob : public PlaterJob
- {
- public:
- using PlaterJob::PlaterJob;
- void process() override;
- };
-
-
- // Jobs defined inside the group class will be managed so that only one can
- // run at a time. Also, the background process will be stopped if a job is
- // started.
- class ExclusiveJobGroup {
-
- static const int ABORT_WAIT_MAX_MS = 10000;
-
- priv * m_plater;
-
- ArrangeJob arrange_job{m_plater};
- RotoptimizeJob rotoptimize_job{m_plater};
-
- // To create a new job, just define a new subclass of Job, implement
- // the process and the optional prepare() and finalize() methods
- // Register the instance of the class in the m_jobs container
- // if it cannot run concurrently with other jobs in this group
-
- std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
- rotoptimize_job};
-
- public:
- ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {}
-
- void start(Jobs jid) {
- m_plater->background_process.stop();
- stop_all();
- m_jobs[size_t(jid)].get().start();
+ m->take_snapshot(_(L("Arrange")));
+ start(m_arrange_id);
}
-
- void cancel_all() { for (Job& j : m_jobs) j.cancel(); }
-
- void join_all(int wait_ms = 0)
+
+ void optimize_rotation()
{
- std::vector<bool> aborted(m_jobs.size(), false);
-
- for (size_t jid = 0; jid < m_jobs.size(); ++jid)
- aborted[jid] = m_jobs[jid].get().join(wait_ms);
-
- if (!all_of(aborted))
- BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
+ m->take_snapshot(_(L("Optimize Rotation")));
+ start(m_rotoptimize_id);
}
-
- void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
-
- const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; }
-
- bool is_any_running() const
+
+ void import_sla_arch()
{
- return std::any_of(m_jobs.begin(),
- m_jobs.end(),
- [](const Job &j) { return j.is_running(); });
+ m->take_snapshot(_(L("Import SLA archive")));
+ start(m_sla_import_id);
}
-
- } m_ui_jobs{this};
+
+ } m_ui_jobs;
bool delayed_scene_refresh;
std::string delayed_error_message;
@@ -1808,10 +1535,10 @@ struct Plater::priv
priv(Plater *q, MainFrame *main_frame);
~priv();
- enum class UpdateParams {
- FORCE_FULL_SCREEN_REFRESH = 1,
- FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
- POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
+ enum class UpdateParams {
+ FORCE_FULL_SCREEN_REFRESH = 1,
+ FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
+ POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
};
void update(unsigned int flags = 0);
void select_view(const std::string& direction);
@@ -1847,9 +1574,7 @@ struct Plater::priv
std::string get_config(const std::string &key) const;
BoundingBoxf bed_shape_bb() const;
BoundingBox scaled_bed_shape_bb() const;
- arrangement::BedShapeHint get_bed_shape_hint() const;
- void find_new_position(const ModelInstancePtrs &instances, coord_t min_d);
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
wxString get_export_file(GUI::FileType file_type);
@@ -1867,8 +1592,6 @@ struct Plater::priv
void delete_object_from_model(size_t obj_idx);
void reset();
void mirror(Axis axis);
- void arrange();
- void sla_optimize_rotation();
void split_object();
void split_volume();
void scale_selection_to_fit_print_volume();
@@ -2035,6 +1758,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
"support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers"
}))
, sidebar(new Sidebar(q))
+ , m_ui_jobs(this)
, delayed_scene_refresh(false)
, view_toolbar(GLToolbar::Radio, "View")
, m_project_filename(wxEmptyString)
@@ -2110,14 +1834,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas();
+
// 3DScene events:
view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this);
view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this);
view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); });
- view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); });
+ view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
- view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
+ view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int> &evt)
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
@@ -2142,7 +1867,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
- view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); });
+ view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); });
@@ -2810,40 +2535,12 @@ void Plater::priv::mirror(Axis axis)
view3D->mirror_selection(axis);
}
-void Plater::priv::arrange()
-{
- this->take_snapshot(_L("Arrange"));
- m_ui_jobs.start(Jobs::Arrange);
-}
-
-
-// This method will find an optimal orientation for the currently selected item
-// Very similar in nature to the arrange method above...
-void Plater::priv::sla_optimize_rotation() {
- this->take_snapshot(_L("Optimize Rotation"));
- m_ui_jobs.start(Jobs::Rotoptimize);
-}
-
-arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const {
-
- const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
- assert(bed_shape_opt);
-
- if (!bed_shape_opt) return {};
-
- auto &bedpoints = bed_shape_opt->values;
- Polyline bedpoly; bedpoly.points.reserve(bedpoints.size());
- for (auto &v : bedpoints) bedpoly.append(scaled(v));
-
- return arrangement::BedShapeHint(bedpoly);
-}
-
-void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
+void Plater::find_new_position(const ModelInstancePtrs &instances,
coord_t min_d)
{
arrangement::ArrangePolygons movable, fixed;
-
- for (const ModelObject *mo : model.objects)
+
+ for (const ModelObject *mo : p->model.objects)
for (const ModelInstance *inst : mo->instances) {
auto it = std::find(instances.begin(), instances.end(), inst);
auto arrpoly = inst->get_arrange_polygon();
@@ -2853,11 +2550,12 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
else
movable.emplace_back(std::move(arrpoly));
}
-
- if (updated_wipe_tower())
- fixed.emplace_back(wipetower.get_arrange_polygon());
-
- arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint());
+
+ if (p->view3D->get_canvas3d()->get_wipe_tower_info())
+ fixed.emplace_back(get_wipe_tower_arrangepoly(*this));
+
+ arrangement::arrange(movable, fixed, get_bed_shape(*config()),
+ arrangement::ArrangeParams{min_d});
for (size_t i = 0; i < instances.size(); ++i)
if (movable[i].bed_idx == 0)
@@ -2865,95 +2563,6 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
movable[i].rotation);
}
-void Plater::priv::ArrangeJob::process() {
- static const auto arrangestr = _L("Arranging");
-
- // FIXME: I don't know how to obtain the minimum distance, it depends
- // on printer technology. I guess the following should work but it crashes.
- double dist = 6; // PrintConfig::min_object_distance(config);
- if (plater().printer_technology == ptFFF) {
- dist = PrintConfig::min_object_distance(plater().config);
- }
-
- coord_t min_d = scaled(dist);
- auto count = unsigned(m_selected.size() + m_unprintable.size());
- arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint();
-
- auto stopfn = [this]() { return was_canceled(); };
-
- try {
- arrangement::arrange(m_selected, m_unselected, min_d, bedshape,
- [this, count](unsigned st) {
- st += m_unprintable.size();
- if (st > 0) update_status(int(count - st), arrangestr);
- }, stopfn);
- arrangement::arrange(m_unprintable, {}, min_d, bedshape,
- [this, count](unsigned st) {
- if (st > 0) update_status(int(count - st), arrangestr);
- }, stopfn);
- } catch (std::exception & /*e*/) {
- GUI::show_error(plater().q,
- _L("Could not arrange model objects! "
- "Some geometries may be invalid."));
- }
-
- // finalize just here.
- update_status(int(count),
- was_canceled() ? _L("Arranging canceled.")
- : _L("Arranging done."));
-}
-
-void Plater::priv::RotoptimizeJob::process()
-{
- int obj_idx = plater().get_selected_object_idx();
- if (obj_idx < 0) { return; }
-
- ModelObject *o = plater().model.objects[size_t(obj_idx)];
-
- auto r = sla::find_best_rotation(
- *o,
- .005f,
- [this](unsigned s) {
- if (s < 100)
- update_status(int(s),
- _L("Searching for optimal orientation"));
- },
- [this]() { return was_canceled(); });
-
-
- double mindist = 6.0; // FIXME
-
- if (!was_canceled()) {
- for(ModelInstance * oi : o->instances) {
- oi->set_rotation({r[X], r[Y], r[Z]});
-
- auto trmatrix = oi->get_transformation().get_matrix();
- Polygon trchull = o->convex_hull_2d(trmatrix);
-
- MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
- double r = rotbb.angle_to_X();
-
- // The box should be landscape
- if(rotbb.width() < rotbb.height()) r += PI / 2;
-
- Vec3d rt = oi->get_rotation(); rt(Z) += r;
-
- oi->set_rotation(rt);
- }
-
- plater().find_new_position(o->instances, scaled(mindist));
-
- // Correct the z offset of the object which was corrupted be
- // the rotation
- o->ensure_on_bed();
- }
-
- update_status(100,
- was_canceled() ? _L("Orientation search canceled.")
- : _L("Orientation found."));
-}
-
-
void Plater::priv::split_object()
{
int obj_idx = get_selected_object_idx();
@@ -3594,7 +3203,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
}
// update plater with new config
- wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config());
+ q->on_config_change(wxGetApp().preset_bundle->full_config());
/* Settings list can be changed after printer preset changing, so
* update all settings items for all item had it.
* Furthermore, Layers editing is implemented only for FFF printers
@@ -4041,8 +3650,12 @@ bool Plater::priv::complit_init_sla_object_menu()
sla_object_menu.AppendSeparator();
// Add the automatic rotation sub-menu
- append_menu_item(&sla_object_menu, wxID_ANY, _L("Optimize orientation"), _L("Optimize the rotation of the object for better print results."),
- [this](wxCommandEvent&) { sla_optimize_rotation(); });
+ append_menu_item(
+ &sla_object_menu, wxID_ANY, _(L("Optimize orientation")),
+ _(L("Optimize the rotation of the object for better print results.")),
+ [this](wxCommandEvent &) {
+ m_ui_jobs.optimize_rotation();
+ });
return true;
}
@@ -4646,6 +4259,11 @@ void Plater::add_model()
load_files(paths, true, false);
}
+void Plater::import_sl1_archive()
+{
+ p->m_ui_jobs.import_sla_arch();
+}
+
void Plater::extract_config_from_project()
{
wxString input_file;
@@ -4738,7 +4356,7 @@ void Plater::increase_instances(size_t num)
sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num);
if (p->get_config("autocenter") == "1")
- p->arrange();
+ arrange();
p->update();
@@ -5472,6 +5090,11 @@ bool Plater::is_export_gcode_scheduled() const
return p->background_process.is_export_scheduled();
}
+const Selection &Plater::get_selection() const
+{
+ return p->get_selection();
+}
+
int Plater::get_selected_object_idx()
{
return p->get_selected_object_idx();
@@ -5497,6 +5120,11 @@ BoundingBoxf Plater::bed_shape_bb() const
return p->bed_shape_bb();
}
+void Plater::arrange()
+{
+ p->m_ui_jobs.arrange();
+}
+
void Plater::set_current_canvas_as_dirty()
{
p->set_current_canvas_as_dirty();
@@ -5519,6 +5147,8 @@ PrinterTechnology Plater::printer_technology() const
return p->printer_technology;
}
+const DynamicPrintConfig * Plater::config() const { return p->config; }
+
void Plater::set_printer_technology(PrinterTechnology printer_technology)
{
p->printer_technology = printer_technology;
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index efdaa75cc..2ac4f23c1 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -9,8 +9,10 @@
#include <wx/bmpcbox.h>
#include "Preset.hpp"
+#include "Selection.hpp"
#include "libslic3r/BoundingBox.hpp"
+#include "Jobs/Job.hpp"
#include "wxExtensions.hpp"
class wxButton;
@@ -157,6 +159,7 @@ public:
void load_project();
void load_project(const wxString& filename);
void add_model();
+ void import_sl1_archive();
void extract_config_from_project();
std::vector<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, bool load_model = true, bool load_config = true);
@@ -252,12 +255,16 @@ public:
void set_project_filename(const wxString& filename);
bool is_export_gcode_scheduled() const;
-
+
+ const Selection& get_selection() const;
int get_selected_object_idx();
bool is_single_full_object_selection() const;
GLCanvas3D* canvas3D();
GLCanvas3D* get_current_canvas3D();
BoundingBoxf bed_shape_bb() const;
+
+ void arrange();
+ void find_new_position(const ModelInstancePtrs &instances, coord_t min_d);
void set_current_canvas_as_dirty();
#if ENABLE_NON_STATIC_CANVAS_MANAGER
@@ -266,6 +273,7 @@ public:
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
PrinterTechnology printer_technology() const;
+ const DynamicPrintConfig * config() const;
void set_printer_technology(PrinterTechnology printer_technology);
void copy_selection_to_clipboard();
@@ -371,6 +379,7 @@ private:
bool m_was_scheduled;
};
-}}
+} // namespace GUI
+} // namespace Slic3r
#endif
diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp
index faeb7a34e..15b10deeb 100644
--- a/src/slic3r/GUI/ProgressStatusBar.hpp
+++ b/src/slic3r/GUI/ProgressStatusBar.hpp
@@ -6,7 +6,7 @@
#include <functional>
#include <string>
-#include "ProgressIndicator.hpp"
+#include "Jobs/ProgressIndicator.hpp"
class wxTimer;
class wxGauge;
diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp
index c9f1de168..b3f279196 100644
--- a/src/slic3r/GUI/wxExtensions.cpp
+++ b/src/slic3r/GUI/wxExtensions.cpp
@@ -754,7 +754,8 @@ ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 0*/) :
std::vector < std::pair < wxString, std::string >> buttons = {
{_(L("Simple")), "mode_simple"},
- {_(L("Advanced")), "mode_advanced"},
+// {_(L("Advanced")), "mode_advanced"},
+ {_CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), "mode_advanced"},
{_(L("Expert")), "mode_expert"},
};
diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp
index 30e25abe2..101654c56 100644
--- a/src/slic3r/Utils/Http.cpp
+++ b/src/slic3r/Utils/Http.cpp
@@ -18,6 +18,8 @@
#include <openssl/x509.h>
#endif
+#define L(s) s
+
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
@@ -32,7 +34,8 @@ namespace Slic3r {
struct CurlGlobalInit
{
static std::unique_ptr<CurlGlobalInit> instance;
-
+ std::string message;
+
CurlGlobalInit()
{
#ifdef OPENSSL_CERT_OVERRIDE // defined if SLIC3R_STATIC=ON
@@ -57,21 +60,39 @@ struct CurlGlobalInit
ssl_cafile = X509_get_default_cert_file();
int replace = true;
-
- if (!ssl_cafile || !fs::exists(fs::path(ssl_cafile)))
- for (const char * bundle : CA_BUNDLES) {
- if (fs::exists(fs::path(bundle))) {
- ::setenv(SSL_CA_FILE, bundle, replace);
+ if (!ssl_cafile || !fs::exists(fs::path(ssl_cafile))) {
+ const char * bundle = nullptr;
+ for (const char * b : CA_BUNDLES) {
+ if (fs::exists(fs::path(b))) {
+ ::setenv(SSL_CA_FILE, bundle = b, replace);
break;
}
}
- BOOST_LOG_TRIVIAL(info)
- << "Detected OpenSSL root CA store: " << ::getenv(SSL_CA_FILE);
-
-#endif
+ if (!bundle)
+ message = L("Could not detect system SSL certificate store. "
+ "PrusaSlicer will be unable to establish secure "
+ "network connections.");
+ else
+ message = string_printf(
+ L("PrusaSlicer detected system SSL certificate store in: %s"),
+ bundle);
+
+ message += string_printf(
+ L("\nTo specify the system certificate store manually, please "
+ "set the %s environment variable to the correct CA bundle "
+ "and restart the application."),
+ SSL_CA_FILE);
+ }
+
+#endif // OPENSSL_CERT_OVERRIDE
- ::curl_global_init(CURL_GLOBAL_DEFAULT);
+ if (CURLcode ec = ::curl_global_init(CURL_GLOBAL_DEFAULT)) {
+ message = L("CURL init has failed. PrusaSlicer will be unable to establish "
+ "network connections. See logs for additional details.");
+
+ BOOST_LOG_TRIVIAL(error) << ::curl_easy_strerror(ec);
+ }
}
~CurlGlobalInit() { ::curl_global_cleanup(); }
@@ -132,8 +153,7 @@ Http::priv::priv(const std::string &url)
, limit(0)
, cancel(false)
{
- if (!CurlGlobalInit::instance)
- CurlGlobalInit::instance = std::make_unique<CurlGlobalInit>();
+ Http::tls_global_init();
if (curl == nullptr) {
throw std::runtime_error(std::string("Could not construct Curl object"));
@@ -494,7 +514,26 @@ bool Http::ca_file_supported()
::CURL *curl = ::curl_easy_init();
bool res = priv::ca_file_supported(curl);
if (curl != nullptr) { ::curl_easy_cleanup(curl); }
- return res;
+ return res;
+}
+
+std::string Http::tls_global_init()
+{
+ if (!CurlGlobalInit::instance)
+ CurlGlobalInit::instance = std::make_unique<CurlGlobalInit>();
+
+ return CurlGlobalInit::instance->message;
+}
+
+std::string Http::tls_system_cert_store()
+{
+ std::string ret;
+
+#ifdef OPENSSL_CERT_OVERRIDE
+ ret = ::getenv(X509_get_default_cert_file_env());
+#endif
+
+ return ret;
}
std::string Http::url_encode(const std::string &str)
diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp
index 076fa4a0c..f16236279 100644
--- a/src/slic3r/Utils/Http.hpp
+++ b/src/slic3r/Utils/Http.hpp
@@ -100,6 +100,10 @@ public:
// Tells whether current backend supports seting up a CA file using ca_file()
static bool ca_file_supported();
+
+ // Return empty string on success or error message on fail.
+ static std::string tls_global_init();
+ static std::string tls_system_cert_store();
// converts the given string to an url_encoded_string
static std::string url_encode(const std::string &str);
diff --git a/src/slic3r/Utils/SLAImport.cpp b/src/slic3r/Utils/SLAImport.cpp
new file mode 100644
index 000000000..442025a77
--- /dev/null
+++ b/src/slic3r/Utils/SLAImport.cpp
@@ -0,0 +1,314 @@
+#include "SLAImport.hpp"
+
+#include <sstream>
+
+#include "libslic3r/SlicesToTriangleMesh.hpp"
+#include "libslic3r/MarchingSquares.hpp"
+#include "libslic3r/ClipperUtils.hpp"
+#include "libslic3r/MTUtils.hpp"
+#include "libslic3r/PrintConfig.hpp"
+#include "libslic3r/SLA/RasterBase.hpp"
+#include "libslic3r/miniz_extension.hpp"
+
+#include <boost/property_tree/ini_parser.hpp>
+#include <boost/filesystem/path.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <wx/image.h>
+#include <wx/mstream.h>
+
+namespace marchsq {
+
+// Specialize this struct to register a raster type for the Marching squares alg
+template<> struct _RasterTraits<wxImage> {
+ using Rst = wxImage;
+
+ // The type of pixel cell in the raster
+ using ValueType = uint8_t;
+
+ // Value at a given position
+ static uint8_t get(const Rst &rst, size_t row, size_t col)
+ {
+ return rst.GetRed(col, row);
+ }
+
+ // Number of rows and cols of the raster
+ static size_t rows(const Rst &rst) { return rst.GetHeight(); }
+ static size_t cols(const Rst &rst) { return rst.GetWidth(); }
+};
+
+} // namespace marchsq
+
+namespace Slic3r {
+
+namespace {
+
+struct ArchiveData {
+ boost::property_tree::ptree profile, config;
+ std::vector<sla::EncodedRaster> images;
+};
+
+static const constexpr char *CONFIG_FNAME = "config.ini";
+static const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
+
+boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
+ MZ_Archive & zip)
+{
+ std::string buf(size_t(entry.m_uncomp_size), '\0');
+
+ if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
+ buf.data(), buf.size(), 0))
+ throw std::runtime_error(zip.get_errorstr());
+
+ boost::property_tree::ptree tree;
+ std::stringstream ss(buf);
+ boost::property_tree::read_ini(ss, tree);
+ return tree;
+}
+
+sla::EncodedRaster read_png(const mz_zip_archive_file_stat &entry,
+ MZ_Archive & zip,
+ const std::string & name)
+{
+ std::vector<uint8_t> buf(entry.m_uncomp_size);
+
+ if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
+ buf.data(), buf.size(), 0))
+ throw std::runtime_error(zip.get_errorstr());
+
+ return sla::EncodedRaster(std::move(buf),
+ name.empty() ? entry.m_filename : name);
+}
+
+ArchiveData extract_sla_archive(const std::string &zipfname,
+ const std::string &exclude)
+{
+ ArchiveData arch;
+
+ // Little RAII
+ struct Arch: public MZ_Archive {
+ Arch(const std::string &fname) {
+ if (!open_zip_reader(&arch, fname))
+ throw std::runtime_error(get_errorstr());
+ }
+
+ ~Arch() { close_zip_reader(&arch); }
+ } zip (zipfname);
+
+ mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
+
+ for (mz_uint i = 0; i < num_entries; ++i)
+ {
+ mz_zip_archive_file_stat entry;
+
+ if (mz_zip_reader_file_stat(&zip.arch, i, &entry))
+ {
+ std::string name = entry.m_filename;
+ boost::algorithm::to_lower(name);
+
+ if (boost::algorithm::contains(name, exclude)) continue;
+
+ if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip);
+ if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip);
+
+ if (boost::filesystem::path(name).extension().string() == ".png") {
+ auto it = std::lower_bound(
+ arch.images.begin(), arch.images.end(), sla::EncodedRaster({}, name),
+ [](const sla::EncodedRaster &r1, const sla::EncodedRaster &r2) {
+ return std::less<std::string>()(r1.extension(), r2.extension());
+ });
+
+ arch.images.insert(it, read_png(entry, zip, name));
+ }
+ }
+ }
+
+ return arch;
+}
+
+ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
+ double px_w, double px_h)
+{
+ ExPolygons polys; polys.reserve(rings.size());
+
+ for (const marchsq::Ring &ring : rings) {
+ Polygon poly; Points &pts = poly.points;
+ pts.reserve(ring.size());
+
+ for (const marchsq::Coord &crd : ring)
+ pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h));
+
+ polys.emplace_back(poly);
+ }
+
+ // reverse the raster transformations
+ return union_ex(polys);
+}
+
+template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
+{
+ for (auto &p : poly.contour.points) fn(p);
+ for (auto &h : poly.holes)
+ for (auto &p : h.points) fn(p);
+}
+
+void invert_raster_trafo(ExPolygons & expolys,
+ const sla::RasterBase::Trafo &trafo,
+ coord_t width,
+ coord_t height)
+{
+ for (auto &expoly : expolys) {
+ if (trafo.mirror_y)
+ foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); });
+
+ if (trafo.mirror_x)
+ foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); });
+
+ expoly.translate(-trafo.center_x, -trafo.center_y);
+
+ if (trafo.flipXY)
+ foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); });
+
+ if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) {
+ expoly.contour.reverse();
+ for (auto &h : expoly.holes) h.reverse();
+ }
+ }
+}
+
+struct RasterParams {
+ sla::RasterBase::Trafo trafo; // Raster transformations
+ coord_t width, height; // scaled raster dimensions (not resolution)
+ double px_h, px_w; // pixel dimesions
+ marchsq::Coord win; // marching squares window size
+};
+
+RasterParams get_raster_params(const DynamicPrintConfig &cfg)
+{
+ auto *opt_disp_cols = cfg.option<ConfigOptionInt>("display_pixels_x");
+ auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
+ auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
+ auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
+ auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
+ auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
+ auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("display_orientation");
+
+ if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
+ !opt_mirror_x || !opt_mirror_y || !opt_orient)
+ throw std::runtime_error("Invalid SL1 file");
+
+ RasterParams rstp;
+
+ rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1);
+ rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1);
+
+ sla::RasterBase::Trafo trafo{opt_orient->value == sladoLandscape ?
+ sla::RasterBase::roLandscape :
+ sla::RasterBase::roPortrait,
+ {opt_mirror_x->value, opt_mirror_y->value}};
+
+ rstp.height = scaled(opt_disp_h->value);
+ rstp.width = scaled(opt_disp_w->value);
+
+ return rstp;
+}
+
+struct SliceParams { double layerh = 0., initial_layerh = 0.; };
+
+SliceParams get_slice_params(const DynamicPrintConfig &cfg)
+{
+ auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
+ auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
+
+ if (!opt_layerh || !opt_init_layerh)
+ throw std::runtime_error("Invalid SL1 file");
+
+ return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
+}
+
+std::vector<ExPolygons> extract_slices_from_sla_archive(
+ ArchiveData & arch,
+ const RasterParams & rstp,
+ std::function<bool(int)> progr)
+{
+ auto jobdir = arch.config.get<std::string>("jobDir");
+ for (auto &c : jobdir) c = std::tolower(c);
+
+ std::vector<ExPolygons> slices(arch.images.size());
+
+ struct Status
+ {
+ double incr, val, prev;
+ bool stop = false;
+ tbb::spin_mutex mutex;
+ } st {100. / slices.size(), 0., 0.};
+
+ tbb::parallel_for(size_t(0), arch.images.size(),
+ [&arch, &slices, &st, &rstp, progr](size_t i) {
+ // Status indication guarded with the spinlock
+ {
+ std::lock_guard<tbb::spin_mutex> lck(st.mutex);
+ if (st.stop) return;
+
+ st.val += st.incr;
+ double curr = std::round(st.val);
+ if (curr > st.prev) {
+ st.prev = curr;
+ st.stop = !progr(int(curr));
+ }
+ }
+
+ auto &buf = arch.images[i];
+ wxMemoryInputStream stream{buf.data(), buf.size()};
+ wxImage img{stream};
+
+ auto rings = marchsq::execute(img, 128, rstp.win);
+ ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
+
+ // Invert the raster transformations indicated in
+ // the profile metadata
+ invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
+
+ slices[i] = std::move(expolys);
+ });
+
+ if (st.stop) slices = {};
+
+ return slices;
+}
+
+} // namespace
+
+void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
+{
+ ArchiveData arch = extract_sla_archive(zipfname, "png");
+ out.load(arch.profile);
+}
+
+void import_sla_archive(
+ const std::string & zipfname,
+ Vec2i windowsize,
+ TriangleMesh & out,
+ DynamicPrintConfig & profile,
+ std::function<bool(int)> progr)
+{
+ // Ensure minimum window size for marching squares
+ windowsize.x() = std::max(2, windowsize.x());
+ windowsize.y() = std::max(2, windowsize.y());
+
+ ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
+ profile.load(arch.profile);
+
+ RasterParams rstp = get_raster_params(profile);
+ rstp.win = {windowsize.y(), windowsize.x()};
+
+ SliceParams slicp = get_slice_params(profile);
+
+ std::vector<ExPolygons> slices =
+ extract_slices_from_sla_archive(arch, rstp, progr);
+
+ if (!slices.empty())
+ out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
+}
+
+} // namespace Slic3r
diff --git a/src/slic3r/Utils/SLAImport.hpp b/src/slic3r/Utils/SLAImport.hpp
new file mode 100644
index 000000000..a819bd7e7
--- /dev/null
+++ b/src/slic3r/Utils/SLAImport.hpp
@@ -0,0 +1,36 @@
+#ifndef SLAIMPORT_HPP
+#define SLAIMPORT_HPP
+
+#include <functional>
+
+#include <libslic3r/Point.hpp>
+#include <libslic3r/TriangleMesh.hpp>
+#include <libslic3r/PrintConfig.hpp>
+
+namespace Slic3r {
+
+class TriangleMesh;
+class DynamicPrintConfig;
+
+void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
+
+void import_sla_archive(
+ const std::string & zipfname,
+ Vec2i windowsize,
+ TriangleMesh & out,
+ DynamicPrintConfig & profile,
+ std::function<bool(int)> progr = [](int) { return true; });
+
+inline void import_sla_archive(
+ const std::string & zipfname,
+ Vec2i windowsize,
+ TriangleMesh & out,
+ std::function<bool(int)> progr = [](int) { return true; })
+{
+ DynamicPrintConfig profile;
+ import_sla_archive(zipfname, windowsize, out, profile, progr);
+}
+
+}
+
+#endif // SLAIMPORT_HPP