mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-25 17:51:10 -06:00
Merge remote-tracking branch 'origin/master' into ys_search
This commit is contained in:
commit
99d49a74d0
151 changed files with 21552 additions and 3527 deletions
|
|
@ -59,6 +59,29 @@ if (SLIC3R_GUI)
|
|||
|
||||
include(${wxWidgets_USE_FILE})
|
||||
|
||||
string(REGEX MATCH "wxpng" WX_PNG_BUILTIN ${wxWidgets_LIBRARIES})
|
||||
if (PNG_FOUND AND NOT WX_PNG_BUILTIN)
|
||||
list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX png)
|
||||
list(APPEND wxWidgets_LIBRARIES ${PNG_LIBRARIES})
|
||||
endif ()
|
||||
|
||||
string(REGEX MATCH "wxexpat" WX_EXPAT_BUILTIN ${wxWidgets_LIBRARIES})
|
||||
if (EXPAT_FOUND AND NOT WX_EXPAT_BUILTIN)
|
||||
list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX expat)
|
||||
list(APPEND wxWidgets_LIBRARIES ${EXPAT_LIBRARIES})
|
||||
endif ()
|
||||
|
||||
# This is an issue in the new wxWidgets cmake build, doesn't deal with librt
|
||||
find_library(LIBRT rt)
|
||||
if(LIBRT)
|
||||
list(APPEND wxWidgets_LIBRARIES ${LIBRT})
|
||||
endif()
|
||||
|
||||
# This fixes a OpenGL linking issue on OSX. wxWidgets cmake build includes
|
||||
# wrong libs for opengl in the link line and it does not link to it by himself.
|
||||
# libslic3r_gui will link to opengl anyway, so lets override wx
|
||||
list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX OpenGL)
|
||||
|
||||
# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc)
|
||||
message(STATUS "wx libs: ${wxWidgets_LIBRARIES}")
|
||||
|
||||
|
|
@ -178,13 +201,13 @@ if (WIN32)
|
|||
elseif (XCODE)
|
||||
# Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level
|
||||
add_custom_command(TARGET PrusaSlicer POST_BUILD
|
||||
COMMAND ln -sf "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/resources"
|
||||
COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/resources"
|
||||
COMMENT "Symlinking the resources directory into the build tree"
|
||||
VERBATIM
|
||||
)
|
||||
else ()
|
||||
add_custom_command(TARGET PrusaSlicer POST_BUILD
|
||||
COMMAND ln -sf "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/../resources"
|
||||
COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/../resources"
|
||||
COMMENT "Symlinking the resources directory into the build tree"
|
||||
VERBATIM
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -49,18 +51,21 @@
|
|||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "slic3r/GUI/InstanceCheck.hpp"
|
||||
#include "slic3r/GUI/AppConfig.hpp"
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
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 +91,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 +120,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 +141,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 +168,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 +178,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 +191,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 +212,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 +432,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 +443,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 +473,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;
|
||||
|
|
@ -505,6 +525,16 @@ int CLI::run(int argc, char **argv)
|
|||
#ifdef SLIC3R_GUI
|
||||
// #ifdef USE_WX
|
||||
GUI::GUI_App *gui = new GUI::GUI_App();
|
||||
|
||||
bool gui_single_instance_setting = gui->app_config->get("single_instance") == "1";
|
||||
if (Slic3r::instance_check(argc, argv, gui_single_instance_setting)) {
|
||||
//TODO: do we have delete gui and other stuff?
|
||||
return -1;
|
||||
}
|
||||
|
||||
//gui->app_config = app_config;
|
||||
//app_config = nullptr;
|
||||
|
||||
// gui->autosave = m_config.opt_string("autosave");
|
||||
GUI::GUI_App::SetInstance(gui);
|
||||
gui->CallAfter([gui, this, &load_configs] {
|
||||
|
|
@ -609,6 +639,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 +648,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
#include <shellapi.h>
|
||||
#include <wchar.h>
|
||||
|
||||
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
extern "C"
|
||||
{
|
||||
|
|
@ -216,7 +218,6 @@ int APIENTRY wWinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */,
|
|||
int wmain(int argc, wchar_t **argv)
|
||||
{
|
||||
#endif
|
||||
|
||||
std::vector<wchar_t*> argv_extended;
|
||||
argv_extended.emplace_back(argv[0]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#include "Arrange.hpp"
|
||||
#include "Geometry.hpp"
|
||||
//#include "Geometry.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
|
||||
#include <libnest2d/backends/clipper/geometries.hpp>
|
||||
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||
|
|
@ -83,7 +82,7 @@ const double BIG_ITEM_TRESHOLD = 0.02;
|
|||
// Fill in the placer algorithm configuration with values carefully chosen for
|
||||
// Slic3r.
|
||||
template<class PConf>
|
||||
void fillConfig(PConf& pcfg) {
|
||||
void fill_config(PConf& pcfg) {
|
||||
|
||||
// Align the arranged pile into the center of the bin
|
||||
pcfg.alignment = PConf::Alignment::CENTER;
|
||||
|
|
@ -105,7 +104,7 @@ void fillConfig(PConf& pcfg) {
|
|||
|
||||
// Apply penalty to object function result. This is used only when alignment
|
||||
// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN)
|
||||
double fixed_overfit(const std::tuple<double, Box>& result, const Box &binbb)
|
||||
static double fixed_overfit(const std::tuple<double, Box>& result, const Box &binbb)
|
||||
{
|
||||
double score = std::get<0>(result);
|
||||
Box pilebb = std::get<1>(result);
|
||||
|
|
@ -312,7 +311,7 @@ public:
|
|||
, m_bin_area(sl::area(bin))
|
||||
, m_norm(std::sqrt(m_bin_area))
|
||||
{
|
||||
fillConfig(m_pconf);
|
||||
fill_config(m_pconf);
|
||||
|
||||
// Set up a callback that is called just before arranging starts
|
||||
// This functionality is provided by the Nester class (m_pack).
|
||||
|
|
@ -363,6 +362,9 @@ public:
|
|||
m_item_count = 0;
|
||||
}
|
||||
|
||||
PConfig& config() { return m_pconf; }
|
||||
const PConfig& config() const { return m_pconf; }
|
||||
|
||||
inline void preload(std::vector<Item>& fixeditems) {
|
||||
m_pconf.alignment = PConfig::Alignment::DONT_ALIGN;
|
||||
auto bb = sl::boundingBox(m_bin);
|
||||
|
|
@ -438,127 +440,6 @@ std::function<double(const Item &)> AutoArranger<clppr::Polygon>::get_objfn()
|
|||
};
|
||||
}
|
||||
|
||||
inline Circle to_lnCircle(const CircleBed& circ) {
|
||||
return Circle({circ.center()(0), circ.center()(1)}, circ.radius());
|
||||
}
|
||||
|
||||
// Get the type of bed geometry from a simple vector of points.
|
||||
void BedShapeHint::reset(BedShapes type)
|
||||
{
|
||||
if (m_type != type) {
|
||||
if (m_type == bsIrregular)
|
||||
m_bed.polygon.Slic3r::Polyline::~Polyline();
|
||||
else if (type == bsIrregular)
|
||||
::new (&m_bed.polygon) Polyline();
|
||||
}
|
||||
|
||||
m_type = type;
|
||||
}
|
||||
|
||||
BedShapeHint::BedShapeHint(const Polyline &bed) {
|
||||
auto x = [](const Point& p) { return p(X); };
|
||||
auto y = [](const Point& p) { return p(Y); };
|
||||
|
||||
auto width = [x](const BoundingBox& box) {
|
||||
return x(box.max) - x(box.min);
|
||||
};
|
||||
|
||||
auto height = [y](const BoundingBox& box) {
|
||||
return y(box.max) - y(box.min);
|
||||
};
|
||||
|
||||
auto area = [&width, &height](const BoundingBox& box) {
|
||||
double w = width(box);
|
||||
double h = height(box);
|
||||
return w * h;
|
||||
};
|
||||
|
||||
auto poly_area = [](Polyline p) {
|
||||
Polygon pp; pp.points.reserve(p.points.size() + 1);
|
||||
pp.points = std::move(p.points);
|
||||
pp.points.emplace_back(pp.points.front());
|
||||
return std::abs(pp.area());
|
||||
};
|
||||
|
||||
auto distance_to = [x, y](const Point& p1, const Point& p2) {
|
||||
double dx = x(p2) - x(p1);
|
||||
double dy = y(p2) - y(p1);
|
||||
return std::sqrt(dx*dx + dy*dy);
|
||||
};
|
||||
|
||||
auto bb = bed.bounding_box();
|
||||
|
||||
auto isCircle = [bb, distance_to](const Polyline& polygon) {
|
||||
auto center = bb.center();
|
||||
std::vector<double> vertex_distances;
|
||||
double avg_dist = 0;
|
||||
for (auto pt: polygon.points)
|
||||
{
|
||||
double distance = distance_to(center, pt);
|
||||
vertex_distances.push_back(distance);
|
||||
avg_dist += distance;
|
||||
}
|
||||
|
||||
avg_dist /= vertex_distances.size();
|
||||
|
||||
CircleBed ret(center, avg_dist);
|
||||
for(auto el : vertex_distances)
|
||||
{
|
||||
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
|
||||
ret = CircleBed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto parea = poly_area(bed);
|
||||
|
||||
if( (1.0 - parea/area(bb)) < 1e-3 ) {
|
||||
m_type = BedShapes::bsBox;
|
||||
m_bed.box = bb;
|
||||
}
|
||||
else if(auto c = isCircle(bed)) {
|
||||
m_type = BedShapes::bsCircle;
|
||||
m_bed.circ = c;
|
||||
} else {
|
||||
assert(m_type != BedShapes::bsIrregular);
|
||||
m_type = BedShapes::bsIrregular;
|
||||
::new (&m_bed.polygon) Polyline(bed);
|
||||
}
|
||||
}
|
||||
|
||||
BedShapeHint &BedShapeHint::operator=(BedShapeHint &&cpy)
|
||||
{
|
||||
reset(cpy.m_type);
|
||||
|
||||
switch(m_type) {
|
||||
case bsBox: m_bed.box = std::move(cpy.m_bed.box); break;
|
||||
case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break;
|
||||
case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break;
|
||||
case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break;
|
||||
case bsUnknown: break;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BedShapeHint &BedShapeHint::operator=(const BedShapeHint &cpy)
|
||||
{
|
||||
reset(cpy.m_type);
|
||||
|
||||
switch(m_type) {
|
||||
case bsBox: m_bed.box = cpy.m_bed.box; break;
|
||||
case bsCircle: m_bed.circ = cpy.m_bed.circ; break;
|
||||
case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break;
|
||||
case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break;
|
||||
case bsUnknown: break;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class Bin> void remove_large_items(std::vector<Item> &items, Bin &&bin)
|
||||
{
|
||||
auto it = items.begin();
|
||||
|
|
@ -572,12 +453,12 @@ void _arrange(
|
|||
std::vector<Item> & shapes,
|
||||
std::vector<Item> & excludes,
|
||||
const BinT & bin,
|
||||
coord_t minobjd,
|
||||
const ArrangeParams & params,
|
||||
std::function<void(unsigned)> progressfn,
|
||||
std::function<bool()> stopfn)
|
||||
{
|
||||
// Integer ceiling the min distance from the bed perimeters
|
||||
coord_t md = minobjd;
|
||||
coord_t md = params.min_obj_distance;
|
||||
md = (md % 2) ? md / 2 + 1 : md / 2;
|
||||
|
||||
auto corrected_bin = bin;
|
||||
|
|
@ -585,7 +466,10 @@ void _arrange(
|
|||
|
||||
AutoArranger<BinT> arranger{corrected_bin, progressfn, stopfn};
|
||||
|
||||
auto infl = coord_t(std::ceil(minobjd / 2.0));
|
||||
arranger.config().accuracy = params.accuracy;
|
||||
arranger.config().parallel = params.parallel;
|
||||
|
||||
auto infl = coord_t(std::ceil(params.min_obj_distance / 2.0));
|
||||
for (Item& itm : shapes) itm.inflate(infl);
|
||||
for (Item& itm : excludes) itm.inflate(infl);
|
||||
|
||||
|
|
@ -603,44 +487,106 @@ void _arrange(
|
|||
for (Item &itm : inp) itm.inflate(-infl);
|
||||
}
|
||||
|
||||
// The final client function for arrangement. A progress indicator and
|
||||
// a stop predicate can be also be passed to control the process.
|
||||
void arrange(ArrangePolygons & arrangables,
|
||||
const ArrangePolygons & excludes,
|
||||
coord_t min_obj_dist,
|
||||
const BedShapeHint & bedhint,
|
||||
std::function<void(unsigned)> progressind,
|
||||
std::function<bool()> stopcondition)
|
||||
inline Box to_nestbin(const BoundingBox &bb) { return Box{{bb.min(X), bb.min(Y)}, {bb.max(X), bb.max(Y)}};}
|
||||
inline Circle to_nestbin(const CircleBed &c) { return Circle({c.center()(0), c.center()(1)}, c.radius()); }
|
||||
inline clppr::Polygon to_nestbin(const Polygon &p) { return sl::create<clppr::Polygon>(Slic3rMultiPoint_to_ClipperPath(p)); }
|
||||
inline Box to_nestbin(const InfiniteBed &bed) { return Box::infinite({bed.center.x(), bed.center.y()}); }
|
||||
|
||||
inline coord_t width(const BoundingBox& box) { return box.max.x() - box.min.x(); }
|
||||
inline coord_t height(const BoundingBox& box) { return box.max.y() - box.min.y(); }
|
||||
inline double area(const BoundingBox& box) { return double(width(box)) * height(box); }
|
||||
inline double poly_area(const Points &pts) { return std::abs(Polygon::area(pts)); }
|
||||
inline double distance_to(const Point& p1, const Point& p2)
|
||||
{
|
||||
double dx = p2.x() - p1.x();
|
||||
double dy = p2.y() - p1.y();
|
||||
return std::sqrt(dx*dx + dy*dy);
|
||||
}
|
||||
|
||||
static CircleBed to_circle(const Point ¢er, const Points& points) {
|
||||
std::vector<double> vertex_distances;
|
||||
double avg_dist = 0;
|
||||
|
||||
for (auto pt : points)
|
||||
{
|
||||
double distance = distance_to(center, pt);
|
||||
vertex_distances.push_back(distance);
|
||||
avg_dist += distance;
|
||||
}
|
||||
|
||||
avg_dist /= vertex_distances.size();
|
||||
|
||||
CircleBed ret(center, avg_dist);
|
||||
for(auto el : vertex_distances)
|
||||
{
|
||||
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
|
||||
ret = {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Create Item from Arrangeable
|
||||
static void process_arrangeable(const ArrangePolygon &arrpoly,
|
||||
std::vector<Item> & outp)
|
||||
{
|
||||
Polygon p = arrpoly.poly.contour;
|
||||
const Vec2crd &offs = arrpoly.translation;
|
||||
double rotation = arrpoly.rotation;
|
||||
|
||||
if (p.is_counter_clockwise()) p.reverse();
|
||||
|
||||
clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
|
||||
|
||||
if (!clpath.Contour.empty()) {
|
||||
auto firstp = clpath.Contour.front();
|
||||
clpath.Contour.emplace_back(firstp);
|
||||
}
|
||||
|
||||
outp.emplace_back(std::move(clpath));
|
||||
outp.back().rotation(rotation);
|
||||
outp.back().translation({offs.x(), offs.y()});
|
||||
outp.back().binId(arrpoly.bed_idx);
|
||||
outp.back().priority(arrpoly.priority);
|
||||
}
|
||||
|
||||
template<>
|
||||
void arrange(ArrangePolygons & items,
|
||||
const ArrangePolygons &excludes,
|
||||
const Points & bed,
|
||||
const ArrangeParams & params)
|
||||
{
|
||||
if (bed.empty())
|
||||
arrange(items, excludes, InfiniteBed{}, params);
|
||||
else if (bed.size() == 1)
|
||||
arrange(items, excludes, InfiniteBed{bed.front()}, params);
|
||||
else {
|
||||
auto bb = BoundingBox(bed);
|
||||
CircleBed circ = to_circle(bb.center(), bed);
|
||||
auto parea = poly_area(bed);
|
||||
|
||||
if ((1.0 - parea / area(bb)) < 1e-3)
|
||||
arrange(items, excludes, bb, params);
|
||||
else if (!std::isnan(circ.radius()))
|
||||
arrange(items, excludes, circ, params);
|
||||
else
|
||||
arrange(items, excludes, Polygon(bed), params);
|
||||
}
|
||||
}
|
||||
|
||||
template<class BedT>
|
||||
void arrange(ArrangePolygons & arrangables,
|
||||
const ArrangePolygons &excludes,
|
||||
const BedT & bed,
|
||||
const ArrangeParams & params)
|
||||
{
|
||||
namespace clppr = ClipperLib;
|
||||
|
||||
std::vector<Item> items, fixeditems;
|
||||
items.reserve(arrangables.size());
|
||||
|
||||
// Create Item from Arrangeable
|
||||
auto process_arrangeable = [](const ArrangePolygon &arrpoly,
|
||||
std::vector<Item> & outp)
|
||||
{
|
||||
Polygon p = arrpoly.poly.contour;
|
||||
const Vec2crd &offs = arrpoly.translation;
|
||||
double rotation = arrpoly.rotation;
|
||||
|
||||
if (p.is_counter_clockwise()) p.reverse();
|
||||
|
||||
clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
|
||||
|
||||
if (!clpath.Contour.empty()) {
|
||||
auto firstp = clpath.Contour.front();
|
||||
clpath.Contour.emplace_back(firstp);
|
||||
}
|
||||
|
||||
outp.emplace_back(std::move(clpath));
|
||||
outp.back().rotation(rotation);
|
||||
outp.back().translation({offs.x(), offs.y()});
|
||||
outp.back().binId(arrpoly.bed_idx);
|
||||
outp.back().priority(arrpoly.priority);
|
||||
};
|
||||
|
||||
for (ArrangePolygon &arrangeable : arrangables)
|
||||
process_arrangeable(arrangeable, items);
|
||||
|
||||
|
|
@ -649,45 +595,10 @@ void arrange(ArrangePolygons & arrangables,
|
|||
|
||||
for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON));
|
||||
|
||||
auto &cfn = stopcondition;
|
||||
auto &pri = progressind;
|
||||
auto &cfn = params.stopcondition;
|
||||
auto &pri = params.progressind;
|
||||
|
||||
switch (bedhint.get_type()) {
|
||||
case bsBox: {
|
||||
// Create the arranger for the box shaped bed
|
||||
BoundingBox bbb = bedhint.get_box();
|
||||
Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}};
|
||||
|
||||
_arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn);
|
||||
break;
|
||||
}
|
||||
case bsCircle: {
|
||||
auto cc = to_lnCircle(bedhint.get_circle());
|
||||
|
||||
_arrange(items, fixeditems, cc, min_obj_dist, pri, cfn);
|
||||
break;
|
||||
}
|
||||
case bsIrregular: {
|
||||
auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular());
|
||||
auto irrbed = sl::create<clppr::Polygon>(std::move(ctour));
|
||||
BoundingBox polybb(bedhint.get_irregular());
|
||||
|
||||
_arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn);
|
||||
break;
|
||||
}
|
||||
case bsInfinite: {
|
||||
const InfiniteBed& nobin = bedhint.get_infinite();
|
||||
auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()});
|
||||
|
||||
_arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn);
|
||||
break;
|
||||
}
|
||||
case bsUnknown: {
|
||||
// We know nothing about the bed, let it be infinite and zero centered
|
||||
_arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_arrange(items, fixeditems, to_nestbin(bed), params, pri, cfn);
|
||||
|
||||
for(size_t i = 0; i < items.size(); ++i) {
|
||||
clppr::IntPoint tr = items[i].translation();
|
||||
|
|
@ -697,15 +608,10 @@ void arrange(ArrangePolygons & arrangables,
|
|||
}
|
||||
}
|
||||
|
||||
// Arrange, without the fixed items (excludes)
|
||||
void arrange(ArrangePolygons & inp,
|
||||
coord_t min_d,
|
||||
const BedShapeHint & bedhint,
|
||||
std::function<void(unsigned)> prfn,
|
||||
std::function<bool()> stopfn)
|
||||
{
|
||||
arrange(inp, {}, min_d, bedhint, prfn, stopfn);
|
||||
}
|
||||
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms);
|
||||
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms);
|
||||
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
||||
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
||||
|
||||
} // namespace arr
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
#ifndef MODELARRANGE_HPP
|
||||
#define MODELARRANGE_HPP
|
||||
#ifndef ARRANGE_HPP
|
||||
#define ARRANGE_HPP
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace arrangement {
|
||||
namespace Slic3r { namespace arrangement {
|
||||
|
||||
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
|
||||
class CircleBed {
|
||||
|
|
@ -15,96 +13,16 @@ class CircleBed {
|
|||
public:
|
||||
|
||||
inline CircleBed(): center_(0, 0), radius_(std::nan("")) {}
|
||||
inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
|
||||
explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
|
||||
|
||||
inline double radius() const { return radius_; }
|
||||
inline const Point& center() const { return center_; }
|
||||
inline operator bool() { return !std::isnan(radius_); }
|
||||
};
|
||||
|
||||
/// Representing an unbounded bed.
|
||||
struct InfiniteBed { Point center; };
|
||||
|
||||
/// Types of print bed shapes.
|
||||
enum BedShapes {
|
||||
bsBox,
|
||||
bsCircle,
|
||||
bsIrregular,
|
||||
bsInfinite,
|
||||
bsUnknown
|
||||
};
|
||||
|
||||
/// Info about the print bed for the arrange() function. This is a variant
|
||||
/// holding one of the four shapes a bed can be.
|
||||
class BedShapeHint {
|
||||
BedShapes m_type = BedShapes::bsInfinite;
|
||||
|
||||
// The union neither calls constructors nor destructors of its members.
|
||||
// The only member with non-trivial constructor / destructor is the polygon,
|
||||
// a placement new / delete needs to be called over it.
|
||||
union BedShape_u { // TODO: use variant from cpp17?
|
||||
CircleBed circ;
|
||||
BoundingBox box;
|
||||
Polyline polygon;
|
||||
InfiniteBed infbed{};
|
||||
~BedShape_u() {}
|
||||
BedShape_u() {}
|
||||
} m_bed;
|
||||
|
||||
// Reset the type, allocate m_bed properly
|
||||
void reset(BedShapes type);
|
||||
|
||||
public:
|
||||
|
||||
BedShapeHint(){}
|
||||
|
||||
/// Get a bed shape hint for arrange() from a naked Polyline.
|
||||
explicit BedShapeHint(const Polyline &polyl);
|
||||
explicit BedShapeHint(const BoundingBox &bb)
|
||||
{
|
||||
m_type = bsBox; m_bed.box = bb;
|
||||
}
|
||||
|
||||
explicit BedShapeHint(const CircleBed &c)
|
||||
{
|
||||
m_type = bsCircle; m_bed.circ = c;
|
||||
}
|
||||
|
||||
explicit BedShapeHint(const InfiniteBed &ibed)
|
||||
{
|
||||
m_type = bsInfinite; m_bed.infbed = ibed;
|
||||
}
|
||||
|
||||
~BedShapeHint()
|
||||
{
|
||||
if (m_type == BedShapes::bsIrregular)
|
||||
m_bed.polygon.Slic3r::Polyline::~Polyline();
|
||||
}
|
||||
|
||||
BedShapeHint(const BedShapeHint &cpy) { *this = cpy; }
|
||||
BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); }
|
||||
|
||||
BedShapeHint &operator=(const BedShapeHint &cpy);
|
||||
BedShapeHint& operator=(BedShapeHint &&cpy);
|
||||
|
||||
BedShapes get_type() const { return m_type; }
|
||||
|
||||
const BoundingBox &get_box() const
|
||||
{
|
||||
assert(m_type == bsBox); return m_bed.box;
|
||||
}
|
||||
const CircleBed &get_circle() const
|
||||
{
|
||||
assert(m_type == bsCircle); return m_bed.circ;
|
||||
}
|
||||
const Polyline &get_irregular() const
|
||||
{
|
||||
assert(m_type == bsIrregular); return m_bed.polygon;
|
||||
}
|
||||
const InfiniteBed &get_infinite() const
|
||||
{
|
||||
assert(m_type == bsInfinite); return m_bed.infbed;
|
||||
}
|
||||
struct InfiniteBed {
|
||||
Point center;
|
||||
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
|
||||
};
|
||||
|
||||
/// A logical bed representing an object not being arranged. Either the arrange
|
||||
|
|
@ -125,9 +43,14 @@ struct ArrangePolygon {
|
|||
ExPolygon poly; /// The 2D silhouette to be arranged
|
||||
Vec2crd translation{0, 0}; /// The translation of the poly
|
||||
double rotation{0.0}; /// The rotation of the poly in radians
|
||||
coord_t inflation = 0; /// Arrange with inflated polygon
|
||||
int bed_idx{UNARRANGED}; /// To which logical bed does poly belong...
|
||||
int priority{0};
|
||||
|
||||
// If empty, any rotation is allowed (currently unsupported)
|
||||
// If only a zero is there, no rotation is allowed
|
||||
std::vector<double> allowed_rotations = {0.};
|
||||
|
||||
/// Optional setter function which can store arbitrary data in its closure
|
||||
std::function<void(const ArrangePolygon&)> setter = nullptr;
|
||||
|
||||
|
|
@ -140,6 +63,30 @@ struct ArrangePolygon {
|
|||
|
||||
using ArrangePolygons = std::vector<ArrangePolygon>;
|
||||
|
||||
struct ArrangeParams {
|
||||
|
||||
/// The minimum distance which is allowed for any
|
||||
/// pair of items on the print bed in any direction.
|
||||
coord_t min_obj_distance = 0.;
|
||||
|
||||
/// The accuracy of optimization.
|
||||
/// Goes from 0.0 to 1.0 and scales performance as well
|
||||
float accuracy = 0.65f;
|
||||
|
||||
/// Allow parallel execution.
|
||||
bool parallel = true;
|
||||
|
||||
/// Progress indicator callback called when an object gets packed.
|
||||
/// The unsigned argument is the number of items remaining to pack.
|
||||
std::function<void(unsigned)> progressind;
|
||||
|
||||
/// A predicate returning true if abort is needed.
|
||||
std::function<bool(void)> stopcondition;
|
||||
|
||||
ArrangeParams() = default;
|
||||
explicit ArrangeParams(coord_t md) : min_obj_distance(md) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Arranges the input polygons.
|
||||
*
|
||||
|
|
@ -150,33 +97,23 @@ using ArrangePolygons = std::vector<ArrangePolygon>;
|
|||
* \param items Input vector of ArrangePolygons. The transformation, rotation
|
||||
* and bin_idx fields will be changed after the call finished and can be used
|
||||
* to apply the result on the input polygon.
|
||||
*
|
||||
* \param min_obj_distance The minimum distance which is allowed for any
|
||||
* pair of items on the print bed in any direction.
|
||||
*
|
||||
* \param bedhint Info about the shape and type of the bed.
|
||||
*
|
||||
* \param progressind Progress indicator callback called when
|
||||
* an object gets packed. The unsigned argument is the number of items
|
||||
* remaining to pack.
|
||||
*
|
||||
* \param stopcondition A predicate returning true if abort is needed.
|
||||
*/
|
||||
void arrange(ArrangePolygons & items,
|
||||
coord_t min_obj_distance,
|
||||
const BedShapeHint & bedhint,
|
||||
std::function<void(unsigned)> progressind = nullptr,
|
||||
std::function<bool(void)> stopcondition = nullptr);
|
||||
template<class TBed> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams ¶ms = {});
|
||||
|
||||
/// Same as the previous, only that it takes unmovable items as an
|
||||
/// additional argument. Those will be considered as already arranged objects.
|
||||
void arrange(ArrangePolygons & items,
|
||||
const ArrangePolygons & excludes,
|
||||
coord_t min_obj_distance,
|
||||
const BedShapeHint & bedhint,
|
||||
std::function<void(unsigned)> progressind = nullptr,
|
||||
std::function<bool(void)> stopcondition = nullptr);
|
||||
// A dispatch function that determines the bed shape from a set of points.
|
||||
template<> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Points &bed, const ArrangeParams ¶ms);
|
||||
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
|
||||
}} // namespace Slic3r::arrangement
|
||||
|
||||
} // arr
|
||||
} // Slic3r
|
||||
#endif // MODELARRANGE_HPP
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -312,6 +312,7 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
|
|||
case erOverhangPerimeter : return L("Overhang perimeter");
|
||||
case erInternalInfill : return L("Internal infill");
|
||||
case erSolidInfill : return L("Solid infill");
|
||||
case erIroning : return L("Ironing");
|
||||
case erTopSolidInfill : return L("Top solid infill");
|
||||
case erBridgeInfill : return L("Bridge infill");
|
||||
case erGapFill : return L("Gap fill");
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ enum ExtrusionRole : uint8_t {
|
|||
erInternalInfill,
|
||||
erSolidInfill,
|
||||
erTopSolidInfill,
|
||||
erIroning,
|
||||
erBridgeInfill,
|
||||
erGapFill,
|
||||
erSkirt,
|
||||
|
|
@ -54,14 +55,16 @@ inline bool is_infill(ExtrusionRole role)
|
|||
return role == erBridgeInfill
|
||||
|| role == erInternalInfill
|
||||
|| role == erSolidInfill
|
||||
|| role == erTopSolidInfill;
|
||||
|| role == erTopSolidInfill
|
||||
|| role == erIroning;
|
||||
}
|
||||
|
||||
inline bool is_solid_infill(ExtrusionRole role)
|
||||
{
|
||||
return role == erBridgeInfill
|
||||
|| role == erSolidInfill
|
||||
|| role == erTopSolidInfill;
|
||||
|| role == erTopSolidInfill
|
||||
|| role == erIroning;
|
||||
}
|
||||
|
||||
inline bool is_bridge(ExtrusionRole role) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "../Surface.hpp"
|
||||
|
||||
#include "FillBase.hpp"
|
||||
#include "FillRectilinear2.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
|
@ -372,7 +373,11 @@ void Layer::make_fills()
|
|||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
f->spacing = surface_fill.params.spacing;
|
||||
surface_fill.surface.expolygon = std::move(expoly);
|
||||
Polylines polylines = f->fill_surface(&surface_fill.surface, params);
|
||||
Polylines polylines;
|
||||
try {
|
||||
polylines = f->fill_surface(&surface_fill.surface, params);
|
||||
} catch (InfillFailedException &) {
|
||||
}
|
||||
if (! polylines.empty()) {
|
||||
// calculate actual flow from spacing (which might have been adjusted by the infill
|
||||
// pattern generator)
|
||||
|
|
@ -388,8 +393,8 @@ void Layer::make_fills()
|
|||
flow_width = new_flow.width;
|
||||
}
|
||||
// Save into layer.
|
||||
auto *eec = new ExtrusionEntityCollection();
|
||||
m_regions[surface_fill.region_id]->fills.entities.push_back(eec);
|
||||
ExtrusionEntityCollection* eec = nullptr;
|
||||
m_regions[surface_fill.region_id]->fills.entities.push_back(eec = new ExtrusionEntityCollection());
|
||||
// Only concentric fills are not sorted.
|
||||
eec->no_sort = f->no_sort();
|
||||
extrusion_entities_append_paths(
|
||||
|
|
@ -418,4 +423,170 @@ void Layer::make_fills()
|
|||
#endif
|
||||
}
|
||||
|
||||
// Create ironing extrusions over top surfaces.
|
||||
void Layer::make_ironing()
|
||||
{
|
||||
// LayerRegion::slices contains surfaces marked with SurfaceType.
|
||||
// Here we want to collect top surfaces extruded with the same extruder.
|
||||
// A surface will be ironed with the same extruder to not contaminate the print with another material leaking from the nozzle.
|
||||
|
||||
// First classify regions based on the extruder used.
|
||||
struct IroningParams {
|
||||
int extruder = -1;
|
||||
bool just_infill = false;
|
||||
// Spacing of the ironing lines, also to calculate the extrusion flow from.
|
||||
double line_spacing;
|
||||
// Height of the extrusion, to calculate the extrusion flow from.
|
||||
double height;
|
||||
double speed;
|
||||
double angle;
|
||||
|
||||
bool operator<(const IroningParams &rhs) const {
|
||||
if (this->extruder < rhs.extruder)
|
||||
return true;
|
||||
if (this->extruder > rhs.extruder)
|
||||
return false;
|
||||
if (int(this->just_infill) < int(rhs.just_infill))
|
||||
return true;
|
||||
if (int(this->just_infill) > int(rhs.just_infill))
|
||||
return false;
|
||||
if (this->line_spacing < rhs.line_spacing)
|
||||
return true;
|
||||
if (this->line_spacing > rhs.line_spacing)
|
||||
return false;
|
||||
if (this->height < rhs.height)
|
||||
return true;
|
||||
if (this->height > rhs.height)
|
||||
return false;
|
||||
if (this->speed < rhs.speed)
|
||||
return true;
|
||||
if (this->speed > rhs.speed)
|
||||
return false;
|
||||
if (this->angle < rhs.angle)
|
||||
return true;
|
||||
if (this->angle > rhs.angle)
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator==(const IroningParams &rhs) const {
|
||||
return this->extruder == rhs.extruder && this->just_infill == rhs.just_infill &&
|
||||
this->line_spacing == rhs.line_spacing && this->height == rhs.height && this->speed == rhs.speed &&
|
||||
this->angle == rhs.angle;
|
||||
}
|
||||
|
||||
LayerRegion *layerm = nullptr;
|
||||
|
||||
// IdeaMaker: ironing
|
||||
// ironing flowrate (5% percent)
|
||||
// ironing speed (10 mm/sec)
|
||||
|
||||
// Kisslicer:
|
||||
// iron off, Sweep, Group
|
||||
// ironing speed: 15 mm/sec
|
||||
|
||||
// Cura:
|
||||
// Pattern (zig-zag / concentric)
|
||||
// line spacing (0.1mm)
|
||||
// flow: from normal layer height. 10%
|
||||
// speed: 20 mm/sec
|
||||
};
|
||||
|
||||
std::vector<IroningParams> by_extruder;
|
||||
bool extruder_dont_care = this->object()->config().wipe_into_objects;
|
||||
double default_layer_height = this->object()->config().layer_height;
|
||||
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
if (! layerm->slices.empty()) {
|
||||
IroningParams ironing_params;
|
||||
const PrintRegionConfig &config = layerm->region()->config();
|
||||
if (config.ironing &&
|
||||
(config.ironing_type == IroningType::AllSolid ||
|
||||
(config.top_solid_layers > 0 &&
|
||||
(config.ironing_type == IroningType::TopSurfaces ||
|
||||
(config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) {
|
||||
if (config.perimeter_extruder == config.solid_infill_extruder || config.perimeters == 0) {
|
||||
// Iron the whole face.
|
||||
ironing_params.extruder = config.solid_infill_extruder;
|
||||
} else {
|
||||
// Iron just the infill.
|
||||
ironing_params.extruder = config.solid_infill_extruder;
|
||||
}
|
||||
}
|
||||
if (ironing_params.extruder != -1) {
|
||||
ironing_params.just_infill = false;
|
||||
ironing_params.line_spacing = config.ironing_spacing;
|
||||
ironing_params.height = default_layer_height * 0.01 * config.ironing_flowrate;
|
||||
ironing_params.speed = config.ironing_speed;
|
||||
ironing_params.angle = config.fill_angle * M_PI / 180.;
|
||||
ironing_params.layerm = layerm;
|
||||
by_extruder.emplace_back(ironing_params);
|
||||
}
|
||||
}
|
||||
std::sort(by_extruder.begin(), by_extruder.end());
|
||||
|
||||
FillRectilinear2 fill;
|
||||
FillParams fill_params;
|
||||
fill.set_bounding_box(this->object()->bounding_box());
|
||||
fill.layer_id = this->id();
|
||||
fill.z = this->print_z;
|
||||
fill.overlap = 0;
|
||||
fill_params.density = 1.;
|
||||
// fill_params.dont_connect = true;
|
||||
fill_params.dont_connect = false;
|
||||
fill_params.monotonous = true;
|
||||
|
||||
for (size_t i = 0; i < by_extruder.size(); ++ i) {
|
||||
// Find span of regions equivalent to the ironing operation.
|
||||
IroningParams &ironing_params = by_extruder[i];
|
||||
size_t j = i;
|
||||
for (++ j; j < by_extruder.size() && ironing_params == by_extruder[j]; ++ j) ;
|
||||
|
||||
// Create the ironing extrusions for regions <i, j)
|
||||
ExPolygons ironing_areas;
|
||||
double nozzle_dmr = this->object()->print()->config().nozzle_diameter.values[ironing_params.extruder - 1];
|
||||
if (ironing_params.just_infill) {
|
||||
// Just infill.
|
||||
} else {
|
||||
// Infill and perimeter.
|
||||
// Merge top surfaces with the same ironing parameters.
|
||||
Polygons polys;
|
||||
for (size_t k = i; k < j; ++ k)
|
||||
for (const Surface &surface : by_extruder[k].layerm->slices.surfaces)
|
||||
if (surface.surface_type == stTop)
|
||||
polygons_append(polys, surface.expolygon);
|
||||
// Trim the top surfaces with half the nozzle diameter.
|
||||
ironing_areas = intersection_ex(polys, offset(this->lslices, - float(scale_(0.5 * nozzle_dmr))));
|
||||
}
|
||||
|
||||
// Create the filler object.
|
||||
fill.spacing = ironing_params.line_spacing;
|
||||
fill.angle = float(ironing_params.angle + 0.25 * M_PI);
|
||||
fill.link_max_length = (coord_t)scale_(3. * fill.spacing);
|
||||
double height = ironing_params.height * fill.spacing / nozzle_dmr;
|
||||
Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height), false);
|
||||
double flow_mm3_per_mm = flow.mm3_per_mm();
|
||||
Surface surface_fill(stTop, ExPolygon());
|
||||
for (ExPolygon &expoly : ironing_areas) {
|
||||
surface_fill.expolygon = std::move(expoly);
|
||||
Polylines polylines;
|
||||
try {
|
||||
polylines = fill.fill_surface(&surface_fill, fill_params);
|
||||
} catch (InfillFailedException &) {
|
||||
}
|
||||
if (! polylines.empty()) {
|
||||
// Save into layer.
|
||||
ExtrusionEntityCollection *eec = nullptr;
|
||||
ironing_params.layerm->fills.entities.push_back(eec = new ExtrusionEntityCollection());
|
||||
// Don't sort the ironing infill lines as they are monotonously ordered.
|
||||
eec->no_sort = true;
|
||||
extrusion_entities_append_paths(
|
||||
eec->entities, std::move(polylines),
|
||||
erIroning,
|
||||
flow_mm3_per_mm, float(flow.width), float(height));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
|
|||
case ip3DHoneycomb: return new Fill3DHoneycomb();
|
||||
case ipGyroid: return new FillGyroid();
|
||||
case ipRectilinear: return new FillRectilinear2();
|
||||
// case ipRectilinear: return new FillRectilinear();
|
||||
case ipMonotonous: return new FillMonotonous();
|
||||
case ipLine: return new FillLine();
|
||||
case ipGrid: return new FillGrid2();
|
||||
case ipTriangles: return new FillTriangles();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <memory.h>
|
||||
#include <float.h>
|
||||
#include <stdint.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
|
|
@ -18,29 +19,31 @@ namespace Slic3r {
|
|||
class ExPolygon;
|
||||
class Surface;
|
||||
|
||||
class InfillFailedException : public std::runtime_error {
|
||||
public:
|
||||
InfillFailedException() : std::runtime_error("Infill failed") {}
|
||||
};
|
||||
|
||||
struct FillParams
|
||||
{
|
||||
FillParams() {
|
||||
memset(this, 0, sizeof(FillParams));
|
||||
// Adjustment does not work.
|
||||
dont_adjust = true;
|
||||
}
|
||||
|
||||
bool full_infill() const { return density > 0.9999f; }
|
||||
|
||||
// Fill density, fraction in <0, 1>
|
||||
float density;
|
||||
float density { 0.f };
|
||||
|
||||
// Don't connect the fill lines around the inner perimeter.
|
||||
bool dont_connect;
|
||||
bool dont_connect { false };
|
||||
|
||||
// Don't adjust spacing to fill the space evenly.
|
||||
bool dont_adjust;
|
||||
bool dont_adjust { true };
|
||||
|
||||
// Monotonous infill - strictly left to right for better surface quality of top infills.
|
||||
bool monotonous { false };
|
||||
|
||||
// For Honeycomb.
|
||||
// we were requested to complete each loop;
|
||||
// in this case we don't try to make more continuous paths
|
||||
bool complete;
|
||||
bool complete { false };
|
||||
};
|
||||
static_assert(IsTriviallyCopyable<FillParams>::value, "FillParams class is not POD (and it should be - see constructor).");
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -13,18 +13,27 @@ class FillRectilinear2 : public Fill
|
|||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillRectilinear2(*this); };
|
||||
virtual ~FillRectilinear2() {}
|
||||
virtual ~FillRectilinear2() = default;
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
|
||||
protected:
|
||||
bool fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out);
|
||||
};
|
||||
|
||||
class FillMonotonous : public FillRectilinear2
|
||||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillMonotonous(*this); };
|
||||
virtual ~FillMonotonous() = default;
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
virtual bool no_sort() const { return true; }
|
||||
};
|
||||
|
||||
class FillGrid2 : public FillRectilinear2
|
||||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillGrid2(*this); };
|
||||
virtual ~FillGrid2() {}
|
||||
virtual ~FillGrid2() = default;
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
|
||||
protected:
|
||||
|
|
@ -36,7 +45,7 @@ class FillTriangles : public FillRectilinear2
|
|||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillTriangles(*this); };
|
||||
virtual ~FillTriangles() {}
|
||||
virtual ~FillTriangles() = default;
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
|
||||
protected:
|
||||
|
|
@ -48,7 +57,7 @@ class FillStars : public FillRectilinear2
|
|||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillStars(*this); };
|
||||
virtual ~FillStars() {}
|
||||
virtual ~FillStars() = default;
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
|
||||
protected:
|
||||
|
|
@ -60,7 +69,7 @@ class FillCubic : public FillRectilinear2
|
|||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillCubic(*this); };
|
||||
virtual ~FillCubic() {}
|
||||
virtual ~FillCubic() = default;
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
|
||||
protected:
|
||||
|
|
|
|||
171
src/libslic3r/Format/SL1.cpp
Normal file
171
src/libslic3r/Format/SL1.cpp
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
#include "SL1.hpp"
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#include "libslic3r/Time.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using ConfMap = std::map<std::string, std::string>;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string to_ini(const ConfMap &m)
|
||||
{
|
||||
std::string ret;
|
||||
for (auto ¶m : m) ret += param.first + " = " + param.second + "\n";
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key)
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
if (cfg.has(key)) {
|
||||
auto opt = cfg.option(key);
|
||||
if (opt) ret = opt->serialize();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void fill_iniconf(ConfMap &m, const SLAPrint &print)
|
||||
{
|
||||
auto &cfg = print.full_print_config();
|
||||
m["layerHeight"] = get_cfg_value(cfg, "layer_height");
|
||||
m["expTime"] = get_cfg_value(cfg, "exposure_time");
|
||||
m["expTimeFirst"] = get_cfg_value(cfg, "initial_exposure_time");
|
||||
m["materialName"] = get_cfg_value(cfg, "sla_material_settings_id");
|
||||
m["printerModel"] = get_cfg_value(cfg, "printer_model");
|
||||
m["printerVariant"] = get_cfg_value(cfg, "printer_variant");
|
||||
m["printerProfile"] = get_cfg_value(cfg, "printer_settings_id");
|
||||
m["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id");
|
||||
m["fileCreationTimestamp"] = Utils::utc_timestamp();
|
||||
m["prusaSlicerVersion"] = SLIC3R_BUILD_ID;
|
||||
|
||||
SLAPrintStatistics stats = print.print_statistics();
|
||||
// Set statistics values to the printer
|
||||
|
||||
double used_material = (stats.objects_used_material +
|
||||
stats.support_used_material) / 1000;
|
||||
|
||||
int num_fade = print.default_object_config().faded_layers.getInt();
|
||||
num_fade = num_fade >= 0 ? num_fade : 0;
|
||||
|
||||
m["usedMaterial"] = std::to_string(used_material);
|
||||
m["numFade"] = std::to_string(num_fade);
|
||||
m["numSlow"] = std::to_string(stats.slow_layers_count);
|
||||
m["numFast"] = std::to_string(stats.fast_layers_count);
|
||||
m["printTime"] = std::to_string(stats.estimated_print_time);
|
||||
|
||||
m["action"] = "print";
|
||||
}
|
||||
|
||||
void fill_slicerconf(ConfMap &m, const SLAPrint &print)
|
||||
{
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
// Sorted list of config keys, which shall not be stored into the ini.
|
||||
static constexpr auto banned_keys = {
|
||||
"compatible_printers"sv,
|
||||
"compatible_prints"sv,
|
||||
"print_host"sv,
|
||||
"printhost_apikey"sv,
|
||||
"printhost_cafile"sv
|
||||
};
|
||||
|
||||
assert(std::is_sorted(banned_keys.begin(), banned_keys.end()));
|
||||
auto is_banned = [](const std::string &key) {
|
||||
return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
|
||||
};
|
||||
|
||||
auto &cfg = print.full_print_config();
|
||||
for (const std::string &key : cfg.keys())
|
||||
if (! is_banned(key) && ! cfg.option(key)->is_nil())
|
||||
m[key] = cfg.opt_serialize(key);
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uqptr<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
|
||||
44
src/libslic3r/Format/SL1.hpp
Normal file
44
src/libslic3r/Format/SL1.hpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef ARCHIVETRAITS_HPP
|
||||
#define ARCHIVETRAITS_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SL1Archive: public SLAPrinter {
|
||||
SLAPrinterConfig m_cfg;
|
||||
|
||||
protected:
|
||||
uqptr<sla::RasterBase> create_raster() const override;
|
||||
sla::EncodedRaster encode_raster(const sla::RasterBase &rst) const override;
|
||||
|
||||
public:
|
||||
|
||||
SL1Archive() = default;
|
||||
explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {}
|
||||
explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {}
|
||||
|
||||
void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "");
|
||||
void export_print(const std::string &fname, const SLAPrint &print, const std::string &projectname = "")
|
||||
{
|
||||
Zipper zipper(fname);
|
||||
export_print(zipper, print, projectname);
|
||||
}
|
||||
|
||||
void apply(const SLAPrinterConfig &cfg) override
|
||||
{
|
||||
auto diff = m_cfg.diff(cfg);
|
||||
if (!diff.empty()) {
|
||||
m_cfg.apply_only(cfg, diff);
|
||||
m_layers = {};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
|
||||
#endif // ARCHIVETRAITS_HPP
|
||||
|
|
@ -2246,12 +2246,14 @@ void GCode::process_layer(
|
|||
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast<unsigned int>(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region;
|
||||
//FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
|
||||
if (print.config().infill_first) {
|
||||
gcode += this->extrude_infill(print, by_region_specific);
|
||||
gcode += this->extrude_infill(print, by_region_specific, false);
|
||||
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
|
||||
} else {
|
||||
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
|
||||
gcode += this->extrude_infill(print,by_region_specific);
|
||||
gcode += this->extrude_infill(print,by_region_specific, false);
|
||||
}
|
||||
// ironing
|
||||
gcode += this->extrude_infill(print,by_region_specific, true);
|
||||
}
|
||||
if (this->config().gcode_label_objects)
|
||||
gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
|
||||
|
|
@ -2873,22 +2875,30 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vector<Obje
|
|||
}
|
||||
|
||||
// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
|
||||
std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region)
|
||||
std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, bool ironing)
|
||||
{
|
||||
std::string gcode;
|
||||
std::string gcode;
|
||||
ExtrusionEntitiesPtr extrusions;
|
||||
const char* extrusion_name = ironing ? "ironing" : "infill";
|
||||
for (const ObjectByExtruder::Island::Region ®ion : by_region)
|
||||
if (! region.infills.empty()) {
|
||||
m_config.apply(print.regions()[®ion - &by_region.front()]->config());
|
||||
ExtrusionEntitiesPtr extrusions { region.infills };
|
||||
chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
|
||||
for (const ExtrusionEntity *fill : extrusions) {
|
||||
auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
|
||||
if (eec) {
|
||||
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
|
||||
gcode += this->extrude_entity(*ee, "infill");
|
||||
} else
|
||||
gcode += this->extrude_entity(*fill, "infill");
|
||||
}
|
||||
extrusions.clear();
|
||||
extrusions.reserve(region.infills.size());
|
||||
for (ExtrusionEntity *ee : region.infills)
|
||||
if ((ee->role() == erIroning) == ironing)
|
||||
extrusions.emplace_back(ee);
|
||||
if (! extrusions.empty()) {
|
||||
m_config.apply(print.regions()[®ion - &by_region.front()]->config());
|
||||
chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
|
||||
for (const ExtrusionEntity *fill : extrusions) {
|
||||
auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
|
||||
if (eec) {
|
||||
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
|
||||
gcode += this->extrude_entity(*ee, extrusion_name);
|
||||
} else
|
||||
gcode += this->extrude_entity(*fill, extrusion_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return gcode;
|
||||
}
|
||||
|
|
@ -3027,6 +3037,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||
speed = m_config.get_abs_value("solid_infill_speed");
|
||||
} else if (path.role() == erTopSolidInfill) {
|
||||
speed = m_config.get_abs_value("top_solid_infill_speed");
|
||||
} else if (path.role() == erIroning) {
|
||||
speed = m_config.get_abs_value("ironing_speed");
|
||||
} else if (path.role() == erGapFill) {
|
||||
speed = m_config.get_abs_value("gap_fill_speed");
|
||||
} else {
|
||||
|
|
@ -3427,10 +3439,13 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr
|
|||
|
||||
// First we append the entities, there are eec->entities.size() of them:
|
||||
size_t old_size = perimeters_or_infills->size();
|
||||
size_t new_size = old_size + eec->entities.size();
|
||||
size_t new_size = old_size + (eec->can_reverse() ? eec->entities.size() : 1);
|
||||
perimeters_or_infills->reserve(new_size);
|
||||
for (auto* ee : eec->entities)
|
||||
perimeters_or_infills->emplace_back(ee);
|
||||
if (eec->can_reverse()) {
|
||||
for (auto* ee : eec->entities)
|
||||
perimeters_or_infills->emplace_back(ee);
|
||||
} else
|
||||
perimeters_or_infills->emplace_back(const_cast<ExtrusionEntityCollection*>(eec));
|
||||
|
||||
if (copies_extruder != nullptr) {
|
||||
// Don't reallocate overrides if not needed.
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ private:
|
|||
const size_t single_object_instance_idx);
|
||||
|
||||
std::string extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, std::unique_ptr<EdgeGrid::Grid> &lower_layer_edge_grid);
|
||||
std::string extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region);
|
||||
std::string extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, bool ironing);
|
||||
std::string extrude_support(const ExtrusionEntityCollection &support_fills);
|
||||
|
||||
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ const Color GCodePreviewData::Extrusion::Default_Extrusion_Role_Colors[erCount]
|
|||
Color(1.0f, 1.0f, 0.0f, 1.0f), // erInternalInfill
|
||||
Color(1.0f, 0.0f, 1.0f, 1.0f), // erSolidInfill
|
||||
Color(0.0f, 1.0f, 1.0f, 1.0f), // erTopSolidInfill
|
||||
Color(0.0f, 1.0f, 1.0f, 1.0f), // erIroning
|
||||
Color(0.5f, 0.5f, 0.5f, 1.0f), // erBridgeInfill
|
||||
Color(1.0f, 1.0f, 1.0f, 1.0f), // erGapFill
|
||||
Color(0.5f, 0.0f, 0.0f, 1.0f), // erSkirt
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ public:
|
|||
// collection of surfaces for infill generation
|
||||
SurfaceCollection fill_surfaces;
|
||||
|
||||
// Collection of perimeter surfaces. This is a cached result of diff(slices, fill_surfaces).
|
||||
// While not necessary, the memory consumption is meager and it speeds up calculation.
|
||||
// The perimeter_surfaces keep the IDs of the slices (top/bottom/)
|
||||
SurfaceCollection perimeter_surfaces;
|
||||
|
||||
// collection of expolygons representing the bridged areas (thus not
|
||||
// needing support material)
|
||||
Polygons bridged;
|
||||
|
|
@ -140,6 +135,7 @@ public:
|
|||
}
|
||||
void make_perimeters();
|
||||
void make_fills();
|
||||
void make_ironing();
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
void export_region_fill_surfaces_to_svg(const char *path) const;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
448
src/libslic3r/MarchingSquares.hpp
Normal file
448
src/libslic3r/MarchingSquares.hpp
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
#ifndef MARCHINGSQUARES_HPP
|
||||
#define MARCHINGSQUARES_HPP
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
namespace marchsq {
|
||||
|
||||
// Marks a square in the grid
|
||||
struct Coord {
|
||||
long r = 0, c = 0;
|
||||
|
||||
Coord() = default;
|
||||
explicit Coord(long s) : r(s), c(s) {}
|
||||
Coord(long _r, long _c): r(_r), c(_c) {}
|
||||
|
||||
size_t seq(const Coord &res) const { return r * res.c + c; }
|
||||
Coord& operator+=(const Coord& b) { r += b.r; c += b.c; return *this; }
|
||||
Coord operator+(const Coord& b) const { Coord a = *this; a += b; return a; }
|
||||
};
|
||||
|
||||
// Closed ring of cell coordinates
|
||||
using Ring = std::vector<Coord>;
|
||||
|
||||
// Specialize this struct to register a raster type for the Marching squares alg
|
||||
template<class T, class Enable = void> struct _RasterTraits {
|
||||
|
||||
// The type of pixel cell in the raster
|
||||
using ValueType = typename T::ValueType;
|
||||
|
||||
// Value at a given position
|
||||
static ValueType get(const T &raster, size_t row, size_t col);
|
||||
|
||||
// Number of rows and cols of the raster
|
||||
static size_t rows(const T &raster);
|
||||
static size_t cols(const T &raster);
|
||||
};
|
||||
|
||||
// Specialize this to use parellel loops within the algorithm
|
||||
template<class ExecutionPolicy, class Enable = void> struct _Loop {
|
||||
template<class It, class Fn> static void for_each(It from, It to, Fn &&fn)
|
||||
{
|
||||
for (auto it = from; it < to; ++it) fn(*it, size_t(it - from));
|
||||
}
|
||||
};
|
||||
|
||||
namespace __impl {
|
||||
|
||||
template<class T> using RasterTraits = _RasterTraits<std::decay_t<T>>;
|
||||
template<class T> using TRasterValue = typename RasterTraits<T>::ValueType;
|
||||
|
||||
template<class T> size_t rows(const T &raster)
|
||||
{
|
||||
return RasterTraits<T>::rows(raster);
|
||||
}
|
||||
|
||||
template<class T> size_t cols(const T &raster)
|
||||
{
|
||||
return RasterTraits<T>::cols(raster);
|
||||
}
|
||||
|
||||
template<class T> TRasterValue<T> isoval(const T &rst, const Coord &crd)
|
||||
{
|
||||
return RasterTraits<T>::get(rst, crd.r, crd.c);
|
||||
}
|
||||
|
||||
template<class ExecutionPolicy, class It, class Fn>
|
||||
void for_each(ExecutionPolicy&& policy, It from, It to, Fn &&fn)
|
||||
{
|
||||
_Loop<ExecutionPolicy>::for_each(from, to, fn);
|
||||
}
|
||||
|
||||
// Type of squares (tiles) depending on which vertices are inside an ROI
|
||||
// The vertices would be marked a, b, c, d in counter clockwise order from the
|
||||
// bottom left vertex of a square.
|
||||
// d --- c
|
||||
// | |
|
||||
// | |
|
||||
// a --- b
|
||||
enum class SquareTag : uint8_t {
|
||||
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
||||
none, a, b, ab, c, ac, bc, abc, d, ad, bd, abd, cd, acd, bcd, full
|
||||
};
|
||||
|
||||
template<class E> constexpr std::underlying_type_t<E> _t(E e) noexcept
|
||||
{
|
||||
return static_cast<std::underlying_type_t<E>>(e);
|
||||
}
|
||||
|
||||
enum class Dir: uint8_t { left, down, right, up, none};
|
||||
|
||||
static const constexpr Dir NEXT_CCW[] = {
|
||||
/* 00 */ Dir::none, // SquareTag::none (empty square, nowhere to go)
|
||||
/* 01 */ Dir::left, // SquareTag::a
|
||||
/* 02 */ Dir::down, // SquareTag::b
|
||||
/* 03 */ Dir::left, // SquareTag::ab
|
||||
/* 04 */ Dir::right, // SquareTag::c
|
||||
/* 05 */ Dir::none, // SquareTag::ac (ambiguous case)
|
||||
/* 06 */ Dir::down, // SquareTag::bc
|
||||
/* 07 */ Dir::left, // SquareTag::abc
|
||||
/* 08 */ Dir::up, // SquareTag::d
|
||||
/* 09 */ Dir::up, // SquareTag::ad
|
||||
/* 10 */ Dir::none, // SquareTag::bd (ambiguous case)
|
||||
/* 11 */ Dir::up, // SquareTag::abd
|
||||
/* 12 */ Dir::right, // SquareTag::cd
|
||||
/* 13 */ Dir::right, // SquareTag::acd
|
||||
/* 14 */ Dir::down, // SquareTag::bcd
|
||||
/* 15 */ Dir::none // SquareTag::full (full covered, nowhere to go)
|
||||
};
|
||||
|
||||
static const constexpr uint8_t PREV_CCW[] = {
|
||||
/* 00 */ 1 << _t(Dir::none),
|
||||
/* 01 */ 1 << _t(Dir::up),
|
||||
/* 02 */ 1 << _t(Dir::left),
|
||||
/* 03 */ 1 << _t(Dir::left),
|
||||
/* 04 */ 1 << _t(Dir::down),
|
||||
/* 05 */ 1 << _t(Dir::up) | 1 << _t(Dir::down),
|
||||
/* 06 */ 1 << _t(Dir::down),
|
||||
/* 07 */ 1 << _t(Dir::down),
|
||||
/* 08 */ 1 << _t(Dir::right),
|
||||
/* 09 */ 1 << _t(Dir::up),
|
||||
/* 10 */ 1 << _t(Dir::left) | 1 << _t(Dir::right),
|
||||
/* 11 */ 1 << _t(Dir::left),
|
||||
/* 12 */ 1 << _t(Dir::right),
|
||||
/* 13 */ 1 << _t(Dir::up),
|
||||
/* 14 */ 1 << _t(Dir::right),
|
||||
/* 15 */ 1 << _t(Dir::none)
|
||||
};
|
||||
|
||||
const constexpr uint8_t DIRMASKS[] = {
|
||||
/*left: */ 0x01, /*down*/ 0x12, /*right */0x21, /*up*/ 0x10, /*none*/ 0x00
|
||||
};
|
||||
|
||||
inline Coord step(const Coord &crd, Dir d)
|
||||
{
|
||||
uint8_t dd = DIRMASKS[uint8_t(d)];
|
||||
return {crd.r - 1 + (dd & 0x0f), crd.c - 1 + (dd >> 4)};
|
||||
}
|
||||
|
||||
template<class Rst> class Grid {
|
||||
const Rst * m_rst = nullptr;
|
||||
Coord m_cellsize, m_res_1, m_window, m_gridsize, m_grid_1;
|
||||
std::vector<uint8_t> m_tags; // Assign tags to each square
|
||||
|
||||
Coord rastercoord(const Coord &crd) const
|
||||
{
|
||||
return {(crd.r - 1) * m_window.r, (crd.c - 1) * m_window.c};
|
||||
}
|
||||
|
||||
Coord bl(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, 0}; }
|
||||
Coord br(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, m_res_1.c}; }
|
||||
Coord tr(const Coord &crd) const { return tl(crd) + Coord{0, m_res_1.c}; }
|
||||
Coord tl(const Coord &crd) const { return rastercoord(crd); }
|
||||
|
||||
bool is_within(const Coord &crd)
|
||||
{
|
||||
long R = rows(*m_rst), C = cols(*m_rst);
|
||||
return crd.r >= 0 && crd.r < R && crd.c >= 0 && crd.c < C;
|
||||
};
|
||||
|
||||
// Calculate the tag for a cell (or square). The cell coordinates mark the
|
||||
// top left vertex of a square in the raster. v is the isovalue
|
||||
uint8_t get_tag_for_cell(const Coord &cell, TRasterValue<Rst> v)
|
||||
{
|
||||
Coord sqr[] = {bl(cell), br(cell), tr(cell), tl(cell)};
|
||||
|
||||
uint8_t t = ((is_within(sqr[0]) && isoval(*m_rst, sqr[0]) >= v)) +
|
||||
((is_within(sqr[1]) && isoval(*m_rst, sqr[1]) >= v) << 1) +
|
||||
((is_within(sqr[2]) && isoval(*m_rst, sqr[2]) >= v) << 2) +
|
||||
((is_within(sqr[3]) && isoval(*m_rst, sqr[3]) >= v) << 3);
|
||||
|
||||
assert(t < 16);
|
||||
return t;
|
||||
}
|
||||
|
||||
// Get a cell coordinate from a sequential index
|
||||
Coord coord(size_t i) const
|
||||
{
|
||||
return {long(i) / m_gridsize.c, long(i) % m_gridsize.c};
|
||||
}
|
||||
|
||||
size_t seq(const Coord &crd) const { return crd.seq(m_gridsize); }
|
||||
|
||||
bool is_visited(size_t idx, Dir d = Dir::none) const
|
||||
{
|
||||
SquareTag t = get_tag(idx);
|
||||
uint8_t ref = d == Dir::none ? PREV_CCW[_t(t)] : uint8_t(1 << _t(d));
|
||||
return t == SquareTag::full || t == SquareTag::none ||
|
||||
((m_tags[idx] & 0xf0) >> 4) == ref;
|
||||
}
|
||||
|
||||
void set_visited(size_t idx, Dir d = Dir::none)
|
||||
{
|
||||
m_tags[idx] |= (1 << (_t(d)) << 4);
|
||||
}
|
||||
|
||||
bool is_ambiguous(size_t idx) const
|
||||
{
|
||||
SquareTag t = get_tag(idx);
|
||||
return t == SquareTag::ac || t == SquareTag::bd;
|
||||
}
|
||||
|
||||
// Search for a new starting square
|
||||
size_t search_start_cell(size_t i = 0) const
|
||||
{
|
||||
// Skip ambiguous tags as starting tags due to unknown previous
|
||||
// direction.
|
||||
while ((i < m_tags.size()) && (is_visited(i) || is_ambiguous(i))) ++i;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
SquareTag get_tag(size_t idx) const { return SquareTag(m_tags[idx] & 0x0f); }
|
||||
|
||||
Dir next_dir(Dir prev, SquareTag tag) const
|
||||
{
|
||||
// Treat ambiguous cases as two separate regions in one square.
|
||||
switch (tag) {
|
||||
case SquareTag::ac:
|
||||
switch (prev) {
|
||||
case Dir::down: return Dir::right;
|
||||
case Dir::up: return Dir::left;
|
||||
default: assert(false); return Dir::none;
|
||||
}
|
||||
case SquareTag::bd:
|
||||
switch (prev) {
|
||||
case Dir::right: return Dir::up;
|
||||
case Dir::left: return Dir::down;
|
||||
default: assert(false); return Dir::none;
|
||||
}
|
||||
default:
|
||||
return NEXT_CCW[uint8_t(tag)];
|
||||
}
|
||||
|
||||
return Dir::none;
|
||||
}
|
||||
|
||||
struct CellIt {
|
||||
Coord crd; Dir dir= Dir::none; const Rst *grid = nullptr;
|
||||
|
||||
TRasterValue<Rst> operator*() const { return isoval(*grid, crd); }
|
||||
CellIt& operator++() { crd = step(crd, dir); return *this; }
|
||||
CellIt operator++(int) { CellIt it = *this; ++(*this); return it; }
|
||||
bool operator!=(const CellIt &it) { return crd.r != it.crd.r || crd.c != it.crd.c; }
|
||||
|
||||
using value_type = TRasterValue<Rst>;
|
||||
using pointer = TRasterValue<Rst> *;
|
||||
using reference = TRasterValue<Rst> &;
|
||||
using difference_type = long;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
};
|
||||
|
||||
// Two cell iterators representing an edge of a square. This is then
|
||||
// used for binary search for the first active pixel on the edge.
|
||||
struct Edge { CellIt from, to; };
|
||||
|
||||
Edge _edge(const Coord &ringvertex) const
|
||||
{
|
||||
size_t idx = ringvertex.r;
|
||||
Coord cell = coord(idx);
|
||||
uint8_t tg = m_tags[ringvertex.r];
|
||||
SquareTag t = SquareTag(tg & 0x0f);
|
||||
|
||||
switch (t) {
|
||||
case SquareTag::a:
|
||||
case SquareTag::ab:
|
||||
case SquareTag::abc:
|
||||
return {{tl(cell), Dir::down, m_rst}, {bl(cell)}};
|
||||
case SquareTag::b:
|
||||
case SquareTag::bc:
|
||||
case SquareTag::bcd:
|
||||
return {{bl(cell), Dir::right, m_rst}, {br(cell)}};
|
||||
case SquareTag::c:
|
||||
return {{br(cell), Dir::up, m_rst}, {tr(cell)}};
|
||||
case SquareTag::ac:
|
||||
switch (Dir(ringvertex.c)) {
|
||||
case Dir::left: return {{tl(cell), Dir::down, m_rst}, {bl(cell)}};
|
||||
case Dir::right: return {{br(cell), Dir::up, m_rst}, {tr(cell)}};
|
||||
default: assert(false);
|
||||
}
|
||||
case SquareTag::d:
|
||||
case SquareTag::ad:
|
||||
case SquareTag::abd:
|
||||
return {{tr(cell), Dir::left, m_rst}, {tl(cell)}};
|
||||
case SquareTag::bd:
|
||||
switch (Dir(ringvertex.c)) {
|
||||
case Dir::down: return {{bl(cell), Dir::right, m_rst}, {br(cell)}};
|
||||
case Dir::up: return {{tr(cell), Dir::left, m_rst}, {tl(cell)}};
|
||||
default: assert(false);
|
||||
}
|
||||
case SquareTag::cd:
|
||||
case SquareTag::acd:
|
||||
return {{br(cell), Dir::up, m_rst}, {tr(cell)}};
|
||||
case SquareTag::full:
|
||||
case SquareTag::none: {
|
||||
Coord crd{tl(cell) + Coord{m_cellsize.r / 2, m_cellsize.c / 2}};
|
||||
return {{crd, Dir::none, m_rst}, crd};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Edge edge(const Coord &ringvertex) const
|
||||
{
|
||||
const long R = rows(*m_rst), C = cols(*m_rst);
|
||||
const long R_1 = R - 1, C_1 = C - 1;
|
||||
|
||||
Edge e = _edge(ringvertex);
|
||||
e.to.dir = e.from.dir;
|
||||
++e.to;
|
||||
|
||||
e.from.crd.r = std::min(e.from.crd.r, R_1);
|
||||
e.from.crd.r = std::max(e.from.crd.r, 0l);
|
||||
e.from.crd.c = std::min(e.from.crd.c, C_1);
|
||||
e.from.crd.c = std::max(e.from.crd.c, 0l);
|
||||
|
||||
e.to.crd.r = std::min(e.to.crd.r, R);
|
||||
e.to.crd.r = std::max(e.to.crd.r, 0l);
|
||||
e.to.crd.c = std::min(e.to.crd.c, C);
|
||||
e.to.crd.c = std::max(e.to.crd.c, 0l);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit Grid(const Rst &rst, const Coord &cellsz, const Coord &overlap)
|
||||
: m_rst{&rst}
|
||||
, m_cellsize{cellsz}
|
||||
, m_res_1{m_cellsize.r - 1, m_cellsize.c - 1}
|
||||
, m_window{overlap.r < cellsz.r ? cellsz.r - overlap.r : cellsz.r,
|
||||
overlap.c < cellsz.c ? cellsz.c - overlap.c : cellsz.c}
|
||||
, m_gridsize{2 + (long(rows(rst)) - overlap.r) / m_window.r,
|
||||
2 + (long(cols(rst)) - overlap.c) / m_window.c}
|
||||
, m_tags(m_gridsize.r * m_gridsize.c, 0)
|
||||
{}
|
||||
|
||||
// Go through the cells and mark them with the appropriate tag.
|
||||
template<class ExecutionPolicy>
|
||||
void tag_grid(ExecutionPolicy &&policy, TRasterValue<Rst> isoval)
|
||||
{
|
||||
// parallel for r
|
||||
for_each (std::forward<ExecutionPolicy>(policy),
|
||||
m_tags.begin(), m_tags.end(),
|
||||
[this, isoval](uint8_t& tag, size_t idx) {
|
||||
tag = get_tag_for_cell(coord(idx), isoval);
|
||||
});
|
||||
}
|
||||
|
||||
// Scan for the rings on the tagged grid. Each ring vertex stores the
|
||||
// sequential index of the cell and the next direction (Dir).
|
||||
// This info can be used later to calculate the exact raster coordinate.
|
||||
std::vector<Ring> scan_rings()
|
||||
{
|
||||
std::vector<Ring> rings;
|
||||
size_t startidx = 0;
|
||||
while ((startidx = search_start_cell(startidx)) < m_tags.size()) {
|
||||
Ring ring;
|
||||
|
||||
size_t idx = startidx;
|
||||
Dir prev = Dir::none, next = next_dir(prev, get_tag(idx));
|
||||
|
||||
while (next != Dir::none && !is_visited(idx, prev)) {
|
||||
Coord ringvertex{long(idx), long(next)};
|
||||
ring.emplace_back(ringvertex);
|
||||
set_visited(idx, prev);
|
||||
|
||||
idx = seq(step(coord(idx), next));
|
||||
prev = next;
|
||||
next = next_dir(next, get_tag(idx));
|
||||
}
|
||||
|
||||
// To prevent infinite loops in case of degenerate input
|
||||
if (next == Dir::none) m_tags[startidx] = _t(SquareTag::none);
|
||||
|
||||
if (ring.size() > 1) {
|
||||
ring.pop_back();
|
||||
rings.emplace_back(ring);
|
||||
}
|
||||
}
|
||||
|
||||
return rings;
|
||||
}
|
||||
|
||||
// Calculate the exact raster position from the cells which store the
|
||||
// sequantial index of the square and the next direction
|
||||
template<class ExecutionPolicy>
|
||||
void interpolate_rings(ExecutionPolicy && policy,
|
||||
std::vector<Ring> &rings,
|
||||
TRasterValue<Rst> isov)
|
||||
{
|
||||
for_each(std::forward<ExecutionPolicy>(policy),
|
||||
rings.begin(), rings.end(), [this, isov] (Ring &ring, size_t)
|
||||
{
|
||||
for (Coord &ringvertex : ring) {
|
||||
Edge e = edge(ringvertex);
|
||||
|
||||
CellIt found = std::lower_bound(e.from, e.to, isov);
|
||||
ringvertex = found.crd;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template<class Raster, class ExecutionPolicy>
|
||||
std::vector<marchsq::Ring> execute_with_policy(ExecutionPolicy && policy,
|
||||
const Raster & raster,
|
||||
TRasterValue<Raster> isoval,
|
||||
Coord windowsize = {})
|
||||
{
|
||||
if (!rows(raster) || !cols(raster)) return {};
|
||||
|
||||
size_t ratio = cols(raster) / rows(raster);
|
||||
|
||||
if (!windowsize.r) windowsize.r = 2;
|
||||
if (!windowsize.c)
|
||||
windowsize.c = std::max(2l, long(windowsize.r * ratio));
|
||||
|
||||
Coord overlap{1};
|
||||
|
||||
Grid<Raster> grid{raster, windowsize, overlap};
|
||||
|
||||
grid.tag_grid(std::forward<ExecutionPolicy>(policy), isoval);
|
||||
std::vector<marchsq::Ring> rings = grid.scan_rings();
|
||||
grid.interpolate_rings(std::forward<ExecutionPolicy>(policy), rings, isoval);
|
||||
|
||||
return rings;
|
||||
}
|
||||
|
||||
template<class Raster>
|
||||
std::vector<marchsq::Ring> execute(const Raster &raster,
|
||||
TRasterValue<Raster> isoval,
|
||||
Coord windowsize = {})
|
||||
{
|
||||
return execute_with_policy(nullptr, raster, isoval, windowsize);
|
||||
}
|
||||
|
||||
} // namespace __impl
|
||||
|
||||
using __impl::execute_with_policy;
|
||||
using __impl::execute;
|
||||
|
||||
} // namespace marchsq
|
||||
|
||||
#endif // MARCHINGSQUARES_HPP
|
||||
|
|
@ -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 */
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
83
src/libslic3r/ModelArrange.cpp
Normal file
83
src/libslic3r/ModelArrange.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#include "ModelArrange.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
arrangement::ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances)
|
||||
{
|
||||
size_t count = 0;
|
||||
for (auto obj : model.objects) count += obj->instances.size();
|
||||
|
||||
ArrangePolygons input;
|
||||
input.reserve(count);
|
||||
instances.clear(); instances.reserve(count);
|
||||
for (ModelObject *mo : model.objects)
|
||||
for (ModelInstance *minst : mo->instances) {
|
||||
input.emplace_back(minst->get_arrange_polygon());
|
||||
instances.emplace_back(minst);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
bool apply_arrange_polys(ArrangePolygons &input, ModelInstancePtrs &instances, VirtualBedFn vfn)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
for(size_t i = 0; i < input.size(); ++i) {
|
||||
if (input[i].bed_idx != 0) { ret = false; if (vfn) vfn(input[i]); }
|
||||
if (input[i].bed_idx >= 0)
|
||||
instances[i]->apply_arrange_result(input[i].translation.cast<double>(),
|
||||
input[i].rotation);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Slic3r::arrangement::ArrangePolygon get_arrange_poly(const Model &model)
|
||||
{
|
||||
ArrangePolygon ap;
|
||||
Points &apts = ap.poly.contour.points;
|
||||
for (const ModelObject *mo : model.objects)
|
||||
for (const ModelInstance *minst : mo->instances) {
|
||||
ArrangePolygon obj_ap = minst->get_arrange_polygon();
|
||||
ap.poly.contour.rotate(obj_ap.rotation);
|
||||
ap.poly.contour.translate(obj_ap.translation.x(), obj_ap.translation.y());
|
||||
const Points &pts = obj_ap.poly.contour.points;
|
||||
std::copy(pts.begin(), pts.end(), std::back_inserter(apts));
|
||||
}
|
||||
|
||||
apts = Geometry::convex_hull(apts);
|
||||
return ap;
|
||||
}
|
||||
|
||||
void duplicate(Model &model, Slic3r::arrangement::ArrangePolygons &copies, VirtualBedFn vfn)
|
||||
{
|
||||
for (ModelObject *o : model.objects) {
|
||||
// make a copy of the pointers in order to avoid recursion when appending their copies
|
||||
ModelInstancePtrs instances = o->instances;
|
||||
o->instances.clear();
|
||||
for (const ModelInstance *i : instances) {
|
||||
for (arrangement::ArrangePolygon &ap : copies) {
|
||||
if (ap.bed_idx != 0) vfn(ap);
|
||||
ModelInstance *instance = o->add_instance(*i);
|
||||
Vec2d pos = unscale(ap.translation);
|
||||
instance->set_offset(instance->get_offset() + to_3d(pos, 0.));
|
||||
}
|
||||
}
|
||||
o->invalidate_bounding_box();
|
||||
}
|
||||
}
|
||||
|
||||
void duplicate_objects(Model &model, size_t copies_num)
|
||||
{
|
||||
for (ModelObject *o : model.objects) {
|
||||
// make a copy of the pointers in order to avoid recursion when appending their copies
|
||||
ModelInstancePtrs instances = o->instances;
|
||||
for (const ModelInstance *i : instances)
|
||||
for (size_t k = 2; k <= copies_num; ++ k)
|
||||
o->add_instance(*i);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
68
src/libslic3r/ModelArrange.hpp
Normal file
68
src/libslic3r/ModelArrange.hpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#ifndef MODELARRANGE_HPP
|
||||
#define MODELARRANGE_HPP
|
||||
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/Arrange.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using arrangement::ArrangePolygon;
|
||||
using arrangement::ArrangePolygons;
|
||||
using arrangement::ArrangeParams;
|
||||
using arrangement::InfiniteBed;
|
||||
using arrangement::CircleBed;
|
||||
|
||||
// Do something with ArrangePolygons in virtual beds
|
||||
using VirtualBedFn = std::function<void(arrangement::ArrangePolygon&)>;
|
||||
|
||||
[[noreturn]] inline void throw_if_out_of_bed(arrangement::ArrangePolygon&)
|
||||
{
|
||||
throw std::runtime_error("Objects could not fit on the bed");
|
||||
}
|
||||
|
||||
ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances);
|
||||
ArrangePolygon get_arrange_poly(const Model &model);
|
||||
bool apply_arrange_polys(ArrangePolygons &polys, ModelInstancePtrs &instances, VirtualBedFn);
|
||||
|
||||
void duplicate(Model &model, ArrangePolygons &copies, VirtualBedFn);
|
||||
void duplicate_objects(Model &model, size_t copies_num);
|
||||
|
||||
template<class TBed>
|
||||
bool arrange_objects(Model & model,
|
||||
const TBed & bed,
|
||||
const ArrangeParams ¶ms,
|
||||
VirtualBedFn vfn = throw_if_out_of_bed)
|
||||
{
|
||||
ModelInstancePtrs instances;
|
||||
auto&& input = get_arrange_polys(model, instances);
|
||||
arrangement::arrange(input, bed, params);
|
||||
|
||||
return apply_arrange_polys(input, instances, vfn);
|
||||
}
|
||||
|
||||
template<class TBed>
|
||||
void duplicate(Model & model,
|
||||
size_t copies_num,
|
||||
const TBed & bed,
|
||||
const ArrangeParams ¶ms,
|
||||
VirtualBedFn vfn = throw_if_out_of_bed)
|
||||
{
|
||||
ArrangePolygons copies(copies_num, get_arrange_poly(model));
|
||||
arrangement::arrange(copies, bed, params);
|
||||
duplicate(model, copies, vfn);
|
||||
}
|
||||
|
||||
template<class TBed>
|
||||
void duplicate_objects(Model & model,
|
||||
size_t copies_num,
|
||||
const TBed & bed,
|
||||
const ArrangeParams ¶ms,
|
||||
VirtualBedFn vfn = throw_if_out_of_bed)
|
||||
{
|
||||
duplicate_objects(model, copies_num);
|
||||
arrange_objects(model, bed, params, vfn);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // MODELARRANGE_HPP
|
||||
|
|
@ -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 ¢er);
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -1583,6 +1586,8 @@ void Print::process()
|
|||
this->set_status(70, L("Infilling layers"));
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->infill();
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->ironing();
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->generate_support_material();
|
||||
if (this->set_started(psWipeTower)) {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ enum PrintStep {
|
|||
|
||||
enum PrintObjectStep {
|
||||
posSlice, posPerimeters, posPrepareInfill,
|
||||
posInfill, posSupportMaterial, posCount,
|
||||
posInfill, posIroning, posSupportMaterial, posCount,
|
||||
};
|
||||
|
||||
// A PrintRegion object represents a group of volumes to print
|
||||
|
|
@ -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;
|
||||
|
|
@ -218,6 +223,7 @@ private:
|
|||
void make_perimeters();
|
||||
void prepare_infill();
|
||||
void infill();
|
||||
void ironing();
|
||||
void generate_support_material();
|
||||
|
||||
void _slice(const std::vector<coordf_t> &layer_height_profile);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ void PrintConfigDef::init_common_params()
|
|||
{
|
||||
ConfigOptionDef* def;
|
||||
|
||||
def = this->add("single_instance", coBool);
|
||||
def->label = L("Single Instance");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("printer_technology", coEnum);
|
||||
def->label = L("Printer technology");
|
||||
def->tooltip = L("Printer technology");
|
||||
|
|
@ -419,18 +424,20 @@ void PrintConfigDef::init_fff_params()
|
|||
def->cli = "top-fill-pattern|external-fill-pattern|solid-fill-pattern";
|
||||
def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values();
|
||||
def->enum_values.push_back("rectilinear");
|
||||
def->enum_values.push_back("monotonous");
|
||||
def->enum_values.push_back("concentric");
|
||||
def->enum_values.push_back("hilbertcurve");
|
||||
def->enum_values.push_back("archimedeanchords");
|
||||
def->enum_values.push_back("octagramspiral");
|
||||
def->enum_labels.push_back(L("Rectilinear"));
|
||||
def->enum_labels.push_back(L("Monotonous"));
|
||||
def->enum_labels.push_back(L("Concentric"));
|
||||
def->enum_labels.push_back(L("Hilbert Curve"));
|
||||
def->enum_labels.push_back(L("Archimedean Chords"));
|
||||
def->enum_labels.push_back(L("Octagram Spiral"));
|
||||
// solid_fill_pattern is an obsolete equivalent to top_fill_pattern/bottom_fill_pattern.
|
||||
def->aliases = { "solid_fill_pattern", "external_fill_pattern" };
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipRectilinear));
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipMonotonous));
|
||||
|
||||
def = this->add("bottom_fill_pattern", coEnum);
|
||||
def->label = L("Bottom fill pattern");
|
||||
|
|
@ -1081,6 +1088,53 @@ void PrintConfigDef::init_fff_params()
|
|||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("ironing", coBool);
|
||||
def->label = L("Enable ironing");
|
||||
def->tooltip = L("Enable ironing of the top layers with the hot print head for smooth surface");
|
||||
def->category = L("Ironing");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("ironing_type", coEnum);
|
||||
def->label = L("Ironingy Type");
|
||||
def->tooltip = L("Ironingy Type");
|
||||
def->enum_keys_map = &ConfigOptionEnum<IroningType>::get_enum_values();
|
||||
def->enum_values.push_back("top");
|
||||
def->enum_values.push_back("topmost");
|
||||
def->enum_values.push_back("solid");
|
||||
def->enum_labels.push_back("All top surfaces");
|
||||
def->enum_labels.push_back("Topmost surface only");
|
||||
def->enum_labels.push_back("All solid surfaces");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<IroningType>(IroningType::TopSurfaces));
|
||||
|
||||
def = this->add("ironing_flowrate", coPercent);
|
||||
def->label = L("Flow rate");
|
||||
def->category = L("Ironing");
|
||||
def->tooltip = L("Percent of a flow rate relative to object's normal layer height.");
|
||||
def->sidetext = L("%");
|
||||
def->ratio_over = "layer_height";
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionPercent(15));
|
||||
|
||||
def = this->add("ironing_spacing", coFloat);
|
||||
def->label = L("Spacing between ironing passes");
|
||||
def->tooltip = L("Distance between ironing lins");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0.1));
|
||||
|
||||
def = this->add("ironing_speed", coFloat);
|
||||
def->label = L("Ironing speed");
|
||||
def->category = L("Speed");
|
||||
def->tooltip = L("Ironing speed");
|
||||
def->sidetext = L("mm/s");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(15));
|
||||
|
||||
def = this->add("layer_gcode", coString);
|
||||
def->label = L("After layer change G-code");
|
||||
def->tooltip = L("This custom code is inserted at every layer change, right after the Z move "
|
||||
|
|
@ -3066,6 +3120,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")) {
|
||||
|
|
@ -3136,22 +3226,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()
|
||||
{
|
||||
|
|
@ -3561,8 +3635,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)
|
||||
|
|
|
|||
|
|
@ -34,10 +34,17 @@ enum PrintHostType {
|
|||
};
|
||||
|
||||
enum InfillPattern {
|
||||
ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
|
||||
ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
|
||||
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount,
|
||||
};
|
||||
|
||||
enum class IroningType {
|
||||
TopSurfaces,
|
||||
TopmostOnly,
|
||||
AllSolid,
|
||||
Count,
|
||||
};
|
||||
|
||||
enum SupportMaterialPattern {
|
||||
smpRectilinear, smpRectilinearGrid, smpHoneycomb,
|
||||
};
|
||||
|
|
@ -106,6 +113,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<InfillPattern>::g
|
|||
static t_config_enum_values keys_map;
|
||||
if (keys_map.empty()) {
|
||||
keys_map["rectilinear"] = ipRectilinear;
|
||||
keys_map["monotonous"] = ipMonotonous;
|
||||
keys_map["grid"] = ipGrid;
|
||||
keys_map["triangles"] = ipTriangles;
|
||||
keys_map["stars"] = ipStars;
|
||||
|
|
@ -122,6 +130,16 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<InfillPattern>::g
|
|||
return keys_map;
|
||||
}
|
||||
|
||||
template<> inline const t_config_enum_values& ConfigOptionEnum<IroningType>::get_enum_values() {
|
||||
static t_config_enum_values keys_map;
|
||||
if (keys_map.empty()) {
|
||||
keys_map["top"] = int(IroningType::TopSurfaces);
|
||||
keys_map["topmost"] = int(IroningType::TopmostOnly);
|
||||
keys_map["solid"] = int(IroningType::AllSolid);
|
||||
}
|
||||
return keys_map;
|
||||
}
|
||||
|
||||
template<> inline const t_config_enum_values& ConfigOptionEnum<SupportMaterialPattern>::get_enum_values() {
|
||||
static t_config_enum_values keys_map;
|
||||
if (keys_map.empty()) {
|
||||
|
|
@ -194,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,
|
||||
|
|
@ -485,6 +506,12 @@ public:
|
|||
ConfigOptionInt infill_every_layers;
|
||||
ConfigOptionFloatOrPercent infill_overlap;
|
||||
ConfigOptionFloat infill_speed;
|
||||
// Ironing options
|
||||
ConfigOptionBool ironing;
|
||||
ConfigOptionEnum<IroningType> ironing_type;
|
||||
ConfigOptionPercent ironing_flowrate;
|
||||
ConfigOptionFloat ironing_spacing;
|
||||
ConfigOptionFloat ironing_speed;
|
||||
// Detect bridging perimeters
|
||||
ConfigOptionBool overhangs;
|
||||
ConfigOptionInt perimeter_extruder;
|
||||
|
|
@ -530,6 +557,11 @@ protected:
|
|||
OPT_PTR(infill_every_layers);
|
||||
OPT_PTR(infill_overlap);
|
||||
OPT_PTR(infill_speed);
|
||||
OPT_PTR(ironing);
|
||||
OPT_PTR(ironing_type);
|
||||
OPT_PTR(ironing_flowrate);
|
||||
OPT_PTR(ironing_spacing);
|
||||
OPT_PTR(ironing_speed);
|
||||
OPT_PTR(overhangs);
|
||||
OPT_PTR(perimeter_extruder);
|
||||
OPT_PTR(perimeter_extrusion_width);
|
||||
|
|
@ -749,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;
|
||||
|
|
@ -1305,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
|
||||
|
|
|
|||
|
|
@ -387,6 +387,25 @@ void PrintObject::infill()
|
|||
}
|
||||
}
|
||||
|
||||
void PrintObject::ironing()
|
||||
{
|
||||
if (this->set_started(posIroning)) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Ironing in parallel - start";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(1, m_layers.size()),
|
||||
[this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
m_print->throw_if_canceled();
|
||||
m_layers[layer_idx]->make_ironing();
|
||||
}
|
||||
}
|
||||
);
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Ironing in parallel - end";
|
||||
this->set_done(posIroning);
|
||||
}
|
||||
}
|
||||
|
||||
void PrintObject::generate_support_material()
|
||||
{
|
||||
if (this->set_started(posSupportMaterial)) {
|
||||
|
|
@ -2610,6 +2629,7 @@ void PrintObject::combine_infill()
|
|||
// Because fill areas for rectilinear and honeycomb are grown
|
||||
// later to overlap perimeters, we need to counteract that too.
|
||||
((region->config().fill_pattern == ipRectilinear ||
|
||||
region->config().fill_pattern == ipMonotonous ||
|
||||
region->config().fill_pattern == ipGrid ||
|
||||
region->config().fill_pattern == ipLine ||
|
||||
region->config().fill_pattern == ipHoneycomb) ? 1.5f : 0.5f) *
|
||||
|
|
@ -2645,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
|
||||
|
|
|
|||
222
src/libslic3r/SLA/AGGRaster.hpp
Normal file
222
src/libslic3r/SLA/AGGRaster.hpp
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
#ifndef AGGRASTER_HPP
|
||||
#define AGGRASTER_HPP
|
||||
|
||||
#include <libslic3r/SLA/RasterBase.hpp>
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
|
||||
// For rasterizing
|
||||
#include <agg/agg_basics.h>
|
||||
#include <agg/agg_rendering_buffer.h>
|
||||
#include <agg/agg_pixfmt_gray.h>
|
||||
#include <agg/agg_pixfmt_rgb.h>
|
||||
#include <agg/agg_renderer_base.h>
|
||||
#include <agg/agg_renderer_scanline.h>
|
||||
|
||||
#include <agg/agg_scanline_p.h>
|
||||
#include <agg/agg_rasterizer_scanline_aa.h>
|
||||
#include <agg/agg_path_storage.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
inline const Polygon& contour(const ExPolygon& p) { return p.contour; }
|
||||
inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; }
|
||||
|
||||
inline const Polygons& holes(const ExPolygon& p) { return p.holes; }
|
||||
inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; }
|
||||
|
||||
namespace sla {
|
||||
|
||||
template<class Color> struct Colors {
|
||||
static const Color White;
|
||||
static const Color Black;
|
||||
};
|
||||
|
||||
template<class Color> const Color Colors<Color>::White = Color{255};
|
||||
template<class Color> const Color Colors<Color>::Black = Color{0};
|
||||
|
||||
template<class PixelRenderer,
|
||||
template<class /*agg::renderer_base<PixelRenderer>*/> class Renderer,
|
||||
class Rasterizer = agg::rasterizer_scanline_aa<>,
|
||||
class Scanline = agg::scanline_p8>
|
||||
class AGGRaster: public RasterBase {
|
||||
public:
|
||||
using TColor = typename PixelRenderer::color_type;
|
||||
using TValue = typename TColor::value_type;
|
||||
using TPixel = typename PixelRenderer::pixel_type;
|
||||
using TRawBuffer = agg::rendering_buffer;
|
||||
|
||||
protected:
|
||||
|
||||
Resolution m_resolution;
|
||||
PixelDim m_pxdim_scaled; // used for scaled coordinate polygons
|
||||
|
||||
std::vector<TPixel> m_buf;
|
||||
agg::rendering_buffer m_rbuf;
|
||||
|
||||
PixelRenderer m_pixrenderer;
|
||||
|
||||
agg::renderer_base<PixelRenderer> m_raw_renderer;
|
||||
Renderer<agg::renderer_base<PixelRenderer>> m_renderer;
|
||||
|
||||
Trafo m_trafo;
|
||||
Scanline m_scanlines;
|
||||
Rasterizer m_rasterizer;
|
||||
|
||||
void flipy(agg::path_storage &path) const
|
||||
{
|
||||
path.flip_y(0, double(m_resolution.height_px));
|
||||
}
|
||||
|
||||
void flipx(agg::path_storage &path) const
|
||||
{
|
||||
path.flip_x(0, double(m_resolution.width_px));
|
||||
}
|
||||
|
||||
double getPx(const Point &p) { return p(0) * m_pxdim_scaled.w_mm; }
|
||||
double getPy(const Point &p) { return p(1) * m_pxdim_scaled.h_mm; }
|
||||
agg::path_storage to_path(const Polygon &poly) { return to_path(poly.points); }
|
||||
double getPx(const ClipperLib::IntPoint &p) { return p.X * m_pxdim_scaled.w_mm; }
|
||||
double getPy(const ClipperLib::IntPoint& p) { return p.Y * m_pxdim_scaled.h_mm; }
|
||||
|
||||
template<class PointVec> agg::path_storage _to_path(const PointVec& v)
|
||||
{
|
||||
agg::path_storage path;
|
||||
|
||||
auto it = v.begin();
|
||||
path.move_to(getPx(*it), getPy(*it));
|
||||
while(++it != v.end()) path.line_to(getPx(*it), getPy(*it));
|
||||
path.line_to(getPx(v.front()), getPy(v.front()));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
template<class PointVec> agg::path_storage _to_path_flpxy(const PointVec& v)
|
||||
{
|
||||
agg::path_storage path;
|
||||
|
||||
auto it = v.begin();
|
||||
path.move_to(getPy(*it), getPx(*it));
|
||||
while(++it != v.end()) path.line_to(getPy(*it), getPx(*it));
|
||||
path.line_to(getPy(v.front()), getPx(v.front()));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
template<class PointVec> agg::path_storage to_path(const PointVec &v)
|
||||
{
|
||||
auto path = m_trafo.flipXY ? _to_path_flpxy(v) : _to_path(v);
|
||||
|
||||
path.translate_all_paths(m_trafo.center_x * m_pxdim_scaled.w_mm,
|
||||
m_trafo.center_y * m_pxdim_scaled.h_mm);
|
||||
|
||||
if(m_trafo.mirror_x) flipx(path);
|
||||
if(m_trafo.mirror_y) flipy(path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
template<class P> void _draw(const P &poly)
|
||||
{
|
||||
m_rasterizer.reset();
|
||||
|
||||
m_rasterizer.add_path(to_path(contour(poly)));
|
||||
for(auto& h : holes(poly)) m_rasterizer.add_path(to_path(h));
|
||||
|
||||
agg::render_scanlines(m_rasterizer, m_scanlines, m_renderer);
|
||||
}
|
||||
|
||||
public:
|
||||
template<class GammaFn> AGGRaster(const Resolution &res,
|
||||
const PixelDim & pd,
|
||||
const Trafo & trafo,
|
||||
const TColor & foreground,
|
||||
const TColor & background,
|
||||
GammaFn && gammafn)
|
||||
: m_resolution(res)
|
||||
, m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm)
|
||||
, m_buf(res.pixels())
|
||||
, m_rbuf(reinterpret_cast<TValue *>(m_buf.data()),
|
||||
unsigned(res.width_px),
|
||||
unsigned(res.height_px),
|
||||
int(res.width_px *PixelRenderer::num_components))
|
||||
, m_pixrenderer(m_rbuf)
|
||||
, m_raw_renderer(m_pixrenderer)
|
||||
, m_renderer(m_raw_renderer)
|
||||
, m_trafo(trafo)
|
||||
{
|
||||
m_renderer.color(foreground);
|
||||
clear(background);
|
||||
|
||||
m_rasterizer.gamma(gammafn);
|
||||
}
|
||||
|
||||
Trafo trafo() const override { return m_trafo; }
|
||||
Resolution resolution() const override { return m_resolution; }
|
||||
PixelDim pixel_dimensions() const override
|
||||
{
|
||||
return {SCALING_FACTOR / m_pxdim_scaled.w_mm,
|
||||
SCALING_FACTOR / m_pxdim_scaled.h_mm};
|
||||
}
|
||||
|
||||
void draw(const ExPolygon &poly) override { _draw(poly); }
|
||||
void draw(const ClipperLib::Polygon &poly) override { _draw(poly); }
|
||||
|
||||
EncodedRaster encode(RasterEncoder encoder) const override
|
||||
{
|
||||
return encoder(m_buf.data(), m_resolution.width_px, m_resolution.height_px, 1);
|
||||
}
|
||||
|
||||
void clear(const TColor color) { m_raw_renderer.clear(color); }
|
||||
};
|
||||
|
||||
/*
|
||||
* Captures an anti-aliased monochrome canvas where vectorial
|
||||
* polygons can be rasterized. Fill color is always white and the background is
|
||||
* black. Contours are anti-aliased.
|
||||
*
|
||||
* A gamma function can be specified at compile time to make it more flexible.
|
||||
*/
|
||||
using _RasterGrayscaleAA =
|
||||
AGGRaster<agg::pixfmt_gray8, agg::renderer_scanline_aa_solid>;
|
||||
|
||||
class RasterGrayscaleAA : public _RasterGrayscaleAA {
|
||||
using Base = _RasterGrayscaleAA;
|
||||
using typename Base::TColor;
|
||||
using typename Base::TValue;
|
||||
public:
|
||||
template<class GammaFn>
|
||||
RasterGrayscaleAA(const RasterBase::Resolution &res,
|
||||
const RasterBase::PixelDim & pd,
|
||||
const RasterBase::Trafo & trafo,
|
||||
GammaFn && fn)
|
||||
: Base(res, pd, trafo, Colors<TColor>::White, Colors<TColor>::Black,
|
||||
std::forward<GammaFn>(fn))
|
||||
{}
|
||||
|
||||
uint8_t read_pixel(size_t col, size_t row) const
|
||||
{
|
||||
static_assert(std::is_same<TValue, uint8_t>::value, "Not grayscale pix");
|
||||
|
||||
uint8_t px;
|
||||
Base::m_buf[row * Base::resolution().width_px + col].get(px);
|
||||
return px;
|
||||
}
|
||||
|
||||
void clear() { Base::clear(Colors<TColor>::Black); }
|
||||
};
|
||||
|
||||
class RasterGrayscaleAAGammaPower: public RasterGrayscaleAA {
|
||||
public:
|
||||
RasterGrayscaleAAGammaPower(const RasterBase::Resolution &res,
|
||||
const RasterBase::PixelDim & pd,
|
||||
const RasterBase::Trafo & trafo,
|
||||
double gamma = 1.)
|
||||
: RasterGrayscaleAA(res, pd, trafo, agg::gamma_power(gamma))
|
||||
{}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // AGGRASTER_HPP
|
||||
|
|
@ -11,6 +11,8 @@
|
|||
#include "Tesselate.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
|
||||
#include "TriangulateWall.hpp"
|
||||
|
||||
// For debugging:
|
||||
// #include <fstream>
|
||||
// #include <libnest2d/tools/benchmark.h>
|
||||
|
|
@ -27,186 +29,27 @@ namespace Slic3r { namespace sla {
|
|||
|
||||
namespace {
|
||||
|
||||
/// This function will return a triangulation of a sheet connecting an upper
|
||||
/// and a lower plate given as input polygons. It will not triangulate the
|
||||
/// plates themselves only the sheet. The caller has to specify the lower and
|
||||
/// upper z levels in world coordinates as well as the offset difference
|
||||
/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the
|
||||
/// offset difference is negative, the resulting triangle orientation will be
|
||||
/// reversed.
|
||||
///
|
||||
/// IMPORTANT: This is not a universal triangulation algorithm. It assumes
|
||||
/// that the lower and upper polygons are offsetted versions of the same
|
||||
/// original polygon. In general, it assumes that one of the polygons is
|
||||
/// completely inside the other. The offset difference is the reference
|
||||
/// distance from the inner polygon's perimeter to the outer polygon's
|
||||
/// perimeter. The real distance will be variable as the clipper offset has
|
||||
/// different strategies (rounding, etc...). This algorithm should have
|
||||
/// O(2n + 3m) complexity where n is the number of upper vertices and m is the
|
||||
/// number of lower vertices.
|
||||
Contour3D walls(
|
||||
const Polygon &lower,
|
||||
const Polygon &upper,
|
||||
double lower_z_mm,
|
||||
double upper_z_mm,
|
||||
double offset_difference_mm,
|
||||
ThrowOnCancel thr = [] {})
|
||||
double upper_z_mm)
|
||||
{
|
||||
Wall w = triangulate_wall(lower, upper, lower_z_mm, upper_z_mm);
|
||||
|
||||
Contour3D ret;
|
||||
|
||||
if(upper.points.size() < 3 || lower.size() < 3) return ret;
|
||||
|
||||
// The concept of the algorithm is relatively simple. It will try to find
|
||||
// the closest vertices from the upper and the lower polygon and use those
|
||||
// as starting points. Then it will create the triangles sequentially using
|
||||
// an edge from the upper polygon and a vertex from the lower or vice versa,
|
||||
// depending on the resulting triangle's quality.
|
||||
// The quality is measured by a scalar value. So far it looks like it is
|
||||
// enough to derive it from the slope of the triangle's two edges connecting
|
||||
// the upper and the lower part. A reference slope is calculated from the
|
||||
// height and the offset difference.
|
||||
|
||||
// Offset in the index array for the ceiling
|
||||
const auto offs = upper.points.size();
|
||||
|
||||
// Shorthand for the vertex arrays
|
||||
auto& upts = upper.points, &lpts = lower.points;
|
||||
auto& rpts = ret.points; auto& ind = ret.faces3;
|
||||
|
||||
// If the Z levels are flipped, or the offset difference is negative, we
|
||||
// will interpret that as the triangles normals should be inverted.
|
||||
bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0;
|
||||
|
||||
// Copy the points into the mesh, convert them from 2D to 3D
|
||||
rpts.reserve(upts.size() + lpts.size());
|
||||
ind.reserve(2 * upts.size() + 2 * lpts.size());
|
||||
for (auto &p : upts)
|
||||
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm);
|
||||
for (auto &p : lpts)
|
||||
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm);
|
||||
|
||||
// Create pointing indices into vertex arrays. u-upper, l-lower
|
||||
size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1;
|
||||
|
||||
// Simple squared distance calculation.
|
||||
auto distfn = [](const Vec3d& p1, const Vec3d& p2) {
|
||||
auto p = p1 - p2; return p.transpose() * p;
|
||||
};
|
||||
|
||||
// We need to find the closest point on lower polygon to the first point on
|
||||
// the upper polygon. These will be our starting points.
|
||||
double distmin = std::numeric_limits<double>::max();
|
||||
for(size_t l = lidx; l < rpts.size(); ++l) {
|
||||
thr();
|
||||
double d = distfn(rpts[l], rpts[uidx]);
|
||||
if(d < distmin) { lidx = l; distmin = d; }
|
||||
}
|
||||
|
||||
// Set up lnextidx to be ahead of lidx in cyclic mode
|
||||
lnextidx = lidx + 1;
|
||||
if(lnextidx == rpts.size()) lnextidx = offs;
|
||||
|
||||
// This will be the flip switch to toggle between upper and lower triangle
|
||||
// creation mode
|
||||
enum class Proceed {
|
||||
UPPER, // A segment from the upper polygon and one vertex from the lower
|
||||
LOWER // A segment from the lower polygon and one vertex from the upper
|
||||
} proceed = Proceed::UPPER;
|
||||
|
||||
// Flags to help evaluating loop termination.
|
||||
bool ustarted = false, lstarted = false;
|
||||
|
||||
// The variables for the fitness values, one for the actual and one for the
|
||||
// previous.
|
||||
double current_fit = 0, prev_fit = 0;
|
||||
|
||||
// Every triangle of the wall has two edges connecting the upper plate with
|
||||
// the lower plate. From the length of these two edges and the zdiff we
|
||||
// can calculate the momentary squared offset distance at a particular
|
||||
// position on the wall. The average of the differences from the reference
|
||||
// (squared) offset distance will give us the driving fitness value.
|
||||
const double offsdiff2 = std::pow(offset_difference_mm, 2);
|
||||
const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2);
|
||||
|
||||
// Mark the current vertex iterator positions. If the iterators return to
|
||||
// the same position, the loop can be terminated.
|
||||
size_t uendidx = uidx, lendidx = lidx;
|
||||
|
||||
do { thr(); // check throw if canceled
|
||||
|
||||
prev_fit = current_fit;
|
||||
|
||||
switch(proceed) { // proceed depending on the current state
|
||||
case Proceed::UPPER:
|
||||
if(!ustarted || uidx != uendidx) { // there are vertices remaining
|
||||
// Get the 3D vertices in order
|
||||
const Vec3d& p_up1 = rpts[uidx];
|
||||
const Vec3d& p_low = rpts[lidx];
|
||||
const Vec3d& p_up2 = rpts[unextidx];
|
||||
|
||||
// Calculate fitness: the average of the two connecting edges
|
||||
double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2);
|
||||
double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2);
|
||||
current_fit = (std::abs(a) + std::abs(b)) / 2;
|
||||
|
||||
if(current_fit > prev_fit) { // fit is worse than previously
|
||||
proceed = Proceed::LOWER;
|
||||
} else { // good to go, create the triangle
|
||||
inverted
|
||||
? ind.emplace_back(int(unextidx), int(lidx), int(uidx))
|
||||
: ind.emplace_back(int(uidx), int(lidx), int(unextidx));
|
||||
|
||||
// Increment the iterators, rotate if necessary
|
||||
++uidx; ++unextidx;
|
||||
if(unextidx == offs) unextidx = 0;
|
||||
if(uidx == offs) uidx = 0;
|
||||
|
||||
ustarted = true; // mark the movement of the iterators
|
||||
// so that the comparison to uendidx can be made correctly
|
||||
}
|
||||
} else proceed = Proceed::LOWER;
|
||||
|
||||
break;
|
||||
case Proceed::LOWER:
|
||||
// Mode with lower segment, upper vertex. Same structure:
|
||||
if(!lstarted || lidx != lendidx) {
|
||||
const Vec3d& p_low1 = rpts[lidx];
|
||||
const Vec3d& p_low2 = rpts[lnextidx];
|
||||
const Vec3d& p_up = rpts[uidx];
|
||||
|
||||
double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2);
|
||||
double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2);
|
||||
current_fit = (std::abs(a) + std::abs(b)) / 2;
|
||||
|
||||
if(current_fit > prev_fit) {
|
||||
proceed = Proceed::UPPER;
|
||||
} else {
|
||||
inverted
|
||||
? ind.emplace_back(int(uidx), int(lnextidx), int(lidx))
|
||||
: ind.emplace_back(int(lidx), int(lnextidx), int(uidx));
|
||||
|
||||
++lidx; ++lnextidx;
|
||||
if(lnextidx == rpts.size()) lnextidx = offs;
|
||||
if(lidx == rpts.size()) lidx = offs;
|
||||
|
||||
lstarted = true;
|
||||
}
|
||||
} else proceed = Proceed::UPPER;
|
||||
|
||||
break;
|
||||
} // end of switch
|
||||
} while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx);
|
||||
|
||||
ret.points = std::move(w.first);
|
||||
ret.faces3 = std::move(w.second);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Same as walls() but with identical higher and lower polygons.
|
||||
Contour3D inline straight_walls(const Polygon &plate,
|
||||
double lo_z,
|
||||
double hi_z,
|
||||
ThrowOnCancel thr)
|
||||
double hi_z)
|
||||
{
|
||||
return walls(plate, plate, lo_z, hi_z, .0 /*offset_diff*/, thr);
|
||||
return walls(plate, plate, lo_z, hi_z);
|
||||
}
|
||||
|
||||
// Function to cut tiny connector cavities for a given polygon. The input poly
|
||||
|
|
@ -534,10 +377,8 @@ bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg,
|
|||
top_poly = pdiff.front();
|
||||
|
||||
double z_min = -cfg.wing_height, z_max = 0;
|
||||
double offset_difference = -wing_distance;
|
||||
pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max,
|
||||
offset_difference, thr));
|
||||
|
||||
pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max));
|
||||
thr();
|
||||
pad.merge(triangulate_expolygon_3d(inner_base, z_min, NORMALS_UP));
|
||||
|
||||
return true;
|
||||
|
|
@ -555,17 +396,17 @@ Contour3D create_outer_pad_geometry(const ExPolygons & skeleton,
|
|||
offset_contour_only(pad_part, -scaled(cfg.bottom_offset()));
|
||||
|
||||
if (bottom_poly.empty()) continue;
|
||||
|
||||
thr();
|
||||
|
||||
double z_min = -cfg.height, z_max = 0;
|
||||
ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min,
|
||||
cfg.bottom_offset(), thr));
|
||||
ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min));
|
||||
|
||||
if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr))
|
||||
z_max = -cfg.wing_height;
|
||||
|
||||
for (auto &h : bottom_poly.holes)
|
||||
ret.merge(straight_walls(h, z_max, z_min, thr));
|
||||
|
||||
ret.merge(straight_walls(h, z_max, z_min));
|
||||
|
||||
ret.merge(triangulate_expolygon_3d(bottom_poly, z_min, NORMALS_DOWN));
|
||||
ret.merge(triangulate_expolygon_3d(top_poly, NORMALS_UP));
|
||||
}
|
||||
|
|
@ -581,11 +422,12 @@ Contour3D create_inner_pad_geometry(const ExPolygons & skeleton,
|
|||
|
||||
double z_max = 0., z_min = -cfg.height;
|
||||
for (const ExPolygon &pad_part : skeleton) {
|
||||
ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr));
|
||||
thr();
|
||||
ret.merge(straight_walls(pad_part.contour, z_max, z_min));
|
||||
|
||||
for (auto &h : pad_part.holes)
|
||||
ret.merge(straight_walls(h, z_max, z_min, thr));
|
||||
|
||||
ret.merge(straight_walls(h, z_max, z_min));
|
||||
|
||||
ret.merge(triangulate_expolygon_3d(pad_part, z_min, NORMALS_DOWN));
|
||||
ret.merge(triangulate_expolygon_3d(pad_part, z_max, NORMALS_UP));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
89
src/libslic3r/SLA/RasterBase.cpp
Normal file
89
src/libslic3r/SLA/RasterBase.cpp
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#ifndef SLARASTER_CPP
|
||||
#define SLARASTER_CPP
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <libslic3r/SLA/RasterBase.hpp>
|
||||
#include <libslic3r/SLA/AGGRaster.hpp>
|
||||
|
||||
// minz image write:
|
||||
#include <miniz.h>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
const RasterBase::TMirroring RasterBase::NoMirror = {false, false};
|
||||
const RasterBase::TMirroring RasterBase::MirrorX = {true, false};
|
||||
const RasterBase::TMirroring RasterBase::MirrorY = {false, true};
|
||||
const RasterBase::TMirroring RasterBase::MirrorXY = {true, true};
|
||||
|
||||
EncodedRaster PNGRasterEncoder::operator()(const void *ptr, size_t w, size_t h,
|
||||
size_t num_components)
|
||||
{
|
||||
std::vector<uint8_t> buf;
|
||||
size_t s = 0;
|
||||
|
||||
void *rawdata = tdefl_write_image_to_png_file_in_memory(
|
||||
ptr, int(w), int(h), int(num_components), &s);
|
||||
|
||||
// On error, data() will return an empty vector. No other info can be
|
||||
// retrieved from miniz anyway...
|
||||
if (rawdata == nullptr) return EncodedRaster({}, "png");
|
||||
|
||||
auto pptr = static_cast<std::uint8_t*>(rawdata);
|
||||
|
||||
buf.reserve(s);
|
||||
std::copy(pptr, pptr + s, std::back_inserter(buf));
|
||||
|
||||
MZ_FREE(rawdata);
|
||||
return EncodedRaster(std::move(buf), "png");
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &stream, const EncodedRaster &bytes)
|
||||
{
|
||||
stream.write(reinterpret_cast<const char *>(bytes.data()),
|
||||
std::streamsize(bytes.size()));
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
EncodedRaster PPMRasterEncoder::operator()(const void *ptr, size_t w, size_t h,
|
||||
size_t num_components)
|
||||
{
|
||||
std::vector<uint8_t> buf;
|
||||
|
||||
auto header = std::string("P5 ") +
|
||||
std::to_string(w) + " " +
|
||||
std::to_string(h) + " " + "255 ";
|
||||
|
||||
auto sz = w * h * num_components;
|
||||
size_t s = sz + header.size();
|
||||
|
||||
buf.reserve(s);
|
||||
|
||||
auto buff = reinterpret_cast<const std::uint8_t*>(ptr);
|
||||
std::copy(header.begin(), header.end(), std::back_inserter(buf));
|
||||
std::copy(buff, buff+sz, std::back_inserter(buf));
|
||||
|
||||
return EncodedRaster(std::move(buf), "ppm");
|
||||
}
|
||||
|
||||
std::unique_ptr<RasterBase> create_raster_grayscale_aa(
|
||||
const RasterBase::Resolution &res,
|
||||
const RasterBase::PixelDim & pxdim,
|
||||
double gamma,
|
||||
const RasterBase::Trafo & tr)
|
||||
{
|
||||
std::unique_ptr<RasterBase> rst;
|
||||
|
||||
if (gamma > 0)
|
||||
rst = std::make_unique<RasterGrayscaleAAGammaPower>(res, pxdim, tr, gamma);
|
||||
else
|
||||
rst = std::make_unique<RasterGrayscaleAA>(res, pxdim, tr, agg::gamma_threshold(.5));
|
||||
|
||||
return rst;
|
||||
}
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // SLARASTER_CPP
|
||||
124
src/libslic3r/SLA/RasterBase.hpp
Normal file
124
src/libslic3r/SLA/RasterBase.hpp
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#ifndef SLA_RASTERBASE_HPP
|
||||
#define SLA_RASTERBASE_HPP
|
||||
|
||||
#include <ostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
|
||||
namespace ClipperLib { struct Polygon; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
template<class T> using uqptr = std::unique_ptr<T>;
|
||||
template<class T> using shptr = std::shared_ptr<T>;
|
||||
template<class T> using wkptr = std::weak_ptr<T>;
|
||||
|
||||
namespace sla {
|
||||
|
||||
// Raw byte buffer paired with its size. Suitable for compressed image data.
|
||||
class EncodedRaster {
|
||||
protected:
|
||||
std::vector<uint8_t> m_buffer;
|
||||
std::string m_ext;
|
||||
public:
|
||||
EncodedRaster() = default;
|
||||
explicit EncodedRaster(std::vector<uint8_t> &&buf, std::string ext)
|
||||
: m_buffer(std::move(buf)), m_ext(std::move(ext))
|
||||
{}
|
||||
|
||||
size_t size() const { return m_buffer.size(); }
|
||||
const void * data() const { return m_buffer.data(); }
|
||||
const char * extension() const { return m_ext.c_str(); }
|
||||
};
|
||||
|
||||
using RasterEncoder =
|
||||
std::function<EncodedRaster(const void *ptr, size_t w, size_t h, size_t num_components)>;
|
||||
|
||||
class RasterBase {
|
||||
public:
|
||||
|
||||
enum Orientation { roLandscape, roPortrait };
|
||||
|
||||
using TMirroring = std::array<bool, 2>;
|
||||
static const TMirroring NoMirror;
|
||||
static const TMirroring MirrorX;
|
||||
static const TMirroring MirrorY;
|
||||
static const TMirroring MirrorXY;
|
||||
|
||||
struct Trafo {
|
||||
bool mirror_x = false, mirror_y = false, flipXY = false;
|
||||
coord_t center_x = 0, center_y = 0;
|
||||
|
||||
// Portrait orientation will make sure the drawed polygons are rotated
|
||||
// by 90 degrees.
|
||||
Trafo(Orientation o = roLandscape, const TMirroring &mirror = NoMirror)
|
||||
// XY flipping implicitly does an X mirror
|
||||
: mirror_x(o == roPortrait ? !mirror[0] : mirror[0])
|
||||
, mirror_y(!mirror[1]) // Makes raster origin to be top left corner
|
||||
, flipXY(o == roPortrait)
|
||||
{}
|
||||
|
||||
TMirroring get_mirror() const { return { (roPortrait ? !mirror_x : mirror_x), mirror_y}; }
|
||||
Orientation get_orientation() const { return flipXY ? roPortrait : roLandscape; }
|
||||
Point get_center() const { return {center_x, center_y}; }
|
||||
};
|
||||
|
||||
/// Type that represents a resolution in pixels.
|
||||
struct Resolution {
|
||||
size_t width_px = 0;
|
||||
size_t height_px = 0;
|
||||
|
||||
Resolution(size_t w = 0, size_t h = 0) : width_px(w), height_px(h) {}
|
||||
size_t pixels() const { return width_px * height_px; }
|
||||
};
|
||||
|
||||
/// Types that represents the dimension of a pixel in millimeters.
|
||||
struct PixelDim {
|
||||
double w_mm = 0.;
|
||||
double h_mm = 0.;
|
||||
|
||||
PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0)
|
||||
: w_mm(px_width_mm), h_mm(px_height_mm)
|
||||
{}
|
||||
};
|
||||
|
||||
virtual ~RasterBase() = default;
|
||||
|
||||
/// Draw a polygon with holes.
|
||||
virtual void draw(const ExPolygon& poly) = 0;
|
||||
virtual void draw(const ClipperLib::Polygon& poly) = 0;
|
||||
|
||||
/// Get the resolution of the raster.
|
||||
virtual Resolution resolution() const = 0;
|
||||
virtual PixelDim pixel_dimensions() const = 0;
|
||||
virtual Trafo trafo() const = 0;
|
||||
|
||||
virtual EncodedRaster encode(RasterEncoder encoder) const = 0;
|
||||
};
|
||||
|
||||
struct PNGRasterEncoder {
|
||||
EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components);
|
||||
};
|
||||
|
||||
struct PPMRasterEncoder {
|
||||
EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components);
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream &stream, const EncodedRaster &bytes);
|
||||
|
||||
// If gamma is zero, thresholding will be performed which disables AA.
|
||||
uqptr<RasterBase> create_raster_grayscale_aa(
|
||||
const RasterBase::Resolution &res,
|
||||
const RasterBase::PixelDim & pxdim,
|
||||
double gamma = 1.0,
|
||||
const RasterBase::Trafo & tr = {});
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // SLARASTERBASE_HPP
|
||||
91
src/libslic3r/SLA/RasterToPolygons.cpp
Normal file
91
src/libslic3r/SLA/RasterToPolygons.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#include "RasterToPolygons.hpp"
|
||||
|
||||
#include "AGGRaster.hpp"
|
||||
#include "libslic3r/MarchingSquares.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
|
||||
namespace marchsq {
|
||||
|
||||
// Specialize this struct to register a raster type for the Marching squares alg
|
||||
template<> struct _RasterTraits<Slic3r::sla::RasterGrayscaleAA> {
|
||||
using Rst = Slic3r::sla::RasterGrayscaleAA;
|
||||
|
||||
// The type of pixel cell in the raster
|
||||
using ValueType = uint8_t;
|
||||
|
||||
// Value at a given position
|
||||
static uint8_t get(const Rst &rst, size_t row, size_t col) { return rst.read_pixel(col, row); }
|
||||
|
||||
// Number of rows and cols of the raster
|
||||
static size_t rows(const Rst &rst) { return rst.resolution().height_px; }
|
||||
static size_t cols(const Rst &rst) { return rst.resolution().width_px; }
|
||||
};
|
||||
|
||||
} // namespace Slic3r::marchsq
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
|
||||
{
|
||||
for (auto &p : poly.contour.points) fn(p);
|
||||
for (auto &h : poly.holes)
|
||||
for (auto &p : h.points) fn(p);
|
||||
}
|
||||
|
||||
ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, Vec2i windowsize)
|
||||
{
|
||||
size_t rows = rst.resolution().height_px, cols = rst.resolution().width_px;
|
||||
|
||||
if (rows < 2 || cols < 2) return {};
|
||||
|
||||
Polygons polys;
|
||||
long w_rows = std::max(2l, long(windowsize.y()));
|
||||
long w_cols = std::max(2l, long(windowsize.x()));
|
||||
|
||||
std::vector<marchsq::Ring> rings =
|
||||
marchsq::execute(rst, 128, {w_rows, w_cols});
|
||||
|
||||
polys.reserve(rings.size());
|
||||
|
||||
auto pxd = rst.pixel_dimensions();
|
||||
pxd.w_mm = (rst.resolution().width_px * pxd.w_mm) / (rst.resolution().width_px - 1);
|
||||
pxd.h_mm = (rst.resolution().height_px * pxd.h_mm) / (rst.resolution().height_px - 1);
|
||||
|
||||
for (const marchsq::Ring &ring : rings) {
|
||||
Polygon poly; Points &pts = poly.points;
|
||||
pts.reserve(ring.size());
|
||||
|
||||
for (const marchsq::Coord &crd : ring)
|
||||
pts.emplace_back(scaled(crd.c * pxd.w_mm), scaled(crd.r * pxd.h_mm));
|
||||
|
||||
polys.emplace_back(poly);
|
||||
}
|
||||
|
||||
// reverse the raster transformations
|
||||
ExPolygons unioned = union_ex(polys);
|
||||
coord_t width = scaled(cols * pxd.h_mm), height = scaled(rows * pxd.w_mm);
|
||||
|
||||
auto tr = rst.trafo();
|
||||
for (ExPolygon &expoly : unioned) {
|
||||
if (tr.mirror_y)
|
||||
foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); });
|
||||
|
||||
if (tr.mirror_x)
|
||||
foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); });
|
||||
|
||||
expoly.translate(-tr.center_x, -tr.center_y);
|
||||
|
||||
if (tr.flipXY)
|
||||
foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); });
|
||||
|
||||
if ((tr.mirror_x + tr.mirror_y + tr.flipXY) % 2) {
|
||||
expoly.contour.reverse();
|
||||
for (auto &h : expoly.holes) h.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
return unioned;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r
|
||||
15
src/libslic3r/SLA/RasterToPolygons.hpp
Normal file
15
src/libslic3r/SLA/RasterToPolygons.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef RASTERTOPOLYGONS_HPP
|
||||
#define RASTERTOPOLYGONS_HPP
|
||||
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
class RasterGrayscaleAA;
|
||||
|
||||
ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, Vec2i windowsize = {2, 2});
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // RASTERTOPOLYGONS_HPP
|
||||
|
|
@ -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 ¶m : m) ini += param.first + " = " + param.second + "\n";
|
||||
}
|
||||
|
||||
std::string RasterWriter::create_ini_content(const std::string& projectname) const
|
||||
{
|
||||
std::string out("action = print\njobDir = ");
|
||||
out += projectname + "\n";
|
||||
write_ini(m_config, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
RasterWriter::RasterWriter(const Raster::Resolution &res,
|
||||
const Raster::PixelDim & pixdim,
|
||||
const Raster::Trafo & trafo,
|
||||
double gamma)
|
||||
: m_res(res), m_pxdim(pixdim), m_trafo(trafo), m_gamma(gamma)
|
||||
{}
|
||||
|
||||
void RasterWriter::save(const std::string &fpath, const std::string &prjname)
|
||||
{
|
||||
try {
|
||||
Zipper zipper(fpath); // zipper with no compression
|
||||
save(zipper, prjname);
|
||||
zipper.finalize();
|
||||
} catch(std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||
// Rethrow the exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterWriter::save(Zipper &zipper, const std::string &prjname)
|
||||
{
|
||||
try {
|
||||
std::string project =
|
||||
prjname.empty() ?
|
||||
boost::filesystem::path(zipper.get_filename()).stem().string() :
|
||||
prjname;
|
||||
|
||||
zipper.add_entry("config.ini");
|
||||
|
||||
zipper << create_ini_content(project);
|
||||
|
||||
zipper.add_entry("prusaslicer.ini");
|
||||
std::string prusaslicer_ini;
|
||||
write_ini(m_slicer_config, prusaslicer_ini);
|
||||
zipper << prusaslicer_ini;
|
||||
|
||||
for(unsigned i = 0; i < m_layers_rst.size(); i++)
|
||||
{
|
||||
if(m_layers_rst[i].rawbytes.size() > 0) {
|
||||
char lyrnum[6];
|
||||
std::sprintf(lyrnum, "%.5d", i);
|
||||
auto zfilename = project + lyrnum + ".png";
|
||||
|
||||
// Add binary entry to the zipper
|
||||
zipper.add_entry(zfilename,
|
||||
m_layers_rst[i].rawbytes.data(),
|
||||
m_layers_rst[i].rawbytes.size());
|
||||
}
|
||||
}
|
||||
} catch(std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||
// Rethrow the exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key)
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
if (cfg.has(key)) {
|
||||
auto opt = cfg.option(key);
|
||||
if (opt) ret = opt->serialize();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void append_full_config(const DynamicPrintConfig &cfg, std::map<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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -38,11 +38,12 @@ std::vector<std::pair<size_t, bool>> chain_segments_closest_point(std::vector<En
|
|||
// Ignore the starting point as the starting point is considered to be occupied, no end point coud connect to it.
|
||||
size_t next_idx = find_closest_point(kdtree, this_point.pos,
|
||||
[this_idx, &end_points, &could_reverse_func](size_t idx) {
|
||||
return (idx ^ this_idx) > 1 && end_points[idx].chain_id == 0 && ((idx ^ 1) == 0 || could_reverse_func(idx >> 1));
|
||||
return (idx ^ this_idx) > 1 && end_points[idx].chain_id == 0 && ((idx & 1) == 0 || could_reverse_func(idx >> 1));
|
||||
});
|
||||
assert(next_idx < end_points.size());
|
||||
EndPointType &end_point = end_points[next_idx];
|
||||
end_point.chain_id = 1;
|
||||
assert((next_idx & 1) == 0 || could_reverse_func(next_idx >> 1));
|
||||
out.emplace_back(next_idx / 2, (next_idx & 1) != 0);
|
||||
this_idx = next_idx ^ 1;
|
||||
}
|
||||
|
|
@ -165,7 +166,9 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals
|
|||
EndPoint *first_point = nullptr;
|
||||
size_t first_point_idx = std::numeric_limits<size_t>::max();
|
||||
if (start_near != nullptr) {
|
||||
size_t idx = find_closest_point(kdtree, start_near->template cast<double>());
|
||||
size_t idx = find_closest_point(kdtree, start_near->template cast<double>(),
|
||||
// Don't start with a reverse segment, if flipping of the segment is not allowed.
|
||||
[&could_reverse_func](size_t idx) { return (idx & 1) == 0 || could_reverse_func(idx >> 1); });
|
||||
assert(idx < end_points.size());
|
||||
first_point = &end_points[idx];
|
||||
first_point->distance_out = 0.;
|
||||
|
|
|
|||
128
src/libslic3r/SlicesToTriangleMesh.cpp
Normal file
128
src/libslic3r/SlicesToTriangleMesh.cpp
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
|
||||
#include "SlicesToTriangleMesh.hpp"
|
||||
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include "libslic3r/SLA/Contour3D.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/Tesselate.hpp"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_reduce.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
inline sla::Contour3D wall_strip(const Polygon &poly,
|
||||
double lower_z_mm,
|
||||
double upper_z_mm)
|
||||
{
|
||||
sla::Contour3D ret;
|
||||
|
||||
size_t startidx = ret.points.size();
|
||||
size_t offs = poly.points.size();
|
||||
|
||||
ret.points.reserve(ret.points.size() + 2 *offs);
|
||||
|
||||
for (const Point &p : poly.points)
|
||||
ret.points.emplace_back(to_3d(unscaled(p), lower_z_mm));
|
||||
|
||||
for (const Point &p : poly.points)
|
||||
ret.points.emplace_back(to_3d(unscaled(p), upper_z_mm));
|
||||
|
||||
for (size_t i = startidx + 1; i < startidx + offs; ++i) {
|
||||
ret.faces3.emplace_back(i - 1, i, i + offs - 1);
|
||||
ret.faces3.emplace_back(i, i + offs, i + offs - 1);
|
||||
}
|
||||
|
||||
ret.faces3.emplace_back(startidx + offs - 1, startidx, startidx + 2 * offs - 1);
|
||||
ret.faces3.emplace_back(startidx, startidx + offs, startidx + 2 * offs - 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Same as walls() but with identical higher and lower polygons.
|
||||
sla::Contour3D inline straight_walls(const Polygon &plate,
|
||||
double lo_z,
|
||||
double hi_z)
|
||||
{
|
||||
return wall_strip(plate, lo_z, hi_z);
|
||||
}
|
||||
|
||||
sla::Contour3D inline straight_walls(const ExPolygon &plate,
|
||||
double lo_z,
|
||||
double hi_z)
|
||||
{
|
||||
sla::Contour3D ret;
|
||||
ret.merge(straight_walls(plate.contour, lo_z, hi_z));
|
||||
for (auto &h : plate.holes) ret.merge(straight_walls(h, lo_z, hi_z));
|
||||
return ret;
|
||||
}
|
||||
|
||||
sla::Contour3D inline straight_walls(const ExPolygons &slice,
|
||||
double lo_z,
|
||||
double hi_z)
|
||||
{
|
||||
sla::Contour3D ret;
|
||||
for (const ExPolygon &poly : slice)
|
||||
ret.merge(straight_walls(poly, lo_z, hi_z));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sla::Contour3D slices_to_triangle_mesh(const std::vector<ExPolygons> &slices,
|
||||
double zmin,
|
||||
const std::vector<float> & grid)
|
||||
{
|
||||
assert(slices.size() == grid.size());
|
||||
|
||||
using Layers = std::vector<sla::Contour3D>;
|
||||
std::vector<sla::Contour3D> layers(slices.size());
|
||||
size_t len = slices.size() - 1;
|
||||
|
||||
tbb::parallel_for(size_t(0), len, [&slices, &layers, &grid](size_t i) {
|
||||
const ExPolygons &upper = slices[i + 1];
|
||||
const ExPolygons &lower = slices[i];
|
||||
|
||||
ExPolygons dff1 = diff_ex(lower, upper);
|
||||
ExPolygons dff2 = diff_ex(upper, lower);
|
||||
layers[i].merge(triangulate_expolygons_3d(dff1, grid[i], NORMALS_UP));
|
||||
layers[i].merge(triangulate_expolygons_3d(dff2, grid[i], NORMALS_DOWN));
|
||||
layers[i].merge(straight_walls(upper, grid[i], grid[i + 1]));
|
||||
|
||||
});
|
||||
|
||||
sla::Contour3D ret = tbb::parallel_reduce(
|
||||
tbb::blocked_range(layers.begin(), layers.end()),
|
||||
sla::Contour3D{},
|
||||
[](const tbb::blocked_range<Layers::iterator>& r, sla::Contour3D init) {
|
||||
for(auto it = r.begin(); it != r.end(); ++it ) init.merge(*it);
|
||||
return init;
|
||||
},
|
||||
[]( const sla::Contour3D &a, const sla::Contour3D &b ) {
|
||||
sla::Contour3D res{a}; res.merge(b); return res;
|
||||
});
|
||||
|
||||
ret.merge(triangulate_expolygons_3d(slices.front(), zmin, NORMALS_DOWN));
|
||||
ret.merge(straight_walls(slices.front(), zmin, grid.front()));
|
||||
ret.merge(triangulate_expolygons_3d(slices.back(), grid.back(), NORMALS_UP));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void slices_to_triangle_mesh(TriangleMesh & mesh,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
double zmin,
|
||||
double lh,
|
||||
double ilh)
|
||||
{
|
||||
std::vector<sla::Contour3D> wall_meshes(slices.size());
|
||||
std::vector<float> grid(slices.size(), zmin + ilh);
|
||||
|
||||
for (size_t i = 1; i < grid.size(); ++i) grid[i] = grid[i - 1] + lh;
|
||||
|
||||
sla::Contour3D cntr = slices_to_triangle_mesh(slices, zmin, grid);
|
||||
mesh.merge(sla::to_triangle_mesh(cntr));
|
||||
mesh.repaired = true;
|
||||
mesh.require_shared_vertices();
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
24
src/libslic3r/SlicesToTriangleMesh.hpp
Normal file
24
src/libslic3r/SlicesToTriangleMesh.hpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef SLICESTOTRIANGLEMESH_HPP
|
||||
#define SLICESTOTRIANGLEMESH_HPP
|
||||
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void slices_to_triangle_mesh(TriangleMesh & mesh,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
double zmin,
|
||||
double lh,
|
||||
double ilh);
|
||||
|
||||
inline TriangleMesh slices_to_triangle_mesh(
|
||||
const std::vector<ExPolygons> &slices, double zmin, double lh, double ilh)
|
||||
{
|
||||
TriangleMesh out; slices_to_triangle_mesh(out, slices, zmin, lh, ilh);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // SLICESTOTRIANGLEMESH_HPP
|
||||
|
|
@ -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);
|
||||
|
|
@ -2317,10 +2328,15 @@ static inline void fill_expolygons_generate_paths(
|
|||
fill_params.dont_adjust = true;
|
||||
for (const ExPolygon &expoly : expolygons) {
|
||||
Surface surface(stInternal, expoly);
|
||||
Polylines polylines;
|
||||
try {
|
||||
polylines = filler->fill_surface(&surface, fill_params);
|
||||
} catch (InfillFailedException &) {
|
||||
}
|
||||
extrusion_entities_append_paths(
|
||||
dst,
|
||||
filler->fill_surface(&surface, fill_params),
|
||||
role,
|
||||
std::move(polylines),
|
||||
role,
|
||||
flow.mm3_per_mm(), flow.width, flow.height);
|
||||
}
|
||||
}
|
||||
|
|
@ -2339,9 +2355,14 @@ static inline void fill_expolygons_generate_paths(
|
|||
fill_params.dont_adjust = true;
|
||||
for (ExPolygon &expoly : expolygons) {
|
||||
Surface surface(stInternal, std::move(expoly));
|
||||
Polylines polylines;
|
||||
try {
|
||||
polylines = filler->fill_surface(&surface, fill_params);
|
||||
} catch (InfillFailedException &) {
|
||||
}
|
||||
extrusion_entities_append_paths(
|
||||
dst,
|
||||
filler->fill_surface(&surface, fill_params),
|
||||
std::move(polylines),
|
||||
role,
|
||||
flow.mm3_per_mm(), flow.width, flow.height);
|
||||
}
|
||||
|
|
|
|||
133
src/libslic3r/TriangulateWall.cpp
Normal file
133
src/libslic3r/TriangulateWall.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#include "TriangulateWall.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Ring {
|
||||
size_t idx = 0, nextidx = 1, startidx = 0, begin = 0, end = 0;
|
||||
|
||||
public:
|
||||
explicit Ring(size_t from, size_t to) : begin(from), end(to) { init(begin); }
|
||||
|
||||
size_t size() const { return end - begin; }
|
||||
std::pair<size_t, size_t> pos() const { return {idx, nextidx}; }
|
||||
bool is_lower() const { return idx < size(); }
|
||||
|
||||
void inc()
|
||||
{
|
||||
if (nextidx != startidx) nextidx++;
|
||||
if (nextidx == end) nextidx = begin;
|
||||
idx ++;
|
||||
if (idx == end) idx = begin;
|
||||
}
|
||||
|
||||
void init(size_t pos)
|
||||
{
|
||||
startidx = begin + (pos - begin) % size();
|
||||
idx = startidx;
|
||||
nextidx = begin + (idx + 1 - begin) % size();
|
||||
}
|
||||
|
||||
bool is_finished() const { return nextidx == idx; }
|
||||
};
|
||||
|
||||
static double sq_dst(const Vec3d &v1, const Vec3d& v2)
|
||||
{
|
||||
Vec3d v = v1 - v2;
|
||||
return v.x() * v.x() + v.y() * v.y() /*+ v.z() * v.z()*/;
|
||||
}
|
||||
|
||||
static double score(const Ring& onring, const Ring &offring,
|
||||
const std::vector<Vec3d> &pts)
|
||||
{
|
||||
double a = sq_dst(pts[onring.pos().first], pts[offring.pos().first]);
|
||||
double b = sq_dst(pts[onring.pos().second], pts[offring.pos().first]);
|
||||
return (std::abs(a) + std::abs(b)) / 2.;
|
||||
}
|
||||
|
||||
class Triangulator {
|
||||
const std::vector<Vec3d> *pts;
|
||||
Ring *onring, *offring;
|
||||
|
||||
double calc_score() const
|
||||
{
|
||||
return Slic3r::score(*onring, *offring, *pts);
|
||||
}
|
||||
|
||||
void synchronize_rings()
|
||||
{
|
||||
Ring lring = *offring;
|
||||
auto minsc = Slic3r::score(*onring, lring, *pts);
|
||||
size_t imin = lring.pos().first;
|
||||
|
||||
lring.inc();
|
||||
|
||||
while(!lring.is_finished()) {
|
||||
double score = Slic3r::score(*onring, lring, *pts);
|
||||
if (score < minsc) { minsc = score; imin = lring.pos().first; }
|
||||
lring.inc();
|
||||
}
|
||||
|
||||
offring->init(imin);
|
||||
}
|
||||
|
||||
void emplace_indices(std::vector<Vec3i> &indices)
|
||||
{
|
||||
Vec3i tr{int(onring->pos().first), int(onring->pos().second),
|
||||
int(offring->pos().first)};
|
||||
if (onring->is_lower()) std::swap(tr(0), tr(1));
|
||||
indices.emplace_back(tr);
|
||||
}
|
||||
|
||||
public:
|
||||
void run(std::vector<Vec3i> &indices)
|
||||
{
|
||||
synchronize_rings();
|
||||
|
||||
double score = 0, prev_score = 0;
|
||||
while (!onring->is_finished() || !offring->is_finished()) {
|
||||
prev_score = score;
|
||||
if (onring->is_finished() || (score = calc_score()) > prev_score) {
|
||||
std::swap(onring, offring);
|
||||
} else {
|
||||
emplace_indices(indices);
|
||||
onring->inc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
explicit Triangulator(const std::vector<Vec3d> *points,
|
||||
Ring & lower,
|
||||
Ring & upper)
|
||||
: pts{points}, onring{&upper}, offring{&lower}
|
||||
{}
|
||||
};
|
||||
|
||||
Wall triangulate_wall(
|
||||
const Polygon & lower,
|
||||
const Polygon & upper,
|
||||
double lower_z_mm,
|
||||
double upper_z_mm)
|
||||
{
|
||||
if (upper.points.size() < 3 || lower.points.size() < 3) return {};
|
||||
|
||||
Wall wall;
|
||||
auto &pts = wall.first;
|
||||
auto &ind = wall.second;
|
||||
|
||||
pts.reserve(lower.points.size() + upper.points.size());
|
||||
for (auto &p : lower.points)
|
||||
wall.first.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm);
|
||||
for (auto &p : upper.points)
|
||||
wall.first.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm);
|
||||
|
||||
ind.reserve(2 * (lower.size() + upper.size()));
|
||||
|
||||
Ring lring{0, lower.points.size()}, uring{lower.points.size(), pts.size()};
|
||||
Triangulator t{&pts, lring, uring};
|
||||
t.run(ind);
|
||||
|
||||
return wall;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
17
src/libslic3r/TriangulateWall.hpp
Normal file
17
src/libslic3r/TriangulateWall.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef TRIANGULATEWALL_HPP
|
||||
#define TRIANGULATEWALL_HPP
|
||||
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using Wall = std::pair<std::vector<Vec3d>, std::vector<Vec3i>>;
|
||||
|
||||
Wall triangulate_wall(
|
||||
const Polygon & lower,
|
||||
const Polygon & upper,
|
||||
double lower_z_mm,
|
||||
double upper_z_mm);
|
||||
}
|
||||
|
||||
#endif // TRIANGULATEWALL_HPP
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Technologies.hpp"
|
||||
#include "Semver.hpp"
|
||||
|
|
@ -25,6 +26,7 @@
|
|||
// Saves around 32% RAM after slicing step, 6.7% after G-code export (tested on PrusaSlicer 2.2.0 final).
|
||||
typedef int32_t coord_t;
|
||||
#else
|
||||
//FIXME At least FillRectilinear2 requires coord_t to be 32bit.
|
||||
typedef int64_t coord_t;
|
||||
#endif
|
||||
|
||||
|
|
@ -247,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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -142,18 +142,27 @@ 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
|
||||
GUI/DoubleSlider.hpp
|
||||
GUI/ObjectDataViewModel.cpp
|
||||
GUI/ObjectDataViewModel.hpp
|
||||
GUI/InstanceCheck.cpp
|
||||
GUI/InstanceCheck.hpp
|
||||
GUI/Search.cpp
|
||||
GUI/Search.hpp
|
||||
Utils/Http.cpp
|
||||
|
|
@ -179,6 +188,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
Utils/HexFile.cpp
|
||||
Utils/HexFile.hpp
|
||||
Utils/Thread.hpp
|
||||
Utils/SLAImport.hpp
|
||||
Utils/SLAImport.cpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
|
|
@ -188,6 +199,8 @@ if (APPLE)
|
|||
GUI/RemovableDriveManagerMM.mm
|
||||
GUI/RemovableDriveManagerMM.h
|
||||
GUI/Mouse3DHandlerMac.mm
|
||||
GUI/InstanceCheckMac.mm
|
||||
GUI/InstanceCheckMac.h
|
||||
)
|
||||
FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration)
|
||||
|
||||
|
|
@ -199,6 +212,10 @@ encoding_check(libslic3r_gui)
|
|||
|
||||
target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL OpenGL::GLU hidapi libcurl ${wxWidgets_LIBRARIES})
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_link_libraries(libslic3r_gui ${DBUS_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY})
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ void AppConfig::set_defaults()
|
|||
set("use_retina_opengl", "1");
|
||||
#endif
|
||||
|
||||
if (get("single_instance").empty())
|
||||
set("single_instance", "0");
|
||||
|
||||
if (get("remember_output_path").empty())
|
||||
set("remember_output_path", "1");
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -298,6 +298,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
|||
toggle_field("support_material_extruder", have_support_material || have_skirt);
|
||||
toggle_field("support_material_speed", have_support_material || have_brim || have_skirt);
|
||||
|
||||
bool has_ironing = config->opt_bool("ironing");
|
||||
for (auto el : { "ironing_type", "ironing_flowrate", "ironing_spacing", "ironing_speed" })
|
||||
toggle_field(el, has_ironing);
|
||||
|
||||
bool have_sequential_printing = config->opt_bool("complete_objects");
|
||||
for (auto el : { "extruder_clearance_radius", "extruder_clearance_height" })
|
||||
toggle_field(el, have_sequential_printing);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -945,7 +945,7 @@ void Choice::set_value(const boost::any& value, bool change_event)
|
|||
}
|
||||
case coEnum: {
|
||||
int val = boost::any_cast<int>(value);
|
||||
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern")
|
||||
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern")
|
||||
{
|
||||
if (!m_opt.enum_values.empty()) {
|
||||
std::string key;
|
||||
|
|
@ -1015,7 +1015,7 @@ boost::any& Choice::get_value()
|
|||
if (m_opt.type == coEnum)
|
||||
{
|
||||
int ret_enum = field->GetSelection();
|
||||
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern")
|
||||
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern")
|
||||
{
|
||||
if (!m_opt.enum_values.empty()) {
|
||||
std::string key = m_opt.enum_values[ret_enum];
|
||||
|
|
@ -1027,8 +1027,8 @@ boost::any& Choice::get_value()
|
|||
else
|
||||
m_value = static_cast<InfillPattern>(0);
|
||||
}
|
||||
if (m_opt_id.compare("fill_pattern") == 0)
|
||||
m_value = static_cast<InfillPattern>(ret_enum);
|
||||
else if (m_opt_id.compare("ironing_type") == 0)
|
||||
m_value = static_cast<IroningType>(ret_enum);
|
||||
else if (m_opt_id.compare("gcode_flavor") == 0)
|
||||
m_value = static_cast<GCodeFlavor>(ret_enum);
|
||||
else if (m_opt_id.compare("support_material_pattern") == 0)
|
||||
|
|
|
|||
|
|
@ -1745,6 +1745,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)
|
||||
|
|
@ -5565,14 +5567,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
|
||||
|
|
|
|||
|
|
@ -760,7 +760,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
|
||||
|
|
|
|||
|
|
@ -188,6 +188,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
|
|||
opt_key == "bottom_fill_pattern" ||
|
||||
opt_key == "fill_pattern")
|
||||
config.set_key_value(opt_key, new ConfigOptionEnum<InfillPattern>(boost::any_cast<InfillPattern>(value)));
|
||||
else if (opt_key.compare("ironing_type") == 0)
|
||||
config.set_key_value(opt_key, new ConfigOptionEnum<IroningType>(boost::any_cast<IroningType>(value)));
|
||||
else if (opt_key.compare("gcode_flavor") == 0)
|
||||
config.set_key_value(opt_key, new ConfigOptionEnum<GCodeFlavor>(boost::any_cast<GCodeFlavor>(value)));
|
||||
else if (opt_key.compare("support_material_pattern") == 0)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
@ -49,6 +50,7 @@
|
|||
#include "UpdateDialogs.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#include <dbt.h>
|
||||
|
|
@ -208,6 +210,17 @@ static void register_win32_device_notification_event()
|
|||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
|
||||
|
||||
COPYDATASTRUCT* copy_data_structure = { 0 };
|
||||
copy_data_structure = (COPYDATASTRUCT*)lParam;
|
||||
if (copy_data_structure->dwData == 1) {
|
||||
LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData;
|
||||
Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
#endif // WIN32
|
||||
|
||||
|
|
@ -252,7 +265,11 @@ GUI_App::GUI_App()
|
|||
, m_imgui(new ImGuiWrapper())
|
||||
, m_wizard(nullptr)
|
||||
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
|
||||
{}
|
||||
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
|
||||
{
|
||||
//app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp
|
||||
this->init_app_config();
|
||||
}
|
||||
|
||||
GUI_App::~GUI_App()
|
||||
{
|
||||
|
|
@ -283,6 +300,30 @@ bool GUI_App::init_opengl()
|
|||
}
|
||||
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
|
||||
|
||||
void GUI_App::init_app_config()
|
||||
{
|
||||
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
|
||||
SetAppName(SLIC3R_APP_KEY);
|
||||
//SetAppName(SLIC3R_APP_KEY "-beta");
|
||||
SetAppDisplayName(SLIC3R_APP_NAME);
|
||||
|
||||
// Set the Slic3r data directory at the Slic3r XS module.
|
||||
// Unix: ~/ .Slic3r
|
||||
// Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
|
||||
// Mac : "~/Library/Application Support/Slic3r"
|
||||
|
||||
if (data_dir().empty())
|
||||
set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
|
||||
|
||||
if (!app_config)
|
||||
app_config = new AppConfig();
|
||||
|
||||
// load settings
|
||||
app_conf_exists = app_config->exists();
|
||||
if (app_conf_exists) {
|
||||
app_config->load();
|
||||
}
|
||||
}
|
||||
bool GUI_App::OnInit()
|
||||
{
|
||||
try {
|
||||
|
|
@ -300,42 +341,41 @@ bool GUI_App::on_init_inner()
|
|||
wxCHECK_MSG(wxDirExists(resources_dir), false,
|
||||
wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir));
|
||||
|
||||
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
|
||||
SetAppName(SLIC3R_APP_KEY);
|
||||
// SetAppName(SLIC3R_APP_KEY "-beta");
|
||||
SetAppDisplayName(SLIC3R_APP_NAME);
|
||||
|
||||
// Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
|
||||
// Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
|
||||
// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0);
|
||||
// Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible
|
||||
// performance when working on high resolution multi-display setups.
|
||||
// Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible
|
||||
// performance when working on high resolution multi-display setups.
|
||||
// wxSystemOptions::SetOption("msw.notebook.themed-background", 0);
|
||||
|
||||
// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION;
|
||||
|
||||
std::string msg = Http::tls_global_init();
|
||||
std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location");
|
||||
bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store();
|
||||
|
||||
if (!msg.empty() && !ssl_accept) {
|
||||
wxRichMessageDialog
|
||||
dlg(nullptr,
|
||||
wxString::Format(_(L("%s\nDo you want to continue?")), msg),
|
||||
"PrusaSlicer", wxICON_QUESTION | wxYES_NO);
|
||||
dlg.ShowCheckBox(_(L("Remember my choice")));
|
||||
if (dlg.ShowModal() != wxID_YES) return false;
|
||||
|
||||
// Set the Slic3r data directory at the Slic3r XS module.
|
||||
// Unix: ~/ .Slic3r
|
||||
// Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
|
||||
// Mac : "~/Library/Application Support/Slic3r"
|
||||
if (data_dir().empty())
|
||||
set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
|
||||
|
||||
app_config = new AppConfig();
|
||||
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();
|
||||
|
||||
// load settings
|
||||
app_conf_exists = app_config->exists();
|
||||
if (app_conf_exists) {
|
||||
app_config->load();
|
||||
}
|
||||
|
||||
app_config->set("version", SLIC3R_VERSION);
|
||||
app_config->save();
|
||||
|
||||
#ifdef __WXMSW__
|
||||
associate_3mf_files();
|
||||
#endif // __WXMSW__
|
||||
|
|
@ -387,6 +427,8 @@ bool GUI_App::on_init_inner()
|
|||
if (! plater_)
|
||||
return;
|
||||
|
||||
//m_other_instance_message_handler->report();
|
||||
|
||||
if (app_config->dirty() && app_config->get("autosave") == "1")
|
||||
app_config->save();
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class PrintHostJobQueue;
|
|||
|
||||
namespace GUI{
|
||||
class RemovableDriveManager;
|
||||
class OtherInstanceMessageHandler;
|
||||
enum FileType
|
||||
{
|
||||
FT_STL,
|
||||
|
|
@ -108,7 +109,7 @@ class GUI_App : public wxApp
|
|||
std::unique_ptr<ImGuiWrapper> m_imgui;
|
||||
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
|
||||
ConfigWizard* m_wizard; // Managed by wxWindow tree
|
||||
|
||||
std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
|
||||
public:
|
||||
bool OnInit() override;
|
||||
bool initialized() const { return m_initialized; }
|
||||
|
|
@ -196,6 +197,7 @@ public:
|
|||
std::vector<Tab *> tabs_list;
|
||||
|
||||
RemovableDriveManager* removable_drive_manager() { return m_removable_drive_manager.get(); }
|
||||
OtherInstanceMessageHandler* other_instance_message_handler() { return m_other_instance_message_handler.get(); }
|
||||
|
||||
ImGuiWrapper* imgui() { return m_imgui.get(); }
|
||||
|
||||
|
|
@ -211,6 +213,7 @@ public:
|
|||
|
||||
private:
|
||||
bool on_init_inner();
|
||||
void init_app_config();
|
||||
void window_pos_save(wxTopLevelWindow* window, const std::string &name);
|
||||
void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false);
|
||||
void window_pos_sanitize(wxTopLevelWindow* window);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ bool GLGizmoFdmSupports::on_init()
|
|||
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;
|
||||
}
|
||||
|
|
@ -101,10 +103,14 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
|
|||
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glMultMatrixd(trafo_matrix.data()));
|
||||
glsafe(::glColor4f(0.2f, 0.2f, 1.0f, 0.5f));
|
||||
m_ivas[mesh_id][0].render();
|
||||
glsafe(::glColor4f(1.f, 0.2f, 0.2f, 0.5f));
|
||||
m_ivas[mesh_id][1].render();
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -187,7 +193,16 @@ void GLGizmoFdmSupports::update_mesh()
|
|||
// 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(), SelType::NONE);
|
||||
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
|
||||
|
|
@ -243,16 +258,16 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
|| action == SLAGizmoEventType::RightDown
|
||||
|| (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) {
|
||||
|
||||
SelType new_state = SelType::NONE;
|
||||
FacetSupportType new_state = FacetSupportType::NONE;
|
||||
if (! shift_down) {
|
||||
if (action == SLAGizmoEventType::Dragging)
|
||||
new_state = m_button_down == Button::Left
|
||||
? SelType::ENFORCER
|
||||
: SelType::BLOCKER;
|
||||
? FacetSupportType::ENFORCER
|
||||
: FacetSupportType::BLOCKER;
|
||||
else
|
||||
new_state = action == SLAGizmoEventType::LeftDown
|
||||
? SelType::ENFORCER
|
||||
: SelType::BLOCKER;
|
||||
? FacetSupportType::ENFORCER
|
||||
: FacetSupportType::BLOCKER;
|
||||
}
|
||||
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
|
|
@ -379,9 +394,9 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
|
||||
// Now just select all facets that passed.
|
||||
for (size_t next_facet : facets_to_select) {
|
||||
SelType& facet = m_selected_facets[mesh_id][next_facet];
|
||||
FacetSupportType& facet = m_selected_facets[mesh_id][next_facet];
|
||||
|
||||
if (facet != new_state && facet != SelType::NONE) {
|
||||
if (facet != new_state && facet != FacetSupportType::NONE) {
|
||||
// this triangle is currently in the other VBA.
|
||||
// Both VBAs need to be refreshed.
|
||||
update_both = true;
|
||||
|
|
@ -391,8 +406,8 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
}
|
||||
|
||||
update_vertex_buffers(mv, mesh_id,
|
||||
new_state == SelType::ENFORCER || update_both,
|
||||
new_state == SelType::BLOCKER || update_both
|
||||
new_state == FacetSupportType::ENFORCER || update_both,
|
||||
new_state == FacetSupportType::BLOCKER || update_both
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -416,6 +431,18 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
@ -430,16 +457,16 @@ void GLGizmoFdmSupports::update_vertex_buffers(const ModelVolume* mv,
|
|||
{
|
||||
const TriangleMesh* mesh = &mv->mesh();
|
||||
|
||||
for (SelType type : {SelType::ENFORCER, SelType::BLOCKER}) {
|
||||
if ((type == SelType::ENFORCER && ! update_enforcers)
|
||||
|| (type == SelType::BLOCKER && ! update_blockers))
|
||||
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==SelType::ENFORCER ? 0 : 1];
|
||||
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) {
|
||||
SelType status = m_selected_facets[mesh_id][facet_idx];
|
||||
FacetSupportType status = m_selected_facets[mesh_id][facet_idx];
|
||||
if (status != type)
|
||||
continue;
|
||||
for (int i=0; i<3; ++i)
|
||||
|
|
@ -448,7 +475,8 @@ void GLGizmoFdmSupports::update_vertex_buffers(const ModelVolume* mv,
|
|||
iva.push_triangle(3*triangle_cnt, 3*triangle_cnt+1, 3*triangle_cnt+2);
|
||||
++triangle_cnt;
|
||||
}
|
||||
iva.finalize_geometry(true);
|
||||
if (! m_selected_facets[mesh_id].empty())
|
||||
iva.finalize_geometry(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -466,6 +494,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
|||
// 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;
|
||||
|
|
@ -479,6 +508,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
|||
|
||||
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);
|
||||
|
|
@ -494,6 +524,20 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
|||
|
||||
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"));
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class FacetSupportType : int8_t;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
|
|
@ -26,15 +29,9 @@ private:
|
|||
static constexpr float CursorRadiusMax = 8.f;
|
||||
static constexpr float CursorRadiusStep = 0.2f;
|
||||
|
||||
enum class SelType : int8_t {
|
||||
NONE,
|
||||
ENFORCER,
|
||||
BLOCKER
|
||||
};
|
||||
|
||||
// For each model-part volume, store a list of statuses of
|
||||
// individual facets (one of the enum values above).
|
||||
std::vector<std::vector<SelType>> m_selected_facets;
|
||||
std::vector<std::vector<FacetSupportType>> m_selected_facets;
|
||||
|
||||
// Store two vertex buffer arrays (for enforcers/blockers)
|
||||
// for each model-part volume.
|
||||
|
|
|
|||
495
src/slic3r/GUI/InstanceCheck.cpp
Normal file
495
src/slic3r/GUI/InstanceCheck.cpp
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
#include "GUI_App.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
|
||||
#include "boost/nowide/convert.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#if __linux__
|
||||
#include <dbus/dbus.h> /* Pull in all of D-Bus headers. */
|
||||
#endif //__linux__
|
||||
|
||||
namespace Slic3r {
|
||||
namespace instance_check_internal
|
||||
{
|
||||
struct CommandLineAnalysis
|
||||
{
|
||||
bool should_send;
|
||||
std::string cl_string;
|
||||
};
|
||||
static CommandLineAnalysis process_command_line(int argc, char** argv) //d:\3dmodels\Klapka\Klapka.3mf
|
||||
{
|
||||
CommandLineAnalysis ret { false };
|
||||
if (argc < 2)
|
||||
return ret;
|
||||
ret.cl_string = argv[0];
|
||||
for (size_t i = 1; i < argc; i++) {
|
||||
std::string token = argv[i];
|
||||
if (token == "--single-instance") {
|
||||
ret.should_send = true;
|
||||
} else {
|
||||
ret.cl_string += " ";
|
||||
ret.cl_string += token;
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << "single instance: "<< ret.should_send << ". other params: " << ret.cl_string;
|
||||
return ret;
|
||||
}
|
||||
} //namespace instance_check_internal
|
||||
|
||||
#if _WIN32
|
||||
|
||||
namespace instance_check_internal
|
||||
{
|
||||
static HWND l_prusa_slicer_hwnd;
|
||||
static BOOL CALLBACK EnumWindowsProc(_In_ HWND hwnd, _In_ LPARAM lParam)
|
||||
{
|
||||
//checks for other instances of prusaslicer, if found brings it to front and return false to stop enumeration and quit this instance
|
||||
//search is done by classname(wxWindowNR is wxwidgets thing, so probably not unique) and name in window upper panel
|
||||
//other option would be do a mutex and check for its existence
|
||||
TCHAR wndText[1000];
|
||||
TCHAR className[1000];
|
||||
GetClassName(hwnd, className, 1000);
|
||||
GetWindowText(hwnd, wndText, 1000);
|
||||
std::wstring classNameString(className);
|
||||
std::wstring wndTextString(wndText);
|
||||
if (wndTextString.find(L"PrusaSlicer") != std::wstring::npos && classNameString == L"wxWindowNR") {
|
||||
l_prusa_slicer_hwnd = hwnd;
|
||||
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
|
||||
SetForegroundWindow(hwnd);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static void send_message(const HWND hwnd)
|
||||
{
|
||||
LPWSTR command_line_args = GetCommandLine();
|
||||
//Create a COPYDATASTRUCT to send the information
|
||||
//cbData represents the size of the information we want to send.
|
||||
//lpData represents the information we want to send.
|
||||
//dwData is an ID defined by us(this is a type of ID different than WM_COPYDATA).
|
||||
COPYDATASTRUCT data_to_send = { 0 };
|
||||
data_to_send.dwData = 1;
|
||||
data_to_send.cbData = sizeof(TCHAR) * (wcslen(command_line_args) + 1);
|
||||
data_to_send.lpData = command_line_args;
|
||||
|
||||
SendMessage(hwnd, WM_COPYDATA, 0, (LPARAM)&data_to_send);
|
||||
}
|
||||
} //namespace instance_check_internal
|
||||
|
||||
bool instance_check(int argc, char** argv, bool app_config_single_instance)
|
||||
{
|
||||
instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv);
|
||||
if (cla.should_send || app_config_single_instance) {
|
||||
// Call EnumWidnows with own callback. cons: Based on text in the name of the window and class name which is generic.
|
||||
if (!EnumWindows(instance_check_internal::EnumWindowsProc, 0)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate.";
|
||||
instance_check_internal::send_message(instance_check_internal::l_prusa_slicer_hwnd);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set.";
|
||||
return false;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
namespace instance_check_internal
|
||||
{
|
||||
static int get_lock()
|
||||
{
|
||||
struct flock fl;
|
||||
int fdlock;
|
||||
fl.l_type = F_WRLCK;
|
||||
fl.l_whence = SEEK_SET;
|
||||
fl.l_start = 0;
|
||||
fl.l_len = 1;
|
||||
|
||||
if ((fdlock = open("/tmp/prusaslicer.lock", O_WRONLY | O_CREAT, 0666)) == -1)
|
||||
return 0;
|
||||
|
||||
if (fcntl(fdlock, F_SETLK, &fl) == -1)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
} //namespace instance_check_internal
|
||||
|
||||
bool instance_check(int argc, char** argv, bool app_config_single_instance)
|
||||
{
|
||||
instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv);
|
||||
if (!instance_check_internal::get_lock() && (cla.should_send || app_config_single_instance)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate.";
|
||||
send_message_mac(cla.cl_string);
|
||||
return true;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set.";
|
||||
return false;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
namespace instance_check_internal
|
||||
{
|
||||
static int get_lock()
|
||||
{
|
||||
struct flock fl;
|
||||
int fdlock;
|
||||
fl.l_type = F_WRLCK;
|
||||
fl.l_whence = SEEK_SET;
|
||||
fl.l_start = 0;
|
||||
fl.l_len = 1;
|
||||
|
||||
if ((fdlock = open("/tmp/prusaslicer.lock", O_WRONLY | O_CREAT, 0666)) == -1)
|
||||
return 0;
|
||||
|
||||
if (fcntl(fdlock, F_SETLK, &fl) == -1)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void send_message(std::string message_text)
|
||||
{
|
||||
DBusMessage* msg;
|
||||
DBusMessageIter args;
|
||||
DBusConnection* conn;
|
||||
DBusError err;
|
||||
dbus_uint32_t serial = 0;
|
||||
const char* sigval = message_text.c_str();
|
||||
std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck";
|
||||
std::string method_name = "AnotherInstace";
|
||||
std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck";
|
||||
|
||||
|
||||
// initialise the error value
|
||||
dbus_error_init(&err);
|
||||
|
||||
// connect to bus, and check for errors (use SESSION bus everywhere!)
|
||||
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
|
||||
if (dbus_error_is_set(&err)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DBus Connection Error. Message to another instance wont be send.";
|
||||
BOOST_LOG_TRIVIAL(error) << "DBus Connection Error: "<< err.message;
|
||||
dbus_error_free(&err);
|
||||
return;
|
||||
}
|
||||
if (NULL == conn) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DBus Connection is NULL. Message to another instance wont be send.";
|
||||
return;
|
||||
}
|
||||
|
||||
//some sources do request interface ownership before constructing msg but i think its wrong.
|
||||
|
||||
//create new method call message
|
||||
msg = dbus_message_new_method_call(interface_name.c_str(), object_name.c_str(), interface_name.c_str(), method_name.c_str());
|
||||
if (NULL == msg) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DBus Message is NULL. Message to another instance wont be send.";
|
||||
dbus_connection_unref(conn);
|
||||
return;
|
||||
}
|
||||
//the AnotherInstace method is not sending reply.
|
||||
dbus_message_set_no_reply(msg, TRUE);
|
||||
|
||||
//append arguments to message
|
||||
if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &sigval, DBUS_TYPE_INVALID)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Ran out of memory while constructing args for DBus message. Message to another instance wont be send.";
|
||||
dbus_message_unref(msg);
|
||||
dbus_connection_unref(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
// send the message and flush the connection
|
||||
if (!dbus_connection_send(conn, msg, &serial)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Ran out of memory while sending DBus message.";
|
||||
dbus_message_unref(msg);
|
||||
dbus_connection_unref(conn);
|
||||
return;
|
||||
}
|
||||
dbus_connection_flush(conn);
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "DBus message sent.";
|
||||
|
||||
// free the message and close the connection
|
||||
dbus_message_unref(msg);
|
||||
dbus_connection_unref(conn);
|
||||
}
|
||||
} //namespace instance_check_internal
|
||||
|
||||
bool instance_check(int argc, char** argv, bool app_config_single_instance)
|
||||
{
|
||||
instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv);
|
||||
if (!instance_check_internal::get_lock() && (cla.should_send || app_config_single_instance)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate.";
|
||||
instance_check_internal::send_message(cla.cl_string);
|
||||
return true;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set.";
|
||||
return false;
|
||||
}
|
||||
#endif //_WIN32/__APPLE__/__linux__
|
||||
|
||||
|
||||
|
||||
namespace GUI {
|
||||
|
||||
wxDEFINE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
|
||||
wxDEFINE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);
|
||||
|
||||
void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler)
|
||||
{
|
||||
assert(!m_initialized);
|
||||
assert(m_callback_evt_handler == nullptr);
|
||||
if (m_initialized)
|
||||
return;
|
||||
|
||||
m_initialized = true;
|
||||
m_callback_evt_handler = callback_evt_handler;
|
||||
|
||||
#if _WIN32
|
||||
//create_listener_window();
|
||||
#endif //_WIN32
|
||||
|
||||
#if defined(__APPLE__)
|
||||
this->register_for_messages();
|
||||
#endif //__APPLE__
|
||||
|
||||
#ifdef BACKGROUND_MESSAGE_LISTENER
|
||||
m_thread = boost::thread((boost::bind(&OtherInstanceMessageHandler::listen, this)));
|
||||
#endif //BACKGROUND_MESSAGE_LISTENER
|
||||
}
|
||||
void OtherInstanceMessageHandler::shutdown()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "message handler shutdown().";
|
||||
assert(m_initialized);
|
||||
if (m_initialized) {
|
||||
#if __APPLE__
|
||||
//delete macos implementation
|
||||
this->unregister_for_messages();
|
||||
#endif //__APPLE__
|
||||
#ifdef BACKGROUND_MESSAGE_LISTENER
|
||||
if (m_thread.joinable()) {
|
||||
// Stop the worker thread, if running.
|
||||
{
|
||||
// Notify the worker thread to cancel wait on detection polling.
|
||||
std::lock_guard<std::mutex> lck(m_thread_stop_mutex);
|
||||
m_stop = true;
|
||||
}
|
||||
m_thread_stop_condition.notify_all();
|
||||
// Wait for the worker thread to stop.
|
||||
m_thread.join();
|
||||
m_stop = false;
|
||||
}
|
||||
#endif //BACKGROUND_MESSAGE_LISTENER
|
||||
m_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MessageHandlerInternal
|
||||
{
|
||||
// returns ::path to possible model or empty ::path if input string is not existing path
|
||||
static boost::filesystem::path get_path(const std::string possible_path)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "message part: " << possible_path;
|
||||
|
||||
if (possible_path.empty() || possible_path.size() < 3) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "empty";
|
||||
return boost::filesystem::path();
|
||||
}
|
||||
if (boost::filesystem::exists(possible_path)) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "is path";
|
||||
return boost::filesystem::path(possible_path);
|
||||
} else if (possible_path[0] == '\"') {
|
||||
if(boost::filesystem::exists(possible_path.substr(1, possible_path.size() - 2))) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "is path in quotes";
|
||||
return boost::filesystem::path(possible_path.substr(1, possible_path.size() - 2));
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << "is NOT path";
|
||||
return boost::filesystem::path();
|
||||
}
|
||||
} //namespace MessageHandlerInternal
|
||||
|
||||
void OtherInstanceMessageHandler::handle_message(const std::string message) {
|
||||
std::vector<boost::filesystem::path> paths;
|
||||
auto next_space = message.find(' ');
|
||||
size_t last_space = 0;
|
||||
int counter = 0;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "message from other instance: " << message;
|
||||
|
||||
while (next_space != std::string::npos)
|
||||
{
|
||||
if (counter != 0) {
|
||||
const std::string possible_path = message.substr(last_space, next_space - last_space);
|
||||
boost::filesystem::path p = MessageHandlerInternal::get_path(possible_path);
|
||||
if(!p.string().empty())
|
||||
paths.emplace_back(p);
|
||||
}
|
||||
last_space = next_space;
|
||||
next_space = message.find(' ', last_space + 1);
|
||||
counter++;
|
||||
}
|
||||
if (counter != 0 ) {
|
||||
boost::filesystem::path p = MessageHandlerInternal::get_path(message.substr(last_space + 1));
|
||||
if (!p.string().empty())
|
||||
paths.emplace_back(p);
|
||||
}
|
||||
if (!paths.empty()) {
|
||||
//wxEvtHandler* evt_handler = wxGetApp().plater(); //assert here?
|
||||
//if (evt_handler) {
|
||||
wxPostEvent(m_callback_evt_handler, LoadFromOtherInstanceEvent(GUI::EVT_LOAD_MODEL_OTHER_INSTANCE, std::vector<boost::filesystem::path>(std::move(paths))));
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef BACKGROUND_MESSAGE_LISTENER
|
||||
|
||||
namespace MessageHandlerDBusInternal
|
||||
{
|
||||
//reply to introspect makes our DBus object visible for other programs like D-Feet
|
||||
static void respond_to_introspect(DBusConnection *connection, DBusMessage *request)
|
||||
{
|
||||
DBusMessage *reply;
|
||||
const char *introspection_data =
|
||||
" <!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" "
|
||||
"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">"
|
||||
" <!-- dbus-sharp 0.8.1 -->"
|
||||
" <node>"
|
||||
" <interface name=\"org.freedesktop.DBus.Introspectable\">"
|
||||
" <method name=\"Introspect\">"
|
||||
" <arg name=\"data\" direction=\"out\" type=\"s\" />"
|
||||
" </method>"
|
||||
" </interface>"
|
||||
" <interface name=\"com.prusa3d.prusaslicer.InstanceCheck\">"
|
||||
" <method name=\"AnotherInstace\">"
|
||||
" <arg name=\"data\" direction=\"in\" type=\"s\" />"
|
||||
" </method>"
|
||||
" </interface>"
|
||||
" </node>";
|
||||
|
||||
reply = dbus_message_new_method_return(request);
|
||||
dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_data, DBUS_TYPE_INVALID);
|
||||
dbus_connection_send(connection, reply, NULL);
|
||||
dbus_message_unref(reply);
|
||||
}
|
||||
//method AnotherInstance receives message from another PrusaSlicer instance
|
||||
static void handle_method_another_instance(DBusConnection *connection, DBusMessage *request)
|
||||
{
|
||||
DBusError err;
|
||||
char* text= "";
|
||||
wxEvtHandler* evt_handler;
|
||||
|
||||
dbus_error_init(&err);
|
||||
dbus_message_get_args(request, &err, DBUS_TYPE_STRING, &text, DBUS_TYPE_INVALID);
|
||||
if (dbus_error_is_set(&err)) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Dbus method AnotherInstance received with wrong arguments.";
|
||||
dbus_error_free(&err);
|
||||
return;
|
||||
}
|
||||
wxGetApp().other_instance_message_handler()->handle_message(text);
|
||||
|
||||
evt_handler = wxGetApp().plater();
|
||||
if (evt_handler) {
|
||||
wxPostEvent(evt_handler, InstanceGoToFrontEvent(EVT_INSTANCE_GO_TO_FRONT));
|
||||
}
|
||||
}
|
||||
//every dbus message received comes here
|
||||
static DBusHandlerResult handle_dbus_object_message(DBusConnection *connection, DBusMessage *message, void *user_data)
|
||||
{
|
||||
const char* interface_name = dbus_message_get_interface(message);
|
||||
const char* member_name = dbus_message_get_member(message);
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "DBus message received: interface: " << interface_name << ", member: " << member_name;
|
||||
|
||||
if (0 == strcmp("org.freedesktop.DBus.Introspectable", interface_name) && 0 == strcmp("Introspect", member_name)) {
|
||||
respond_to_introspect(connection, message);
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
} else if (0 == strcmp("com.prusa3d.prusaslicer.InstanceCheck", interface_name) && 0 == strcmp("AnotherInstace", member_name)) {
|
||||
handle_method_another_instance(connection, message);
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
}
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
} //namespace MessageHandlerDBusInternal
|
||||
|
||||
void OtherInstanceMessageHandler::listen()
|
||||
{
|
||||
DBusConnection* conn;
|
||||
DBusError err;
|
||||
int name_req_val;
|
||||
DBusObjectPathVTable vtable;
|
||||
std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck";
|
||||
std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck";
|
||||
|
||||
dbus_error_init(&err);
|
||||
|
||||
// connect to the bus and check for errors (use SESSION bus everywhere!)
|
||||
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
|
||||
if (dbus_error_is_set(&err)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DBus Connection Error: "<< err.message;
|
||||
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
|
||||
dbus_error_free(&err);
|
||||
return;
|
||||
}
|
||||
if (NULL == conn) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DBus Connection is NULL. Dbus Messages listening terminating.";
|
||||
return;
|
||||
}
|
||||
|
||||
// request our name on the bus and check for errors
|
||||
name_req_val = dbus_bus_request_name(conn, interface_name.c_str(), DBUS_NAME_FLAG_REPLACE_EXISTING , &err);
|
||||
if (dbus_error_is_set(&err)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DBus Request name Error: "<< err.message;
|
||||
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
|
||||
dbus_error_free(&err);
|
||||
dbus_connection_unref(conn);
|
||||
return;
|
||||
}
|
||||
if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != name_req_val) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Not primary owner of DBus name - probably another PrusaSlicer instance is running.";
|
||||
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
|
||||
dbus_connection_unref(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set callbacks. Unregister function should not be nessary.
|
||||
vtable.message_function = MessageHandlerDBusInternal::handle_dbus_object_message;
|
||||
vtable.unregister_function = NULL;
|
||||
|
||||
// register new object - this is our access to DBus
|
||||
dbus_connection_try_register_object_path(conn, object_name.c_str(), &vtable, NULL, &err);
|
||||
if ( dbus_error_is_set(&err) ) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DBus Register object Error: "<< err.message;
|
||||
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
|
||||
dbus_connection_unref(conn);
|
||||
dbus_error_free(&err);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Dbus object registered. Starting listening for messages.";
|
||||
|
||||
for (;;) {
|
||||
// Wait for 1 second
|
||||
// Cancellable.
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(m_thread_stop_mutex);
|
||||
m_thread_stop_condition.wait_for(lck, std::chrono::seconds(1), [this] { return m_stop; });
|
||||
}
|
||||
if (m_stop)
|
||||
// Stop the worker thread.
|
||||
|
||||
break;
|
||||
//dispatch should do all the work with incoming messages
|
||||
//second parameter is blocking time that funciton waits for new messages
|
||||
//that is handled here with our own event loop above
|
||||
dbus_connection_read_write_dispatch(conn, 0);
|
||||
}
|
||||
|
||||
dbus_connection_unref(conn);
|
||||
}
|
||||
#endif //BACKGROUND_MESSAGE_LISTENER
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
91
src/slic3r/GUI/InstanceCheck.hpp
Normal file
91
src/slic3r/GUI/InstanceCheck.hpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#ifndef slic3r_InstanceCheck_hpp_
|
||||
#define slic3r_InstanceCheck_hpp_
|
||||
|
||||
#include "Event.hpp"
|
||||
|
||||
#if _WIN32
|
||||
#include <windows.h>
|
||||
#endif //_WIN32
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
#include <tbb/mutex.h>
|
||||
#include <condition_variable>
|
||||
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
// checks for other running instances and sends them argv,
|
||||
// if there is --single-instance argument or AppConfig is set to single_instance=1
|
||||
// returns true if this instance should terminate
|
||||
bool instance_check(int argc, char** argv, bool app_config_single_instance);
|
||||
|
||||
#if __APPLE__
|
||||
// apple implementation of inner functions of instance_check
|
||||
// in InstanceCheckMac.mm
|
||||
void send_message_mac(const std::string msg);
|
||||
#endif //__APPLE__
|
||||
|
||||
namespace GUI {
|
||||
|
||||
#if __linux__
|
||||
#define BACKGROUND_MESSAGE_LISTENER
|
||||
#endif // __linux__
|
||||
|
||||
using LoadFromOtherInstanceEvent = Event<std::vector<boost::filesystem::path>>;
|
||||
wxDECLARE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
|
||||
|
||||
using InstanceGoToFrontEvent = SimpleEvent;
|
||||
wxDECLARE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);
|
||||
|
||||
class OtherInstanceMessageHandler
|
||||
{
|
||||
public:
|
||||
OtherInstanceMessageHandler() = default;
|
||||
OtherInstanceMessageHandler(OtherInstanceMessageHandler const&) = delete;
|
||||
void operator=(OtherInstanceMessageHandler const&) = delete;
|
||||
~OtherInstanceMessageHandler() { assert(!m_initialized); }
|
||||
|
||||
// inits listening, on each platform different. On linux starts background thread
|
||||
void init(wxEvtHandler* callback_evt_handler);
|
||||
// stops listening, on linux stops the background thread
|
||||
void shutdown();
|
||||
|
||||
//finds paths to models in message(= command line arguments, first should be prusaSlicer executable)
|
||||
//and sends them to plater via LoadFromOtherInstanceEvent
|
||||
//security of messages: from message all existing paths are proccesed to load model
|
||||
// win32 - anybody who has hwnd can send message.
|
||||
// mac - anybody who posts notification with name:@"OtherPrusaSlicerTerminating"
|
||||
// linux - instrospectable on dbus
|
||||
void handle_message(const std::string message);
|
||||
private:
|
||||
bool m_initialized { false };
|
||||
wxEvtHandler* m_callback_evt_handler { nullptr };
|
||||
|
||||
#ifdef BACKGROUND_MESSAGE_LISTENER
|
||||
//worker thread to listen incoming dbus communication
|
||||
boost::thread m_thread;
|
||||
std::condition_variable m_thread_stop_condition;
|
||||
mutable std::mutex m_thread_stop_mutex;
|
||||
bool m_stop{ false };
|
||||
bool m_start{ true };
|
||||
|
||||
// background thread method
|
||||
void listen();
|
||||
#endif //BACKGROUND_MESSAGE_LISTENER
|
||||
|
||||
#if __APPLE__
|
||||
//implemented at InstanceCheckMac.mm
|
||||
void register_for_messages();
|
||||
void unregister_for_messages();
|
||||
// Opaque pointer to RemovableDriveManagerMM
|
||||
void* m_impl_osx;
|
||||
#endif //__APPLE__
|
||||
|
||||
};
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_InstanceCheck_hpp_
|
||||
8
src/slic3r/GUI/InstanceCheckMac.h
Normal file
8
src/slic3r/GUI/InstanceCheckMac.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface OtherInstanceMessageHandlerMac : NSObject
|
||||
|
||||
-(instancetype) init;
|
||||
-(void) add_observer;
|
||||
-(void) message_update:(NSNotification *)note;
|
||||
@end
|
||||
68
src/slic3r/GUI/InstanceCheckMac.mm
Normal file
68
src/slic3r/GUI/InstanceCheckMac.mm
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#import "InstanceCheck.hpp"
|
||||
#import "InstanceCheckMac.h"
|
||||
#import "GUI_App.hpp"
|
||||
|
||||
@implementation OtherInstanceMessageHandlerMac
|
||||
|
||||
-(instancetype) init
|
||||
{
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
-(void)add_observer
|
||||
{
|
||||
NSLog(@"adding observer");
|
||||
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(message_update:) name:@"OtherPrusaSlicerInstanceMessage" object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
|
||||
}
|
||||
|
||||
-(void)message_update:(NSNotification *)msg
|
||||
{
|
||||
//NSLog(@"recieved msg %@", msg);
|
||||
//demiaturize all windows
|
||||
for(NSWindow* win in [NSApp windows])
|
||||
{
|
||||
if([win isMiniaturized])
|
||||
{
|
||||
[win deminiaturize:self];
|
||||
}
|
||||
}
|
||||
//bring window to front
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps : YES];
|
||||
//pass message
|
||||
Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(std::string([msg.userInfo[@"data"] UTF8String]));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void send_message_mac(const std::string msg)
|
||||
{
|
||||
NSString *nsmsg = [NSString stringWithCString:msg.c_str() encoding:[NSString defaultCStringEncoding]];
|
||||
//NSLog(@"sending msg %@", nsmsg);
|
||||
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"OtherPrusaSlicerInstanceMessage" object:nil userInfo:[NSDictionary dictionaryWithObject:nsmsg forKey:@"data"] deliverImmediately:YES];
|
||||
}
|
||||
|
||||
namespace GUI {
|
||||
void OtherInstanceMessageHandler::register_for_messages()
|
||||
{
|
||||
m_impl_osx = [[OtherInstanceMessageHandlerMac alloc] init];
|
||||
if(m_impl_osx) {
|
||||
[m_impl_osx add_observer];
|
||||
}
|
||||
}
|
||||
|
||||
void OtherInstanceMessageHandler::unregister_for_messages()
|
||||
{
|
||||
//NSLog(@"unreegistering other instance messages");
|
||||
if (m_impl_osx) {
|
||||
[m_impl_osx release];
|
||||
m_impl_osx = nullptr;
|
||||
} else {
|
||||
NSLog(@"unreegister not required");
|
||||
}
|
||||
}
|
||||
}//namespace GUI
|
||||
}//namespace Slicer
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
223
src/slic3r/GUI/Jobs/ArrangeJob.cpp
Normal file
223
src/slic3r/GUI/Jobs/ArrangeJob.cpp
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
#include "ArrangeJob.hpp"
|
||||
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Cache the wti info
|
||||
class WipeTower: public GLCanvas3D::WipeTowerInfo {
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
public:
|
||||
explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti)
|
||||
: GLCanvas3D::WipeTowerInfo(wti)
|
||||
{}
|
||||
|
||||
explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti)
|
||||
: GLCanvas3D::WipeTowerInfo(std::move(wti))
|
||||
{}
|
||||
|
||||
void apply_arrange_result(const Vec2d& tr, double rotation)
|
||||
{
|
||||
m_pos = unscaled(tr); m_rotation = rotation;
|
||||
apply_wipe_tower();
|
||||
}
|
||||
|
||||
ArrangePolygon get_arrange_polygon() const
|
||||
{
|
||||
Polygon ap({
|
||||
{coord_t(0), coord_t(0)},
|
||||
{scaled(m_bb_size(X)), coord_t(0)},
|
||||
{scaled(m_bb_size)},
|
||||
{coord_t(0), scaled(m_bb_size(Y))},
|
||||
{coord_t(0), coord_t(0)},
|
||||
});
|
||||
|
||||
ArrangePolygon ret;
|
||||
ret.poly.contour = std::move(ap);
|
||||
ret.translation = scaled(m_pos);
|
||||
ret.rotation = m_rotation;
|
||||
ret.priority++;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
static WipeTower get_wipe_tower(Plater &plater)
|
||||
{
|
||||
return WipeTower{plater.canvas3D()->get_wipe_tower_info()};
|
||||
}
|
||||
|
||||
void ArrangeJob::clear_input()
|
||||
{
|
||||
const Model &model = m_plater->model();
|
||||
|
||||
size_t count = 0, cunprint = 0; // To know how much space to reserve
|
||||
for (auto obj : model.objects)
|
||||
for (auto mi : obj->instances)
|
||||
mi->printable ? count++ : cunprint++;
|
||||
|
||||
m_selected.clear();
|
||||
m_unselected.clear();
|
||||
m_unprintable.clear();
|
||||
m_selected.reserve(count + 1 /* for optional wti */);
|
||||
m_unselected.reserve(count + 1 /* for optional wti */);
|
||||
m_unprintable.reserve(cunprint /* for optional wti */);
|
||||
}
|
||||
|
||||
double ArrangeJob::bed_stride() const {
|
||||
double bedwidth = m_plater->bed_shape_bb().size().x();
|
||||
return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
|
||||
}
|
||||
|
||||
void ArrangeJob::prepare_all() {
|
||||
clear_input();
|
||||
|
||||
for (ModelObject *obj: m_plater->model().objects)
|
||||
for (ModelInstance *mi : obj->instances) {
|
||||
ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
|
||||
cont.emplace_back(get_arrange_poly(mi));
|
||||
}
|
||||
|
||||
if (auto wti = get_wipe_tower(*m_plater))
|
||||
m_selected.emplace_back(wti.get_arrange_polygon());
|
||||
}
|
||||
|
||||
void ArrangeJob::prepare_selected() {
|
||||
clear_input();
|
||||
|
||||
Model &model = m_plater->model();
|
||||
double stride = bed_stride();
|
||||
|
||||
std::vector<const Selection::InstanceIdxsList *>
|
||||
obj_sel(model.objects.size(), nullptr);
|
||||
|
||||
for (auto &s : m_plater->get_selection().get_content())
|
||||
if (s.first < int(obj_sel.size()))
|
||||
obj_sel[size_t(s.first)] = &s.second;
|
||||
|
||||
// Go through the objects and check if inside the selection
|
||||
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
|
||||
const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
|
||||
ModelObject *mo = model.objects[oidx];
|
||||
|
||||
std::vector<bool> inst_sel(mo->instances.size(), false);
|
||||
|
||||
if (instlist)
|
||||
for (auto inst_id : *instlist)
|
||||
inst_sel[size_t(inst_id)] = true;
|
||||
|
||||
for (size_t i = 0; i < inst_sel.size(); ++i) {
|
||||
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
|
||||
|
||||
ArrangePolygons &cont = mo->instances[i]->printable ?
|
||||
(inst_sel[i] ? m_selected :
|
||||
m_unselected) :
|
||||
m_unprintable;
|
||||
|
||||
cont.emplace_back(std::move(ap));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto wti = get_wipe_tower(*m_plater)) {
|
||||
ArrangePolygon &&ap = get_arrange_poly(&wti);
|
||||
|
||||
m_plater->get_selection().is_wipe_tower() ?
|
||||
m_selected.emplace_back(std::move(ap)) :
|
||||
m_unselected.emplace_back(std::move(ap));
|
||||
}
|
||||
|
||||
// If the selection was empty arrange everything
|
||||
if (m_selected.empty()) m_selected.swap(m_unselected);
|
||||
|
||||
// The strides have to be removed from the fixed items. For the
|
||||
// arrangeable (selected) items bed_idx is ignored and the
|
||||
// translation is irrelevant.
|
||||
for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
|
||||
}
|
||||
|
||||
void ArrangeJob::prepare()
|
||||
{
|
||||
wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
|
||||
}
|
||||
|
||||
void ArrangeJob::process()
|
||||
{
|
||||
static const auto arrangestr = _(L("Arranging"));
|
||||
|
||||
double dist = min_object_distance(*m_plater->config());
|
||||
|
||||
arrangement::ArrangeParams params;
|
||||
params.min_obj_distance = scaled(dist);
|
||||
|
||||
auto count = unsigned(m_selected.size() + m_unprintable.size());
|
||||
Points bedpts = get_bed_shape(*m_plater->config());
|
||||
|
||||
params.stopcondition = [this]() { return was_canceled(); };
|
||||
|
||||
try {
|
||||
params.progressind = [this, count](unsigned st) {
|
||||
st += m_unprintable.size();
|
||||
if (st > 0) update_status(int(count - st), arrangestr);
|
||||
};
|
||||
|
||||
arrangement::arrange(m_selected, m_unselected, bedpts, params);
|
||||
|
||||
params.progressind = [this, count](unsigned st) {
|
||||
if (st > 0) update_status(int(count - st), arrangestr);
|
||||
};
|
||||
|
||||
arrangement::arrange(m_unprintable, {}, bedpts, params);
|
||||
} catch (std::exception & /*e*/) {
|
||||
GUI::show_error(m_plater,
|
||||
_(L("Could not arrange model objects! "
|
||||
"Some geometries may be invalid.")));
|
||||
}
|
||||
|
||||
// finalize just here.
|
||||
update_status(int(count),
|
||||
was_canceled() ? _(L("Arranging canceled."))
|
||||
: _(L("Arranging done.")));
|
||||
}
|
||||
|
||||
void ArrangeJob::finalize() {
|
||||
// Ignore the arrange result if aborted.
|
||||
if (was_canceled()) return;
|
||||
|
||||
// Unprintable items go to the last virtual bed
|
||||
int beds = 0;
|
||||
|
||||
// Apply the arrange result to all selected objects
|
||||
for (ArrangePolygon &ap : m_selected) {
|
||||
beds = std::max(ap.bed_idx, beds);
|
||||
ap.apply();
|
||||
}
|
||||
|
||||
// Get the virtual beds from the unselected items
|
||||
for (ArrangePolygon &ap : m_unselected)
|
||||
beds = std::max(ap.bed_idx, beds);
|
||||
|
||||
// Move the unprintable items to the last virtual bed.
|
||||
for (ArrangePolygon &ap : m_unprintable) {
|
||||
ap.bed_idx += beds + 1;
|
||||
ap.apply();
|
||||
}
|
||||
|
||||
m_plater->update();
|
||||
|
||||
Job::finalize();
|
||||
}
|
||||
|
||||
arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &plater)
|
||||
{
|
||||
return WipeTower{plater.canvas3D()->get_wipe_tower_info()}.get_arrange_polygon();
|
||||
}
|
||||
|
||||
void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap)
|
||||
{
|
||||
WipeTower{plater.canvas3D()->get_wipe_tower_info()}.apply_arrange_result(ap.translation.cast<double>(), ap.rotation);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
77
src/slic3r/GUI/Jobs/ArrangeJob.hpp
Normal file
77
src/slic3r/GUI/Jobs/ArrangeJob.hpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#ifndef ARRANGEJOB_HPP
|
||||
#define ARRANGEJOB_HPP
|
||||
|
||||
#include "Job.hpp"
|
||||
#include "libslic3r/Arrange.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class Plater;
|
||||
|
||||
class ArrangeJob : public Job
|
||||
{
|
||||
Plater *m_plater;
|
||||
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||
|
||||
// The gap between logical beds in the x axis expressed in ratio of
|
||||
// the current bed width.
|
||||
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
|
||||
|
||||
ArrangePolygons m_selected, m_unselected, m_unprintable;
|
||||
|
||||
// clear m_selected and m_unselected, reserve space for next usage
|
||||
void clear_input();
|
||||
|
||||
// Stride between logical beds
|
||||
double bed_stride() const;
|
||||
|
||||
// Set up arrange polygon for a ModelInstance and Wipe tower
|
||||
template<class T> ArrangePolygon get_arrange_poly(T *obj) const
|
||||
{
|
||||
ArrangePolygon ap = obj->get_arrange_polygon();
|
||||
ap.priority = 0;
|
||||
ap.bed_idx = ap.translation.x() / bed_stride();
|
||||
ap.setter = [obj, this](const ArrangePolygon &p) {
|
||||
if (p.is_arranged()) {
|
||||
Vec2d t = p.translation.cast<double>();
|
||||
t.x() += p.bed_idx * bed_stride();
|
||||
obj->apply_arrange_result(t, p.rotation);
|
||||
}
|
||||
};
|
||||
return ap;
|
||||
}
|
||||
|
||||
// Prepare all objects on the bed regardless of the selection
|
||||
void prepare_all();
|
||||
|
||||
// Prepare the selected and unselected items separately. If nothing is
|
||||
// selected, behaves as if everything would be selected.
|
||||
void prepare_selected();
|
||||
|
||||
protected:
|
||||
|
||||
void prepare() override;
|
||||
|
||||
public:
|
||||
ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: Job{std::move(pri)}, m_plater{plater}
|
||||
{}
|
||||
|
||||
int status_range() const override
|
||||
{
|
||||
return int(m_selected.size() + m_unprintable.size());
|
||||
}
|
||||
|
||||
void process() override;
|
||||
|
||||
void finalize() override;
|
||||
};
|
||||
|
||||
arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &);
|
||||
void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap);
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // ARRANGEJOB_HPP
|
||||
121
src/slic3r/GUI/Jobs/Job.cpp
Normal file
121
src/slic3r/GUI/Jobs/Job.cpp
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include "Job.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void GUI::Job::run()
|
||||
{
|
||||
m_running.store(true);
|
||||
process();
|
||||
m_running.store(false);
|
||||
|
||||
// ensure to call the last status to finalize the job
|
||||
update_status(status_range(), "");
|
||||
}
|
||||
|
||||
void GUI::Job::update_status(int st, const wxString &msg)
|
||||
{
|
||||
auto evt = new wxThreadEvent();
|
||||
evt->SetInt(st);
|
||||
evt->SetString(msg);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
|
||||
: m_progress(std::move(pri))
|
||||
{
|
||||
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
|
||||
auto msg = evt.GetString();
|
||||
if (!msg.empty())
|
||||
m_progress->set_status_text(msg.ToUTF8().data());
|
||||
|
||||
if (m_finalized) return;
|
||||
|
||||
m_progress->set_progress(evt.GetInt());
|
||||
if (evt.GetInt() == status_range()) {
|
||||
// set back the original range and cancel callback
|
||||
m_progress->set_range(m_range);
|
||||
m_progress->set_cancel_callback();
|
||||
wxEndBusyCursor();
|
||||
|
||||
finalize();
|
||||
|
||||
// dont do finalization again for the same process
|
||||
m_finalized = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void GUI::Job::start()
|
||||
{ // Start the job. No effect if the job is already running
|
||||
if (!m_running.load()) {
|
||||
prepare();
|
||||
|
||||
// Save the current status indicatior range and push the new one
|
||||
m_range = m_progress->get_range();
|
||||
m_progress->set_range(status_range());
|
||||
|
||||
// init cancellation flag and set the cancel callback
|
||||
m_canceled.store(false);
|
||||
m_progress->set_cancel_callback(
|
||||
[this]() { m_canceled.store(true); });
|
||||
|
||||
m_finalized = false;
|
||||
|
||||
// Changing cursor to busy
|
||||
wxBeginBusyCursor();
|
||||
|
||||
try { // Execute the job
|
||||
m_thread = create_thread([this] { this->run(); });
|
||||
} catch (std::exception &) {
|
||||
update_status(status_range(),
|
||||
_(L("ERROR: not enough resources to "
|
||||
"execute a new job.")));
|
||||
}
|
||||
|
||||
// The state changes will be undone when the process hits the
|
||||
// last status value, in the status update handler (see ctor)
|
||||
}
|
||||
}
|
||||
|
||||
bool GUI::Job::join(int timeout_ms)
|
||||
{
|
||||
if (!m_thread.joinable()) return true;
|
||||
|
||||
if (timeout_ms <= 0)
|
||||
m_thread.join();
|
||||
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GUI::ExclusiveJobGroup::start(size_t jid) {
|
||||
assert(jid < m_jobs.size());
|
||||
stop_all();
|
||||
m_jobs[jid]->start();
|
||||
}
|
||||
|
||||
void GUI::ExclusiveJobGroup::join_all(int wait_ms)
|
||||
{
|
||||
std::vector<bool> aborted(m_jobs.size(), false);
|
||||
|
||||
for (size_t jid = 0; jid < m_jobs.size(); ++jid)
|
||||
aborted[jid] = m_jobs[jid]->join(wait_ms);
|
||||
|
||||
if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; }))
|
||||
BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
|
||||
}
|
||||
|
||||
bool GUI::ExclusiveJobGroup::is_any_running() const
|
||||
{
|
||||
return std::any_of(m_jobs.begin(), m_jobs.end(),
|
||||
[](const std::unique_ptr<GUI::Job> &j) {
|
||||
return j->is_running();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
110
src/slic3r/GUI/Jobs/Job.hpp
Normal file
110
src/slic3r/GUI/Jobs/Job.hpp
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#ifndef JOB_HPP
|
||||
#define JOB_HPP
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <slic3r/Utils/Thread.hpp>
|
||||
#include <slic3r/GUI/I18N.hpp>
|
||||
|
||||
#include "ProgressIndicator.hpp"
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// A class to handle UI jobs like arranging and optimizing rotation.
|
||||
// These are not instant jobs, the user has to be informed about their
|
||||
// state in the status progress indicator. On the other hand they are
|
||||
// separated from the background slicing process. Ideally, these jobs should
|
||||
// run when the background process is not running.
|
||||
//
|
||||
// TODO: A mechanism would be useful for blocking the plater interactions:
|
||||
// objects would be frozen for the user. In case of arrange, an animation
|
||||
// could be shown, or with the optimize orientations, partial results
|
||||
// could be displayed.
|
||||
class Job : public wxEvtHandler
|
||||
{
|
||||
int m_range = 100;
|
||||
boost::thread m_thread;
|
||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||
bool m_finalized = false;
|
||||
std::shared_ptr<ProgressIndicator> m_progress;
|
||||
|
||||
void run();
|
||||
|
||||
protected:
|
||||
// status range for a particular job
|
||||
virtual int status_range() const { return 100; }
|
||||
|
||||
// status update, to be used from the work thread (process() method)
|
||||
void update_status(int st, const wxString &msg = "");
|
||||
|
||||
bool was_canceled() const { return m_canceled.load(); }
|
||||
|
||||
// Launched just before start(), a job can use it to prepare internals
|
||||
virtual void prepare() {}
|
||||
|
||||
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||
virtual void finalize() { m_finalized = true; }
|
||||
|
||||
public:
|
||||
Job(std::shared_ptr<ProgressIndicator> pri);
|
||||
|
||||
bool is_finalized() const { return m_finalized; }
|
||||
|
||||
Job(const Job &) = delete;
|
||||
Job(Job &&) = delete;
|
||||
Job &operator=(const Job &) = delete;
|
||||
Job &operator=(Job &&) = delete;
|
||||
|
||||
virtual void process() = 0;
|
||||
|
||||
void start();
|
||||
|
||||
// To wait for the running job and join the threads. False is
|
||||
// returned if the timeout has been reached and the job is still
|
||||
// running. Call cancel() before this fn if you want to explicitly
|
||||
// end the job.
|
||||
bool join(int timeout_ms = 0);
|
||||
|
||||
bool is_running() const { return m_running.load(); }
|
||||
void cancel() { m_canceled.store(true); }
|
||||
};
|
||||
|
||||
// Jobs defined inside the group class will be managed so that only one can
|
||||
// run at a time. Also, the background process will be stopped if a job is
|
||||
// started.
|
||||
class ExclusiveJobGroup
|
||||
{
|
||||
static const int ABORT_WAIT_MAX_MS = 10000;
|
||||
|
||||
std::vector<std::unique_ptr<GUI::Job>> m_jobs;
|
||||
|
||||
protected:
|
||||
virtual void before_start() {}
|
||||
|
||||
public:
|
||||
virtual ~ExclusiveJobGroup() = default;
|
||||
|
||||
size_t add_job(std::unique_ptr<GUI::Job> &&job)
|
||||
{
|
||||
m_jobs.emplace_back(std::move(job));
|
||||
return m_jobs.size() - 1;
|
||||
}
|
||||
|
||||
void start(size_t jid);
|
||||
|
||||
void cancel_all() { for (auto& j : m_jobs) j->cancel(); }
|
||||
|
||||
void join_all(int wait_ms = 0);
|
||||
|
||||
void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
|
||||
|
||||
bool is_any_running() const;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // JOB_HPP
|
||||
68
src/slic3r/GUI/Jobs/RotoptimizeJob.cpp
Normal file
68
src/slic3r/GUI/Jobs/RotoptimizeJob.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#include "RotoptimizeJob.hpp"
|
||||
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include "libslic3r/SLA/Rotfinder.hpp"
|
||||
#include "libslic3r/MinAreaBoundingBox.hpp"
|
||||
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
void RotoptimizeJob::process()
|
||||
{
|
||||
int obj_idx = m_plater->get_selected_object_idx();
|
||||
if (obj_idx < 0) { return; }
|
||||
|
||||
ModelObject *o = m_plater->model().objects[size_t(obj_idx)];
|
||||
|
||||
auto r = sla::find_best_rotation(
|
||||
*o,
|
||||
.005f,
|
||||
[this](unsigned s) {
|
||||
if (s < 100)
|
||||
update_status(int(s),
|
||||
_(L("Searching for optimal orientation")));
|
||||
},
|
||||
[this]() { return was_canceled(); });
|
||||
|
||||
|
||||
double mindist = 6.0; // FIXME
|
||||
|
||||
if (!was_canceled()) {
|
||||
for(ModelInstance * oi : o->instances) {
|
||||
oi->set_rotation({r[X], r[Y], r[Z]});
|
||||
|
||||
auto trmatrix = oi->get_transformation().get_matrix();
|
||||
Polygon trchull = o->convex_hull_2d(trmatrix);
|
||||
|
||||
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
|
||||
double phi = rotbb.angle_to_X();
|
||||
|
||||
// The box should be landscape
|
||||
if(rotbb.width() < rotbb.height()) phi += PI / 2;
|
||||
|
||||
Vec3d rt = oi->get_rotation(); rt(Z) += phi;
|
||||
|
||||
oi->set_rotation(rt);
|
||||
}
|
||||
|
||||
m_plater->find_new_position(o->instances, scaled(mindist));
|
||||
|
||||
// Correct the z offset of the object which was corrupted be
|
||||
// the rotation
|
||||
o->ensure_on_bed();
|
||||
}
|
||||
|
||||
update_status(100, was_canceled() ? _(L("Orientation search canceled.")) :
|
||||
_(L("Orientation found.")));
|
||||
}
|
||||
|
||||
void RotoptimizeJob::finalize()
|
||||
{
|
||||
if (!was_canceled())
|
||||
m_plater->update();
|
||||
|
||||
Job::finalize();
|
||||
}
|
||||
|
||||
}}
|
||||
24
src/slic3r/GUI/Jobs/RotoptimizeJob.hpp
Normal file
24
src/slic3r/GUI/Jobs/RotoptimizeJob.hpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef ROTOPTIMIZEJOB_HPP
|
||||
#define ROTOPTIMIZEJOB_HPP
|
||||
|
||||
#include "Job.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class Plater;
|
||||
|
||||
class RotoptimizeJob : public Job
|
||||
{
|
||||
Plater *m_plater;
|
||||
public:
|
||||
RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: Job{std::move(pri)}, m_plater{plater}
|
||||
{}
|
||||
|
||||
void process() override;
|
||||
void finalize() override;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // ROTOPTIMIZEJOB_HPP
|
||||
226
src/slic3r/GUI/Jobs/SLAImportJob.cpp
Normal file
226
src/slic3r/GUI/Jobs/SLAImportJob.cpp
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
#include "SLAImportJob.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/AppConfig.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/Utils/SLAImport.hpp"
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/filepicker.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
enum class Sel { modelAndProfile, profileOnly, modelOnly};
|
||||
|
||||
class ImportDlg: public wxDialog {
|
||||
wxFilePickerCtrl *m_filepicker;
|
||||
wxComboBox *m_import_dropdown, *m_quality_dropdown;
|
||||
|
||||
public:
|
||||
ImportDlg(Plater *plater)
|
||||
: wxDialog{plater, wxID_ANY, "Import SLA archive"}
|
||||
{
|
||||
auto szvert = new wxBoxSizer{wxVERTICAL};
|
||||
auto szfilepck = new wxBoxSizer{wxHORIZONTAL};
|
||||
|
||||
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
|
||||
"SL1 archive files (*.sl1, *.zip)|*.sl1;*.SL1;*.zip;*.ZIP",
|
||||
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
szfilepck->Add(new wxStaticText(this, wxID_ANY, _(L("Import file: "))), 0, wxALIGN_CENTER);
|
||||
szfilepck->Add(m_filepicker, 1);
|
||||
szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
auto szchoices = new wxBoxSizer{wxHORIZONTAL};
|
||||
|
||||
static const std::vector<wxString> inp_choices = {
|
||||
_(L("Import model and profile")),
|
||||
_(L("Import profile only")),
|
||||
_(L("Import model only"))
|
||||
};
|
||||
|
||||
m_import_dropdown = new wxComboBox(
|
||||
this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize,
|
||||
inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
||||
|
||||
szchoices->Add(m_import_dropdown);
|
||||
szchoices->Add(new wxStaticText(this, wxID_ANY, _(L("Quality: "))), 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
static const std::vector<wxString> qual_choices = {
|
||||
_(L("Accurate")),
|
||||
_(L("Balanced")),
|
||||
_(L("Quick"))
|
||||
};
|
||||
|
||||
m_quality_dropdown = new wxComboBox(
|
||||
this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize,
|
||||
qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
||||
szchoices->Add(m_quality_dropdown);
|
||||
|
||||
m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||
if (get_selection() == Sel::profileOnly)
|
||||
m_quality_dropdown->Disable();
|
||||
else m_quality_dropdown->Enable();
|
||||
});
|
||||
|
||||
szvert->Add(szchoices, 0, wxALL, 5);
|
||||
szvert->AddStretchSpacer(1);
|
||||
auto szbtn = new wxBoxSizer(wxHORIZONTAL);
|
||||
szbtn->Add(new wxButton{this, wxID_CANCEL});
|
||||
szbtn->Add(new wxButton{this, wxID_OK});
|
||||
szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5);
|
||||
|
||||
SetSizerAndFit(szvert);
|
||||
}
|
||||
|
||||
Sel get_selection() const
|
||||
{
|
||||
int sel = m_import_dropdown->GetSelection();
|
||||
return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
|
||||
}
|
||||
|
||||
Vec2i get_marchsq_windowsize() const
|
||||
{
|
||||
enum { Accurate, Balanced, Fast};
|
||||
|
||||
switch(m_quality_dropdown->GetSelection())
|
||||
{
|
||||
case Fast: return {8, 8};
|
||||
case Balanced: return {4, 4};
|
||||
default:
|
||||
case Accurate:
|
||||
return {2, 2};
|
||||
}
|
||||
}
|
||||
|
||||
wxString get_path() const
|
||||
{
|
||||
return m_filepicker->GetPath();
|
||||
}
|
||||
};
|
||||
|
||||
class SLAImportJob::priv {
|
||||
public:
|
||||
Plater *plater;
|
||||
|
||||
Sel sel = Sel::modelAndProfile;
|
||||
|
||||
TriangleMesh mesh;
|
||||
DynamicPrintConfig profile;
|
||||
wxString path;
|
||||
Vec2i win = {2, 2};
|
||||
std::string err;
|
||||
|
||||
priv(Plater *plt): plater{plt} {}
|
||||
};
|
||||
|
||||
SLAImportJob::SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: Job{std::move(pri)}, p{std::make_unique<priv>(plater)}
|
||||
{}
|
||||
|
||||
SLAImportJob::~SLAImportJob() = default;
|
||||
|
||||
void SLAImportJob::process()
|
||||
{
|
||||
auto progr = [this](int s) {
|
||||
if (s < 100) update_status(int(s), _(L("Importing SLA archive")));
|
||||
return !was_canceled();
|
||||
};
|
||||
|
||||
if (p->path.empty()) return;
|
||||
|
||||
std::string path = p->path.ToUTF8().data();
|
||||
try {
|
||||
switch (p->sel) {
|
||||
case Sel::modelAndProfile:
|
||||
import_sla_archive(path, p->win, p->mesh, p->profile, progr);
|
||||
break;
|
||||
case Sel::modelOnly:
|
||||
import_sla_archive(path, p->win, p->mesh, progr);
|
||||
break;
|
||||
case Sel::profileOnly:
|
||||
import_sla_archive(path, p->profile);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (std::exception &ex) {
|
||||
p->err = ex.what();
|
||||
}
|
||||
|
||||
update_status(100, was_canceled() ? _(L("Importing canceled.")) :
|
||||
_(L("Importing done.")));
|
||||
}
|
||||
|
||||
void SLAImportJob::reset()
|
||||
{
|
||||
p->sel = Sel::modelAndProfile;
|
||||
p->mesh = {};
|
||||
p->profile = {};
|
||||
p->win = {2, 2};
|
||||
p->path.Clear();
|
||||
}
|
||||
|
||||
void SLAImportJob::prepare()
|
||||
{
|
||||
reset();
|
||||
|
||||
ImportDlg dlg{p->plater};
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
auto path = dlg.get_path();
|
||||
auto nm = wxFileName(path);
|
||||
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : path.ToUTF8();
|
||||
p->sel = dlg.get_selection();
|
||||
p->win = dlg.get_marchsq_windowsize();
|
||||
} else {
|
||||
p->path = "";
|
||||
}
|
||||
}
|
||||
|
||||
void SLAImportJob::finalize()
|
||||
{
|
||||
// Ignore the arrange result if aborted.
|
||||
if (was_canceled()) return;
|
||||
|
||||
if (!p->err.empty()) {
|
||||
show_error(p->plater, p->err);
|
||||
p->err = "";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name = wxFileName(p->path).GetName().ToUTF8().data();
|
||||
|
||||
if (!p->profile.empty()) {
|
||||
const ModelObjectPtrs& objects = p->plater->model().objects;
|
||||
for (auto object : objects)
|
||||
if (object->volumes.size() > 1)
|
||||
{
|
||||
Slic3r::GUI::show_info(nullptr,
|
||||
_(L("You cannot load SLA project with a multi-part object on the bed")) + "\n\n" +
|
||||
_(L("Please check your object list before preset changing.")),
|
||||
_(L("Attention!")) );
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicPrintConfig config = {};
|
||||
config.apply(SLAFullPrintConfig::defaults());
|
||||
config += std::move(p->profile);
|
||||
|
||||
wxGetApp().preset_bundle->load_config_model(name, std::move(config));
|
||||
wxGetApp().load_current_presets();
|
||||
}
|
||||
|
||||
if (!p->mesh.empty())
|
||||
p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
}}
|
||||
31
src/slic3r/GUI/Jobs/SLAImportJob.hpp
Normal file
31
src/slic3r/GUI/Jobs/SLAImportJob.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef SLAIMPORTJOB_HPP
|
||||
#define SLAIMPORTJOB_HPP
|
||||
|
||||
#include "Job.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class Plater;
|
||||
|
||||
class SLAImportJob : public Job {
|
||||
class priv;
|
||||
|
||||
std::unique_ptr<priv> p;
|
||||
|
||||
public:
|
||||
SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater);
|
||||
~SLAImportJob();
|
||||
|
||||
void process() override;
|
||||
|
||||
void reset();
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void finalize() override;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // SLAIMPORTJOB_HPP
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
#include "GUI_ObjectList.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
|
@ -236,7 +237,8 @@ void MainFrame::shutdown()
|
|||
|
||||
// Stop the background thread of the removable drive manager, so that no new updates will be sent to the Plater.
|
||||
wxGetApp().removable_drive_manager()->shutdown();
|
||||
|
||||
//stop listening for messages from other instances
|
||||
wxGetApp().other_instance_message_handler()->shutdown();
|
||||
// Save the slic3r.ini.Usually the ini file is saved from "on idle" callback,
|
||||
// but in rare cases it may not have been called yet.
|
||||
wxGetApp().app_config->save();
|
||||
|
|
@ -603,6 +605,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,
|
||||
|
|
|
|||
|
|
@ -684,6 +684,9 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
|
|||
opt_key == "fill_pattern" ) {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<InfillPattern>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("ironing_type") == 0 ) {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<IroningType>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("gcode_flavor") == 0 ) {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<GCodeFlavor>>(opt_key)->value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -80,6 +74,7 @@
|
|||
#include "../Utils/FixModelByWin10.hpp"
|
||||
#include "../Utils/UndoRedo.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
|
||||
#if ENABLE_NON_STATIC_CANVAS_MANAGER
|
||||
#ifdef __APPLE__
|
||||
|
|
@ -1551,311 +1546,44 @@ struct Plater::priv
|
|||
BackgroundSlicingProcess background_process;
|
||||
bool suppressed_backround_processing_update { false };
|
||||
|
||||
// Cache the wti info
|
||||
class WipeTower: public GLCanvas3D::WipeTowerInfo {
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
friend priv;
|
||||
public:
|
||||
|
||||
void apply_arrange_result(const Vec2d& tr, double rotation)
|
||||
{
|
||||
m_pos = unscaled(tr); m_rotation = rotation;
|
||||
apply_wipe_tower();
|
||||
}
|
||||
|
||||
ArrangePolygon get_arrange_polygon() const
|
||||
{
|
||||
Polygon p({
|
||||
{coord_t(0), coord_t(0)},
|
||||
{scaled(m_bb_size(X)), coord_t(0)},
|
||||
{scaled(m_bb_size)},
|
||||
{coord_t(0), scaled(m_bb_size(Y))},
|
||||
{coord_t(0), coord_t(0)},
|
||||
});
|
||||
|
||||
ArrangePolygon ret;
|
||||
ret.poly.contour = std::move(p);
|
||||
ret.translation = scaled(m_pos);
|
||||
ret.rotation = m_rotation;
|
||||
ret.priority++;
|
||||
return ret;
|
||||
}
|
||||
} wipetower;
|
||||
|
||||
WipeTower& updated_wipe_tower() {
|
||||
auto wti = view3D->get_canvas3d()->get_wipe_tower_info();
|
||||
wipetower.m_pos = wti.pos();
|
||||
wipetower.m_rotation = wti.rotation();
|
||||
wipetower.m_bb_size = wti.bb_size();
|
||||
return wipetower;
|
||||
}
|
||||
|
||||
// A class to handle UI jobs like arranging and optimizing rotation.
|
||||
// These are not instant jobs, the user has to be informed about their
|
||||
// state in the status progress indicator. On the other hand they are
|
||||
// separated from the background slicing process. Ideally, these jobs should
|
||||
// run when the background process is not running.
|
||||
//
|
||||
// TODO: A mechanism would be useful for blocking the plater interactions:
|
||||
// objects would be frozen for the user. In case of arrange, an animation
|
||||
// could be shown, or with the optimize orientations, partial results
|
||||
// could be displayed.
|
||||
class PlaterJob: public Job
|
||||
{
|
||||
priv *m_plater;
|
||||
protected:
|
||||
|
||||
priv & plater() { return *m_plater; }
|
||||
const priv &plater() const { return *m_plater; }
|
||||
|
||||
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||
void finalize() override
|
||||
{
|
||||
// Do a full refresh of scene tree, including regenerating
|
||||
// all the GLVolumes. FIXME The update function shall just
|
||||
// reload the modified matrices.
|
||||
if (!Job::was_canceled())
|
||||
plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
|
||||
|
||||
Job::finalize();
|
||||
}
|
||||
|
||||
public:
|
||||
PlaterJob(priv *_plater)
|
||||
: Job(_plater->statusbar()), m_plater(_plater)
|
||||
{}
|
||||
};
|
||||
|
||||
enum class Jobs : size_t {
|
||||
Arrange,
|
||||
Rotoptimize
|
||||
};
|
||||
|
||||
class ArrangeJob : public PlaterJob
|
||||
{
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||
|
||||
// The gap between logical beds in the x axis expressed in ratio of
|
||||
// the current bed width.
|
||||
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
|
||||
|
||||
ArrangePolygons m_selected, m_unselected, m_unprintable;
|
||||
|
||||
// clear m_selected and m_unselected, reserve space for next usage
|
||||
void clear_input() {
|
||||
const Model &model = plater().model;
|
||||
|
||||
size_t count = 0, cunprint = 0; // To know how much space to reserve
|
||||
for (auto obj : model.objects)
|
||||
for (auto mi : obj->instances)
|
||||
mi->printable ? count++ : cunprint++;
|
||||
|
||||
m_selected.clear();
|
||||
m_unselected.clear();
|
||||
m_unprintable.clear();
|
||||
m_selected.reserve(count + 1 /* for optional wti */);
|
||||
m_unselected.reserve(count + 1 /* for optional wti */);
|
||||
m_unprintable.reserve(cunprint /* for optional wti */);
|
||||
}
|
||||
|
||||
// Stride between logical beds
|
||||
double bed_stride() const {
|
||||
double bedwidth = plater().bed_shape_bb().size().x();
|
||||
return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
|
||||
}
|
||||
|
||||
// Set up arrange polygon for a ModelInstance and Wipe tower
|
||||
template<class T> ArrangePolygon get_arrange_poly(T *obj) const {
|
||||
ArrangePolygon ap = obj->get_arrange_polygon();
|
||||
ap.priority = 0;
|
||||
ap.bed_idx = ap.translation.x() / bed_stride();
|
||||
ap.setter = [obj, this](const ArrangePolygon &p) {
|
||||
if (p.is_arranged()) {
|
||||
Vec2d t = p.translation.cast<double>();
|
||||
t.x() += p.bed_idx * bed_stride();
|
||||
obj->apply_arrange_result(t, p.rotation);
|
||||
}
|
||||
};
|
||||
return ap;
|
||||
}
|
||||
|
||||
// Prepare all objects on the bed regardless of the selection
|
||||
void prepare_all() {
|
||||
clear_input();
|
||||
|
||||
for (ModelObject *obj: plater().model.objects)
|
||||
for (ModelInstance *mi : obj->instances) {
|
||||
ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
|
||||
cont.emplace_back(get_arrange_poly(mi));
|
||||
}
|
||||
|
||||
auto& wti = plater().updated_wipe_tower();
|
||||
if (wti) m_selected.emplace_back(get_arrange_poly(&wti));
|
||||
}
|
||||
|
||||
// Prepare the selected and unselected items separately. If nothing is
|
||||
// selected, behaves as if everything would be selected.
|
||||
void prepare_selected() {
|
||||
clear_input();
|
||||
|
||||
Model &model = plater().model;
|
||||
coord_t stride = bed_stride();
|
||||
|
||||
std::vector<const Selection::InstanceIdxsList *>
|
||||
obj_sel(model.objects.size(), nullptr);
|
||||
|
||||
for (auto &s : plater().get_selection().get_content())
|
||||
if (s.first < int(obj_sel.size()))
|
||||
obj_sel[size_t(s.first)] = &s.second;
|
||||
|
||||
// Go through the objects and check if inside the selection
|
||||
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
|
||||
const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
|
||||
ModelObject *mo = model.objects[oidx];
|
||||
|
||||
std::vector<bool> inst_sel(mo->instances.size(), false);
|
||||
|
||||
if (instlist)
|
||||
for (auto inst_id : *instlist)
|
||||
inst_sel[size_t(inst_id)] = true;
|
||||
|
||||
for (size_t i = 0; i < inst_sel.size(); ++i) {
|
||||
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
|
||||
|
||||
ArrangePolygons &cont = mo->instances[i]->printable ?
|
||||
(inst_sel[i] ? m_selected :
|
||||
m_unselected) :
|
||||
m_unprintable;
|
||||
|
||||
cont.emplace_back(std::move(ap));
|
||||
}
|
||||
}
|
||||
|
||||
auto& wti = plater().updated_wipe_tower();
|
||||
if (wti) {
|
||||
ArrangePolygon &&ap = get_arrange_poly(&wti);
|
||||
|
||||
plater().get_selection().is_wipe_tower() ?
|
||||
m_selected.emplace_back(std::move(ap)) :
|
||||
m_unselected.emplace_back(std::move(ap));
|
||||
}
|
||||
|
||||
// If the selection was empty arrange everything
|
||||
if (m_selected.empty()) m_selected.swap(m_unselected);
|
||||
|
||||
// The strides have to be removed from the fixed items. For the
|
||||
// arrangeable (selected) items bed_idx is ignored and the
|
||||
// translation is irrelevant.
|
||||
for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void prepare() override
|
||||
{
|
||||
wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
|
||||
}
|
||||
|
||||
public:
|
||||
using PlaterJob::PlaterJob;
|
||||
|
||||
int status_range() const override
|
||||
{
|
||||
return int(m_selected.size() + m_unprintable.size());
|
||||
}
|
||||
|
||||
void process() override;
|
||||
|
||||
void finalize() override {
|
||||
// Ignore the arrange result if aborted.
|
||||
if (was_canceled()) return;
|
||||
|
||||
// Unprintable items go to the last virtual bed
|
||||
int beds = 0;
|
||||
|
||||
// Apply the arrange result to all selected objects
|
||||
for (ArrangePolygon &ap : m_selected) {
|
||||
beds = std::max(ap.bed_idx, beds);
|
||||
ap.apply();
|
||||
}
|
||||
|
||||
// Get the virtual beds from the unselected items
|
||||
for (ArrangePolygon &ap : m_unselected)
|
||||
beds = std::max(ap.bed_idx, beds);
|
||||
|
||||
// Move the unprintable items to the last virtual bed.
|
||||
for (ArrangePolygon &ap : m_unprintable) {
|
||||
ap.bed_idx += beds + 1;
|
||||
ap.apply();
|
||||
}
|
||||
|
||||
plater().update();
|
||||
}
|
||||
};
|
||||
|
||||
class RotoptimizeJob : public PlaterJob
|
||||
{
|
||||
public:
|
||||
using PlaterJob::PlaterJob;
|
||||
void process() override;
|
||||
};
|
||||
|
||||
|
||||
// Jobs defined inside the group class will be managed so that only one can
|
||||
// run at a time. Also, the background process will be stopped if a job is
|
||||
// started.
|
||||
class ExclusiveJobGroup {
|
||||
|
||||
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};
|
||||
|
||||
// started. It is up the the plater to ensure that the background slicing
|
||||
// can't be restarted while a ui job is still running.
|
||||
class Jobs: public ExclusiveJobGroup
|
||||
{
|
||||
priv *m;
|
||||
size_t m_arrange_id, m_rotoptimize_id, m_sla_import_id;
|
||||
|
||||
void before_start() override { m->background_process.stop(); }
|
||||
|
||||
public:
|
||||
ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {}
|
||||
|
||||
void start(Jobs jid) {
|
||||
m_plater->background_process.stop();
|
||||
stop_all();
|
||||
m_jobs[size_t(jid)].get().start();
|
||||
}
|
||||
|
||||
void cancel_all() { for (Job& j : m_jobs) j.cancel(); }
|
||||
|
||||
void join_all(int wait_ms = 0)
|
||||
Jobs(priv *_m) : m(_m)
|
||||
{
|
||||
std::vector<bool> aborted(m_jobs.size(), false);
|
||||
|
||||
for (size_t jid = 0; jid < m_jobs.size(); ++jid)
|
||||
aborted[jid] = m_jobs[jid].get().join(wait_ms);
|
||||
|
||||
if (!all_of(aborted))
|
||||
BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
|
||||
m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q));
|
||||
m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q));
|
||||
m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->statusbar(), m->q));
|
||||
}
|
||||
|
||||
void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
|
||||
|
||||
const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; }
|
||||
|
||||
bool is_any_running() const
|
||||
|
||||
void arrange()
|
||||
{
|
||||
return std::any_of(m_jobs.begin(),
|
||||
m_jobs.end(),
|
||||
[](const Job &j) { return j.is_running(); });
|
||||
m->take_snapshot(_(L("Arrange")));
|
||||
start(m_arrange_id);
|
||||
}
|
||||
|
||||
} m_ui_jobs{this};
|
||||
|
||||
void optimize_rotation()
|
||||
{
|
||||
m->take_snapshot(_(L("Optimize Rotation")));
|
||||
start(m_rotoptimize_id);
|
||||
}
|
||||
|
||||
void import_sla_arch()
|
||||
{
|
||||
m->take_snapshot(_(L("Import SLA archive")));
|
||||
start(m_sla_import_id);
|
||||
}
|
||||
|
||||
} m_ui_jobs;
|
||||
|
||||
bool delayed_scene_refresh;
|
||||
std::string delayed_error_message;
|
||||
|
|
@ -1874,10 +1602,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);
|
||||
|
|
@ -1916,9 +1644,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);
|
||||
|
|
@ -1936,8 +1662,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();
|
||||
|
|
@ -2104,6 +1828,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)
|
||||
|
|
@ -2179,14 +1904,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(); });
|
||||
|
|
@ -2212,7 +1938,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(); });
|
||||
|
|
@ -2292,6 +2018,28 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
// Initialize the Undo / Redo stack with a first snapshot.
|
||||
this->take_snapshot(_L("New Project"));
|
||||
|
||||
this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent &evt) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "received load from other instance event ";
|
||||
this->load_files(evt.data, true, true);
|
||||
});
|
||||
this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "prusaslicer window going forward";
|
||||
//this code maximize app window on Fedora
|
||||
wxGetApp().mainframe->Iconize(false);
|
||||
if (wxGetApp().mainframe->IsMaximized())
|
||||
wxGetApp().mainframe->Maximize(true);
|
||||
else
|
||||
wxGetApp().mainframe->Maximize(false);
|
||||
//this code (without code above) maximize window on Ubuntu
|
||||
wxGetApp().mainframe->Restore();
|
||||
wxGetApp().GetTopWindow()->SetFocus(); // focus on my window
|
||||
wxGetApp().GetTopWindow()->Raise(); // bring window to front
|
||||
wxGetApp().GetTopWindow()->Show(true); // show the window
|
||||
|
||||
});
|
||||
wxGetApp().other_instance_message_handler()->init(this->q);
|
||||
|
||||
|
||||
// collapse sidebar according to saved value
|
||||
sidebar->collapse(wxGetApp().app_config->get("collapsed_sidebar") == "1");
|
||||
}
|
||||
|
|
@ -2883,40 +2631,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();
|
||||
|
|
@ -2926,11 +2646,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)
|
||||
|
|
@ -2938,95 +2659,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();
|
||||
|
|
@ -3667,7 +3299,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());
|
||||
if (preset_type == Preset::TYPE_PRINTER) {
|
||||
/* Settings list can be changed after printer preset changing, so
|
||||
* update all settings items for all item had it.
|
||||
|
|
@ -4115,8 +3747,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;
|
||||
}
|
||||
|
|
@ -4720,6 +4356,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;
|
||||
|
|
@ -4815,7 +4456,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();
|
||||
|
||||
|
|
@ -5564,6 +5205,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();
|
||||
|
|
@ -5589,6 +5235,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();
|
||||
|
|
@ -5611,6 +5262,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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
#include "Search.hpp"
|
||||
|
||||
|
|
@ -166,6 +168,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);
|
||||
|
|
@ -265,12 +268,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
|
||||
|
|
@ -279,6 +286,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();
|
||||
|
|
@ -385,6 +393,7 @@ private:
|
|||
bool m_was_scheduled;
|
||||
};
|
||||
|
||||
}}
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -100,6 +100,13 @@ void PreferencesDialog::build()
|
|||
option = Option (def,"show_incompatible_presets");
|
||||
m_optgroup_general->append_single_option_line(option);
|
||||
|
||||
def.label = L("Single Instance");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("If this is enabled, when staring PrusaSlicer and another instance is running, that instance will be reactivated instead.");
|
||||
def.set_default_value(new ConfigOptionBool{ app_config->has("single_instance") ? app_config->get("single_instance") == "1" : false });
|
||||
option = Option(def, "single_instance");
|
||||
m_optgroup_general->append_single_option_line(option);
|
||||
|
||||
#if __APPLE__
|
||||
def.label = L("Use Retina resolution for the 3D scene");
|
||||
def.type = coBool;
|
||||
|
|
@ -177,6 +184,8 @@ void PreferencesDialog::accept()
|
|||
app_config->set(it->first, it->second);
|
||||
}
|
||||
|
||||
app_config->save();
|
||||
|
||||
EndModal(wxID_OK);
|
||||
|
||||
// Nothify the UI to update itself from the ini file.
|
||||
|
|
|
|||
|
|
@ -405,8 +405,9 @@ const std::vector<std::string>& Preset::print_options()
|
|||
"extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs",
|
||||
"seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern",
|
||||
"infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle",
|
||||
"solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "max_print_speed",
|
||||
"max_volumetric_speed",
|
||||
"solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first",
|
||||
"ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing",
|
||||
"max_print_speed", "max_volumetric_speed",
|
||||
#ifdef HAS_PRESSURE_EQUALIZER
|
||||
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
|
||||
#endif /* HAS_PRESSURE_EQUALIZER */
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue