Fixed conflicts after merge with master

This commit is contained in:
enricoturri1966 2021-08-26 12:39:28 +02:00
commit 39ec1a6318
192 changed files with 7204 additions and 2296 deletions

View file

@ -13,6 +13,7 @@ add_subdirectory(qhull)
add_subdirectory(Shiny)
add_subdirectory(semver)
add_subdirectory(libigl)
add_subdirectory(hints)
# Adding libnest2d project for bin packing...
add_subdirectory(libnest2d)

View file

@ -258,7 +258,6 @@ int CLI::run(int argc, char **argv)
Points bed = get_bed_shape(m_print_config);
ArrangeParams arrange_cfg;
arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config));
bool user_ensure_on_bed = true;
for (auto const &opt_key : m_transforms) {
if (opt_key == "merge") {
@ -331,10 +330,8 @@ int CLI::run(int argc, char **argv)
}
} else if (opt_key == "dont_arrange") {
// do nothing - this option alters other transform options
} else if (opt_key == "dont_ensure_on_bed") {
// Remember that we saw this so we don't lift objects from the bed
// after the other transformations are processed.
user_ensure_on_bed = false;
} else if (opt_key == "ensure_on_bed") {
// do nothing, the value is used later
} else if (opt_key == "rotate") {
for (auto &model : m_models)
for (auto &o : model.objects)
@ -439,7 +436,7 @@ int CLI::run(int argc, char **argv)
// All transforms have been dealt with. Now ensure that the objects are on bed.
// (Unless the user said otherwise.)
if (user_ensure_on_bed)
if (m_config.opt_bool("ensure_on_bed"))
for (auto &model : m_models)
for (auto &o : model.objects)
o->ensure_on_bed();

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 2.8.12)
project(Shiny)
cmake_minimum_required(VERSION 2.6)
add_library(Shiny STATIC
Shiny.h

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 2.8.12)
project(admesh)
cmake_minimum_required(VERSION 2.6)
add_library(admesh STATIC
connect.cpp

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 2.8.12)
project(nowide)
cmake_minimum_required(VERSION 2.6)
add_library(nowide STATIC
nowide/args.hpp

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 2.8.12)
project(clipper)
cmake_minimum_required(VERSION 2.6)
add_library(clipper STATIC
# We are using ClipperLib compiled as part of the libslic3r project using Slic3r::Point as its base type.

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 2.8.12)
project(glu-libtess)
cmake_minimum_required(VERSION 2.6)
add_library(glu-libtess STATIC
src/dict-list.h

12
src/hints/CMakeLists.txt Normal file
View file

@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.13)
project(HintsToPot)
add_executable(hintsToPot
HintsToPot.cpp)
target_link_libraries(hintsToPot PRIVATE boost_libs)
#encoding_check(HintsToPot)

84
src/hints/HintsToPot.cpp Normal file
View file

@ -0,0 +1,84 @@
#include <iostream>
#include <vector>
#include <string>
#include <boost/filesystem.hpp>
#include <boost/dll.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/algorithm/string/predicate.hpp>
bool write_to_pot(boost::filesystem::path path, const std::vector<std::pair<std::string, std::string>>& data)
{
boost::filesystem::ofstream file(std::move(path), std::ios_base::app);
for (const auto& element : data)
{
//Example of .pot element
//#: src/slic3r/GUI/GUI_App.cpp:1647 src/slic3r/GUI/wxExtensions.cpp:687
//msgctxt "Mode"
//msgid "Advanced"
//msgstr ""
file << "\n#: resources/data/hints.ini: ["<< element.first << "]\nmsgid \"" << element.second << "\"\nmsgstr \"\"\n";
}
file.close();
return true;
}
bool read_hints_ini(boost::filesystem::path path, std::vector<std::pair<std::string, std::string>>& pot_elements)
{
namespace pt = boost::property_tree;
pt::ptree tree;
boost::nowide::ifstream ifs(path.string());
try {
pt::read_ini(ifs, tree);
}
catch (const boost::property_tree::ini_parser::ini_parser_error& err) {
std::cout << err.what() << std::endl;
return false;
}
for (const auto& section : tree) {
if (boost::starts_with(section.first, "hint:")) {
for (const auto& data : section.second) {
if (data.first == "text")
{
pot_elements.emplace_back(section.first, data.second.data());
break;
}
}
}
}
return true;
}
int main(int argc, char* argv[])
{
std::vector<std::pair<std::string, std::string>> data;
boost::filesystem::path path_to_ini;
boost::filesystem::path path_to_pot;
if (argc != 3)
{
std::cout << "HINTS_TO_POT FAILED: WRONG NUM OF ARGS" << std::endl;
return -1;
}
try {
path_to_ini = boost::filesystem::canonical(boost::filesystem::path(argv[1])).parent_path() / "resources" / "data" / "hints.ini";
path_to_pot = boost::filesystem::canonical(boost::filesystem::path(argv[2])).parent_path() / "localization" /"PrusaSlicer.pot";
} catch (std::exception&) {
std::cout << "HINTS_TO_POT FAILED: BOOST CANNONICAL" << std::endl;
return -1;
}
if (!boost::filesystem::exists(path_to_ini)){
std::cout << "HINTS_TO_POT FAILED: PATH TO INI DOES NOT EXISTS" << std::endl;
std::cout << path_to_ini.string() << std::endl;
return -1;
}
if (!read_hints_ini(std::move(path_to_ini), data)) {
std::cout << "HINTS_TO_POT FAILED TO READ HINTS INI" << std::endl;
return -1;
}
if (!write_to_pot(std::move(path_to_pot), data)) {
std::cout << "HINTS_TO_POT FAILED TO WRITE POT FILE" << std::endl;
return -1;
}
std::cout << "HINTS_TO_POT SUCCESS" << std::endl;
return 0;
}

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 2.8.12)
project(imgui)
cmake_minimum_required(VERSION 2.6)
add_library(imgui STATIC
imconfig.h

View file

@ -124,32 +124,34 @@ namespace ImGui
const char ColorMarkerEnd = 0x3; // ETX
// Special ASCII characters are used here as an ikons markers
const char PrintIconMarker = 0x4;
const char PrinterIconMarker = 0x5;
const char PrinterSlaIconMarker = 0x6;
const char FilamentIconMarker = 0x7;
const char MaterialIconMarker = 0x8;
const char CloseNotifButton = 0xB;
const char CloseNotifHoverButton = 0xC;
const char MinimalizeButton = 0xE;
const char MinimalizeHoverButton = 0xF;
const char WarningMarker = 0x10;
const char ErrorMarker = 0x11;
const char EjectButton = 0x12;
const char EjectHoverButton = 0x13;
const char CancelButton = 0x14;
const char CancelHoverButton = 0x15;
const char VarLayerHeightMarker = 0x16;
const wchar_t PrintIconMarker = 0x4;
const wchar_t PrinterIconMarker = 0x5;
const wchar_t PrinterSlaIconMarker = 0x6;
const wchar_t FilamentIconMarker = 0x7;
const wchar_t MaterialIconMarker = 0x8;
const wchar_t CloseNotifButton = 0xB;
const wchar_t CloseNotifHoverButton = 0xC;
const wchar_t MinimalizeButton = 0xE;
const wchar_t MinimalizeHoverButton = 0xF;
const wchar_t WarningMarker = 0x10;
const wchar_t ErrorMarker = 0x11;
const wchar_t EjectButton = 0x12;
const wchar_t EjectHoverButton = 0x13;
const wchar_t CancelButton = 0x14;
const wchar_t CancelHoverButton = 0x15;
const wchar_t VarLayerHeightMarker = 0x16;
const char RightArrowButton = 0x18;
const char RightArrowHoverButton = 0x19;
const char PreferencesButton = 0x1A;
const char PreferencesHoverButton = 0x1B;
const char SinkingObjectMarker = 0x1C;
const char CustomSupportsMarker = 0x1D;
const char CustomSeamMarker = 0x1E;
const char MmuSegmentationMarker = 0x1F;
const wchar_t RightArrowButton = 0x18;
const wchar_t RightArrowHoverButton = 0x19;
const wchar_t PreferencesButton = 0x1A;
const wchar_t PreferencesHoverButton = 0x1B;
const wchar_t SinkingObjectMarker = 0x1C;
const wchar_t CustomSupportsMarker = 0x1D;
const wchar_t CustomSeamMarker = 0x1E;
const wchar_t MmuSegmentationMarker = 0x1F;
const wchar_t DocumentationButton = 0x2600;
const wchar_t DocumentationHoverButton = 0x2601;
const wchar_t ClippyMarker = 0x2602;
// void MyFunction(const char* name, const MyMatrix44& v);

View file

@ -1,5 +1,5 @@
project(libigl)
cmake_minimum_required(VERSION 3.0)
project(libigl)
add_library(libigl INTERFACE)

View file

@ -167,9 +167,6 @@ void AppConfig::set_defaults()
if (get("show_splash_screen").empty())
set("show_splash_screen", "1");
if (get("last_hint").empty())
set("last_hint", "0");
if (get("show_hints").empty())
set("show_hints", "1");

View file

@ -45,14 +45,40 @@ static float max_brim_width(const ConstPrintObjectPtrsAdaptor &objects)
}));
}
static ConstPrintObjectPtrs get_top_level_objects_with_brim(const Print &print)
// Returns ExPolygons of the bottom layer of the print object after elephant foot compensation.
static ExPolygons get_print_object_bottom_layer_expolygons(const PrintObject &print_object)
{
ExPolygons ex_polygons;
for (LayerRegion *region : print_object.layers().front()->regions())
Slic3r::append(ex_polygons, offset_ex(offset_ex(region->slices.surfaces, float(SCALED_EPSILON)), -float(SCALED_EPSILON)));
return ex_polygons;
}
// Returns ExPolygons of bottom layer for every print object in Print after elephant foot compensation.
static std::vector<ExPolygons> get_print_bottom_layers_expolygons(const Print &print)
{
std::vector<ExPolygons> bottom_layers_expolygons;
bottom_layers_expolygons.reserve(print.objects().size());
for (const PrintObject *object : print.objects())
bottom_layers_expolygons.emplace_back(get_print_object_bottom_layer_expolygons(*object));
return bottom_layers_expolygons;
}
static ConstPrintObjectPtrs get_top_level_objects_with_brim(const Print &print, const std::vector<ExPolygons> &bottom_layers_expolygons)
{
assert(print.objects().size() == bottom_layers_expolygons.size());
Polygons islands;
ConstPrintObjectPtrs island_to_object;
for (const PrintObject *object : print.objects()) {
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
const PrintObject *object = print.objects()[print_object_idx];
if (! object->has_brim())
continue;
Polygons islands_object;
islands_object.reserve(object->layers().front()->lslices.size());
for (const ExPolygon &ex_poly : object->layers().front()->lslices)
islands_object.reserve(bottom_layers_expolygons[print_object_idx].size());
for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx])
islands_object.emplace_back(ex_poly.contour);
islands.reserve(islands.size() + object->instances().size() * islands_object.size());
@ -110,7 +136,7 @@ static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_lev
//FIXME how about the brim type?
auto brim_offset = float(scale_(object->config().brim_offset.value));
Polygons islands_object;
for (const ExPolygon &ex_poly : object->layers().front()->lslices) {
for (const ExPolygon &ex_poly : get_print_object_bottom_layer_expolygons(*object)) {
Polygons contour_offset = offset(ex_poly.contour, brim_offset);
for (Polygon &poly : contour_offset)
poly.douglas_peucker(SCALED_RESOLUTION);
@ -124,8 +150,12 @@ static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_lev
return islands;
}
static ExPolygons top_level_outer_brim_area(const Print &print, const ConstPrintObjectPtrs &top_level_objects_with_brim, const float no_brim_offset)
static ExPolygons top_level_outer_brim_area(const Print &print,
const ConstPrintObjectPtrs &top_level_objects_with_brim,
const std::vector<ExPolygons> &bottom_layers_expolygons,
const float no_brim_offset)
{
assert(print.objects().size() == bottom_layers_expolygons.size());
std::unordered_set<size_t> top_level_objects_idx;
top_level_objects_idx.reserve(top_level_objects_with_brim.size());
for (const PrintObject *object : top_level_objects_with_brim)
@ -133,15 +163,16 @@ static ExPolygons top_level_outer_brim_area(const Print &print, const ConstPrint
ExPolygons brim_area;
ExPolygons no_brim_area;
for (const PrintObject *object : print.objects()) {
const BrimType brim_type = object->config().brim_type.value;
const float brim_offset = scale_(object->config().brim_offset.value);
const float brim_width = scale_(object->config().brim_width.value);
const bool is_top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
const PrintObject *object = print.objects()[print_object_idx];
const BrimType brim_type = object->config().brim_type.value;
const float brim_offset = scale_(object->config().brim_offset.value);
const float brim_width = scale_(object->config().brim_width.value);
const bool is_top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
ExPolygons brim_area_object;
ExPolygons no_brim_area_object;
for (const ExPolygon &ex_poly : object->layers().front()->lslices) {
for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx]) {
if ((brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner) && is_top_outer_brim)
append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_offset), offset(ex_poly.contour, brim_offset)));
@ -166,8 +197,12 @@ static ExPolygons top_level_outer_brim_area(const Print &print, const ConstPrint
return diff_ex(brim_area, no_brim_area);
}
static ExPolygons inner_brim_area(const Print &print, const ConstPrintObjectPtrs &top_level_objects_with_brim, const float no_brim_offset)
static ExPolygons inner_brim_area(const Print &print,
const ConstPrintObjectPtrs &top_level_objects_with_brim,
const std::vector<ExPolygons> &bottom_layers_expolygons,
const float no_brim_offset)
{
assert(print.objects().size() == bottom_layers_expolygons.size());
std::unordered_set<size_t> top_level_objects_idx;
top_level_objects_idx.reserve(top_level_objects_with_brim.size());
for (const PrintObject *object : top_level_objects_with_brim)
@ -176,16 +211,17 @@ static ExPolygons inner_brim_area(const Print &print, const ConstPrintObjectPtrs
ExPolygons brim_area;
ExPolygons no_brim_area;
Polygons holes;
for (const PrintObject *object : print.objects()) {
const BrimType brim_type = object->config().brim_type.value;
const float brim_offset = scale_(object->config().brim_offset.value);
const float brim_width = scale_(object->config().brim_width.value);
const bool top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
const PrintObject *object = print.objects()[print_object_idx];
const BrimType brim_type = object->config().brim_type.value;
const float brim_offset = scale_(object->config().brim_offset.value);
const float brim_width = scale_(object->config().brim_width.value);
const bool top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
ExPolygons brim_area_object;
ExPolygons no_brim_area_object;
Polygons holes_object;
for (const ExPolygon &ex_poly : object->layers().front()->lslices) {
for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx]) {
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner) {
if (top_outer_brim)
no_brim_area_object.emplace_back(ex_poly);
@ -204,7 +240,7 @@ static ExPolygons inner_brim_area(const Print &print, const ConstPrintObjectPtrs
append(holes_object, ex_poly.holes);
}
append(no_brim_area_object, offset_ex(object->layers().front()->lslices, brim_offset));
append(no_brim_area_object, offset_ex(bottom_layers_expolygons[print_object_idx], brim_offset));
for (const PrintInstance &instance : object->instances()) {
append_and_translate(brim_area, brim_area_object, instance);
@ -236,7 +272,7 @@ static void optimize_polylines_by_reversing(Polylines *polylines)
static Polylines connect_brim_lines(Polylines &&polylines, const Polygons &brim_area, float max_connection_length)
{
if (polylines.empty())
return Polylines();
return {};
BoundingBox bbox = get_extents(polylines);
bbox.merge(get_extents(brim_area));
@ -305,16 +341,20 @@ static Polylines connect_brim_lines(Polylines &&polylines, const Polygons &brim_
}
}
if (end < polylines.size())
polylines.erase(polylines.begin() + end, polylines.end());
polylines.erase(polylines.begin() + int(end), polylines.end());
}
return std::move(polylines);
}
static void make_inner_brim(const Print &print, const ConstPrintObjectPtrs &top_level_objects_with_brim, ExtrusionEntityCollection &brim)
static void make_inner_brim(const Print &print,
const ConstPrintObjectPtrs &top_level_objects_with_brim,
const std::vector<ExPolygons> &bottom_layers_expolygons,
ExtrusionEntityCollection &brim)
{
assert(print.objects().size() == bottom_layers_expolygons.size());
Flow flow = print.brim_flow();
ExPolygons islands_ex = inner_brim_area(print, top_level_objects_with_brim, float(flow.scaled_spacing()));
ExPolygons islands_ex = inner_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing()));
Polygons loops;
islands_ex = offset_ex(islands_ex, -0.5f * float(flow.scaled_spacing()), jtSquare);
for (size_t i = 0; !islands_ex.empty(); ++i) {
@ -334,11 +374,12 @@ static void make_inner_brim(const Print &print, const ConstPrintObjectPtrs &top_
// Collect islands_area to be merged into the final 1st layer convex hull.
ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cancel, Polygons &islands_area)
{
Flow flow = print.brim_flow();
ConstPrintObjectPtrs top_level_objects_with_brim = get_top_level_objects_with_brim(print);
Polygons islands = top_level_outer_brim_islands(top_level_objects_with_brim);
ExPolygons islands_area_ex = top_level_outer_brim_area(print, top_level_objects_with_brim, float(flow.scaled_spacing()));
islands_area = to_polygons(islands_area_ex);
Flow flow = print.brim_flow();
std::vector<ExPolygons> bottom_layers_expolygons = get_print_bottom_layers_expolygons(print);
ConstPrintObjectPtrs top_level_objects_with_brim = get_top_level_objects_with_brim(print, bottom_layers_expolygons);
Polygons islands = top_level_outer_brim_islands(top_level_objects_with_brim);
ExPolygons islands_area_ex = top_level_outer_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing()));
islands_area = to_polygons(islands_area_ex);
Polygons loops;
size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing()));
@ -536,7 +577,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
}
make_inner_brim(print, top_level_objects_with_brim, brim);
make_inner_brim(print, top_level_objects_with_brim, bottom_layers_expolygons, brim);
return brim;
}

View file

@ -178,6 +178,8 @@ add_library(libslic3r STATIC
PrintRegion.cpp
PNGReadWrite.hpp
PNGReadWrite.cpp
QuadricEdgeCollapse.cpp
QuadricEdgeCollapse.hpp
Semver.cpp
ShortestPath.cpp
ShortestPath.hpp

View file

@ -223,10 +223,13 @@ std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const
{
std::vector<std::string> args;
if (this->cli != ConfigOptionDef::nocli) {
std::string cli = this->cli.substr(0, this->cli.find("="));
boost::trim_right_if(cli, boost::is_any_of("!"));
const std::string &cli = this->cli;
//FIXME What was that for? Check the "readline" documentation.
// Neither '=' nor '!' is used in any of the cli parameters currently defined by PrusaSlicer.
// std::string cli = this->cli.substr(0, this->cli.find("="));
// boost::trim_right_if(cli, boost::is_any_of("!"));
if (cli.empty()) {
// Add the key
// Convert an option key to CLI argument by replacing underscores with dashes.
std::string opt = key;
boost::replace_all(opt, "_", "-");
args.emplace_back(std::move(opt));
@ -245,7 +248,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coPercents: return new ConfigOptionPercentsNullable();
case coFloatsOrPercents: return new ConfigOptionFloatsOrPercentsNullable();
case coBools: return new ConfigOptionBoolsNullable();
default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label);
default: throw ConfigurationError(std::string("Unknown option type for nullable option ") + this->label);
}
} else {
switch (this->type) {
@ -266,7 +269,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coBool: return new ConfigOptionBool();
case coBools: return new ConfigOptionBools();
case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map);
default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label);
default: throw ConfigurationError(std::string("Unknown option type for option ") + this->label);
}
}
}
@ -423,7 +426,19 @@ void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys
}
}
// this will *ignore* options not present in both configs
// Are the two configs equal? Ignoring options not present in both configs.
bool ConfigBase::equals(const ConfigBase &other) const
{
for (const t_config_option_key &opt_key : this->keys()) {
const ConfigOption *this_opt = this->option(opt_key);
const ConfigOption *other_opt = other.option(opt_key);
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
return false;
}
return true;
}
// Returns options differing in the two configs, ignoring options not present in both configs.
t_config_option_keys ConfigBase::diff(const ConfigBase &other) const
{
t_config_option_keys diff;
@ -436,6 +451,7 @@ t_config_option_keys ConfigBase::diff(const ConfigBase &other) const
return diff;
}
// Returns options being equal in the two configs, ignoring options not present in both configs.
t_config_option_keys ConfigBase::equal(const ConfigBase &other) const
{
t_config_option_keys equal;
@ -494,7 +510,7 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src,
void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append)
{
if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append))
throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src));
throw BadOptionValueException(format("Invalid value provided for parameter %1%: %2%", opt_key_src, value_src));
}
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions_ctxt)
@ -539,26 +555,50 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
ConfigOption *opt = this->option(opt_key, true);
assert(opt != nullptr);
bool success = opt->deserialize(value, append);
if (! success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable &&
// Only allow substitutions of an enum value by another enum value or a boolean value with an enum value.
// That means, we expect enum values being added in the future and possibly booleans being converted to enums.
(optdef->type == coEnum || optdef->type == coBool))
{
// Deserialize failed, try to substitute with a default value.
assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
bool success = false;
bool substituted = false;
if (optdef->type == coBools && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable) {
//FIXME Special handling of vectors of bools, quick and not so dirty solution before PrusaSlicer 2.3.2 release.
bool nullable = opt->nullable();
ConfigHelpers::DeserializationSubstitution default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToFalse;
if (optdef->default_value) {
// Default value for vectors of booleans used in a "per extruder" context, thus the default contains just a single value.
assert(dynamic_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get()));
auto &values = static_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get())->values;
if (values.size() == 1 && values.front() == 1)
default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
}
auto result = nullable ?
static_cast<ConfigOptionBoolsNullable*>(opt)->deserialize_with_substitutions(value, append, default_value) :
static_cast<ConfigOptionBools*>(opt)->deserialize_with_substitutions(value, append, default_value);
success = result != ConfigHelpers::DeserializationResult::Failed;
substituted = result == ConfigHelpers::DeserializationResult::Substituted;
} else {
success = opt->deserialize(value, append);
if (! success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable &&
// Only allow substitutions of an enum value by another enum value or a boolean value with an enum value.
// That means, we expect enum values being added in the future and possibly booleans being converted to enums.
(optdef->type == coEnum || optdef->type == coBool) && ConfigHelpers::looks_like_enum_value(value)) {
// Deserialize failed, try to substitute with a default value.
assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
if (optdef->type == coBool)
static_cast<ConfigOptionBool*>(opt)->value = ConfigHelpers::enum_looks_like_true_value(value);
else
// Just use the default of the option.
opt->set(optdef->default_value.get());
success = true;
substituted = true;
}
}
opt->set(optdef->default_value.get());
if (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable) {
// Log the substitution.
ConfigSubstitution config_substitution;
config_substitution.opt_def = optdef;
config_substitution.old_value = value;//std::unique_ptr<ConfigOption>(opt);
config_substitution.new_value = ConfigOptionUniquePtr(this->option(opt_key, true)->clone());
substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
}
return true;
if (substituted && (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable ||
substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)) {
// Log the substitution.
ConfigSubstitution config_substitution;
config_substitution.opt_def = optdef;
config_substitution.old_value = value;
config_substitution.new_value = ConfigOptionUniquePtr(opt->clone());
substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
}
return success;
}
@ -585,7 +625,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
return opt_def->ratio_over.empty() ? 0. :
static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over));
}
throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
throw ConfigurationError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
}
// Return an absolute value of a possibly relative config variable.
@ -596,7 +636,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
const ConfigOption *raw_opt = this->option(opt_key);
assert(raw_opt != nullptr);
if (raw_opt->type() != coFloatOrPercent)
throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
// Compute absolute value.
return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over);
}
@ -622,18 +662,71 @@ void ConfigBase::setenv_() const
ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
return is_gcode_file(file) ?
this->load_from_gcode_file(file, true /* check header */, compatibility_rule) :
this->load_from_gcode_file(file, compatibility_rule) :
this->load_from_ini(file, compatibility_rule);
}
ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
try {
boost::property_tree::ptree tree;
boost::nowide::ifstream ifs(file);
boost::property_tree::read_ini(ifs, tree);
return this->load(tree, compatibility_rule);
} catch (const ConfigurationError &e) {
throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file, e.what()));
}
}
ConfigSubstitutions ConfigBase::load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
boost::property_tree::ptree tree;
boost::nowide::ifstream ifs(file);
boost::property_tree::read_ini(ifs, tree);
std::istringstream iss(data);
boost::property_tree::read_ini(iss, tree);
return this->load(tree, compatibility_rule);
}
// Loading a "will be one day a legacy format" of configuration stored into 3MF or AMF.
// Accepts the same data as load_from_ini_string(), only with each configuration line possibly prefixed with a semicolon (G-code comment).
ConfigSubstitutions ConfigBase::load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// Convert the "data" string into INI format by removing the semi-colons at the start of a line.
// Also the "; generated by PrusaSlicer ..." comment line will be removed.
size_t j = 0;
for (size_t i = 0; i < data.size();)
if (i == 0 || data[i] == '\n') {
// Start of a line.
if (i != 0) {
// Consume LF.
assert(data[i] == '\n');
// Don't keep empty lines.
if (j > 0 && data[j - 1] != '\n')
data[j ++] = data[i];
++ i;
}
// Skip all leading spaces;
for (; i < data.size() && (data[i] == ' ' || data[i] == '\t'); ++ i) ;
// Skip the semicolon (comment indicator).
if (i < data.size() && data[i] == ';')
++ i;
// Skip all leading spaces after semicolon.
for (; i < data.size() && (data[i] == ' ' || data[i] == '\t'); ++ i) ;
if (strncmp(data.data() + i, "generated by ", 13) == 0) {
// Skip the "; generated by ..." line.
for (; i < data.size() && data[i] != '\n'; ++ i);
}
} else if (data[i] == '\r' && i + 1 < data.size() && data[i + 1] == '\n') {
// Skip CR.
++ i;
} else {
// Consume the rest of the data.
data[j ++] = data[i ++];
}
data.erase(data.begin() + j, data.end());
return this->load_from_ini_string(data, compatibility_rule);
}
ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
@ -648,37 +741,8 @@ ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, Fo
return std::move(substitutions_ctxt.substitutions);
}
// Load the config keys from the tail of a G-code file.
ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, bool check_header, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// Read a 64k block from the end of the G-code.
boost::nowide::ifstream ifs(file);
if (check_header) {
const char slic3r_gcode_header[] = "; generated by Slic3r ";
const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
std::string firstline;
std::getline(ifs, firstline);
if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 &&
strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0)
throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code.");
}
ifs.seekg(0, ifs.end);
auto file_length = ifs.tellg();
auto data_length = std::min<std::fstream::pos_type>(65535, file_length);
ifs.seekg(file_length - data_length, ifs.beg);
std::vector<char> data(size_t(data_length) + 1, 0);
ifs.read(data.data(), data_length);
ifs.close();
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
size_t key_value_pairs = load_from_gcode_string(data.data(), substitutions_ctxt);
if (key_value_pairs < 80)
throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
return std::move(substitutions_ctxt.substitutions);
}
// Load the config keys from the given string.
size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions)
static inline size_t load_from_gcode_string_legacy(ConfigBase &config, const char *str, ConfigSubstitutionContext &substitutions)
{
if (str == nullptr)
return 0;
@ -701,7 +765,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionCon
if (end - (++ start) < 10 || start[0] != ';' || start[1] != ' ')
break;
const char *key = start + 2;
if (!(*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z'))
if (!((*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z')))
// A key must start with a letter.
break;
const char *sep = key;
@ -723,7 +787,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionCon
if (key == nullptr)
break;
try {
this->set_deserialize(std::string(key, key_end), std::string(value, end), substitutions);
config.set_deserialize(std::string(key, key_end), std::string(value, end), substitutions);
++num_key_value_pairs;
}
catch (UnknownOptionException & /* e */) {
@ -732,7 +796,175 @@ size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionCon
end = start;
}
return num_key_value_pairs;
return num_key_value_pairs;
}
// Reading a config from G-code back to front for performance reasons: We don't want to scan
// hundreds of MB file for a short config block, which we expect to find at the end of the G-code.
class ReverseLineReader
{
public:
using pos_type = boost::nowide::ifstream::pos_type;
// Stop at file_start
ReverseLineReader(boost::nowide::ifstream &ifs, pos_type file_start) : m_ifs(ifs), m_file_start(file_start)
{
m_ifs.seekg(0, m_ifs.end);
m_file_pos = m_ifs.tellg();
m_block.assign(m_block_size, 0);
}
bool getline(std::string &out) {
out.clear();
for (;;) {
if (m_block_len == 0) {
// Read the next block.
m_block_len = size_t(std::min<std::fstream::pos_type>(m_block_size, m_file_pos - m_file_start));
if (m_block_len == 0)
return false;
m_file_pos -= m_block_len;
m_ifs.seekg(m_file_pos, m_ifs.beg);
if (! m_ifs.read(m_block.data(), m_block_len))
return false;
}
assert(m_block_len > 0);
// Non-empty buffer. Find another LF.
int i = int(m_block_len) - 1;
for (; i >= 0; -- i)
if (m_block[i] == '\n')
break;
// i is position of LF or -1 if not found.
if (i == -1) {
// LF not found. Just make a backup of the buffer and continue.
out.insert(out.begin(), m_block.begin(), m_block.begin() + m_block_len);
m_block_len = 0;
} else {
assert(i >= 0);
// Copy new line to the output. It may be empty.
out.insert(out.begin(), m_block.begin() + i + 1, m_block.begin() + m_block_len);
// Block length without the newline.
m_block_len = i;
// Remove CRLF from the end of the block.
if (m_block_len > 0 && m_block[m_block_len - 1] == '\r')
-- m_block_len;
return true;
}
}
assert(false);
return false;
}
private:
boost::nowide::ifstream &m_ifs;
std::vector<char> m_block;
size_t m_block_size = 65536;
size_t m_block_len = 0;
pos_type m_file_start;
pos_type m_file_pos = 0;
};
// Load the config keys from the tail of a G-code file.
ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// Read a 64k block from the end of the G-code.
boost::nowide::ifstream ifs(file);
// Look for Slic3r or PrusaSlicer header.
// Look for the header across the whole file as the G-code may have been extended at the start by a post-processing script or the user.
bool has_delimiters = false;
{
static constexpr const char slic3r_gcode_header[] = "; generated by Slic3r ";
static constexpr const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
std::string header;
bool header_found = false;
while (std::getline(ifs, header)) {
if (strncmp(slic3r_gcode_header, header.c_str(), strlen(slic3r_gcode_header)) == 0) {
header_found = true;
break;
} else if (strncmp(prusaslicer_gcode_header, header.c_str(), strlen(prusaslicer_gcode_header)) == 0) {
// Parse PrusaSlicer version.
size_t i = strlen(prusaslicer_gcode_header);
for (; i < header.size() && header[i] == ' '; ++ i) ;
size_t j = i;
for (; j < header.size() && header[j] != ' '; ++ j) ;
try {
Semver semver(header.substr(i, j - i));
has_delimiters = semver >= Semver(2, 4, 0, nullptr, "alpha0");
} catch (const RuntimeError &) {
}
header_found = true;
break;
}
}
if (! header_found)
throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code.");
}
auto header_end_pos = ifs.tellg();
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
size_t key_value_pairs = 0;
if (has_delimiters)
{
// PrusaSlicer starting with 2.4.0-alpha0 delimits the config section stored into G-code with
// ; prusaslicer_config = begin
// ...
// ; prusaslicer_config = end
// The begin / end tags look like any other key / value pairs on purpose to be compatible with older G-code viewer.
// Read the file in reverse line by line.
ReverseLineReader reader(ifs, header_end_pos);
// Read the G-code file by 64k blocks back to front.
bool begin_found = false;
bool end_found = false;
std::string line;
while (reader.getline(line))
if (line == "; prusaslicer_config = end") {
end_found = true;
break;
}
if (! end_found)
throw Slic3r::RuntimeError(format("Configuration block closing tag \"; prusaslicer_config = end\" not found when reading %1%", file));
std::string key, value;
while (reader.getline(line)) {
if (line == "; prusaslicer_config = begin") {
begin_found = true;
break;
}
// line should be a valid key = value pair.
auto pos = line.find('=');
if (pos != std::string::npos && pos > 1 && line.front() == ';') {
key = line.substr(1, pos - 1);
value = line.substr(pos + 1);
boost::trim(key);
boost::trim(value);
try {
this->set_deserialize(key, value, substitutions_ctxt);
++ key_value_pairs;
} catch (UnknownOptionException & /* e */) {
// ignore
}
}
}
if (! begin_found)
throw Slic3r::RuntimeError(format("Configuration block opening tag \"; prusaslicer_config = begin\" not found when reading %1%", file));
}
else
{
// Slic3r or PrusaSlicer older than 2.4.0-alpha0 do not emit any delimiter.
// Try a heuristics reading the G-code from back.
ifs.seekg(0, ifs.end);
auto file_length = ifs.tellg();
auto data_length = std::min<std::fstream::pos_type>(65535, file_length - header_end_pos);
ifs.seekg(file_length - data_length, ifs.beg);
std::vector<char> data(size_t(data_length) + 1, 0);
ifs.read(data.data(), data_length);
ifs.close();
key_value_pairs = load_from_gcode_string_legacy(*this, data.data(), substitutions_ctxt);
}
if (key_value_pairs < 80)
throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
return std::move(substitutions_ctxt.substitutions);
}
void ConfigBase::save(const std::string &file) const
@ -803,7 +1035,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
throw NoDefinitionException(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr)
// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key);
// throw ConfigurationError(std::string("Invalid option name: ") + opt_key);
// Let the parent decide what to do if the opt_key is not defined by this->def().
return nullptr;
ConfigOption *opt = optdef->create_default_option();
@ -817,22 +1049,12 @@ const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key) co
return (it == options.end()) ? nullptr : it->second.get();
}
void DynamicConfig::read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra, t_config_option_keys* keys)
{
std::vector<const char*> args;
// push a bogus executable name (argv[0])
args.emplace_back("");
for (size_t i = 0; i < tokens.size(); ++ i)
args.emplace_back(tokens[i].c_str());
this->read_cli(int(args.size()), args.data(), extra, keys);
}
bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys)
{
// cache the CLI option => opt_key mapping
std::map<std::string,std::string> opts;
for (const auto &oit : this->def()->options)
for (auto t : oit.second.cli_args(oit.first))
for (const std::string &t : oit.second.cli_args(oit.first))
opts[t] = oit.first;
bool parse_options = true;
@ -854,14 +1076,8 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
parse_options = false;
continue;
}
// Remove leading dashes
boost::trim_left_if(token, boost::is_any_of("-"));
// Remove the "no-" prefix used to negate boolean options.
bool no = false;
if (boost::starts_with(token, "no-")) {
no = true;
boost::replace_first(token, "no-", "");
}
// Remove leading dashes (one or two).
token.erase(token.begin(), token.begin() + (boost::starts_with(token, "--") ? 2 : 1));
// Read value when supplied in the --key=value form.
std::string value;
{
@ -872,22 +1088,45 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
}
}
// Look for the cli -> option mapping.
const auto it = opts.find(token);
auto it = opts.find(token);
bool no = false;
if (it == opts.end()) {
boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl;
return false;
// Remove the "no-" prefix used to negate boolean options.
std::string yes_token;
if (boost::starts_with(token, "no-")) {
yes_token = token.substr(3);
it = opts.find(yes_token);
no = true;
}
if (it == opts.end()) {
boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl;
return false;
}
if (no)
token = yes_token;
}
const t_config_option_key opt_key = it->second;
const ConfigOptionDef &optdef = this->def()->options.at(opt_key);
const t_config_option_key &opt_key = it->second;
const ConfigOptionDef &optdef = this->def()->options.at(opt_key);
// If the option type expects a value and it was not already provided,
// look for it in the next token.
if (optdef.type != coBool && optdef.type != coBools && value.empty()) {
if (i == (argc-1)) {
boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl;
if (value.empty() && optdef.type != coBool && optdef.type != coBools) {
if (i == argc-1) {
boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl;
return false;
}
value = argv[++ i];
}
if (no) {
assert(optdef.type == coBool || optdef.type == coBools);
if (! value.empty()) {
boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl;
return false;
}
}
// Store the option value.
const bool existing = this->has(opt_key);
if (keys != nullptr && ! existing) {
@ -902,7 +1141,7 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
opt_vector->clear();
// Vector values will be chained. Repeated use of a parameter will append the parameter or parameters
// to the end of the value.
if (opt_base->type() == coBools)
if (opt_base->type() == coBools && value.empty())
static_cast<ConfigOptionBools*>(opt_base)->values.push_back(!no);
else
// Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way
@ -911,7 +1150,10 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
// unescaped by the calling shell.
opt_vector->deserialize(value, true);
} else if (opt_base->type() == coBool) {
static_cast<ConfigOptionBool*>(opt_base)->value = !no;
if (value.empty())
static_cast<ConfigOptionBool*>(opt_base)->value = !no;
else
opt_base->deserialize(value);
} else if (opt_base->type() == coString) {
// Do not unescape single string values, the unescaping is left to the calling shell.
static_cast<ConfigOptionString*>(opt_base)->value = value;
@ -961,6 +1203,65 @@ t_config_option_keys StaticConfig::keys() const
return keys;
}
// Iterate over the pairs of options with equal keys, call the fn.
// Returns true on early exit by fn().
template<typename Fn>
static inline bool dynamic_config_iterate(const DynamicConfig &lhs, const DynamicConfig &rhs, Fn fn)
{
std::map<t_config_option_key, std::unique_ptr<ConfigOption>>::const_iterator i = lhs.cbegin();
std::map<t_config_option_key, std::unique_ptr<ConfigOption>>::const_iterator j = rhs.cbegin();
while (i != lhs.cend() && j != rhs.cend())
if (i->first < j->first)
++ i;
else if (i->first > j->first)
++ j;
else {
assert(i->first == j->first);
if (fn(i->first, i->second.get(), j->second.get()))
// Early exit by fn.
return true;
++ i;
++ j;
}
// Finished to the end.
return false;
}
// Are the two configs equal? Ignoring options not present in both configs.
bool DynamicConfig::equals(const DynamicConfig &other) const
{
return ! dynamic_config_iterate(*this, other,
[](const t_config_option_key & /* key */, const ConfigOption *l, const ConfigOption *r) { return *l != *r; });
}
// Returns options differing in the two configs, ignoring options not present in both configs.
t_config_option_keys DynamicConfig::diff(const DynamicConfig &other) const
{
t_config_option_keys diff;
dynamic_config_iterate(*this, other,
[&diff](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) {
if (*l != *r)
diff.emplace_back(key);
// Continue iterating.
return false;
});
return diff;
}
// Returns options being equal in the two configs, ignoring options not present in both configs.
t_config_option_keys DynamicConfig::equal(const DynamicConfig &other) const
{
t_config_option_keys equal;
dynamic_config_iterate(*this, other,
[&equal](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) {
if (*l == *r)
equal.emplace_back(key);
// Continue iterating.
return false;
});
return equal;
}
}
#include <cereal/types/polymorphic.hpp>

View file

@ -80,32 +80,77 @@ extern bool unescape_strings_cstyle(const std::string &str, std::vector<
extern std::string escape_ampersand(const std::string& str);
/// Specialization of std::exception to indicate that an unknown config option has been encountered.
class UnknownOptionException : public Slic3r::RuntimeError {
public:
UnknownOptionException() :
Slic3r::RuntimeError("Unknown option exception") {}
UnknownOptionException(const std::string &opt_key) :
Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {}
namespace ConfigHelpers {
inline bool looks_like_enum_value(std::string value)
{
boost::trim(value);
if (value.empty() || value.size() > 64 || ! isalpha(value.front()))
return false;
for (const char c : value)
if (! (isalnum(c) || c == '_' || c == '-'))
return false;
return true;
}
inline bool enum_looks_like_true_value(std::string value) {
boost::trim(value);
return boost::iequals(value, "enabled") || boost::iequals(value, "on");
}
enum class DeserializationSubstitution {
Disabled,
DefaultsToFalse,
DefaultsToTrue
};
enum class DeserializationResult {
Loaded,
Substituted,
Failed,
};
};
/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
class NoDefinitionException : public Slic3r::RuntimeError
// Base for all exceptions thrown by the configuration layer.
class ConfigurationError : public Slic3r::RuntimeError {
public:
using RuntimeError::RuntimeError;
};
// Specialization of std::exception to indicate that an unknown config option has been encountered.
class UnknownOptionException : public ConfigurationError {
public:
UnknownOptionException() :
ConfigurationError("Unknown option exception") {}
UnknownOptionException(const std::string &opt_key) :
ConfigurationError(std::string("Unknown option exception: ") + opt_key) {}
};
// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
class NoDefinitionException : public ConfigurationError
{
public:
NoDefinitionException() :
Slic3r::RuntimeError("No definition exception") {}
ConfigurationError("No definition exception") {}
NoDefinitionException(const std::string &opt_key) :
Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {}
ConfigurationError(std::string("No definition exception: ") + opt_key) {}
};
/// Indicate that an unsupported accessor was called on a config option.
class BadOptionTypeException : public Slic3r::RuntimeError
// Indicate that an unsupported accessor was called on a config option.
class BadOptionTypeException : public ConfigurationError
{
public:
BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {}
BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {}
BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {}
BadOptionTypeException() : ConfigurationError("Bad option type exception") {}
BadOptionTypeException(const std::string &message) : ConfigurationError(message) {}
BadOptionTypeException(const char* message) : ConfigurationError(message) {}
};
// Indicate that an option has been deserialized from an invalid value.
class BadOptionValueException : public ConfigurationError
{
public:
BadOptionValueException() : ConfigurationError("Bad option value exception") {}
BadOptionValueException(const std::string &message) : ConfigurationError(message) {}
BadOptionValueException(const char* message) : ConfigurationError(message) {}
};
// Type of a configuration value.
@ -166,9 +211,16 @@ enum PrinterTechnology : unsigned char
enum ForwardCompatibilitySubstitutionRule
{
// Disable susbtitution, throw exception if an option value is not recognized.
Disable,
// Enable substitution of an unknown option value with default. Log the substitution.
Enable,
// Enable substitution of an unknown option value with default. Don't log the substitution.
EnableSilent,
// Enable substitution of an unknown option value with default. Log substitutions in user profiles, don't log substitutions in system profiles.
EnableSystemSilent,
// Enable silent substitution of an unknown option value with default when loading user profiles. Throw on an unknown option value in a system profile.
EnableSilentDisableSystem,
};
class ConfigOption;
@ -252,7 +304,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionSingle: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
}
@ -260,7 +312,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types");
throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs));
return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value;
}
@ -327,7 +379,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionVector: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs));
this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
}
@ -344,12 +396,12 @@ public:
if (opt->type() == this->type()) {
auto other = static_cast<const ConfigOptionVector<T>*>(opt);
if (other->values.empty())
throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector");
throw ConfigurationError("ConfigOptionVector::set(): Assigning from an empty vector");
this->values.emplace_back(other->values.front());
} else if (opt->type() == this->scalar_type())
this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value);
else
throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionVector::set():: Assigning an incompatible type");
}
}
@ -368,12 +420,12 @@ public:
// Assign the first value of the rhs vector.
auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
if (other->values.empty())
throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector");
throw ConfigurationError("ConfigOptionVector::set_at(): Assigning from an empty vector");
this->values[i] = other->get_at(j);
} else if (rhs->type() == this->scalar_type())
this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
else
throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type");
throw ConfigurationError("ConfigOptionVector::set_at(): Assigning an incompatible type");
}
const T& get_at(size_t i) const
@ -398,9 +450,9 @@ public:
else if (n > this->values.size()) {
if (this->values.empty()) {
if (opt_default == nullptr)
throw Slic3r::RuntimeError("ConfigOptionVector::resize(): No default value provided.");
throw ConfigurationError("ConfigOptionVector::resize(): No default value provided.");
if (opt_default->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type.");
throw ConfigurationError("ConfigOptionVector::resize(): Extending with an incompatible type.");
this->values.resize(n, static_cast<const ConfigOptionVector<T>*>(opt_default)->values.front());
} else {
// Resize by duplicating the last value.
@ -417,7 +469,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types");
throw ConfigurationError("ConfigOptionVector: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<T>*>(&rhs));
return this->values == static_cast<const ConfigOptionVector<T>*>(&rhs)->values;
}
@ -437,9 +489,9 @@ public:
// An option overrides another option if it is not nil and not equal.
bool overriden_by(const ConfigOption *rhs) const override {
if (this->nullable())
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
throw ConfigurationError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types.");
throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types.");
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
if (! rhs->nullable())
// Overridding a non-nullable object with another non-nullable object.
@ -457,9 +509,9 @@ public:
// Apply an override option, possibly a nullable one.
bool apply_override(const ConfigOption *rhs) override {
if (this->nullable())
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
throw ConfigurationError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types.");
throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types.");
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
if (! rhs->nullable()) {
// Overridding a non-nullable object with another non-nullable object.
@ -550,7 +602,7 @@ public:
bool operator< (const ConfigOptionFloatsTempl &rhs) const throw() { return vectors_lower(this->values, rhs.values); }
bool operator==(const ConfigOption &rhs) const override {
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types");
throw ConfigurationError("ConfigOptionFloatsTempl: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<double>*>(&rhs));
return vectors_equal(this->values, static_cast<const ConfigOptionVector<double>*>(&rhs)->values);
}
@ -597,7 +649,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
double value;
@ -622,9 +674,9 @@ protected:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
throw Slic3r::RuntimeError("Serializing invalid number");
throw ConfigurationError("Serializing invalid number");
}
static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) {
if (NULLABLE) {
@ -756,7 +808,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
int value;
@ -773,7 +825,7 @@ private:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
ss << v;
}
@ -963,7 +1015,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types");
throw ConfigurationError("ConfigOptionFloatOrPercent: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(&rhs));
return *this == *static_cast<const ConfigOptionFloatOrPercent*>(&rhs);
}
@ -979,7 +1031,7 @@ public:
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(rhs));
*this = *static_cast<const ConfigOptionFloatOrPercent*>(rhs);
}
@ -1023,7 +1075,7 @@ public:
bool operator==(const ConfigOptionFloatsOrPercentsTempl &rhs) const throw() { return vectors_equal(this->values, rhs.values); }
bool operator==(const ConfigOption &rhs) const override {
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
throw ConfigurationError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs));
return vectors_equal(this->values, static_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs)->values);
}
@ -1072,7 +1124,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
bool percent = item_str.find_first_of("%") != std::string::npos;
std::istringstream iss(item_str);
@ -1100,9 +1152,9 @@ protected:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
throw Slic3r::RuntimeError("Serializing invalid number");
throw ConfigurationError("Serializing invalid number");
}
static bool vectors_equal(const std::vector<FloatOrPercent> &v1, const std::vector<FloatOrPercent> &v2) {
if (NULLABLE) {
@ -1308,11 +1360,11 @@ public:
bool deserialize(const std::string &str, bool append = false) override
{
UNUSED(append);
if (str == "1" || boost::iequals(str, "enabled") || boost::iequals(str, "on")) {
if (str == "1") {
this->value = true;
return true;
}
if (str == "0" || boost::iequals(str, "disabled") || boost::iequals(str, "off")) {
if (str == "0") {
this->value = false;
return true;
}
@ -1378,24 +1430,39 @@ public:
}
return vv;
}
bool deserialize(const std::string &str, bool append = false) override
ConfigHelpers::DeserializationResult deserialize_with_substitutions(const std::string &str, bool append, ConfigHelpers::DeserializationSubstitution substitution)
{
if (! append)
this->values.clear();
std::istringstream is(str);
std::string item_str;
bool substituted = false;
while (std::getline(is, item_str, ',')) {
boost::trim(item_str);
unsigned char new_value = 0;
if (item_str == "nil") {
if (NULLABLE)
this->values.push_back(nil_value());
new_value = nil_value();
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else if (item_str == "1") {
new_value = true;
} else if (item_str == "0") {
new_value = false;
} else if (substitution != ConfigHelpers::DeserializationSubstitution::Disabled && ConfigHelpers::looks_like_enum_value(item_str)) {
new_value = ConfigHelpers::enum_looks_like_true_value(item_str) || substitution == ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
substituted = true;
} else
this->values.push_back(item_str.compare("1") == 0);
return ConfigHelpers::DeserializationResult::Failed;
this->values.push_back(new_value);
}
return true;
return substituted ? ConfigHelpers::DeserializationResult::Substituted : ConfigHelpers::DeserializationResult::Loaded;
}
bool deserialize(const std::string &str, bool append = false) override
{
return this->deserialize_with_substitutions(str, append, ConfigHelpers::DeserializationSubstitution::Disabled) == ConfigHelpers::DeserializationResult::Loaded;
}
protected:
@ -1404,7 +1471,7 @@ protected:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
ss << (v ? "1" : "0");
}
@ -1442,14 +1509,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Comparing incompatible types");
throw ConfigurationError("ConfigOptionEnum<T>: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
return this->value == (T)rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionEnum<T>: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
this->value = (T)rhs->getInt();
}
@ -1512,14 +1579,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types");
throw ConfigurationError("ConfigOptionEnumGeneric: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
return this->value == rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
this->value = rhs->getInt();
}
@ -1592,7 +1659,7 @@ public:
case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; }
case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; }
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@ -1611,7 +1678,7 @@ public:
case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; }
case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; }
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
}
}
}
@ -1623,7 +1690,7 @@ public:
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); break;
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@ -1642,7 +1709,7 @@ public:
case coBool: archive(*static_cast<const ConfigOptionBool*>(opt)); break;
case coBools: archive(*static_cast<const ConfigOptionBools*>(opt)); break;
case coEnum: archive(*static_cast<const ConfigOptionEnumGeneric*>(opt)); break;
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
}
}
// Make the compiler happy, shut up the warnings.
@ -1765,7 +1832,7 @@ public:
return out;
}
/// Iterate through all of the CLI options and write them to a stream.
// Iterate through all of the CLI options and write them to a stream.
std::ostream& print_cli_help(
std::ostream& out, bool show_defaults,
std::function<bool(const ConfigOptionDef &)> filter = [](const ConfigOptionDef &){ return true; }) const;
@ -1826,8 +1893,8 @@ public:
// The configuration definition is static: It does not carry the actual configuration values,
// but it carries the defaults of the configuration values.
ConfigBase() {}
~ConfigBase() override {}
ConfigBase() = default;
~ConfigBase() override = default;
// Virtual overridables:
public:
@ -1886,8 +1953,11 @@ public:
// An UnknownOptionException is thrown in case some option keys are not defined by this->def(),
// or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false);
bool equals(const ConfigBase &other) const { return this->diff(other).empty(); }
// Are the two configs equal? Ignoring options not present in both configs.
bool equals(const ConfigBase &other) const;
// Returns options differing in the two configs, ignoring options not present in both configs.
t_config_option_keys diff(const ConfigBase &other) const;
// Returns options being equal in the two configs, ignoring options not present in both configs.
t_config_option_keys equal(const ConfigBase &other) const;
std::string opt_serialize(const t_config_option_key &opt_key) const;
@ -1934,9 +2004,11 @@ public:
void setenv_() const;
ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load_from_gcode_file(const std::string &file, bool check_header /* = true */, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Returns number of key/value pairs extracted.
size_t load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions);
ConfigSubstitutions load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Loading a "will be one day a legacy format" of configuration stored into 3MF or AMF.
// Accepts the same data as load_from_ini_string(), only with each configuration line possibly prefixed with a semicolon (G-code comment).
ConfigSubstitutions load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
void save(const std::string &file) const;
@ -1953,12 +2025,12 @@ private:
class DynamicConfig : public virtual ConfigBase
{
public:
DynamicConfig() {}
DynamicConfig() = default;
DynamicConfig(const DynamicConfig &rhs) { *this = rhs; }
DynamicConfig(DynamicConfig &&rhs) noexcept : options(std::move(rhs.options)) { rhs.options.clear(); }
explicit DynamicConfig(const ConfigBase &rhs, const t_config_option_keys &keys);
explicit DynamicConfig(const ConfigBase& rhs) : DynamicConfig(rhs, rhs.keys()) {}
virtual ~DynamicConfig() override { clear(); }
virtual ~DynamicConfig() override = default;
// Copy a content of one DynamicConfig to another DynamicConfig.
// If rhs.def() is not null, then it has to be equal to this->def().
@ -2075,6 +2147,13 @@ public:
}
}
// Are the two configs equal? Ignoring options not present in both configs.
bool equals(const DynamicConfig &other) const;
// Returns options differing in the two configs, ignoring options not present in both configs.
t_config_option_keys diff(const DynamicConfig &other) const;
// Returns options being equal in the two configs, ignoring options not present in both configs.
t_config_option_keys equal(const DynamicConfig &other) const;
std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option<ConfigOptionString>(opt_key, create)->value; }
const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); }
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
@ -2099,7 +2178,6 @@ public:
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
// Command line processing
void read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra, t_config_option_keys* keys = nullptr);
bool read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys = nullptr);
std::map<t_config_option_key, std::unique_ptr<ConfigOption>>::const_iterator cbegin() const { return options.cbegin(); }
@ -2113,9 +2191,9 @@ private:
template<class Archive> void serialize(Archive &ar) { ar(options); }
};
/// Configuration store with a static definition of configuration values.
/// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
/// because the configuration values could be accessed directly.
// Configuration store with a static definition of configuration values.
// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
// because the configuration values could be accessed directly.
class StaticConfig : public virtual ConfigBase
{
public:

View file

@ -595,7 +595,7 @@ namespace Slic3r {
mz_zip_archive_file_stat stat;
m_name = boost::filesystem::path(filename).filename().stem().string();
m_name = boost::filesystem::path(filename).stem().string();
// we first loop the entries to read from the archive the .model file only, in order to extract the version from it
for (mz_uint i = 0; i < num_entries; ++i) {
@ -875,7 +875,9 @@ namespace Slic3r {
add_error("Error while reading config data to buffer");
return;
}
config.load_from_gcode_string(buffer.data(), config_substitutions);
//FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment.
// Each config line is prefixed with a semicolon (G-code comment), that is ugly.
config_substitutions.substitutions = config.load_from_ini_string_commented(std::move(buffer), config_substitutions.rule);
}
}
@ -1406,6 +1408,13 @@ namespace Slic3r {
m_model->delete_object(model_object);
}
if (m_version == 0) {
// if the 3mf was not produced by PrusaSlicer and there is only one object,
// set the object name to match the filename
if (m_model->objects.size() == 1)
m_model->objects.front()->name = m_name;
}
// applies instances' matrices
for (Instance& instance : m_instances) {
if (instance.instance != nullptr && instance.instance->get_object() != nullptr)
@ -2860,9 +2869,10 @@ namespace Slic3r {
stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n";
stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n";
}
assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters);
if (volume->source.is_converted_from_inches)
stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n";
if (volume->source.is_converted_from_meters)
else if (volume->source.is_converted_from_meters)
stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n";
}

View file

@ -706,7 +706,9 @@ void AMFParserContext::endElement(const char * /* name */)
case NODE_TYPE_METADATA:
if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) {
m_config->load_from_gcode_string(m_value[1].c_str(), *m_config_substitutions);
//FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment.
// Each config line is prefixed with a semicolon (G-code comment), that is ugly.
m_config_substitutions->substitutions = m_config->load_from_ini_string_commented(std::move(m_value[1].c_str()), m_config_substitutions->rule);
}
else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
const char *opt_key = m_value[0].c_str() + 7;
@ -1241,9 +1243,10 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
stream << " <metadata type=\"slic3r.source_offset_y\">" << volume->source.mesh_offset(1) << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_offset_z\">" << volume->source.mesh_offset(2) << "</metadata>\n";
}
assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters);
if (volume->source.is_converted_from_inches)
stream << " <metadata type=\"slic3r.source_in_inches\">1</metadata>\n";
if (volume->source.is_converted_from_meters)
else if (volume->source.is_converted_from_meters)
stream << " <metadata type=\"slic3r.source_in_meters\">1</metadata>\n";
stream << std::setprecision(std::numeric_limits<float>::max_digits10);
const indexed_triangle_set &its = volume->mesh().its;

View file

@ -203,7 +203,7 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg)
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
throw Slic3r::FileIOError("Invalid SL1 file");
throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
RasterParams rstp;
@ -229,7 +229,7 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg)
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
if (!opt_layerh || !opt_init_layerh)
throw Slic3r::FileIOError("Invalid SL1 file");
throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
}

View file

@ -1464,13 +1464,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
_write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
_write_format(file, ";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
// Append full config.
_write(file, "\n");
// Append full config, delimited by two 'phony' configuration keys prusaslicer_config = begin and prusaslicer_config = end.
// The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer.
{
_write(file, "\n; prusaslicer_config = begin\n");
std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
_write(file, full_config);
_write(file, "; prusaslicer_config = end\n");
}
print.throw_if_canceled();
}
@ -2714,7 +2716,9 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// calculate extrusion length per distance unit
double e_per_mm = m_writer.extruder()->e_per_mm3() * path.mm3_per_mm;
if (m_writer.extrusion_axis().empty()) e_per_mm = 0;
if (m_writer.extrusion_axis().empty())
// gcfNoExtrusion
e_per_mm = 0;
// set speed
if (speed == -1) {

View file

@ -24,6 +24,9 @@
static const float INCHES_TO_MM = 25.4f;
static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2
#if ENABLE_RETRACT_ACCELERATION
static const float DEFAULT_RETRACT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2
#endif // ENABLE_RETRACT_ACCELERATION
static const float DEFAULT_TRAVEL_ACCELERATION = 1250.0f;
static const size_t MIN_EXTRUDERS_COUNT = 5;
@ -178,6 +181,10 @@ void GCodeProcessor::TimeMachine::reset()
enabled = false;
acceleration = 0.0f;
max_acceleration = 0.0f;
#if ENABLE_RETRACT_ACCELERATION
retract_acceleration = 0.0f;
max_retract_acceleration = 0.0f;
#endif // ENABLE_RETRACT_ACCELERATION
travel_acceleration = 0.0f;
max_travel_acceleration = 0.0f;
extrude_factor_override_percentage = 1.0f;
@ -834,6 +841,11 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i);
m_time_processor.machines[i].max_acceleration = max_acceleration;
m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION;
#if ENABLE_RETRACT_ACCELERATION
float max_retract_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i);
m_time_processor.machines[i].max_retract_acceleration = max_retract_acceleration;
m_time_processor.machines[i].retract_acceleration = (max_retract_acceleration > 0.0f) ? max_retract_acceleration : DEFAULT_RETRACT_ACCELERATION;
#endif // ENABLE_RETRACT_ACCELERATION
float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i);
m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration;
m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION;
@ -1052,6 +1064,11 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i);
m_time_processor.machines[i].max_acceleration = max_acceleration;
m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION;
#if ENABLE_RETRACT_ACCELERATION
float max_retract_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i);
m_time_processor.machines[i].max_retract_acceleration = max_retract_acceleration;
m_time_processor.machines[i].retract_acceleration = (max_retract_acceleration > 0.0f) ? max_retract_acceleration : DEFAULT_RETRACT_ACCELERATION;
#endif // ENABLE_RETRACT_ACCELERATION
float max_travel_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_travel, i);
m_time_processor.machines[i].max_travel_acceleration = max_travel_acceleration;
m_time_processor.machines[i].travel_acceleration = (max_travel_acceleration > 0.0f) ? max_travel_acceleration : DEFAULT_TRAVEL_ACCELERATION;
@ -1172,7 +1189,7 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr
// Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code.
// Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config,
// thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways.
config.load_from_gcode_file(filename, false, ForwardCompatibilitySubstitutionRule::EnableSilent);
config.load_from_gcode_file(filename, ForwardCompatibilitySubstitutionRule::EnableSilent);
apply_config(config);
}
else if (m_producer == EProducer::Simplify3D)
@ -2713,14 +2730,22 @@ void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line)
set_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
set_travel_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
if (line.has_value('T', value))
#if ENABLE_RETRACT_ACCELERATION
set_retract_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
#else
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value);
#endif // ENABLE_RETRACT_ACCELERATION
}
else {
// New acceleration format, compatible with the upstream Marlin.
if (line.has_value('P', value))
set_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
if (line.has_value('R', value))
#if ENABLE_RETRACT_ACCELERATION
set_retract_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
#else
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value);
#endif // ENABLE_RETRACT_ACCELERATION
if (line.has_value('T', value))
// Interpret the T value as the travel acceleration in the new Marlin format.
set_travel_acceleration(static_cast<PrintEstimatedStatistics::ETimeMode>(i), value);
@ -2979,10 +3004,30 @@ float GCodeProcessor::get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode
}
}
#if ENABLE_RETRACT_ACCELERATION
float GCodeProcessor::get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const
{
size_t id = static_cast<size_t>(mode);
return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].retract_acceleration : DEFAULT_RETRACT_ACCELERATION;
}
#else
float GCodeProcessor::get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const
{
return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, static_cast<size_t>(mode));
}
#endif // ENABLE_RETRACT_ACCELERATION
#if ENABLE_RETRACT_ACCELERATION
void GCodeProcessor::set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value)
{
size_t id = static_cast<size_t>(mode);
if (id < m_time_processor.machines.size()) {
m_time_processor.machines[id].retract_acceleration = (m_time_processor.machines[id].max_retract_acceleration == 0.0f) ? value :
// Clamp the acceleration with the maximum.
std::min(value, m_time_processor.machines[id].max_retract_acceleration);
}
}
#endif // ENABLE_RETRACT_ACCELERATION
float GCodeProcessor::get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const
{

View file

@ -242,6 +242,11 @@ namespace Slic3r {
float acceleration; // mm/s^2
// hard limit for the acceleration, to which the firmware will clamp.
float max_acceleration; // mm/s^2
#if ENABLE_RETRACT_ACCELERATION
float retract_acceleration; // mm/s^2
// hard limit for the acceleration, to which the firmware will clamp.
float max_retract_acceleration; // mm/s^2
#endif // ENABLE_RETRACT_ACCELERATION
float travel_acceleration; // mm/s^2
// hard limit for the travel acceleration, to which the firmware will clamp.
float max_travel_acceleration; // mm/s^2
@ -337,9 +342,9 @@ namespace Slic3r {
std::string printer;
void reset() {
print = "";
filament = std::vector<std::string>();
printer = "";
print.clear();
filament.clear();
printer.clear();
}
};
std::string filename;
@ -669,6 +674,9 @@ namespace Slic3r {
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
#if ENABLE_RETRACT_ACCELERATION
void set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value);
#endif // ENABLE_RETRACT_ACCELERATION
float get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
void set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value);
float get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;

View file

@ -1,10 +1,15 @@
#include "PostProcessor.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/format.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cenv.hpp>
#include <boost/nowide/fstream.hpp>
#ifdef WIN32
@ -179,41 +184,146 @@ static int run_script(const std::string &script, const std::string &gcode, std::
namespace Slic3r {
void run_post_process_scripts(const std::string &path, const DynamicPrintConfig &config)
// Run post processing script / scripts if defined.
// Returns true if a post-processing script was executed.
// Returns false if no post-processing script was defined.
// Throws an exception on error.
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
// For a "File" target, a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
// In that case the caller is responsible to delete the temp file created.
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
// The post-processing script may change the output_name.
bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config)
{
const auto* post_process = config.opt<ConfigOptionStrings>("post_process");
const auto *post_process = config.opt<ConfigOptionStrings>("post_process");
if (// likely running in SLA mode
post_process == nullptr ||
// no post-processing script
post_process->values.empty())
return;
return false;
std::string path;
if (make_copy) {
// Don't run the post-processing script on the input file, it will be memory mapped by the G-code viewer.
// Make a copy.
path = src_path + ".pp";
// First delete an old file if it exists.
try {
if (boost::filesystem::exists(path))
boost::filesystem::remove(path);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting an old temporary file %1% before running a post-processing script: %2%", path, err.what());
}
// Second make a copy.
std::string error_message;
if (copy_file(src_path, path, error_message, false) != SUCCESS)
throw Slic3r::RuntimeError(Slic3r::format("Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%", src_path, error_message));
} else {
// Don't make a copy of the G-code before running the post-processing script.
path = src_path;
}
auto delete_copy = [&path, &src_path, make_copy]() {
if (make_copy)
try {
if (boost::filesystem::exists(path))
boost::filesystem::remove(path);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a temporary copy %1% of a G-code file %2% : %3%", path, src_path, err.what());
}
};
// Store print configuration into environment variables.
config.setenv_();
auto gcode_file = boost::filesystem::path(path);
if (! boost::filesystem::exists(gcode_file))
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
for (const std::string &scripts : post_process->values) {
std::vector<std::string> lines;
boost::split(lines, scripts, boost::is_any_of("\r\n"));
for (std::string script : lines) {
// Ignore empty post processing script lines.
boost::trim(script);
if (script.empty())
continue;
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
// Store print configuration into environment variables.
config.setenv_();
// Let the post-processing script know the target host ("File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...)
boost::nowide::setenv("SLIC3R_PP_HOST", host.c_str(), 1);
// Let the post-processing script know the final file name. For "File" host, it is a full path of the target file name and its location, for example pointing to an SD card.
// For "PrusaLink" or "OctoPrint", it is a file name optionally with a directory on the target host.
boost::nowide::setenv("SLIC3R_PP_OUTPUT_NAME", output_name.c_str(), 1);
std::string std_err;
const int result = run_script(script, gcode_file.string(), std_err);
if (result != 0) {
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
BOOST_LOG_TRIVIAL(error) << msg;
throw Slic3r::RuntimeError(msg);
// Path to an optional file that the post-processing script may create and populate it with a single line containing the output_name replacement.
std::string path_output_name = path + ".output_name";
auto remove_output_name_file = [&path_output_name, &src_path]() {
try {
if (boost::filesystem::exists(path_output_name))
boost::filesystem::remove(path_output_name);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a file %1% carrying the final name / path of a G-code file %2%: %3%", path_output_name, src_path, err.what());
}
};
// Remove possible stalled path_output_name of the previous run.
remove_output_name_file();
try {
for (const std::string &scripts : post_process->values) {
std::vector<std::string> lines;
boost::split(lines, scripts, boost::is_any_of("\r\n"));
for (std::string script : lines) {
// Ignore empty post processing script lines.
boost::trim(script);
if (script.empty())
continue;
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
std::string std_err;
const int result = run_script(script, gcode_file.string(), std_err);
if (result != 0) {
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
BOOST_LOG_TRIVIAL(error) << msg;
delete_copy();
throw Slic3r::RuntimeError(msg);
}
}
}
if (boost::filesystem::exists(path_output_name)) {
try {
// Read a single line from path_output_name, which should contain the new output name of the post-processed G-code.
boost::nowide::fstream f;
f.open(path_output_name, std::ios::in);
std::string new_output_name;
std::getline(f, new_output_name);
f.close();
if (host == "File") {
namespace fs = boost::filesystem;
fs::path op(new_output_name);
if (op.is_relative() && op.has_filename() && op.parent_path().empty()) {
// Is this just a filename? Make it an absolute path.
auto outpath = fs::path(output_name).parent_path();
outpath /= op.string();
new_output_name = outpath.string();
}
else {
if (! op.is_absolute() || ! op.has_filename())
throw Slic3r::RuntimeError("Unable to parse desired new path from output name file");
}
if (! fs::exists(fs::path(new_output_name).parent_path()))
throw Slic3r::RuntimeError(Slic3r::format("Output directory does not exist: %1%",
fs::path(new_output_name).parent_path().string()));
}
BOOST_LOG_TRIVIAL(trace) << "Post-processing script changed the file name from " << output_name << " to " << new_output_name;
output_name = new_output_name;
} catch (const std::exception &err) {
throw Slic3r::RuntimeError(Slic3r::format("run_post_process_scripts: Failed reading a file %1% "
"carrying the final name / path of a G-code file: %2%",
path_output_name, err.what()));
}
remove_output_name_file();
}
} catch (...) {
remove_output_name_file();
delete_copy();
throw;
}
src_path = std::move(path);
return true;
}
} // namespace Slic3r

View file

@ -8,7 +8,23 @@
namespace Slic3r {
extern void run_post_process_scripts(const std::string &path, const DynamicPrintConfig &config);
// Run post processing script / scripts if defined.
// Returns true if a post-processing script was executed.
// Returns false if no post-processing script was defined.
// Throws an exception on error.
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
// If make_copy, then a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
// In that case the caller is responsible to delete the temp file created.
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
// The post-processing script may change the output_name.
extern bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config);
inline bool run_post_process_scripts(std::string &src_path, const DynamicPrintConfig &config)
{
std::string src_path_name = src_path;
return run_post_process_scripts(src_path, false, "File", src_path_name, config);
}
} // namespace Slic3r

View file

@ -12,16 +12,24 @@
namespace Slic3r {
static inline char get_extrusion_axis_char(const GCodeConfig &config)
{
std::string axis = get_extrusion_axis(config);
assert(axis.size() <= 1);
// Return 0 for gcfNoExtrusion
return axis.empty() ? 0 : axis[0];
}
void GCodeReader::apply_config(const GCodeConfig &config)
{
m_config = config;
m_extrusion_axis = get_extrusion_axis(m_config)[0];
m_extrusion_axis = get_extrusion_axis_char(m_config);
}
void GCodeReader::apply_config(const DynamicPrintConfig &config)
{
m_config.apply(config, true);
m_extrusion_axis = get_extrusion_axis(m_config)[0];
m_extrusion_axis = get_extrusion_axis_char(m_config);
}
const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command)
@ -52,9 +60,10 @@ const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline,
case 'Z': axis = Z; break;
case 'F': axis = F; break;
default:
if (*c == m_extrusion_axis)
axis = E;
else if (*c >= 'A' && *c <= 'Z')
if (*c == m_extrusion_axis) {
if (m_extrusion_axis != 0)
axis = E;
} else if (*c >= 'A' && *c <= 'Z')
// Unknown axis, but we still want to remember that such a axis was seen.
axis = UNKNOWN_AXIS;
break;
@ -190,6 +199,8 @@ void GCodeReader::GCodeLine::set(const GCodeReader &reader, const Axis axis, con
match[1] = 'F';
else {
assert(axis == E);
// Extruder axis is set.
assert(reader.extrusion_axis() != 0);
match[1] = reader.extrusion_axis();
}

View file

@ -122,8 +122,9 @@ public:
float& f() { return m_position[F]; }
float f() const { return m_position[F]; }
// Returns 0 for gcfNoExtrusion.
char extrusion_axis() const { return m_extrusion_axis; }
void set_extrusion_axis(char axis) { m_extrusion_axis = axis; }
// void set_extrusion_axis(char axis) { m_extrusion_axis = axis; }
private:
const char* parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command);

View file

@ -405,8 +405,10 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " " << m_extrusion_axis << E_NUM(m_extruder->E());
<< " Y" << XYZF_NUM(point(1));
if (! m_extrusion_axis.empty())
// not gcfNoExtrusion
gcode << " " << m_extrusion_axis << E_NUM(m_extruder->E());
COMMENT(comment);
gcode << "\n";
return gcode.str();
@ -421,8 +423,10 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " Z" << XYZF_NUM(point(2))
<< " " << m_extrusion_axis << E_NUM(m_extruder->E());
<< " Z" << XYZF_NUM(point(2));
if (! m_extrusion_axis.empty())
// not gcfNoExtrusion
gcode << " " << m_extrusion_axis << E_NUM(m_extruder->E());
COMMENT(comment);
gcode << "\n";
return gcode.str();
@ -474,7 +478,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std
gcode << "G22 ; retract\n";
else
gcode << "G10 ; retract\n";
} else {
} else if (! m_extrusion_axis.empty()) {
gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E())
<< " F" << XYZF_NUM(m_extruder->retract_speed() * 60.);
COMMENT(comment);
@ -503,7 +507,7 @@ std::string GCodeWriter::unretract()
else
gcode << "G11 ; unretract\n";
gcode << this->reset_e();
} else {
} else if (! m_extrusion_axis.empty()) {
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E())
<< " F" << XYZF_NUM(m_extruder->deretract_speed() * 60.);

View file

@ -25,6 +25,7 @@ public:
Extruder* extruder() { return m_extruder; }
const Extruder* extruder() const { return m_extruder; }
// Returns empty string for gcfNoExtrusion.
std::string extrusion_axis() const { return m_extrusion_axis; }
void apply_print_config(const PrintConfig &print_config);
// Extruders are expected to be sorted in an increasing order.

View file

@ -118,6 +118,11 @@ void triangle_mesh_to_cgal(const std::vector<stl_vertex> & V,
{
if (F.empty()) return;
size_t vertices_count = V.size();
size_t edges_count = (F.size()* 3) / 2;
size_t faces_count = F.size();
out.reserve(vertices_count, edges_count, faces_count);
for (auto &v : V)
out.add_vertex(typename _Mesh::Point{v.x(), v.y(), v.z()});

View file

@ -157,6 +157,7 @@ template<class Its> bool its_is_splittable(const Its &m)
const auto& neighbor_index = ItsWithNeighborsIndex_<Its>::get_index(m);
std::vector<char> visited(its.indices.size(), false);
its_find_unvisited_neighbors(its, neighbor_index, visited);
auto faces = its_find_unvisited_neighbors(its, neighbor_index, visited);
return !faces.empty();

View file

@ -424,7 +424,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
ModelObject* object = new ModelObject(this);
object->input_file = this->objects.front()->input_file;
object->name = this->objects.front()->name;
object->name = boost::filesystem::path(this->objects.front()->input_file).stem().string();
//FIXME copy the config etc?
unsigned int extruder_counter = 0;
@ -439,7 +439,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
int counter = 1;
auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) {
assert(new_v != nullptr);
new_v->name = o->name + "_" + std::to_string(counter++);
new_v->name = (counter > 1) ? o->name + "_" + std::to_string(counter++) : o->name;
new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
return new_v;
};
@ -460,13 +460,15 @@ void Model::convert_multipart_object(unsigned int max_extruders)
this->objects.push_back(object);
}
static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3;
bool Model::looks_like_imperial_units() const
{
if (this->objects.size() == 0)
return false;
for (ModelObject* obj : this->objects)
if (obj->get_object_stl_stats().volume < 9.0) // 9 = 3*3*3;
if (obj->get_object_stl_stats().volume < volume_threshold_inches)
return true;
return false;
@ -474,22 +476,26 @@ bool Model::looks_like_imperial_units() const
void Model::convert_from_imperial_units(bool only_small_volumes)
{
double in_to_mm = 25.4;
static constexpr const double in_to_mm = 25.4;
for (ModelObject* obj : this->objects)
if (! only_small_volumes || obj->get_object_stl_stats().volume < 9.0) { // 9 = 3*3*3;
if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_inches) {
obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
for (ModelVolume* v : obj->volumes)
for (ModelVolume* v : obj->volumes) {
assert(! v->source.is_converted_from_meters);
v->source.is_converted_from_inches = true;
}
}
}
static constexpr const double volume_threshold_meters = 0.001; // 0.001 = 0.1*0.1*0.1
bool Model::looks_like_saved_in_meters() const
{
if (this->objects.size() == 0)
return false;
for (ModelObject* obj : this->objects)
if (obj->get_object_stl_stats().volume < 0.001) // 0.001 = 0.1*0.1*0.1;
if (obj->get_object_stl_stats().volume < volume_threshold_meters)
return true;
return false;
@ -497,12 +503,14 @@ bool Model::looks_like_saved_in_meters() const
void Model::convert_from_meters(bool only_small_volumes)
{
double m_to_mm = 1000;
static constexpr const double m_to_mm = 1000;
for (ModelObject* obj : this->objects)
if (! only_small_volumes || obj->get_object_stl_stats().volume < 0.001) { // 0.001 = 0.1*0.1*0.1;
if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_meters) {
obj->scale_mesh_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm));
for (ModelVolume* v : obj->volumes)
for (ModelVolume* v : obj->volumes) {
assert(! v->source.is_converted_from_inches);
v->source.is_converted_from_meters = true;
}
}
}
@ -948,9 +956,26 @@ void ModelObject::center_around_origin(bool include_modifiers)
void ModelObject::ensure_on_bed(bool allow_negative_z)
{
const double min_z = get_min_z();
if (!allow_negative_z || min_z > SINKING_Z_THRESHOLD)
translate_instances({ 0.0, 0.0, -min_z });
double z_offset = 0.0;
if (allow_negative_z) {
if (parts_count() == 1) {
const double min_z = get_min_z();
const double max_z = get_max_z();
if (min_z >= SINKING_Z_THRESHOLD || max_z < 0.0)
z_offset = -min_z;
}
else {
const double max_z = get_max_z();
if (max_z < SINKING_MIN_Z_THRESHOLD)
z_offset = SINKING_MIN_Z_THRESHOLD - max_z;
}
}
else
z_offset = -get_min_z();
if (z_offset != 0.0)
translate_instances(z_offset * Vec3d::UnitZ());
}
void ModelObject::translate_instances(const Vec3d& vector)
@ -1075,6 +1100,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
vol->source.is_converted_from_inches = conv_type == ConversionType::CONV_FROM_INCH;
if (conv_type == ConversionType::CONV_FROM_METER || conv_type == ConversionType::CONV_TO_METER)
vol->source.is_converted_from_meters = conv_type == ConversionType::CONV_FROM_METER;
assert(! vol->source.is_converted_from_inches || ! vol->source.is_converted_from_meters);
}
else
vol->set_offset(volume->get_offset());
@ -1105,6 +1131,15 @@ size_t ModelObject::facets_count() const
return num;
}
size_t ModelObject::parts_count() const
{
size_t num = 0;
for (const ModelVolume* v : this->volumes)
if (v->is_model_part())
++num;
return num;
}
bool ModelObject::needed_repair() const
{
for (const ModelVolume *v : this->volumes)
@ -1420,6 +1455,19 @@ double ModelObject::get_min_z() const
}
}
double ModelObject::get_max_z() const
{
if (instances.empty())
return 0.0;
else {
double max_z = -DBL_MAX;
for (size_t i = 0; i < instances.size(); ++i) {
max_z = std::max(max_z, get_instance_max_z(i));
}
return max_z;
}
}
double ModelObject::get_instance_min_z(size_t instance_idx) const
{
double min_z = DBL_MAX;
@ -1441,6 +1489,27 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const
return min_z + inst->get_offset(Z);
}
double ModelObject::get_instance_max_z(size_t instance_idx) const
{
double max_z = -DBL_MAX;
const ModelInstance* inst = instances[instance_idx];
const Transform3d& mi = inst->get_matrix(true);
for (const ModelVolume* v : volumes) {
if (!v->is_model_part())
continue;
const Transform3d mv = mi * v->get_matrix();
const TriangleMesh& hull = v->get_convex_hull();
for (const stl_facet& facet : hull.stl.facet_start)
for (int i = 0; i < 3; ++i)
max_z = std::max(max_z, (mv * facet.vertex[i].cast<double>()).z());
}
return max_z + inst->get_offset(Z);
}
unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume)
{
unsigned int num_printable = 0;
@ -1827,6 +1896,7 @@ void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_hand
void ModelVolume::convert_from_imperial_units()
{
assert(! this->source.is_converted_from_meters);
double in_to_mm = 25.4;
this->scale_geometry_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
this->set_offset(Vec3d(0, 0, 0));
@ -1835,6 +1905,7 @@ void ModelVolume::convert_from_imperial_units()
void ModelVolume::convert_from_meters()
{
assert(! this->source.is_converted_from_inches);
double m_to_mm = 1000;
this->scale_geometry_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm));
this->set_offset(Vec3d(0, 0, 0));

View file

@ -347,6 +347,7 @@ public:
size_t materials_count() const;
size_t facets_count() const;
size_t parts_count() const;
bool needed_repair() const;
ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes);
void split(ModelObjectPtrs* new_objects);
@ -358,7 +359,9 @@ public:
void bake_xy_rotation_into_meshes(size_t instance_idx);
double get_min_z() const;
double get_max_z() const;
double get_instance_min_z(size_t instance_idx) const;
double get_instance_max_z(size_t instance_idx) const;
// Called by Print::validate() from the UI thread.
unsigned int check_instances_print_volume_state(const BoundingBoxf3& print_volume);
@ -1177,6 +1180,7 @@ void check_model_ids_equal(const Model &model1, const Model &model2);
#endif /* NDEBUG */
static const float SINKING_Z_THRESHOLD = -0.001f;
static const double SINKING_MIN_Z_THRESHOLD = 0.05;
} // namespace Slic3r

View file

@ -1240,221 +1240,6 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
const size_t num_layers = input_expolygons.size();
const ConstLayerPtrsAdaptor layers = print_object.layers();
#if 0
auto get_extrusion_width = [&layers = std::as_const(layers)](const size_t layer_idx) -> float {
auto extrusion_width_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(),
[](const LayerRegion *l1, const LayerRegion *l2) {
return l1->region().config().perimeter_extrusion_width <
l2->region().config().perimeter_extrusion_width;
});
assert(extrusion_width_it != layers[layer_idx]->regions().end());
return float((*extrusion_width_it)->region().config().perimeter_extrusion_width);
};
auto get_top_solid_layers = [&layers = std::as_const(layers)](const size_t layer_idx) -> int {
auto top_solid_layer_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(),
[](const LayerRegion *l1, const LayerRegion *l2) {
return l1->region().config().top_solid_layers < l2->region().config().top_solid_layers;
});
assert(top_solid_layer_it != layers[layer_idx]->regions().end());
return (*top_solid_layer_it)->region().config().top_solid_layers;
};
auto get_bottom_solid_layers = [&layers = std::as_const(layers)](const size_t layer_idx) -> int {
auto top_bottom_layer_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(),
[](const LayerRegion *l1, const LayerRegion *l2) {
return l1->region().config().bottom_solid_layers < l2->region().config().bottom_solid_layers;
});
assert(top_bottom_layer_it != layers[layer_idx]->regions().end());
return (*top_bottom_layer_it)->region().config().bottom_solid_layers;
};
std::vector<ExPolygons> top_layers(num_layers);
top_layers.back() = input_expolygons.back();
tbb::parallel_for(tbb::blocked_range<size_t>(1, num_layers), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
top_layers[layer_idx - 1] = diff_ex(input_expolygons[layer_idx - 1], offset_ex(input_expolygons[layer_idx], extrusion_width));
}
}); // end of parallel_for
std::vector<ExPolygons> bottom_layers(num_layers);
bottom_layers.front() = input_expolygons.front();
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers - 1), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
bottom_layers[layer_idx + 1] = diff_ex(input_expolygons[layer_idx + 1], offset_ex(input_expolygons[layer_idx], extrusion_width));
}
}); // end of parallel_for
std::vector<std::vector<ClipperLib::Paths>> triangles_by_color_raw(num_extruders, std::vector<ClipperLib::Paths>(layers.size()));
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - begin";
{
auto delta = float(10 * SCALED_EPSILON);
std::vector<float> deltas { delta, delta, delta };
Points projected_facet;
for (const ModelVolume *mv : print_object.model_object()->volumes)
if (mv->is_model_part()) {
const Transform3f tr = print_object.trafo().cast<float>() * mv->get_matrix().cast<float>();
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) {
const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx));
if (custom_facets.indices.empty())
continue;
throw_on_cancel_callback();
for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) {
float min_z = std::numeric_limits<float>::max();
float max_z = std::numeric_limits<float>::lowest();
std::array<Vec3f, 3> facet;
for (int p_idx = 0; p_idx < 3; ++p_idx) {
facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)];
max_z = std::max(max_z, facet[p_idx].z());
min_z = std::min(min_z, facet[p_idx].z());
}
// Sort the vertices by z-axis for simplification of projected_facet on slices
std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); });
projected_facet.clear();
projected_facet.reserve(3);
for (int p_idx = 0; p_idx < 3; ++p_idx)
projected_facet.emplace_back(Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y())) - print_object.center_offset());
if (cross2((projected_facet[1] - projected_facet[0]).cast<int64_t>(), (projected_facet[2] - projected_facet[1]).cast<int64_t>()) < 0)
// Make CCW.
std::swap(projected_facet[1], projected_facet[2]);
ClipperLib::Path offsetted = mittered_offset_path_scaled(projected_facet, deltas, 3.);
// Find lowest slice not below the triangle.
auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON),
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z - EPSILON),
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
if (last_layer == layers.end())
--last_layer;
if (first_layer == layers.end() || (first_layer != layers.begin() && facet[0].z() < (*first_layer)->print_z - EPSILON))
--first_layer;
for (auto layer_it = first_layer; (layer_it != (last_layer + 1) && layer_it != layers.end()); ++layer_it)
if (size_t layer_idx = layer_it - layers.begin(); ! top_layers[layer_idx].empty() || ! bottom_layers[layer_idx].empty())
triangles_by_color_raw[extruder_idx][layer_idx].emplace_back(offsetted);
}
}
}
}
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - end";
std::vector<std::vector<ExPolygons>> triangles_by_color(num_extruders, std::vector<ExPolygons>(layers.size()));
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
float offset_factor = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id)
if (ClipperLib::Paths &src_paths = triangles_by_color_raw[extruder_id][layer_idx]; !src_paths.empty())
triangles_by_color[extruder_id][layer_idx] = offset_ex(offset_ex(ClipperPaths_to_Slic3rExPolygons(src_paths), -offset_factor), offset_factor);
}
}); // end of parallel_for
triangles_by_color_raw.clear();
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_extruders);
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_extruders);
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers));
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers));
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - begin";
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
float extrusion_width = scale_(get_extrusion_width(layer_idx));
int top_solid_layers = get_top_solid_layers(layer_idx);
ExPolygons top_expolygon = top_layers[layer_idx];
if (top_expolygon.empty())
continue;
for (size_t color_idx = 0; color_idx < triangles_by_color.size(); ++color_idx) {
throw_on_cancel_callback();
if (triangles_by_color[color_idx][layer_idx].empty())
continue;
ExPolygons intersection_poly = intersection_ex(triangles_by_color[color_idx][layer_idx], top_expolygon);
if (!intersection_poly.empty()) {
triangles_by_color_top[color_idx][layer_idx].insert(triangles_by_color_top[color_idx][layer_idx].end(), intersection_poly.begin(),
intersection_poly.end());
for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - top_solid_layers), int(0)); --last_idx) {
float offset_value = float(layer_idx - last_idx) * (-1.0f) * extrusion_width;
if (offset_ex(top_expolygon, offset_value).empty())
continue;
ExPolygons layer_slices_trimmed = input_expolygons[last_idx];
for (int last_idx_1 = last_idx; last_idx_1 < int(layer_idx); ++last_idx_1) {
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx_1 + 1]);
}
ExPolygons offset_e = offset_ex(layer_slices_trimmed, offset_value);
ExPolygons intersection_poly_2 = intersection_ex(triangles_by_color_top[color_idx][layer_idx], offset_e);
triangles_by_color_top[color_idx][last_idx].insert(triangles_by_color_top[color_idx][last_idx].end(), intersection_poly_2.begin(),
intersection_poly_2.end());
}
}
}
}
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - end";
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - begin";
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
float extrusion_width = scale_(get_extrusion_width(layer_idx));
int bottom_solid_layers = get_bottom_solid_layers(layer_idx);
const ExPolygons &bottom_expolygon = bottom_layers[layer_idx];
if (bottom_expolygon.empty())
continue;
for (size_t color_idx = 0; color_idx < triangles_by_color.size(); ++color_idx) {
throw_on_cancel_callback();
if (triangles_by_color[color_idx][layer_idx].empty())
continue;
ExPolygons intersection_poly = intersection_ex(triangles_by_color[color_idx][layer_idx], bottom_expolygon);
if (!intersection_poly.empty()) {
triangles_by_color_bottom[color_idx][layer_idx].insert(triangles_by_color_bottom[color_idx][layer_idx].end(), intersection_poly.begin(),
intersection_poly.end());
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, num_layers); ++last_idx) {
float offset_value = float(last_idx - layer_idx) * (-1.0f) * extrusion_width;
if (offset_ex(bottom_expolygon, offset_value).empty())
continue;
ExPolygons layer_slices_trimmed = input_expolygons[last_idx];
for (int last_idx_1 = int(last_idx); last_idx_1 > int(layer_idx); --last_idx_1) {
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, offset_ex(input_expolygons[last_idx_1 - 1], offset_value));
}
ExPolygons offset_e = offset_ex(layer_slices_trimmed, offset_value);
ExPolygons intersection_poly_2 = intersection_ex(triangles_by_color_bottom[color_idx][layer_idx], offset_e);
append(triangles_by_color_bottom[color_idx][last_idx], std::move(intersection_poly_2));
}
}
}
}
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - end";
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
throw_on_cancel_callback();
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
auto &self = triangles_by_color_merged[color_idx][layer_idx];
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
self = union_ex(self);
}
// Cut all colors for cases when two colors are overlapping
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) {
triangles_by_color_merged[color_idx][layer_idx] = diff_ex(triangles_by_color_merged[color_idx][layer_idx],
triangles_by_color_merged[color_idx - 1][layer_idx]);
}
}
#else
// Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group.
int max_top_layers = 0;
int max_bottom_layers = 0;
@ -1470,8 +1255,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
// project downards pointing painted triangles over bottom surfaces.
std::vector<std::vector<Polygons>> top_raw(num_extruders), bottom_raw(num_extruders);
std::vector<float> zs = zs_from_layers(print_object.layers());
Transform3d object_trafo = print_object.trafo();
object_trafo.pretranslate(Vec3d(- unscale<double>(print_object.center_offset().x()), - unscale<double>(print_object.center_offset().y()), 0));
Transform3d object_trafo = print_object.trafo_centered();
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
static int iRun = 0;
@ -1650,7 +1434,6 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
triangles_by_color_merged[color_idx - 1][layer_idx]);
}
});
#endif
return triangles_by_color_merged;
}

View file

@ -354,7 +354,7 @@ inline void MutableSkipHeapPriorityQueue<T, LessPredicate, IndexSetter, blocking
#endif /* NDEBUG */
{
// Mark as removed from the queue.
m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max());
m_index_setter(m_heap[1], std::numeric_limits<size_t>::max());
}
// Zero'th element is padding, thus non-empty queue must have at least two elements.
if (m_heap.size() > 2) {

View file

@ -24,6 +24,7 @@ public:
PlaceholderParser(const DynamicConfig *external_config = nullptr);
void clear_config() { m_config.clear(); }
// Return a list of keys, which should be changed in m_config from rhs.
// This contains keys, which are found in rhs, but not in m_config.
std::vector<std::string> config_diff(const DynamicPrintConfig &rhs);

View file

@ -413,212 +413,181 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config)
}
}
const std::vector<std::string>& Preset::print_options()
{
static std::vector<std::string> s_opts {
"layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "slicing_mode",
"top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness",
"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",
"ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing",
"max_print_speed", "max_volumetric_speed", "avoid_crossing_perimeters_max_detour",
"fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_dist",
static std::vector<std::string> s_Preset_print_options {
"layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "slicing_mode",
"top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness",
"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",
"ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing",
"max_print_speed", "max_volumetric_speed", "avoid_crossing_perimeters_max_detour",
"fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_dist",
#ifdef HAS_PRESSURE_EQUALIZER
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
#endif /* HAS_PRESSURE_EQUALIZER */
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed",
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
"bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
"bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield",
"min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
"support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_closing_radius", "support_material_style",
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers",
"support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",
"support_material_contact_distance", "support_material_bottom_contact_distance",
"support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder",
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "clip_multipart_objects",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits"
};
return s_opts;
}
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed",
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
"bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
"bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield",
"min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
"support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_closing_radius", "support_material_style",
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers",
"support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",
"support_material_contact_distance", "support_material_bottom_contact_distance",
"support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder",
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "clip_multipart_objects",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits"
};
const std::vector<std::string>& Preset::filament_options()
{
static std::vector<std::string> s_opts {
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
"extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time",
"filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves",
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
"temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
"start_filament_gcode", "end_filament_gcode",
// Retract overrides
"filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel",
"filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe",
// Profile compatibility
"filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits"
};
return s_opts;
}
static std::vector<std::string> s_Preset_filament_options {
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
"extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time",
"filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves",
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
"temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
"start_filament_gcode", "end_filament_gcode",
// Retract overrides
"filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel",
"filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe",
// Profile compatibility
"filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits"
};
const std::vector<std::string>& Preset::machine_limits_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_travel",
"machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e",
"machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e",
"machine_min_extruding_rate", "machine_min_travel_rate",
"machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e",
};
}
return s_opts;
}
static std::vector<std::string> s_Preset_machine_limits_options {
"machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_travel",
"machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e",
"machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e",
"machine_min_extruding_rate", "machine_min_travel_rate",
"machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e",
};
static std::vector<std::string> s_Preset_printer_options {
"printer_technology",
"bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances",
"use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
"host_type", "print_host", "printhost_apikey", "printhost_cafile",
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
"color_change_gcode", "pause_print_gcode", "template_custom_gcode",
"between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height",
"default_print_profile", "inherits",
"remaining_times", "silent_mode",
"machine_limits_usage", "thumbnails"
};
static std::vector<std::string> s_Preset_sla_print_options {
"layer_height",
"faded_layers",
"supports_enable",
"support_head_front_diameter",
"support_head_penetration",
"support_head_width",
"support_pillar_diameter",
"support_small_pillar_diameter_percent",
"support_max_bridges_on_pillar",
"support_pillar_connection_mode",
"support_buildplate_only",
"support_pillar_widening_factor",
"support_base_diameter",
"support_base_height",
"support_base_safety_distance",
"support_critical_angle",
"support_max_bridge_length",
"support_max_pillar_link_distance",
"support_object_elevation",
"support_points_density_relative",
"support_points_minimal_distance",
"slice_closing_radius",
"slicing_mode",
"pad_enable",
"pad_wall_thickness",
"pad_wall_height",
"pad_brim_size",
"pad_max_merge_distance",
// "pad_edge_radius",
"pad_wall_slope",
"pad_object_gap",
"pad_around_object",
"pad_around_object_everywhere",
"pad_object_connector_stride",
"pad_object_connector_width",
"pad_object_connector_penetration",
"hollowing_enable",
"hollowing_min_thickness",
"hollowing_quality",
"hollowing_closing_distance",
"output_filename_format",
"default_sla_print_profile",
"compatible_printers",
"compatible_printers_condition",
"inherits"
};
static std::vector<std::string> s_Preset_sla_material_options {
"material_type",
"initial_layer_height",
"bottle_cost",
"bottle_volume",
"bottle_weight",
"material_density",
"exposure_time",
"initial_exposure_time",
"material_correction",
"material_notes",
"material_vendor",
"default_sla_material_profile",
"compatible_prints", "compatible_prints_condition",
"compatible_printers", "compatible_printers_condition", "inherits"
};
static std::vector<std::string> s_Preset_sla_printer_options {
"printer_technology",
"bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height",
"display_width", "display_height", "display_pixels_x", "display_pixels_y",
"display_mirror_x", "display_mirror_y",
"display_orientation",
"fast_tilt_time", "slow_tilt_time", "area_fill",
"relative_correction",
"absolute_correction",
"elefant_foot_compensation",
"elefant_foot_min_width",
"gamma_correction",
"min_exposure_time", "max_exposure_time",
"min_initial_exposure_time", "max_initial_exposure_time",
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
"print_host", "printhost_apikey", "printhost_cafile",
"printer_notes",
"inherits"
};
const std::vector<std::string>& Preset::print_options() { return s_Preset_print_options; }
const std::vector<std::string>& Preset::filament_options() { return s_Preset_filament_options; }
const std::vector<std::string>& Preset::machine_limits_options() { return s_Preset_machine_limits_options; }
// The following nozzle options of a printer profile will be adjusted to match the size
// of the nozzle_diameter vector.
const std::vector<std::string>& Preset::nozzle_options() { return print_config_def.extruder_option_keys(); }
const std::vector<std::string>& Preset::sla_print_options() { return s_Preset_sla_print_options; }
const std::vector<std::string>& Preset::sla_material_options() { return s_Preset_sla_material_options; }
const std::vector<std::string>& Preset::sla_printer_options() { return s_Preset_sla_printer_options; }
const std::vector<std::string>& Preset::printer_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"printer_technology",
"bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances",
"use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
"host_type", "print_host", "printhost_apikey", "printhost_cafile",
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
"color_change_gcode", "pause_print_gcode", "template_custom_gcode",
"between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height",
"default_print_profile", "inherits",
"remaining_times", "silent_mode",
"machine_limits_usage", "thumbnails"
};
s_opts.insert(s_opts.end(), Preset::machine_limits_options().begin(), Preset::machine_limits_options().end());
s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end());
}
return s_opts;
}
// The following nozzle options of a printer profile will be adjusted to match the size
// of the nozzle_diameter vector.
const std::vector<std::string>& Preset::nozzle_options()
{
return print_config_def.extruder_option_keys();
}
const std::vector<std::string>& Preset::sla_print_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"layer_height",
"faded_layers",
"supports_enable",
"support_head_front_diameter",
"support_head_penetration",
"support_head_width",
"support_pillar_diameter",
"support_small_pillar_diameter_percent",
"support_max_bridges_on_pillar",
"support_pillar_connection_mode",
"support_buildplate_only",
"support_pillar_widening_factor",
"support_base_diameter",
"support_base_height",
"support_base_safety_distance",
"support_critical_angle",
"support_max_bridge_length",
"support_max_pillar_link_distance",
"support_object_elevation",
"support_points_density_relative",
"support_points_minimal_distance",
"slice_closing_radius",
"slicing_mode",
"pad_enable",
"pad_wall_thickness",
"pad_wall_height",
"pad_brim_size",
"pad_max_merge_distance",
// "pad_edge_radius",
"pad_wall_slope",
"pad_object_gap",
"pad_around_object",
"pad_around_object_everywhere",
"pad_object_connector_stride",
"pad_object_connector_width",
"pad_object_connector_penetration",
"hollowing_enable",
"hollowing_min_thickness",
"hollowing_quality",
"hollowing_closing_distance",
"output_filename_format",
"default_sla_print_profile",
"compatible_printers",
"compatible_printers_condition",
"inherits"
};
}
return s_opts;
}
const std::vector<std::string>& Preset::sla_material_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"material_type",
"initial_layer_height",
"bottle_cost",
"bottle_volume",
"bottle_weight",
"material_density",
"exposure_time",
"initial_exposure_time",
"material_correction",
"material_notes",
"material_vendor",
"default_sla_material_profile",
"compatible_prints", "compatible_prints_condition",
"compatible_printers", "compatible_printers_condition", "inherits"
};
}
return s_opts;
}
const std::vector<std::string>& Preset::sla_printer_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"printer_technology",
"bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height",
"display_width", "display_height", "display_pixels_x", "display_pixels_y",
"display_mirror_x", "display_mirror_y",
"display_orientation",
"fast_tilt_time", "slow_tilt_time", "area_fill",
"relative_correction",
"absolute_correction",
"elefant_foot_compensation",
"elefant_foot_min_width",
"gamma_correction",
"min_exposure_time", "max_exposure_time",
"min_initial_exposure_time", "max_initial_exposure_time",
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
"print_host", "printhost_apikey", "printhost_cafile",
"printer_notes",
"inherits"
};
}
static std::vector<std::string> s_opts = [](){
std::vector<std::string> opts = s_Preset_printer_options;
append(opts, s_Preset_machine_limits_options);
append(opts, Preset::nozzle_options());
return opts;
}();
return s_opts;
}
@ -1194,21 +1163,38 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
return diff;
}
static constexpr const std::initializer_list<const char*> optional_keys { "compatible_prints", "compatible_printers" };
bool PresetCollection::is_dirty(const Preset *edited, const Preset *reference)
{
if (edited != nullptr && reference != nullptr) {
// Only compares options existing in both configs.
if (! reference->config.equals(edited->config))
return true;
// The "compatible_printers" option key is handled differently from the others:
// It is not mandatory. If the key is missing, it means it is compatible with any printer.
// If the key exists and it is empty, it means it is compatible with no printer.
for (auto &opt_key : optional_keys)
if (reference->config.has(opt_key) != edited->config.has(opt_key))
return true;
}
return false;
}
std::vector<std::string> PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/)
{
std::vector<std::string> changed;
if (edited != nullptr && reference != nullptr) {
// Only compares options existing in both configs.
changed = deep_compare ?
deep_diff(edited->config, reference->config) :
reference->config.diff(edited->config);
// The "compatible_printers" option key is handled differently from the others:
// It is not mandatory. If the key is missing, it means it is compatible with any printer.
// If the key exists and it is empty, it means it is compatible with no printer.
std::initializer_list<const char*> optional_keys { "compatible_prints", "compatible_printers" };
for (auto &opt_key : optional_keys) {
for (auto &opt_key : optional_keys)
if (reference->config.has(opt_key) != edited->config.has(opt_key))
changed.emplace_back(opt_key);
}
}
return changed;
}
@ -1385,12 +1371,16 @@ const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConf
return this->default_preset((opt_printer_technology == nullptr || opt_printer_technology->value == ptFFF) ? 0 : 1);
}
const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model_id) const
const Preset* PrinterPresetCollection::find_system_preset_by_model_and_variant(const std::string &model_id, const std::string& variant) const
{
if (model_id.empty()) { return nullptr; }
const auto it = std::find_if(cbegin(), cend(), [&](const Preset &preset) {
return preset.config.opt_string("printer_model") == model_id;
if (!preset.is_system || preset.config.opt_string("printer_model") != model_id)
return false;
if (variant.empty())
return true;
return preset.config.opt_string("printer_variant") == variant;
});
return it != cend() ? &*it : nullptr;
@ -1405,26 +1395,25 @@ std::string PhysicalPrinter::separator()
return " * ";
}
static std::vector<std::string> s_PhysicalPrinter_opts {
"preset_name", // temporary option to compatibility with older Slicer
"preset_names",
"printer_technology",
"host_type",
"print_host",
"printhost_apikey",
"printhost_cafile",
"printhost_port",
"printhost_authorization_type",
// HTTP digest authentization (RFC 2617)
"printhost_user",
"printhost_password",
"printhost_ssl_ignore_revoke"
};
const std::vector<std::string>& PhysicalPrinter::printer_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"preset_name", // temporary option to compatibility with older Slicer
"preset_names",
"printer_technology",
"host_type",
"print_host",
"printhost_apikey",
"printhost_cafile",
"printhost_port",
"printhost_authorization_type",
// HTTP digest authentization (RFC 2617)
"printhost_user",
"printhost_password"
};
}
return s_opts;
return s_PhysicalPrinter_opts;
}
static constexpr auto legacy_print_host_options = {

View file

@ -371,7 +371,7 @@ public:
const Preset& get_edited_preset() const { return m_edited_preset; }
// Return the last saved preset.
const Preset& get_saved_preset() const { return m_saved_preset; }
// const Preset& get_saved_preset() const { return m_saved_preset; }
// Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist.
PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const;
@ -395,7 +395,7 @@ public:
void discard_current_changes() {
m_presets[m_idx_selected].reset_dirty();
m_edited_preset = m_presets[m_idx_selected];
update_saved_preset_from_current_preset();
// update_saved_preset_from_current_preset();
}
// Return a preset by its name. If the preset is active, a temporary copy is returned.
@ -463,7 +463,8 @@ public:
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
// Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ.
bool current_is_dirty() const { return ! this->current_dirty_options().empty(); }
bool current_is_dirty() const
{ return is_dirty(&this->get_edited_preset(), &this->get_selected_preset()); }
// Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
std::vector<std::string> current_dirty_options(const bool deep_compare = false) const
{ return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), deep_compare); }
@ -472,10 +473,11 @@ public:
{ return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); }
// Compare the content of get_saved_preset() with get_edited_preset() configs, return true if they differ.
bool saved_is_dirty() const { return !this->saved_dirty_options().empty(); }
bool saved_is_dirty() const
{ return is_dirty(&this->get_edited_preset(), &m_saved_preset); }
// Compare the content of get_saved_preset() with get_edited_preset() configs, return the list of keys where they differ.
std::vector<std::string> saved_dirty_options(const bool deep_compare = false) const
{ return dirty_options(&this->get_edited_preset(), &this->get_saved_preset(), deep_compare); }
// std::vector<std::string> saved_dirty_options() const
// { return dirty_options(&this->get_edited_preset(), &this->get_saved_preset(), /* deep_compare */ false); }
// Copy edited preset into saved preset.
void update_saved_preset_from_current_preset() { m_saved_preset = m_edited_preset; }
@ -552,7 +554,8 @@ private:
size_t update_compatible_internal(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, PresetSelectCompatibleType unselect_if_incompatible);
public:
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false);
static bool is_dirty(const Preset *edited, const Preset *reference);
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare = false);
private:
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
Preset::Type m_type;
@ -592,7 +595,7 @@ public:
const Preset& default_preset_for(const DynamicPrintConfig &config) const override;
const Preset* find_by_model_id(const std::string &model_id) const;
const Preset* find_system_preset_by_model_and_variant(const std::string &model_id, const std::string &variant) const;
private:
PrinterPresetCollection() = default;

View file

@ -188,10 +188,13 @@ void PresetBundle::setup_directories()
}
}
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, const std::string &preferred_model_id)
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule,
const PresetPreferences& preferred_selection/* = PresetPreferences()*/)
{
// First load the vendor specific system presets.
std::string errors_cummulative = this->load_system_presets();
PresetsConfigSubstitutions substitutions;
std::string errors_cummulative;
std::tie(substitutions, errors_cummulative) = this->load_system_presets(substitution_rule);
const std::string dir_user_presets = data_dir()
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
@ -202,7 +205,6 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
#endif
;
PresetsConfigSubstitutions substitutions;
try {
this->prints.load_presets(dir_user_presets, "print", substitutions, substitution_rule);
} catch (const std::runtime_error &err) {
@ -238,19 +240,28 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
if (! errors_cummulative.empty())
throw Slic3r::RuntimeError(errors_cummulative);
this->load_selections(config, preferred_model_id);
// ysToDo : set prefered filament or sla_material (relates to print technology) and force o use of preffered printer model if it was added
this->load_selections(config, preferred_selection);
return substitutions;
}
// Load system presets into this PresetBundle.
// For each vendor, there will be a single PresetBundle loaded.
std::string PresetBundle::load_system_presets()
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule)
{
if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)
// Loading system presets, don't log substitutions.
compatibility_rule = ForwardCompatibilitySubstitutionRule::EnableSilent;
else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem)
// Loading system presets, throw on unknown option value.
compatibility_rule = ForwardCompatibilitySubstitutionRule::Disable;
// Here the vendor specific read only Config Bundles are stored.
boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred();
std::string errors_cummulative;
bool first = true;
boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred();
PresetsConfigSubstitutions substitutions;
std::string errors_cummulative;
bool first = true;
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
if (Slic3r::is_ini_file(dir_entry)) {
std::string name = dir_entry.path().filename().string();
@ -260,13 +271,13 @@ std::string PresetBundle::load_system_presets()
// Load the config bundle, flatten it.
if (first) {
// Reset this PresetBundle and load the first vendor config.
this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem);
append(substitutions, this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first);
first = false;
} else {
// Load the other vendor configs, merge them with this PresetBundle.
// Report duplicate profiles.
PresetBundle other;
other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem);
append(substitutions, other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first);
std::vector<std::string> duplicates = this->merge_presets(std::move(other));
if (! duplicates.empty()) {
errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: ";
@ -288,7 +299,7 @@ std::string PresetBundle::load_system_presets()
}
this->update_system_maps();
return errors_cummulative;
return std::make_pair(std::move(substitutions), errors_cummulative);
}
// Merge one vendor's presets with the other vendor's presets, report duplicates.
@ -432,7 +443,7 @@ void PresetBundle::load_installed_sla_materials(AppConfig &config)
// Load selections (current print, current filaments, current printer) from config.ini
// This is done on application start up or after updates are applied.
void PresetBundle::load_selections(AppConfig &config, const std::string &preferred_model_id)
void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& preferred_selection/* = PresetPreferences()*/)
{
// Update visibility of presets based on application vendor / model / variant configuration.
this->load_installed_printers(config);
@ -455,13 +466,21 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr
// will be selected by the following call of this->update_compatible(PresetSelectCompatibleType::Always).
const Preset *initial_printer = printers.find_preset(initial_printer_profile_name);
const Preset *preferred_printer = printers.find_by_model_id(preferred_model_id);
const Preset *preferred_printer = printers.find_system_preset_by_model_and_variant(preferred_selection.printer_model_id, preferred_selection.printer_variant);
printers.select_preset_by_name(
(preferred_printer != nullptr && (initial_printer == nullptr || !initial_printer->is_visible)) ?
(preferred_printer != nullptr /*&& (initial_printer == nullptr || !initial_printer->is_visible)*/) ?
preferred_printer->name :
initial_printer_profile_name,
true);
// select preferred filament/sla_material profile if any exists and is visible
if (!preferred_selection.filament.empty())
if (auto it = filaments.find_preset_internal(preferred_selection.filament); it != filaments.end() && it->is_visible)
initial_filament_profile_name = it->name;
if (!preferred_selection.sla_material.empty())
if (auto it = sla_materials.find_preset_internal(preferred_selection.sla_material); it != sla_materials.end() && it->is_visible)
initial_sla_material_profile_name = it->name;
// Selects the profile, leaves it to -1 if the initial profile name is empty or if it was not found.
prints.select_preset_by_name_strict(initial_print_profile_name);
filaments.select_preset_by_name_strict(initial_filament_profile_name);
@ -700,7 +719,7 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw
if (is_gcode_file(path)) {
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, true /* check_header */, compatibility_rule);
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, compatibility_rule);
Preset::normalize(config);
load_config_file_config(path, true, std::move(config));
return config_substitutions;
@ -714,8 +733,8 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw
} catch (const std::ifstream::failure &err) {
throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
} catch (const boost::property_tree::file_parser_error &err) {
throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
% err.filename() % err.message() % err.line()).str());
throw Slic3r::RuntimeError(format("Failed loading the Config Bundle \"%1%\": %2% at line %3%",
err.filename(), err.message(), err.line()));
} catch (const std::runtime_error &err) {
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
}
@ -723,23 +742,27 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw
// 2) Continue based on the type of the configuration file.
ConfigFileType config_file_type = guess_config_file_type(tree);
ConfigSubstitutions config_substitutions;
switch (config_file_type) {
case CONFIG_FILE_TYPE_UNKNOWN:
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
case CONFIG_FILE_TYPE_APP_CONFIG:
throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
case CONFIG_FILE_TYPE_CONFIG:
{
// Initialize a config from full defaults.
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config_substitutions = config.load(tree, compatibility_rule);
Preset::normalize(config);
load_config_file_config(path, true, std::move(config));
return config_substitutions;
}
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
return load_config_file_config_bundle(path, tree);
try {
switch (config_file_type) {
case CONFIG_FILE_TYPE_UNKNOWN:
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
case CONFIG_FILE_TYPE_APP_CONFIG:
throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
case CONFIG_FILE_TYPE_CONFIG:
{
// Initialize a config from full defaults.
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config_substitutions = config.load(tree, compatibility_rule);
Preset::normalize(config);
load_config_file_config(path, true, std::move(config));
return config_substitutions;
}
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
return load_config_file_config_bundle(path, tree, compatibility_rule);
}
} catch (const ConfigurationError &e) {
throw Slic3r::RuntimeError(format("Invalid configuration file %1%: %2%", path, e.what()));
}
// This shall never happen. Suppres compiler warnings.
@ -916,13 +939,14 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
}
// Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file.
ConfigSubstitutions PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
ConfigSubstitutions PresetBundle::load_config_file_config_bundle(
const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// 1) Load the config bundle into a temp data.
PresetBundle tmp_bundle;
// Load the config bundle, but don't save the loaded presets to user profile directory, as only the presets marked as active in the loaded preset bundle
// will be loaded into the master PresetBundle and activated.
auto [presets_substitutions, presets_imported] = tmp_bundle.load_configbundle(path, {});
auto [presets_substitutions, presets_imported] = tmp_bundle.load_configbundle(path, {}, compatibility_rule);
UNUSED(presets_imported);
std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string();
@ -1135,15 +1159,11 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree, co
// Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory.
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(const std::string &path, LoadConfigBundleAttributes flags)
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// Enable substitutions for user config bundle, throw an exception when loading a system profile.
ConfigSubstitutionContext substitution_context {
flags.has(LoadConfigBundleAttribute::LoadSystem) ?
ForwardCompatibilitySubstitutionRule::Disable :
ForwardCompatibilitySubstitutionRule::Enable
};
ConfigSubstitutionContext substitution_context { compatibility_rule };
PresetsConfigSubstitutions substitutions;
if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem))
@ -1238,7 +1258,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(co
active_sla_material = kvp.second.data();
} else if (kvp.first == "printer") {
active_printer = kvp.second.data();
}else if (kvp.first == "physical_printer") {
} else if (kvp.first == "physical_printer") {
active_physical_printer = kvp.second.data();
}
}
@ -1275,32 +1295,36 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(co
DynamicPrintConfig config;
std::string alias_name;
std::vector<std::string> renamed_from;
auto parse_config_section = [&section, &alias_name, &renamed_from, &substitution_context, &path](DynamicPrintConfig &config) {
substitution_context.substitutions.clear();
for (auto &kvp : section.second) {
if (kvp.first == "alias")
alias_name = kvp.second.data();
else if (kvp.first == "renamed_from") {
if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
}
}
// Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown.
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
try {
auto parse_config_section = [&section, &alias_name, &renamed_from, &substitution_context, &path](DynamicPrintConfig &config) {
substitution_context.substitutions.clear();
for (auto &kvp : section.second) {
if (kvp.first == "alias")
alias_name = kvp.second.data();
else if (kvp.first == "renamed_from") {
if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
}
}
// Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown.
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
}
};
if (presets == &this->printers) {
// Select the default config based on the printer_technology field extracted from kvp.
DynamicPrintConfig config_src;
parse_config_section(config_src);
default_config = &presets->default_preset_for(config_src).config;
config = *default_config;
config.apply(config_src);
} else {
default_config = &presets->default_preset().config;
config = *default_config;
parse_config_section(config);
}
};
if (presets == &this->printers) {
// Select the default config based on the printer_technology field extracted from kvp.
DynamicPrintConfig config_src;
parse_config_section(config_src);
default_config = &presets->default_preset_for(config_src).config;
config = *default_config;
config.apply(config_src);
} else {
default_config = &presets->default_preset().config;
config = *default_config;
parse_config_section(config);
} catch (const ConfigurationError &e) {
throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what());
}
Preset::normalize(config);
// Report configuration fields, which are misplaced into a wrong group.
@ -1408,8 +1432,12 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(co
DynamicPrintConfig config = default_config;
substitution_context.substitutions.clear();
for (auto& kvp : section.second)
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
try {
for (auto& kvp : section.second)
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
} catch (const ConfigurationError &e) {
throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what());
}
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config);
@ -1449,7 +1477,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(co
if (! active_print.empty())
prints.select_preset_by_name(active_print, true);
if (! active_sla_print.empty())
sla_materials.select_preset_by_name(active_sla_print, true);
sla_prints.select_preset_by_name(active_sla_print, true);
if (! active_sla_material.empty())
sla_materials.select_preset_by_name(active_sla_material, true);
if (! active_printer.empty())

View file

@ -25,9 +25,18 @@ public:
void setup_directories();
struct PresetPreferences {
std::string printer_model_id;// name of a preferred printer model
std::string printer_variant; // name of a preferred printer variant
std::string filament; // name of a preferred filament preset
std::string sla_material; // name of a preferred sla_material preset
};
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
// Load selections (current print, current filaments, current printer) from config.ini
PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const std::string &preferred_model_id = std::string());
// select preferred presets, if any exist
PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule,
const PresetPreferences& preferred_selection = PresetPreferences());
// Export selections (current print, current filaments, current printer) into config.ini
void export_selections(AppConfig &config);
@ -102,7 +111,8 @@ public:
using LoadConfigBundleAttributes = enum_bitmask<LoadConfigBundleAttribute>;
// Load the config bundle based on the flags.
// Don't do any config substitutions when loading a system profile, perform and report substitutions otherwise.
std::pair<PresetsConfigSubstitutions, size_t> load_configbundle(const std::string &path, LoadConfigBundleAttributes flags);
std::pair<PresetsConfigSubstitutions, size_t> load_configbundle(
const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Export a config bundle file containing all the presets and the names of the active presets.
void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
@ -139,7 +149,7 @@ public:
static const char *PRUSA_BUNDLE;
private:
std::string load_system_presets();
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetBundle &&other);
// Update renamed_from and alias maps of system profiles.
@ -152,13 +162,14 @@ private:
// Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up.
void load_selections(AppConfig &config, const std::string &preferred_model_id = "");
void load_selections(AppConfig &config, const PresetPreferences& preferred_selection = PresetPreferences());
// Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path.
// and the external config is just referenced, not stored into user profile directory.
// If it is not an external config, then the config will be stored into the user profile directory.
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
ConfigSubstitutions load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
ConfigSubstitutions load_config_file_config_bundle(
const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
DynamicPrintConfig full_fff_config() const;
DynamicPrintConfig full_sla_config() const;

View file

@ -159,7 +159,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "wipe_tower_rotation_angle") {
steps.emplace_back(psSkirtBrim);
} else if (
opt_key == "nozzle_diameter"
opt_key == "first_layer_height"
|| opt_key == "nozzle_diameter"
|| opt_key == "resolution"
// Spiral Vase forces different kind of slicing than the normal model:
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.

View file

@ -253,6 +253,9 @@ public:
ConstLayerPtrsAdaptor layers() const { return ConstLayerPtrsAdaptor(&m_layers); }
ConstSupportLayerPtrsAdaptor support_layers() const { return ConstSupportLayerPtrsAdaptor(&m_support_layers); }
const Transform3d& trafo() const { return m_trafo; }
// Trafo with the center_offset() applied after the transformation, to center the object in XY before slicing.
Transform3d trafo_centered() const
{ Transform3d t = this->trafo(); t.pretranslate(Vec3d(- unscale<double>(m_center_offset.x()), - unscale<double>(m_center_offset.y()), 0)); return t; }
const PrintInstances& instances() const { return m_instances; }
// Whoever will get a non-const pointer to PrintObject will be able to modify its layers.
@ -268,7 +271,11 @@ public:
// Centering offset of the sliced mesh from the scaled and rotated mesh of the model.
const Point& center_offset() const { return m_center_offset; }
bool has_brim() const { return this->config().brim_type != btNoBrim && this->config().brim_width.value > 0.; }
bool has_brim() const {
return this->config().brim_type != btNoBrim
&& this->config().brim_width.value > 0.
&& ! this->has_raft();
}
// This is the *total* layer count (including support layers)
// this value is not supposed to be compared with Layer::id
@ -318,7 +325,7 @@ public:
bool has_raft() const { return m_config.raft_layers > 0; }
bool has_support_material() const { return this->has_support() || this->has_raft(); }
// Checks if the model object is painted using the multi-material painting gizmo.
bool is_mm_painted() const { return this->model_object()->is_mm_painted(); };
bool is_mm_painted() const { return this->model_object()->is_mm_painted(); }
// returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions)
std::vector<unsigned int> object_extruders() const;

View file

@ -216,22 +216,25 @@ static t_config_option_keys print_config_diffs(
const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr;
if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) {
// An extruder retract override is available at some of the filament presets.
if (*opt_old != *opt_new || opt_new->overriden_by(opt_new_filament)) {
bool overriden = opt_new->overriden_by(opt_new_filament);
if (overriden || *opt_old != *opt_new) {
auto opt_copy = opt_new->clone();
opt_copy->apply_override(opt_new_filament);
if (*opt_old == *opt_copy)
delete opt_copy;
else {
filament_overrides.set_key_value(opt_key, opt_copy);
bool changed = *opt_old != *opt_copy;
if (changed)
print_diff.emplace_back(opt_key);
}
if (changed || overriden) {
// filament_overrides will be applied to the placeholder parser, which layers these parameters over full_print_config.
filament_overrides.set_key_value(opt_key, opt_copy);
} else
delete opt_copy;
}
} else if (*opt_new != *opt_old)
print_diff.emplace_back(opt_key);
}
return print_diff;
}
}
// Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser.
static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig &current_full_config, const DynamicPrintConfig &new_full_config)
@ -812,7 +815,7 @@ static PrintObjectRegions* generate_print_object_regions(
layer_ranges_regions.push_back({ range.layer_height_range, range.config });
}
const bool is_mm_painted = std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation));
std::vector<PrintRegion*> region_set;
@ -928,6 +931,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
bool num_extruders_changed = false;
if (! full_config_diff.empty()) {
update_apply_status(this->invalidate_step(psGCodeExport));
m_placeholder_parser.clear_config();
// Set the profile aliases for the PrintBase::output_filename()
m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone());
m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone());
@ -939,6 +943,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// It is also safe to change m_config now after this->invalidate_state_by_config_options() call.
m_config.apply_only(new_full_config, print_diff, true);
//FIXME use move semantics once ConfigBase supports it.
// Some filament_overrides may contain values different from new_full_config, but equal to m_config.
// As long as these config options don't reallocate memory when copying, we are safe overriding a value, which is in use by a worker thread.
m_config.apply(filament_overrides);
// Handle changes to object config defaults
m_default_object_config.apply_only(new_full_config, object_diff, true);
@ -946,8 +952,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_default_region_config.apply_only(new_full_config, region_diff, true);
m_full_print_config = std::move(new_full_config);
if (num_extruders != m_config.nozzle_diameter.size()) {
num_extruders = m_config.nozzle_diameter.size();
num_extruders_changed = true;
num_extruders = m_config.nozzle_diameter.size();
num_extruders_changed = true;
}
}
@ -1065,7 +1071,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Check whether a model part volume was added or removed, their transformations or order changed.
// Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked.
bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) ||
model_mmu_segmentation_data_changed(model_object, model_object_new);
model_mmu_segmentation_data_changed(model_object, model_object_new) ||
(model_object_new.is_mm_painted() && num_extruders_changed);
bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) ||
model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER);
bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty());
@ -1267,7 +1274,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
print_object_regions->ref_cnt_inc();
}
std::vector<unsigned int> painting_extruders;
if (const auto &volumes = print_object.model_object()->volumes;
if (const auto &volumes = print_object.model_object()->volumes;
num_extruders > 1 &&
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) {
//FIXME be more specific! Don't enumerate extruders that are not used for painting!
painting_extruders.assign(num_extruders, 0);

View file

@ -232,9 +232,19 @@ void PrintConfigDef::init_common_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("elefant_foot_compensation", coFloat);
def->label = L("Elephant foot compensation");
def->category = L("Advanced");
def->tooltip = L("The first layer will be shrunk in the XY plane by the configured value "
"to compensate for the 1st layer squish aka an Elephant Foot effect.");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.));
def = this->add("thumbnails", coPoints);
def->label = L("G-code thumbnails");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files, in the following format: \"XxY, XxY, ...\"");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \"XxY, XxY, ...\"");
def->mode = comExpert;
def->gui_type = ConfigOptionDef::GUIType::one_string;
def->set_default_value(new ConfigOptionPoints());
@ -264,6 +274,7 @@ void PrintConfigDef::init_common_params()
"Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL "
"in the following format: https://username:password@your-octopi-address/");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionString(""));
def = this->add("printhost_apikey", coString);
@ -271,6 +282,7 @@ void PrintConfigDef::init_common_params()
def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
"the API Key or the password required for authentication.");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionString(""));
def = this->add("printhost_port", coString);
@ -278,6 +290,7 @@ void PrintConfigDef::init_common_params()
def->tooltip = L("Name of the printer");
def->gui_type = ConfigOptionDef::GUIType::select_open;
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionString(""));
def = this->add("printhost_cafile", coString);
@ -285,31 +298,33 @@ void PrintConfigDef::init_common_params()
def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
"If left blank, the default OS CA certificate repository is used.");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionString(""));
def = this->add("elefant_foot_compensation", coFloat);
def->label = L("Elephant foot compensation");
def->category = L("Advanced");
def->tooltip = L("The first layer will be shrunk in the XY plane by the configured value "
"to compensate for the 1st layer squish aka an Elephant Foot effect.");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.2));
// Options used by physical printers
def = this->add("printhost_user", coString);
def->label = L("User");
// def->tooltip = L("");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionString(""));
def = this->add("printhost_password", coString);
def->label = L("Password");
// def->tooltip = L("");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionString(""));
// Only available on Windows.
def = this->add("printhost_ssl_ignore_revoke", coBool);
def->label = L("Ignore HTTPS certificate revocation checks");
def->tooltip = L("Ignore HTTPS certificate revocation checks in case of missing or offline distribution points. "
"One may want to enable this option for self signed certificates if connection fails.");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("preset_names", coStrings);
def->label = L("Printer preset names");
@ -317,12 +332,6 @@ void PrintConfigDef::init_common_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionStrings());
// temporary workaround for compatibility with older Slicer
{
def = this->add("preset_name", coString);
def->set_default_value(new ConfigOptionString());
}
def = this->add("printhost_authorization_type", coEnum);
def->label = L("Authorization Type");
// def->tooltip = L("");
@ -332,7 +341,14 @@ void PrintConfigDef::init_common_params()
def->enum_labels.push_back(L("API key"));
def->enum_labels.push_back(L("HTTP digest"));
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionEnum<AuthorizationType>(atKeyPassword));
// temporary workaround for compatibility with older Slicer
{
def = this->add("preset_name", coString);
def->set_default_value(new ConfigOptionString());
}
}
void PrintConfigDef::init_fff_params()
@ -465,7 +481,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("brim_width", coFloat);
def->label = L("Brim width");
def->category = L("Skirt and brim");
def->tooltip = L("Horizontal width of the brim that will be printed around each object on the first layer.");
def->tooltip = L("Horizontal width of the brim that will be printed around each object on the first layer."
"When raft is used, no brim is generated (use raft_first_layer_expansion).");
def->sidetext = L("mm");
def->min = 0;
def->max = 200;
@ -491,10 +508,11 @@ void PrintConfigDef::init_fff_params()
def = this->add("brim_offset", coFloat);
def->label = L("Brim offset");
def->category = L("Skirt and brim");
def->tooltip = L("The offset of the brim from the printed object.");
def->tooltip = L("The offset of the brim from the printed object. The offset is applied after the elephant foot compensation.");
def->sidetext = L("mm");
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(0));
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.f));
def = this->add("clip_multipart_objects", coBool);
def->label = L("Clip multi-part objects");
@ -1808,6 +1826,7 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back("AstroBox");
def->enum_labels.push_back("Repetier");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));
def = this->add("only_retract_when_crossing_perimeters", coBool);
@ -4154,9 +4173,10 @@ CLITransformConfigDef::CLITransformConfigDef()
def->label = L("Don't arrange");
def->tooltip = L("Do not rearrange the given models before merging and keep their original XY coordinates.");
def = this->add("dont_ensure_on_bed", coBool);
def->label = L("Don't ensure on bed");
def->tooltip = L("Do not lift the object above the bed when it is partially below.");
def = this->add("ensure_on_bed", coBool);
def->label = L("Ensure on bed");
def->tooltip = L("Lift the object above the bed when it is partially below. Enabled by default, use --no-ensure-on-bed to disable.");
def->set_default_value(new ConfigOptionBool(true));
def = this->add("duplicate", coInt);
def->label = L("Duplicate");

View file

@ -428,10 +428,8 @@ std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare
indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set();
// Rotate mesh and build octree on it with axis-aligned (standart base) cubes.
Transform3d m = m_trafo;
m.pretranslate(Vec3d(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0));
auto to_octree = transform_to_octree().toRotationMatrix();
its_transform(mesh, to_octree * m, true);
its_transform(mesh, to_octree * this->trafo_centered(), true);
// Triangulate internal bridging surfaces.
std::vector<std::vector<Vec3d>> overhangs(this->layers().size());
@ -537,7 +535,6 @@ bool PrintObject::invalidate_state_by_config_options(
steps.emplace_back(posPerimeters);
} else if (
opt_key == "layer_height"
|| opt_key == "first_layer_height"
|| opt_key == "mmu_segmented_region_max_width"
|| opt_key == "raft_layers"
|| opt_key == "raft_contact_distance"
@ -2298,7 +2295,7 @@ void PrintObject::project_and_append_custom_facets(
: mv->supported_facets.get_facets_strict(*mv, type);
if (! custom_facets.indices.empty())
project_triangles_to_slabs(this->layers(), custom_facets,
(Eigen::Translation3d(to_3d(unscaled<double>(this->center_offset()), 0.)) * this->trafo() * mv->get_matrix()).cast<float>(),
(this->trafo_centered() * mv->get_matrix()).cast<float>(),
seam, out);
}
}

View file

@ -167,8 +167,9 @@ static std::vector<VolumeSlices> slice_volumes_inner(
params_base.mode_below = params_base.mode;
const bool is_mm_painted = std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
const auto extra_offset = is_mm_painted ? 0.f : std::max(0.f, float(print_object_config.xy_size_compensation.value));
const size_t num_extruders = print_config.nozzle_diameter.size();
const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
const auto extra_offset = is_mm_painted ? 0.f : std::max(0.f, float(print_object_config.xy_size_compensation.value));
for (const ModelVolume *model_volume : model_volumes)
if (model_volume_needs_slicing(*model_volume)) {
@ -695,11 +696,9 @@ void PrintObject::slice_volumes()
}
std::vector<float> slice_zs = zs_from_layers(m_layers);
Transform3d trafo = this->trafo();
trafo.pretranslate(Vec3d(- unscale<double>(m_center_offset.x()), - unscale<double>(m_center_offset.y()), 0));
std::vector<std::vector<ExPolygons>> region_slices = slices_to_regions(this->model_object()->volumes, *m_shared_regions, slice_zs,
slice_volumes_inner(
print->config(), this->config(), trafo,
print->config(), this->config(), this->trafo_centered(),
this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback),
m_config.clip_multipart_objects,
throw_on_cancel_callback);
@ -725,6 +724,7 @@ void PrintObject::slice_volumes()
// Is any ModelVolume MMU painted?
if (const auto& volumes = this->model_object()->volumes;
m_print->config().nozzle_diameter.size() > 1 &&
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) {
// If XY Size compensation is also enabled, notify the user that XY Size compensation
@ -745,8 +745,9 @@ void PrintObject::slice_volumes()
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin";
{
// Compensation value, scaled. Only applying the negative scaling here, as the positive scaling has already been applied during slicing.
const auto xy_compensation_scaled = this->is_mm_painted() ? scaled<float>(0.f) : scaled<float>(std::min(m_config.xy_size_compensation.value, 0.));
const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ?
const size_t num_extruders = print->config().nozzle_diameter.size();
const auto xy_compensation_scaled = (num_extruders > 1 && this->is_mm_painted()) ? scaled<float>(0.f) : scaled<float>(std::min(m_config.xy_size_compensation.value, 0.));
const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ?
// Only enable Elephant foot compensation if printing directly on the print bed.
float(scale_(m_config.elefant_foot_compensation.value)) :
0.f;
@ -832,8 +833,7 @@ std::vector<Polygons> PrintObject::slice_support_volumes(const ModelVolumeType m
const Print *print = this->print();
auto throw_on_cancel_callback = std::function<void()>([print](){ print->throw_if_canceled(); });
MeshSlicingParamsEx params;
params.trafo = this->trafo();
params.trafo.pretranslate(Vec3d(-unscale<float>(m_center_offset.x()), -unscale<float>(m_center_offset.y()), 0));
params.trafo = this->trafo_centered();
for (; it_volume != it_volume_end; ++ it_volume)
if ((*it_volume)->type() == model_volume_type) {
std::vector<ExPolygons> slices2 = slice_volume(*(*it_volume), zs, params, throw_on_cancel_callback);

View file

@ -0,0 +1,845 @@
#include "QuadricEdgeCollapse.hpp"
#include <tuple>
#include <optional>
#include "MutablePriorityQueue.hpp"
#include "SimplifyMeshImpl.hpp"
#include <tbb/parallel_for.h>
using namespace Slic3r;
// only private namespace not neccessary be in .hpp
namespace QuadricEdgeCollapse {
using Vertices = std::vector<stl_vertex>;
using Triangle = stl_triangle_vertex_indices;
using Indices = std::vector<stl_triangle_vertex_indices>;
using SymMat = SimplifyMesh::implementation::SymetricMatrix<double>;
using ThrowOnCancel = std::function<void(void)>;
using StatusFn = std::function<void(int)>;
// smallest error caused by edges, identify smallest edge in triangle
struct Error
{
float value = -1.; // identifying of smallest edge is stored inside of TriangleInfo
uint32_t triangle_index = 0;
Error(float value, uint32_t triangle_index)
: value(value)
, triangle_index(triangle_index)
{}
Error() = default;
};
using Errors = std::vector<Error>;
// merge information together - faster access during processing
struct TriangleInfo {
Vec3f n; // normalized normal - used for check when fliped
// range(0 .. 2),
unsigned char min_index = 0; // identify edge for minimal Error -> lightweight Error structure
TriangleInfo() = default;
bool is_deleted() const { return n.x() > 2.f; }
void set_deleted() { n.x() = 3.f; }
};
using TriangleInfos = std::vector<TriangleInfo>;
struct VertexInfo {
SymMat q; // sum quadric of surround triangles
uint32_t start = 0, count = 0; // vertex neighbor triangles
VertexInfo() = default;
bool is_deleted() const { return count == 0; }
};
using VertexInfos = std::vector<VertexInfo>;
struct EdgeInfo {
uint32_t t_index=0; // triangle index
unsigned char edge = 0; // 0 or 1 or 2
EdgeInfo() = default;
};
using EdgeInfos = std::vector<EdgeInfo>;
// DTO for change neighbors
struct CopyEdgeInfo {
uint32_t start;
uint32_t count;
uint32_t move;
CopyEdgeInfo(uint32_t start, uint32_t count, uint32_t move)
: start(start), count(count), move(move)
{}
};
using CopyEdgeInfos = std::vector<CopyEdgeInfo>;
Vec3d create_normal(const Triangle &triangle, const Vertices &vertices);
std::array<Vec3d,3> create_vertices(uint32_t id_v1, uint32_t id_v2, const Vertices &vertices);
std::array<double, 3> vertices_error(const SymMat &q, const std::array<Vec3d, 3> &vertices);
double calculate_determinant(const SymMat &q);
double calculate_error(uint32_t id_v1, uint32_t id_v2, const SymMat & q, const Vertices &vertices);
Vec3f calculate_vertex(uint32_t id_v1, uint32_t id_v2, const SymMat & q, const Vertices &vertices);
Vec3d calculate_vertex(double det, const SymMat &q);
// calculate error for vertex and quadrics, triangle quadrics and triangle vertex give zero, only pozitive number
double vertex_error(const SymMat &q, const Vec3d &vertex);
SymMat create_quadric(const Triangle &t, const Vec3d& n, const Vertices &vertices);
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors>
init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn);
std::optional<uint32_t> find_triangle_index1(uint32_t vi, const VertexInfo& v_info,
uint32_t ti, const EdgeInfos& e_infos, const Indices& indices);
bool is_flipped(const Vec3f &new_vertex, uint32_t ti0, uint32_t ti1, const VertexInfo& v_info,
const TriangleInfos &t_infos, const EdgeInfos &e_infos, const indexed_triangle_set &its);
bool degenerate(uint32_t vi, uint32_t ti0, uint32_t ti1, const VertexInfo &v_info,
const EdgeInfos &e_infos, const Indices &indices);
// find edge with smallest error in triangle
Vec3d calculate_3errors(const Triangle &t, const Vertices &vertices, const VertexInfos &v_infos);
Error calculate_error(uint32_t ti, const Triangle& t,const Vertices &vertices, const VertexInfos& v_infos, unsigned char& min_index);
void remove_triangle(EdgeInfos &e_infos, VertexInfo &v_info, uint32_t ti);
void change_neighbors(EdgeInfos &e_infos, VertexInfos &v_infos, uint32_t ti0, uint32_t ti1,
uint32_t vi0, uint32_t vi1, uint32_t vi_top0,
const Triangle &t1, CopyEdgeInfos& infos, EdgeInfos &e_infos1);
void compact(const VertexInfos &v_infos, const TriangleInfos &t_infos, const EdgeInfos &e_infos, indexed_triangle_set &its);
#ifndef NDEBUG
void store_surround(const char *obj_filename, size_t triangle_index, int depth, const indexed_triangle_set &its,
const VertexInfos &v_infos, const EdgeInfos &e_infos);
bool check_neighbors(const indexed_triangle_set &its, const TriangleInfos &t_infos,
const VertexInfos &v_infos, const EdgeInfos &e_infos);
#endif /* NDEBUG */
} // namespace QuadricEdgeCollapse
using namespace QuadricEdgeCollapse;
void Slic3r::its_quadric_edge_collapse(
indexed_triangle_set & its,
uint32_t triangle_count,
float * max_error,
std::function<void(void)> throw_on_cancel,
std::function<void(int)> status_fn)
{
// constants --> may be move to config
const int status_init_size = 10; // in percents
const int check_cancel_period = 16; // how many edge to reduce before call throw_on_cancel
// check input
if (triangle_count >= its.indices.size()) return;
float maximal_error = (max_error == nullptr)? std::numeric_limits<float>::max() : *max_error;
if (maximal_error <= 0.f) return;
if (throw_on_cancel == nullptr) throw_on_cancel = []() {};
if (status_fn == nullptr) status_fn = [](int) {};
StatusFn init_status_fn = [&](int percent) {
status_fn(std::round((percent * status_init_size) / 100.));
};
TriangleInfos t_infos; // only normals with information about deleted triangle
VertexInfos v_infos;
EdgeInfos e_infos;
Errors errors;
std::tie(t_infos, v_infos, e_infos, errors) = init(its, throw_on_cancel, init_status_fn);
throw_on_cancel();
status_fn(status_init_size);
//its_store_triangle(its, "triangle.obj", 1182);
//store_surround("triangle_surround1.obj", 1182, 1, its, v_infos, e_infos);
// convert from triangle index to mutable priority queue index
std::vector<size_t> ti_2_mpqi(its.indices.size(), {0});
auto setter = [&ti_2_mpqi](const Error &e, size_t index) { ti_2_mpqi[e.triangle_index] = index; };
auto less = [](const Error &e1, const Error &e2) -> bool { return e1.value < e2.value; };
auto mpq = make_miniheap_mutable_priority_queue<Error, 32, false>(std::move(setter), std::move(less));
//MutablePriorityQueue<Error, decltype(setter), decltype(less)> mpq(std::move(setter), std::move(less));
mpq.reserve(its.indices.size());
for (Error &error :errors) mpq.push(error);
const size_t max_triangle_count_for_one_vertex = 50;
CopyEdgeInfos ceis;
ceis.reserve(max_triangle_count_for_one_vertex);
EdgeInfos e_infos_swap;
e_infos_swap.reserve(max_triangle_count_for_one_vertex);
std::vector<uint32_t> changed_triangle_indices;
changed_triangle_indices.reserve(2 * max_triangle_count_for_one_vertex);
uint32_t actual_triangle_count = its.indices.size();
uint32_t count_triangle_to_reduce = actual_triangle_count - triangle_count;
auto increase_status = [&]() {
double reduced = (actual_triangle_count - triangle_count) /
(double) count_triangle_to_reduce;
double status = status_init_size + (100 - status_init_size) *
(1. - reduced);
status_fn(static_cast<int>(std::round(status)));
};
// modulo for update status
uint32_t status_mod = std::max(uint32_t(16), count_triangle_to_reduce / 100);
uint32_t iteration_number = 0;
float last_collapsed_error = 0.f;
while (actual_triangle_count > triangle_count && !mpq.empty()) {
++iteration_number;
if (iteration_number % status_mod == 0) increase_status();
if (iteration_number % check_cancel_period == 0) throw_on_cancel();
// triangle index 0
Error e = mpq.top(); // copy
if (e.value >= maximal_error) break; // Too big error
mpq.pop();
uint32_t ti0 = e.triangle_index;
TriangleInfo &t_info0 = t_infos[ti0];
if (t_info0.is_deleted()) continue;
assert(t_info0.min_index < 3);
const Triangle &t0 = its.indices[ti0];
uint32_t vi0 = t0[t_info0.min_index];
uint32_t vi1 = t0[(t_info0.min_index+1) %3];
// Need by move of neighbor edge infos in function: change_neighbors
if (vi0 > vi1) std::swap(vi0, vi1);
VertexInfo &v_info0 = v_infos[vi0];
VertexInfo &v_info1 = v_infos[vi1];
assert(!v_info0.is_deleted() && !v_info1.is_deleted());
// new vertex position
SymMat q(v_info0.q);
q += v_info1.q;
Vec3f new_vertex0 = calculate_vertex(vi0, vi1, q, its.vertices);
// set of triangle indices that change quadric
auto ti1_opt = (v_info0.count < v_info1.count)?
find_triangle_index1(vi1, v_info0, ti0, e_infos, its.indices) :
find_triangle_index1(vi0, v_info1, ti0, e_infos, its.indices) ;
if (!ti1_opt.has_value() || // edge has only one triangle
degenerate(vi0, ti0, *ti1_opt, v_info1, e_infos, its.indices) ||
degenerate(vi1, ti0, *ti1_opt, v_info0, e_infos, its.indices) ||
is_flipped(new_vertex0, ti0, *ti1_opt, v_info0, t_infos, e_infos, its) ||
is_flipped(new_vertex0, ti0, *ti1_opt, v_info1, t_infos, e_infos, its)) {
// try other triangle's edge
Vec3d errors = calculate_3errors(t0, its.vertices, v_infos);
Vec3i ord = (errors[0] < errors[1]) ?
((errors[0] < errors[2])?
((errors[1] < errors[2]) ? Vec3i(0, 1, 2) : Vec3i(0, 2, 1)) :
Vec3i(2, 0, 1)):
((errors[1] < errors[2])?
((errors[0] < errors[2]) ? Vec3i(1, 0, 2) : Vec3i(1, 2, 0)) :
Vec3i(2, 1, 0));
if (t_info0.min_index == ord[0]) {
t_info0.min_index = ord[1];
e.value = errors[t_info0.min_index];
} else if (t_info0.min_index == ord[1]) {
t_info0.min_index = ord[2];
e.value = errors[t_info0.min_index];
} else {
// error is changed when surround edge is reduced
t_info0.min_index = 3; // bad index -> invalidate
e.value = maximal_error;
}
// IMPROVE: check mpq top if it is ti1 with same edge
mpq.push(e);
continue;
}
uint32_t ti1 = *ti1_opt;
last_collapsed_error = e.value;
changed_triangle_indices.clear();
changed_triangle_indices.reserve(v_info0.count + v_info1.count - 4);
// for each vertex0 triangles
uint32_t v_info0_end = v_info0.start + v_info0.count;
for (uint32_t di = v_info0.start; di < v_info0_end; ++di) {
assert(di < e_infos.size());
uint32_t ti = e_infos[di].t_index;
if (ti == ti0) continue; // ti0 will be deleted
if (ti == ti1) continue; // ti1 will be deleted
changed_triangle_indices.emplace_back(ti);
}
// for each vertex1 triangles
uint32_t v_info1_end = v_info1.start + v_info1.count;
for (uint32_t di = v_info1.start; di < v_info1_end; ++di) {
assert(di < e_infos.size());
EdgeInfo &e_info = e_infos[di];
uint32_t ti = e_info.t_index;
if (ti == ti0) continue; // ti0 will be deleted
if (ti == ti1) continue; // ti1 will be deleted
Triangle &t = its.indices[ti];
t[e_info.edge] = vi0; // change index
changed_triangle_indices.emplace_back(ti);
}
v_info0.q = q;
// fix neighbors
// vertex index of triangle 0 which is not vi0 nor vi1
uint32_t vi_top0 = t0[(t_info0.min_index + 2) % 3];
const Triangle &t1 = its.indices[ti1];
change_neighbors(e_infos, v_infos, ti0, ti1, vi0, vi1,
vi_top0, t1, ceis, e_infos_swap);
// Change vertex
its.vertices[vi0] = new_vertex0;
// fix errors - must be after set neighbors - v_infos
mpq.remove(ti_2_mpqi[ti1]);
for (uint32_t ti : changed_triangle_indices) {
size_t priority_queue_index = ti_2_mpqi[ti];
TriangleInfo& t_info = t_infos[ti];
t_info.n = create_normal(its.indices[ti], its.vertices).cast<float>(); // recalc normals
mpq[priority_queue_index] = calculate_error(ti, its.indices[ti], its.vertices, v_infos, t_info.min_index);
mpq.update(priority_queue_index);
}
// set triangle(0 + 1) indices as deleted
TriangleInfo &t_info1 = t_infos[ti1];
t_info0.set_deleted();
t_info1.set_deleted();
// triangle counter decrementation
actual_triangle_count-=2;
assert(check_neighbors(its, t_infos, v_infos, e_infos));
}
// compact triangle
compact(v_infos, t_infos, e_infos, its);
if (max_error != nullptr) *max_error = last_collapsed_error;
}
Vec3d QuadricEdgeCollapse::create_normal(const Triangle &triangle,
const Vertices &vertices)
{
Vec3d v0 = vertices[triangle[0]].cast<double>();
Vec3d v1 = vertices[triangle[1]].cast<double>();
Vec3d v2 = vertices[triangle[2]].cast<double>();
// n = triangle normal
Vec3d n = (v1 - v0).cross(v2 - v0);
n.normalize();
return n;
}
double QuadricEdgeCollapse::calculate_determinant(const SymMat &q)
{
return q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
}
Vec3d QuadricEdgeCollapse::calculate_vertex(double det, const SymMat &q) {
double det_1 = -1 / det;
double det_x = q.det(1, 2, 3, 4, 5, 6, 5, 7, 8); // vx = A41/det(q_delta)
double det_y = q.det(0, 2, 3, 1, 5, 6, 2, 7, 8); // vy = A42/det(q_delta)
double det_z = q.det(0, 1, 3, 1, 4, 6, 2, 5, 8); // vz = A43/det(q_delta)
return Vec3d(det_1 * det_x, -det_1 * det_y, det_1 * det_z);
}
std::array<Vec3d,3> QuadricEdgeCollapse::create_vertices(uint32_t id_v1, uint32_t id_v2, const Vertices &vertices)
{
Vec3d v0 = vertices[id_v1].cast<double>();
Vec3d v1 = vertices[id_v2].cast<double>();
Vec3d vm = (v0 + v1) / 2.;
return {v0, v1, vm};
}
std::array<double, 3> QuadricEdgeCollapse::vertices_error(
const SymMat &q, const std::array<Vec3d, 3> &vertices)
{
return {
vertex_error(q, vertices[0]),
vertex_error(q, vertices[1]),
vertex_error(q, vertices[2])};
}
double QuadricEdgeCollapse::calculate_error(uint32_t id_v1,
uint32_t id_v2,
const SymMat & q,
const Vertices &vertices)
{
double det = calculate_determinant(q);
if (std::abs(det) < std::numeric_limits<double>::epsilon()) {
// can't divide by zero
auto verts = create_vertices(id_v1, id_v2, vertices);
auto errors = vertices_error(q, verts);
return *std::min_element(std::begin(errors), std::end(errors));
}
Vec3d vertex = calculate_vertex(det, q);
return vertex_error(q, vertex);
}
// similar as calculate error but focus on new vertex without calculation of error
Vec3f QuadricEdgeCollapse::calculate_vertex(uint32_t id_v1,
uint32_t id_v2,
const SymMat & q,
const Vertices &vertices)
{
double det = calculate_determinant(q);
if (std::abs(det) < std::numeric_limits<double>::epsilon()) {
// can't divide by zero
auto verts = create_vertices(id_v1, id_v2, vertices);
auto errors = vertices_error(q, verts);
auto mit = std::min_element(std::begin(errors), std::end(errors));
return verts[mit - std::begin(errors)].cast<float>();
}
return calculate_vertex(det, q).cast<float>();
}
double QuadricEdgeCollapse::vertex_error(const SymMat &q, const Vec3d &vertex)
{
const double &x = vertex.x(), &y = vertex.y(), &z = vertex.z();
return q[0] * x * x + 2 * q[1] * x * y + 2 * q[2] * x * z +
2 * q[3] * x + q[4] * y * y + 2 * q[5] * y * z +
2 * q[6] * y + q[7] * z * z + 2 * q[8] * z + q[9];
}
SymMat QuadricEdgeCollapse::create_quadric(const Triangle &t,
const Vec3d & n,
const Vertices &vertices)
{
Vec3d v0 = vertices[t[0]].cast<double>();
return SymMat(n.x(), n.y(), n.z(), -n.dot(v0));
}
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors>
QuadricEdgeCollapse::init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn)
{
// change speed of progress bargraph
const int status_normal_size = 25;
const int status_sum_quadric = 25;
const int status_set_offsets = 10;
const int status_calc_errors = 30;
const int status_create_refs = 10;
int status_offset = 0;
TriangleInfos t_infos(its.indices.size());
VertexInfos v_infos(its.vertices.size());
{
std::vector<SymMat> triangle_quadrics(its.indices.size());
// calculate normals
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
const Triangle &t = its.indices[i];
TriangleInfo & t_info = t_infos[i];
Vec3d normal = create_normal(t, its.vertices);
t_info.n = normal.cast<float>();
triangle_quadrics[i] = create_quadric(t, normal, its.vertices);
if (i % 1000000 == 0) {
throw_on_cancel();
status_fn(status_offset + (i * status_normal_size) / its.indices.size());
}
}
}); // END parallel for
status_offset += status_normal_size;
// sum quadrics
for (size_t i = 0; i < its.indices.size(); i++) {
const Triangle &t = its.indices[i];
const SymMat & q = triangle_quadrics[i];
for (size_t e = 0; e < 3; e++) {
VertexInfo &v_info = v_infos[t[e]];
v_info.q += q;
++v_info.count; // triangle count
}
if (i % 1000000 == 0) {
throw_on_cancel();
status_fn(status_offset + (i * status_sum_quadric) / its.indices.size());
}
}
status_offset += status_sum_quadric;
} // remove triangle quadrics
// set offseted starts
uint32_t triangle_start = 0;
for (VertexInfo &v_info : v_infos) {
v_info.start = triangle_start;
triangle_start += v_info.count;
// set filled vertex to zero
v_info.count = 0;
}
assert(its.indices.size() * 3 == triangle_start);
status_offset += status_set_offsets;
throw_on_cancel();
status_fn(status_offset);
// calc error
Errors errors(its.indices.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
const Triangle &t = its.indices[i];
TriangleInfo & t_info = t_infos[i];
errors[i] = calculate_error(i, t, its.vertices, v_infos, t_info.min_index);
if (i % 1000000 == 0) {
throw_on_cancel();
status_fn(status_offset + (i * status_calc_errors) / its.indices.size());
}
if (i % 1000000 == 0) throw_on_cancel();
}
}); // END parallel for
status_offset += status_calc_errors;
// create reference
EdgeInfos e_infos(its.indices.size() * 3);
for (size_t i = 0; i < its.indices.size(); i++) {
const Triangle &t = its.indices[i];
for (size_t j = 0; j < 3; ++j) {
VertexInfo &v_info = v_infos[t[j]];
size_t ei = v_info.start + v_info.count;
assert(ei < e_infos.size());
EdgeInfo &e_info = e_infos[ei];
e_info.t_index = i;
e_info.edge = j;
++v_info.count;
}
if (i % 1000000 == 0) {
throw_on_cancel();
status_fn(status_offset + (i * status_create_refs) / its.indices.size());
}
}
throw_on_cancel();
status_fn(100);
return {t_infos, v_infos, e_infos, errors};
}
std::optional<uint32_t> QuadricEdgeCollapse::find_triangle_index1(uint32_t vi,
const VertexInfo &v_info,
uint32_t ti0,
const EdgeInfos & e_infos,
const Indices & indices)
{
coord_t vi_coord = static_cast<coord_t>(vi);
uint32_t end = v_info.start + v_info.count;
for (uint32_t ei = v_info.start; ei < end; ++ei) {
const EdgeInfo &e_info = e_infos[ei];
if (e_info.t_index == ti0) continue;
const Triangle& t = indices[e_info.t_index];
if (t[(e_info.edge + 1) % 3] == vi_coord ||
t[(e_info.edge + 2) % 3] == vi_coord)
return e_info.t_index;
}
// triangle0 is on border and do NOT have twin edge
return {};
}
bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex,
uint32_t ti0,
uint32_t ti1,
const VertexInfo & v_info,
const TriangleInfos & t_infos,
const EdgeInfos & e_infos,
const indexed_triangle_set &its)
{
static const float thr_pos = 1.0f - std::numeric_limits<float>::epsilon();
static const float thr_neg = -thr_pos;
static const float dot_thr = 0.2f; // Value from simplify mesh cca 80 DEG
// for each vertex triangles
size_t v_info_end = v_info.start + v_info.count;
for (size_t ei = v_info.start; ei < v_info_end; ++ei) {
assert(ei < e_infos.size());
const EdgeInfo &e_info = e_infos[ei];
if (e_info.t_index == ti0) continue; // ti0 will be deleted
if (e_info.t_index == ti1) continue; // ti1 will be deleted
const Triangle &t = its.indices[e_info.t_index];
const Vec3f &normal = t_infos[e_info.t_index].n;
const Vec3f &vf = its.vertices[t[(e_info.edge + 1) % 3]];
const Vec3f &vs = its.vertices[t[(e_info.edge + 2) % 3]];
Vec3f d1 = vf - new_vertex;
d1.normalize();
Vec3f d2 = vs - new_vertex;
d2.normalize();
float dot = d1.dot(d2);
if (dot > thr_pos || dot < thr_neg) return true;
// IMPROVE: propagate new normal
Vec3f n = d1.cross(d2);
n.normalize();
if(n.dot(normal) < dot_thr) return true;
}
return false;
}
bool QuadricEdgeCollapse::degenerate(uint32_t vi,
uint32_t ti0,
uint32_t ti1,
const VertexInfo &v_info,
const EdgeInfos & e_infos,
const Indices & indices)
{
// check surround triangle do not contain vertex index
// protect from creation of triangle with two same vertices inside
size_t v_info_end = v_info.start + v_info.count;
for (size_t ei = v_info.start; ei < v_info_end; ++ei) {
assert(ei < e_infos.size());
const EdgeInfo &e_info = e_infos[ei];
if (e_info.t_index == ti0) continue; // ti0 will be deleted
if (e_info.t_index == ti1) continue; // ti1 will be deleted
const Triangle &t = indices[e_info.t_index];
for (size_t i = 0; i < 3; ++i)
if (static_cast<uint32_t>(t[i]) == vi) return true;
}
return false;
}
Vec3d QuadricEdgeCollapse::calculate_3errors(const Triangle & t,
const Vertices & vertices,
const VertexInfos &v_infos)
{
Vec3d error;
for (size_t j = 0; j < 3; ++j) {
size_t j2 = (j == 2) ? 0 : (j + 1);
uint32_t vi0 = t[j];
uint32_t vi1 = t[j2];
SymMat q(v_infos[vi0].q); // copy
q += v_infos[vi1].q;
error[j] = calculate_error(vi0, vi1, q, vertices);
}
return error;
}
Error QuadricEdgeCollapse::calculate_error(uint32_t ti,
const Triangle & t,
const Vertices & vertices,
const VertexInfos &v_infos,
unsigned char & min_index)
{
Vec3d error = calculate_3errors(t, vertices, v_infos);
// select min error
min_index = (error[0] < error[1]) ? ((error[0] < error[2]) ? 0 : 2) :
((error[1] < error[2]) ? 1 : 2);
return Error(static_cast<float>(error[min_index]), ti);
}
void QuadricEdgeCollapse::remove_triangle(EdgeInfos & e_infos,
VertexInfo &v_info,
uint32_t ti)
{
auto e_info = e_infos.begin() + v_info.start;
auto e_info_end = e_info + v_info.count - 1;
for (; e_info != e_info_end; ++e_info) {
if (e_info->t_index == ti) {
*e_info = *e_info_end;
--v_info.count;
return;
}
}
assert(e_info_end->t_index == ti);
// last triangle is ti
--v_info.count;
}
void QuadricEdgeCollapse::change_neighbors(EdgeInfos & e_infos,
VertexInfos & v_infos,
uint32_t ti0,
uint32_t ti1,
uint32_t vi0,
uint32_t vi1,
uint32_t vi_top0,
const Triangle &t1,
CopyEdgeInfos& infos,
EdgeInfos & e_infos1)
{
// have to copy Edge info from higher vertex index into smaller
assert(vi0 < vi1);
// vertex index of triangle 1 which is not vi0 nor vi1
uint32_t vi_top1 = t1[0];
if (vi_top1 == vi0 || vi_top1 == vi1) {
vi_top1 = t1[1];
if (vi_top1 == vi0 || vi_top1 == vi1) vi_top1 = t1[2];
}
remove_triangle(e_infos, v_infos[vi_top0], ti0);
remove_triangle(e_infos, v_infos[vi_top1], ti1);
VertexInfo &v_info0 = v_infos[vi0];
VertexInfo &v_info1 = v_infos[vi1];
uint32_t new_triangle_count = v_info0.count + v_info1.count - 4;
remove_triangle(e_infos, v_info0, ti0);
remove_triangle(e_infos, v_info0, ti1);
// copy second's edge infos out of e_infos, to free size
e_infos1.clear();
e_infos1.reserve(v_info1.count - 2);
uint32_t v_info_s_end = v_info1.start + v_info1.count;
for (uint32_t ei = v_info1.start; ei < v_info_s_end; ++ei) {
const EdgeInfo &e_info = e_infos[ei];
if (e_info.t_index == ti0) continue;
if (e_info.t_index == ti1) continue;
e_infos1.emplace_back(e_info);
}
v_info1.count = 0;
uint32_t need = (new_triangle_count < v_info0.count)? 0:
(new_triangle_count - v_info0.count);
uint32_t act_vi = vi0 + 1;
VertexInfo *act_v_info = &v_infos[act_vi];
uint32_t act_start = act_v_info->start;
uint32_t last_end = v_info0.start + v_info0.count;
infos.clear();
infos.reserve(need);
while (true) {
uint32_t save = act_start - last_end;
if (save > 0) {
if (save >= need) break;
need -= save;
infos.emplace_back(act_v_info->start, act_v_info->count, need);
} else {
infos.back().count += act_v_info->count;
}
last_end = act_v_info->start + act_v_info->count;
act_v_info->start += need;
++act_vi;
if (act_vi < v_infos.size()) {
act_v_info = &v_infos[act_vi];
act_start = act_v_info->start;
} else
act_start = e_infos.size(); // fix for edge between last two triangles
}
// copy by c_infos
for (uint32_t i = infos.size(); i > 0; --i) {
const CopyEdgeInfo &c_info = infos[i - 1];
for (uint32_t ei = c_info.start + c_info.count - 1; ei >= c_info.start; --ei)
e_infos[ei + c_info.move] = e_infos[ei]; // copy
}
// copy triangle from first info into second
for (uint32_t ei_s = 0; ei_s < e_infos1.size(); ++ei_s) {
uint32_t ei_f = v_info0.start + v_info0.count;
e_infos[ei_f] = e_infos1[ei_s]; // copy
++v_info0.count;
}
}
void QuadricEdgeCollapse::compact(const VertexInfos & v_infos,
const TriangleInfos & t_infos,
const EdgeInfos & e_infos,
indexed_triangle_set &its)
{
uint32_t vi_new = 0;
for (uint32_t vi = 0; vi < v_infos.size(); ++vi) {
const VertexInfo &v_info = v_infos[vi];
if (v_info.is_deleted()) continue; // deleted
uint32_t e_info_end = v_info.start + v_info.count;
for (uint32_t ei = v_info.start; ei < e_info_end; ++ei) {
const EdgeInfo &e_info = e_infos[ei];
// change vertex index
its.indices[e_info.t_index][e_info.edge] = vi_new;
}
// compact vertices
its.vertices[vi_new++] = its.vertices[vi];
}
// remove vertices tail
its.vertices.erase(its.vertices.begin() + vi_new, its.vertices.end());
uint32_t ti_new = 0;
for (uint32_t ti = 0; ti < t_infos.size(); ti++) {
const TriangleInfo &t_info = t_infos[ti];
if (t_info.is_deleted()) continue;
its.indices[ti_new++] = its.indices[ti];
}
its.indices.erase(its.indices.begin() + ti_new, its.indices.end());
}
#ifndef NDEBUG
// store triangle surrounding to file
void QuadricEdgeCollapse::store_surround(const char *obj_filename,
size_t triangle_index,
int depth,
const indexed_triangle_set &its,
const VertexInfos & v_infos,
const EdgeInfos & e_infos)
{
std::set<size_t> triangles;
// triangle index, depth
using Item = std::pair<size_t, int>;
std::queue<Item> process;
process.push({triangle_index, depth});
while (!process.empty()) {
Item item = process.front();
process.pop();
size_t ti = item.first;
auto it = triangles.find(ti);
if (it != triangles.end()) continue;
triangles.insert(ti);
if (item.second == 0) continue;
const Vec3i &t = its.indices[ti];
for (size_t i = 0; i < 3; ++i) {
const auto &v_info = v_infos[t[i]];
for (size_t d = 0; d < v_info.count; ++d) {
size_t ei = v_info.start + d;
const auto &e_info = e_infos[ei];
auto it = triangles.find(e_info.t_index);
if (it != triangles.end()) continue;
process.push({e_info.t_index, item.second - 1});
}
}
}
std::vector<size_t> trs;
trs.reserve(triangles.size());
for (size_t ti : triangles) trs.push_back(ti);
its_store_triangles(its, obj_filename, trs);
// its_write_obj(its,"original.obj");
}
bool QuadricEdgeCollapse::check_neighbors(const indexed_triangle_set &its,
const TriangleInfos & t_infos,
const VertexInfos & v_infos,
const EdgeInfos & e_infos)
{
VertexInfos v_infos2(v_infos.size());
size_t count_indices = 0;
for (size_t ti = 0; ti < its.indices.size(); ti++) {
if (t_infos[ti].is_deleted()) continue;
++count_indices;
const Triangle &t = its.indices[ti];
for (size_t e = 0; e < 3; e++) {
VertexInfo &v_info = v_infos2[t[e]];
++v_info.count; // triangle count
}
}
uint32_t triangle_start = 0;
for (VertexInfo &v_info : v_infos2) {
v_info.start = triangle_start;
triangle_start += v_info.count;
// set filled vertex to zero
v_info.count = 0;
}
// create reference
EdgeInfos e_infos2(count_indices * 3);
for (size_t ti = 0; ti < its.indices.size(); ti++) {
if (t_infos[ti].is_deleted()) continue;
const Triangle &t = its.indices[ti];
for (size_t j = 0; j < 3; ++j) {
VertexInfo &v_info = v_infos2[t[j]];
size_t ei = v_info.start + v_info.count;
assert(ei < e_infos2.size());
EdgeInfo &e_info = e_infos2[ei];
e_info.t_index = ti;
e_info.edge = j;
++v_info.count;
}
}
for (size_t vi = 0; vi < its.vertices.size(); vi++) {
const VertexInfo &v_info = v_infos[vi];
if (v_info.is_deleted()) continue;
const VertexInfo &v_info2 = v_infos2[vi];
if (v_info.count != v_info2.count) { return false; }
EdgeInfos eis;
eis.reserve(v_info.count);
std::copy(e_infos.begin() + v_info.start,
e_infos.begin() + v_info.start + v_info.count,
std::back_inserter(eis));
auto compare = [](const EdgeInfo &ei1, const EdgeInfo &ei2) {
return ei1.t_index < ei2.t_index;
};
std::sort(eis.begin(), eis.end(), compare);
std::sort(e_infos2.begin() + v_info2.start,
e_infos2.begin() + v_info2.start + v_info2.count, compare);
for (size_t ei = 0; ei < v_info.count; ++ei) {
if (eis[ei].t_index != e_infos2[ei + v_info2.start].t_index) {
return false;
}
}
}
return true;
}
#endif /* NDEBUG */

View file

@ -0,0 +1,28 @@
// paper: https://people.eecs.berkeley.edu/~jrs/meshpapers/GarlandHeckbert2.pdf
// sum up: https://users.csc.calpoly.edu/~zwood/teaching/csc570/final06/jseeba/
// inspiration: https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
#include <cstdint>
#include <functional>
#include "TriangleMesh.hpp"
namespace Slic3r {
/// <summary>
/// Simplify mesh by Quadric metric
/// </summary>
/// <param name="its">IN/OUT triangle mesh to be simplified.</param>
/// <param name="triangle_count">Wanted triangle count.</param>
/// <param name="max_error">Maximal Quadric for reduce.
/// When nullptr then max float is used
/// Output: Last used ErrorValue to collapse edge</param>
/// <param name="throw_on_cancel">Could stop process of calculation.</param>
/// <param name="statusfn">Give a feed back to user about progress. Values 1 - 100</param>
void its_quadric_edge_collapse(
indexed_triangle_set & its,
uint32_t triangle_count = 0,
float * max_error = nullptr,
std::function<void(void)> throw_on_cancel = nullptr,
std::function<void(int)> statusfn = nullptr);
} // namespace Slic3r

View file

@ -58,29 +58,6 @@ T sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads)
return execution::reduce(ex_tbb, from, to, initv, mergefn, accessfn, grainsize);
}
// Try to guess the number of support points needed to support a mesh
double get_misalginment_score(const TriangleMesh &mesh, const Transform3f &tr)
{
if (mesh.its.vertices.empty()) return std::nan("");
auto accessfn = [&mesh, &tr](size_t fi) {
auto triangle = get_transformed_triangle(mesh, tr, fi);
Vec3f U = triangle[1] - triangle[0];
Vec3f V = triangle[2] - triangle[0];
Vec3f C = U.cross(V);
// We should score against the alignment with the reference planes
return scaled<int_fast64_t>(std::abs(C.dot(Vec3f::UnitX())) +
std::abs(C.dot(Vec3f::UnitY())));
};
size_t facecount = mesh.its.indices.size();
size_t Nthreads = std::thread::hardware_concurrency();
double S = unscaled(sum_score<int_fast64_t>(accessfn, facecount, Nthreads));
return S / facecount;
}
// Get area and normal of a triangle
struct Facestats {
Vec3f normal;
@ -96,21 +73,45 @@ struct Facestats {
}
};
// Try to guess the number of support points needed to support a mesh
double get_misalginment_score(const TriangleMesh &mesh, const Transform3f &tr)
{
if (mesh.its.vertices.empty()) return std::nan("");
auto accessfn = [&mesh, &tr](size_t fi) {
Facestats fc{get_transformed_triangle(mesh, tr, fi)};
float score = fc.area
* (std::abs(fc.normal.dot(Vec3f::UnitX()))
+ std::abs(fc.normal.dot(Vec3f::UnitY()))
+ std::abs(fc.normal.dot(Vec3f::UnitZ())));
// We should score against the alignment with the reference planes
return scaled<int_fast64_t>(score);
};
size_t facecount = mesh.its.indices.size();
size_t Nthreads = std::thread::hardware_concurrency();
double S = unscaled(sum_score<int_fast64_t>(accessfn, facecount, Nthreads));
return S / facecount;
}
// The score function for a particular face
inline double get_supportedness_score(const Facestats &fc)
{
// Simply get the angle (acos of dot product) between the face normal and
// the DOWN vector.
float phi = 1. - std::acos(fc.normal.dot(DOWN)) / float(PI);
// Only consider faces that have slopes below 90 deg:
phi = phi * (phi >= 0.5f);
float cosphi = fc.normal.dot(DOWN);
float phi = 1.f - std::acos(cosphi) / float(PI);
// Make the huge slopes more significant than the smaller slopes
phi = phi * phi * phi;
// Multiply with the area of the current face
return fc.area * POINTS_PER_UNIT_AREA * phi;
// Multiply with the square root of face area of the current face,
// the area is less important as it grows.
// This makes many smaller overhangs a bigger impact.
return std::sqrt(fc.area) * POINTS_PER_UNIT_AREA * phi;
}
// Try to guess the number of support points needed to support a mesh
@ -120,8 +121,7 @@ double get_supportedness_score(const TriangleMesh &mesh, const Transform3f &tr)
auto accessfn = [&mesh, &tr](size_t fi) {
Facestats fc{get_transformed_triangle(mesh, tr, fi)};
return get_supportedness_score(fc);
return scaled<int_fast64_t>(get_supportedness_score(fc));
};
size_t facecount = mesh.its.indices.size();
@ -164,7 +164,7 @@ float get_supportedness_onfloor_score(const TriangleMesh &mesh,
Facestats fc{tri};
if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl)
return -fc.area * POINTS_PER_UNIT_AREA;
return -2 * fc.area * POINTS_PER_UNIT_AREA;
return get_supportedness_score(fc);
};
@ -283,95 +283,81 @@ std::array<double, N> find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn)
} // namespace
template<unsigned MAX_ITER>
struct RotfinderBoilerplate {
static constexpr unsigned MAX_TRIES = MAX_ITER;
int status = 0;
TriangleMesh mesh;
unsigned max_tries;
const RotOptimizeParams &params;
// Assemble the mesh with the correct transformation to be used in rotation
// optimization.
static TriangleMesh get_mesh_to_rotate(const ModelObject &mo)
{
TriangleMesh mesh = mo.raw_mesh();
mesh.require_shared_vertices();
ModelInstance *mi = mo.instances[0];
auto rotation = Vec3d::Zero();
auto offset = Vec3d::Zero();
Transform3d trafo_instance =
Geometry::assemble_transform(offset, rotation,
mi->get_scaling_factor(),
mi->get_mirror());
mesh.transform(trafo_instance);
return mesh;
}
RotfinderBoilerplate(const ModelObject &mo, const RotOptimizeParams &p)
: mesh{get_mesh_to_rotate(mo)}
, params{p}
, max_tries(p.accuracy() * MAX_TRIES)
{
}
void statusfn() { params.statuscb()(++status * 100.0 / max_tries); }
bool stopcond() { return ! params.statuscb()(-1); }
};
Vec2d find_best_misalignment_rotation(const ModelObject & mo,
const RotOptimizeParams &params)
{
static constexpr unsigned MAX_TRIES = 1000;
// return value
XYRotation rot;
// We will use only one instance of this converted mesh to examine different
// rotations
TriangleMesh mesh = mo.raw_mesh();
mesh.require_shared_vertices();
// To keep track of the number of iterations
int status = 0;
// The maximum number of iterations
auto max_tries = unsigned(params.accuracy() * MAX_TRIES);
auto &statuscb = params.statuscb();
// call status callback with zero, because we are at the start
statuscb(status);
auto statusfn = [&statuscb, &status, &max_tries] {
// report status
statuscb(++status * 100.0/max_tries);
};
auto stopcond = [&statuscb] {
return ! statuscb(-1);
};
RotfinderBoilerplate<1000> bp{mo, params};
// Preparing the optimizer.
size_t gridsize = std::sqrt(max_tries);
opt::Optimizer<opt::AlgBruteForce> solver(opt::StopCriteria{}
.max_iterations(max_tries)
.stop_condition(stopcond),
gridsize);
size_t gridsize = std::sqrt(bp.max_tries);
opt::Optimizer<opt::AlgBruteForce> solver(
opt::StopCriteria{}.max_iterations(bp.max_tries)
.stop_condition([&bp] { return bp.stopcond(); }),
gridsize
);
// We are searching rotations around only two axes x, y. Thus the
// problem becomes a 2 dimensional optimization task.
// We can specify the bounds for a dimension in the following way:
auto bounds = opt::bounds({ {-PI/2, PI/2}, {-PI/2, PI/2} });
auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} });
auto result = solver.to_max().optimize(
[&mesh, &statusfn] (const XYRotation &rot)
[&bp] (const XYRotation &rot)
{
statusfn();
return get_misalginment_score(mesh, to_transform3f(rot));
bp.statusfn();
return get_misalginment_score(bp.mesh, to_transform3f(rot));
}, opt::initvals({0., 0.}), bounds);
rot = result.optimum;
return {rot[0], rot[1]};
return {result.optimum[0], result.optimum[1]};
}
Vec2d find_least_supports_rotation(const ModelObject & mo,
const RotOptimizeParams &params)
{
static const unsigned MAX_TRIES = 1000;
// return value
XYRotation rot;
// We will use only one instance of this converted mesh to examine different
// rotations
TriangleMesh mesh = mo.raw_mesh();
mesh.require_shared_vertices();
// To keep track of the number of iterations
unsigned status = 0;
// The maximum number of iterations
auto max_tries = unsigned(params.accuracy() * MAX_TRIES);
auto &statuscb = params.statuscb();
// call status callback with zero, because we are at the start
statuscb(status);
auto statusfn = [&statuscb, &status, &max_tries] {
// report status
statuscb(unsigned(++status * 100.0/max_tries) );
};
auto stopcond = [&statuscb] {
return ! statuscb(-1);
};
RotfinderBoilerplate<1000> bp{mo, params};
SLAPrintObjectConfig pocfg;
if (params.print_config())
@ -379,31 +365,35 @@ Vec2d find_least_supports_rotation(const ModelObject & mo,
pocfg.apply(mo.config.get());
XYRotation rot;
// Different search methods have to be used depending on the model elevation
if (is_on_floor(pocfg)) {
std::vector<XYRotation> inputs = get_chull_rotations(mesh, max_tries);
max_tries = inputs.size();
std::vector<XYRotation> inputs = get_chull_rotations(bp.mesh, bp.max_tries);
bp.max_tries = inputs.size();
// If the model can be placed on the bed directly, we only need to
// check the 3D convex hull face rotations.
auto objfn = [&mesh, &statusfn](const XYRotation &rot) {
statusfn();
auto objfn = [&bp](const XYRotation &rot) {
bp.statusfn();
Transform3f tr = to_transform3f(rot);
return get_supportedness_onfloor_score(mesh, tr);
return get_supportedness_onfloor_score(bp.mesh, tr);
};
rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), stopcond);
rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), [&bp] {
return bp.stopcond();
});
} else {
// Preparing the optimizer.
size_t gridsize = std::sqrt(max_tries); // 2D grid has gridsize^2 calls
opt::Optimizer<opt::AlgBruteForce> solver(opt::StopCriteria{}
.max_iterations(max_tries)
.stop_condition(stopcond),
gridsize);
size_t gridsize = std::sqrt(bp.max_tries); // 2D grid has gridsize^2 calls
opt::Optimizer<opt::AlgBruteForce> solver(
opt::StopCriteria{}.max_iterations(bp.max_tries)
.stop_condition([&bp] { return bp.stopcond(); }),
gridsize
);
// We are searching rotations around only two axes x, y. Thus the
// problem becomes a 2 dimensional optimization task.
@ -411,10 +401,10 @@ Vec2d find_least_supports_rotation(const ModelObject & mo,
auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} });
auto result = solver.to_min().optimize(
[&mesh, &statusfn] (const XYRotation &rot)
[&bp] (const XYRotation &rot)
{
statusfn();
return get_supportedness_score(mesh, to_transform3f(rot));
bp.statusfn();
return get_supportedness_score(bp.mesh, to_transform3f(rot));
}, opt::initvals({0., 0.}), bounds);
// Save the result
@ -424,4 +414,66 @@ Vec2d find_least_supports_rotation(const ModelObject & mo,
return {rot[0], rot[1]};
}
inline BoundingBoxf3 bounding_box_with_tr(const indexed_triangle_set &its,
const Transform3f &tr)
{
if (its.vertices.empty())
return {};
Vec3f bmin = tr * its.vertices.front(), bmax = tr * its.vertices.front();
for (const Vec3f &p : its.vertices) {
Vec3f pp = tr * p;
bmin = pp.cwiseMin(bmin);
bmax = pp.cwiseMax(bmax);
}
return {bmin.cast<double>(), bmax.cast<double>()};
}
Vec2d find_min_z_height_rotation(const ModelObject &mo,
const RotOptimizeParams &params)
{
RotfinderBoilerplate<1000> bp{mo, params};
TriangleMesh chull = bp.mesh.convex_hull_3d();
chull.require_shared_vertices();
auto inputs = reserve_vector<XYRotation>(chull.its.indices.size());
auto rotcmp = [](const XYRotation &r1, const XYRotation &r2) {
double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y];
return std::abs(xdiff) < EPSILON ? ydiff < 0. : xdiff < 0.;
};
auto eqcmp = [](const XYRotation &r1, const XYRotation &r2) {
double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y];
return std::abs(xdiff) < EPSILON && std::abs(ydiff) < EPSILON;
};
for (size_t fi = 0; fi < chull.its.indices.size(); ++fi) {
Facestats fc{get_triangle_vertices(chull, fi)};
auto q = Eigen::Quaternionf{}.FromTwoVectors(fc.normal, DOWN);
XYRotation rot = from_transform3f(Transform3f::Identity() * q);
auto it = std::lower_bound(inputs.begin(), inputs.end(), rot, rotcmp);
if (it == inputs.end() || !eqcmp(*it, rot))
inputs.insert(it, rot);
}
inputs.shrink_to_fit();
bp.max_tries = inputs.size();
auto objfn = [&bp, &chull](const XYRotation &rot) {
bp.statusfn();
Transform3f tr = to_transform3f(rot);
return bounding_box_with_tr(chull.its, tr).size().z();
};
XYRotation rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), [&bp] {
return bp.stopcond();
});
return {rot[0], rot[1]};
}
}} // namespace Slic3r::sla

View file

@ -63,7 +63,8 @@ Vec2d find_best_misalignment_rotation(const ModelObject &modelobj,
Vec2d find_least_supports_rotation(const ModelObject &modelobj,
const RotOptimizeParams & = {});
double find_Z_fit_to_bed_rotation(const ModelObject &mo, const BoundingBox &bed);
Vec2d find_min_z_height_rotation(const ModelObject &mo,
const RotOptimizeParams &params = {});
} // namespace sla
} // namespace Slic3r

View file

@ -519,7 +519,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &hjp,
auto [polar, azimuth] = dir_to_spheric(dir);
polar = PI - m_cfg.bridge_slope;
Vec3d d = spheric_to_dir(polar, azimuth).normalized();
double t = bridge_mesh_distance(endp, dir, radius);
double t = bridge_mesh_distance(endp, d, radius);
double tmax = std::min(m_cfg.max_bridge_length_mm, t);
t = 0.;

View file

@ -25,8 +25,17 @@ public:
Semver() : ver(semver_zero()) {}
Semver(int major, int minor, int patch,
boost::optional<const std::string&> metadata = boost::none,
boost::optional<const std::string&> prerelease = boost::none)
boost::optional<const std::string&> metadata, boost::optional<const std::string&> prerelease)
: ver(semver_zero())
{
ver.major = major;
ver.minor = minor;
ver.patch = patch;
set_metadata(metadata);
set_prerelease(prerelease);
}
Semver(int major, int minor, int patch, const char *metadata = nullptr, const char *prerelease = nullptr)
: ver(semver_zero())
{
ver.major = major;
@ -102,7 +111,9 @@ public:
void set_min(int min) { ver.minor = min; }
void set_patch(int patch) { ver.patch = patch; }
void set_metadata(boost::optional<const std::string&> meta) { ver.metadata = meta ? strdup(*meta) : nullptr; }
void set_metadata(const char *meta) { ver.metadata = meta ? strdup(meta) : nullptr; }
void set_prerelease(boost::optional<const std::string&> pre) { ver.prerelease = pre ? strdup(*pre) : nullptr; }
void set_prerelease(const char *pre) { ver.prerelease = pre ? strdup(pre) : nullptr; }
// Comparison
bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; }

View file

@ -107,7 +107,7 @@ public:
// Determinant
T det(int a11, int a12, int a13,
int a21, int a22, int a23,
int a31, int a32, int a33)
int a31, int a32, int a33) const
{
T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] +
m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] -
@ -121,7 +121,7 @@ public:
for (size_t i = 0; i < N; ++i) m[i] += n[i];
return *this;
}
SymetricMatrix operator+(const SymetricMatrix& n)
{
SymetricMatrix self = *this;

View file

@ -47,6 +47,8 @@
#define ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER (1 && ENABLE_2_4_0_ALPHA0)
// Enable drawing contours, at cut level, for sinking volumes
#define ENABLE_SINKING_CONTOURS (1 && ENABLE_2_4_0_ALPHA0)
// Enable implementation of retract acceleration in gcode processor
#define ENABLE_RETRACT_ACCELERATION (1 && ENABLE_2_4_0_ALPHA0)
// Enable rendering seams (and other options) in preview using models
#define ENABLE_SEAMS_USING_MODELS (1 && ENABLE_2_4_0_ALPHA0)
// Enable rendering seams (and other options) in preview using instanced models

View file

@ -957,6 +957,48 @@ int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit)
return removed;
}
bool its_store_triangle(const indexed_triangle_set &its,
const char * obj_filename,
size_t triangle_index)
{
if (its.indices.size() <= triangle_index) return false;
Vec3i t = its.indices[triangle_index];
indexed_triangle_set its2;
its2.indices = {{0, 1, 2}};
its2.vertices = {its.vertices[t[0]], its.vertices[t[1]],
its.vertices[t[2]]};
return its_write_obj(its2, obj_filename);
}
bool its_store_triangles(const indexed_triangle_set &its,
const char * obj_filename,
const std::vector<size_t> & triangles)
{
indexed_triangle_set its2;
its2.vertices.reserve(triangles.size() * 3);
its2.indices.reserve(triangles.size());
std::map<size_t, size_t> vertex_map;
for (auto ti : triangles) {
if (its.indices.size() <= ti) return false;
Vec3i t = its.indices[ti];
Vec3i new_t;
for (size_t i = 0; i < 3; ++i) {
size_t vi = t[i];
auto it = vertex_map.find(vi);
if (it != vertex_map.end()) {
new_t[i] = it->second;
continue;
}
size_t new_vi = its2.vertices.size();
its2.vertices.push_back(its.vertices[vi]);
vertex_map[vi] = new_vi;
new_t[i] = new_vi;
}
its2.indices.push_back(new_t);
}
return its_write_obj(its2, obj_filename);
}
void its_shrink_to_fit(indexed_triangle_set &its)
{
its.indices.shrink_to_fit();

View file

@ -140,6 +140,10 @@ int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit =
// Remove vertices, which none of the faces references. Return number of freed vertices.
int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit = true);
// store part of index triangle set
bool its_store_triangle(const indexed_triangle_set &its, const char *obj_filename, size_t triangle_index);
bool its_store_triangles(const indexed_triangle_set &its, const char *obj_filename, const std::vector<size_t>& triangles);
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its);
bool its_is_splittable(const indexed_triangle_set &its);

View file

@ -4,7 +4,7 @@
#include <boost/container/small_vector.hpp>
#ifndef NDEBUG
#define EXPENSIVE_DEBUG_CHECKS
// #define EXPENSIVE_DEBUG_CHECKS
#endif // NDEBUG
namespace Slic3r {
@ -178,12 +178,12 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
}
}
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle)
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle, bool force_reselection)
{
assert(facet_start < m_orig_size_indices);
// Recompute seed fill only if the cursor is pointing on facet unselected by seed fill.
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill())
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection)
return;
this->seed_fill_unselect_all_triangles();
@ -278,7 +278,7 @@ void TriangleSelector::append_touching_subtriangles(int itriangle, int vertexi,
return;
auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_subtriangles_out](const int subtriangle_idx, Partition partition) -> void {
assert(subtriangle_idx == -1);
assert(subtriangle_idx != -1);
if (!m_triangles[subtriangle_idx].is_split())
touching_subtriangles_out.emplace_back(subtriangle_idx);
else if (int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj); midpoint != -1)
@ -295,11 +295,48 @@ void TriangleSelector::append_touching_subtriangles(int itriangle, int vertexi,
process_subtriangle(touching.second, Partition::Second);
}
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate)
// It appends all edges that are touching the edge (vertexi, vertexj) of the triangle and are not selected by seed fill
// It doesn't append the edges that are touching the triangle only by part of the edge that means the triangles are from lower depth.
void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector<Vec2i> &touching_edges_out) const
{
if (itriangle == -1)
return;
auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_edges_out](const int subtriangle_idx, Partition partition) -> void {
assert(subtriangle_idx != -1);
if (!m_triangles[subtriangle_idx].is_split()) {
if (!m_triangles[subtriangle_idx].is_selected_by_seed_fill()) {
int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj);
if (partition == Partition::First && midpoint != -1) {
touching_edges_out.emplace_back(vertexi, midpoint);
} else if (partition == Partition::First && midpoint == -1) {
touching_edges_out.emplace_back(vertexi, vertexj);
} else {
assert(midpoint != -1 && partition == Partition::Second);
touching_edges_out.emplace_back(midpoint, vertexj);
}
}
} else if (int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj); midpoint != -1)
append_touching_edges(subtriangle_idx, partition == Partition::First ? vertexi : midpoint, partition == Partition::First ? midpoint : vertexj,
touching_edges_out);
else
append_touching_edges(subtriangle_idx, vertexi, vertexj, touching_edges_out);
};
std::pair<int, int> touching = this->triangle_subtriangles(itriangle, vertexi, vertexj);
if (touching.first != -1)
process_subtriangle(touching.first, Partition::First);
if (touching.second != -1)
process_subtriangle(touching.second, Partition::Second);
}
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate, bool force_reselection)
{
int start_facet_idx = select_unsplit_triangle(hit, facet_start);
assert(start_facet_idx != -1);
// Recompute bucket fill only if the cursor is pointing on facet unselected by bucket fill.
if (start_facet_idx == -1 || m_triangles[start_facet_idx].is_selected_by_seed_fill())
if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection))
return;
assert(!m_triangles[start_facet_idx].is_split());
@ -312,7 +349,7 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_
}
auto get_all_touching_triangles = [this](int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) -> std::vector<int> {
assert(facet_idx != -1 && facet_idx < m_triangles.size());
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
std::vector<int> touching_triangles;
Vec3i vertices = {m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2]};
@ -1358,6 +1395,48 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const
}
}
std::vector<Vec2i> TriangleSelector::get_seed_fill_contour() const {
std::vector<Vec2i> edges_out;
for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) {
const Vec3i neighbors = root_neighbors(*m_mesh, facet_idx);
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
this->get_seed_fill_contour_recursive(facet_idx, neighbors, neighbors, edges_out);
}
return edges_out;
}
void TriangleSelector::get_seed_fill_contour_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec2i> &edges_out) const {
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
const Triangle *tr = &m_triangles[facet_idx];
if (!tr->valid())
return;
if (tr->is_split()) {
int num_of_children = tr->number_of_split_sides() + 1;
if (num_of_children != 1) {
for (int i = 0; i < num_of_children; ++i) {
assert(i < int(tr->children.size()));
assert(tr->children[i] < int(m_triangles.size()));
// Recursion, deep first search over the children of this triangle.
// All children of this triangle were created by splitting a single source triangle of the original mesh.
this->get_seed_fill_contour_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i), this->child_neighbors_propagated(*tr, neighbors_propagated, i), edges_out);
}
}
} else if (tr->is_selected_by_seed_fill()) {
Vec3i vertices = {m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2]};
append_touching_edges(neighbors(0), vertices(1), vertices(0), edges_out);
append_touching_edges(neighbors(1), vertices(2), vertices(1), edges_out);
append_touching_edges(neighbors(2), vertices(0), vertices(2), edges_out);
// It appends the edges that are touching the triangle only by part of the edge that means the triangles are from lower depth.
for (int idx = 0; idx < 3; ++idx)
if (int neighbor_tr_idx = neighbors_propagated(idx); neighbor_tr_idx != -1 && !m_triangles[neighbor_tr_idx].is_split() && !m_triangles[neighbor_tr_idx].is_selected_by_seed_fill())
edges_out.emplace_back(vertices(idx), vertices(next_idx_modulo(idx, 3)));
}
}
std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> TriangleSelector::serialize() const
{
// Each original triangle of the mesh is assigned a number encoding its state

View file

@ -49,11 +49,13 @@ public:
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
float seed_fill_angle); // the maximal angle between two facets to be painted by the same color
float seed_fill_angle, // the maximal angle between two facets to be painted by the same color
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
void bucket_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
bool propagate); // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
bool propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
bool has_facets(EnforcerBlockerType state) const;
static bool has_facets(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, EnforcerBlockerType test_state);
@ -62,6 +64,8 @@ public:
indexed_triangle_set get_facets(EnforcerBlockerType state) const;
// Get facets at a given state. Triangulate T-joints.
indexed_triangle_set get_facets_strict(EnforcerBlockerType state) const;
// Get edges around the selected area by seed fill.
std::vector<Vec2i> get_seed_fill_contour() const;
// Set facet of the mesh to a given state. Only works for original triangles.
void set_facet(int facet_idx, EnforcerBlockerType state);
@ -221,6 +225,7 @@ private:
std::pair<int, int> triangle_subtriangles(int itriangle, int vertexi, int vertexj) const;
void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector<int> &touching_subtriangles_out) const;
void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector<Vec2i> &touching_edges_out) const;
#ifndef NDEBUG
bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const;
@ -234,6 +239,8 @@ private:
std::vector<stl_triangle_vertex_indices> &out_triangles) const;
void get_facets_split_by_tjoints(const Vec3i &vertices, const Vec3i &neighbors, std::vector<stl_triangle_vertex_indices> &out_triangles) const;
void get_seed_fill_contour_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec2i> &edges_out) const;
int m_free_triangles_head { -1 };
int m_free_vertices_head { -1 };
};

View file

@ -238,7 +238,7 @@ inline typename CONTAINER_TYPE::value_type& next_value_modulo(typename CONTAINER
return container[next_idx_modulo(idx, container.size())];
}
extern std::string xml_escape(std::string text);
extern std::string xml_escape(std::string text, bool is_marked = false);
#if defined __GNUC__ && __GNUC__ < 5 && !defined __clang__

View file

@ -888,7 +888,7 @@ unsigned get_current_pid()
#endif
}
std::string xml_escape(std::string text)
std::string xml_escape(std::string text, bool is_marked/* = false*/)
{
std::string::size_type pos = 0;
for (;;)
@ -903,8 +903,8 @@ std::string xml_escape(std::string text)
case '\"': replacement = "&quot;"; break;
case '\'': replacement = "&apos;"; break;
case '&': replacement = "&amp;"; break;
case '<': replacement = "&lt;"; break;
case '>': replacement = "&gt;"; break;
case '<': replacement = is_marked ? "<" :"&lt;"; break;
case '>': replacement = is_marked ? ">" :"&gt;"; break;
default: break;
}

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 2.8.12)
project(miniz)
cmake_minimum_required(VERSION 2.6)
add_library(miniz INTERFACE)

View file

@ -0,0 +1,12 @@
[Desktop Entry]
Name=PrusaSlicer
GenericName=3D Printing Software
Icon=com.prusa3d.PrusaSlicer
Exec=prusa-slicer %F
Terminal=false
Type=Application
MimeType=model/stl;model/x-wavefront-obj;model/3mf;model/x-geomview-off;application/x-amf;
Categories=Graphics;3DGraphics;Engineering;
Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA
StartupNotify=false
StartupWMClass=prusa-slicer

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>com.prusa3d.PrusaSlicer</id>
<launchable type="desktop-id">com.prusa3d.PrusaSlicer.desktop</launchable>
<provides>
<id>prusa-slicer.desktop</id>
</provides>
<name>PrusaSlicer</name>
<summary>Powerful 3D printing slicer optimized for Prusa printers</summary>
<metadata_license>0BSD</metadata_license>
<project_license>AGPL-3.0-only</project_license>
<description>
<p>
PrusaSlicer takes 3D models (STL, OBJ, AMF) and converts them into G-code
instructions for FFF printers or PNG layers for mSLA 3D printers. It's
compatible with any modern printer based on the RepRap toolchain, including all
those based on the Marlin, Prusa, Sprinter and Repetier firmware. It also works
with Mach3, LinuxCNC and Machinekit controllers.
</p>
<p>
PrusaSlicer is based on Slic3r by Alessandro Ranelucci and the RepRap community.
</p>
<p>
What are some of PrusaSlicer's main features?
</p>
<ul>
<li>multi-platform (Linux/Mac/Win) and packaged as standalone-app with no dependencies required</li>
<li>complete command-line interface to use it with no GUI</li>
<li>multi-material (multiple extruders) object printing</li>
<li>multiple G-code flavors supported (RepRap, Makerbot, Mach3, Machinekit etc.)</li>
<li>ability to plate multiple objects having distinct print settings</li>
<li>multithread processing</li>
<li>STL auto-repair (tolerance for broken models)</li>
<li>wide automated unit testing</li>
</ul>
</description>
<url type="homepage">https://www.prusa3d.com/prusaslicer/</url>
<url type="help">https://help.prusa3d.com</url>
<url type="bugtracker">https://github.com/prusa3d/PrusaSlicer/issues</url>
<screenshots>
<screenshot type="default">
<image>https://user-images.githubusercontent.com/590307/78981854-24d07580-7b21-11ea-9441-77923534a659.png</image>
</screenshot>
<screenshot>
<image>https://user-images.githubusercontent.com/590307/78981860-2863fc80-7b21-11ea-8c2d-8ff79ced2578.png</image>
</screenshot>
<screenshot>
<image>https://user-images.githubusercontent.com/590307/78981862-28fc9300-7b21-11ea-9b0d-d03e16b709d3.png</image>
</screenshot>
</screenshots>
<content_rating type="oars-1.1" />
<releases>
<release version="2.2.0" date="2020-03-21">
<description>
<p>This is final release of PrusaSlicer 2.2.0 introducing SLA hollowing and hole drilling, support for 3rd party printer vendors, 3Dconnexion support,
automatic variable layer height, macOS dark mode support, greatly improved ColorPrint feature and much, much more.
Several bugs found in the previous release candidate are fixed in this final release. See the respective change logs of the previous releases for all the
new features, improvements and bugfixes in the 2.2.0 series.</p>
</description>
</release>
</releases>
</component>

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 2.8.12)
project(semver)
cmake_minimum_required(VERSION 2.6)
add_library(semver STATIC
semver.c

View file

@ -57,6 +57,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoPainterBase.hpp
GUI/Gizmos/GLGizmoSeam.cpp
GUI/Gizmos/GLGizmoSeam.hpp
GUI/Gizmos/GLGizmoSimplify.cpp
GUI/Gizmos/GLGizmoSimplify.hpp
GUI/Gizmos/GLGizmoMmuSegmentation.cpp
GUI/Gizmos/GLGizmoMmuSegmentation.hpp
GUI/GLSelectionRectangle.cpp
@ -266,7 +268,7 @@ if(APPLE)
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY})
endif()
if (SLIC3R_STATIC AND UNIX AND NOT APPLE)
if (SLIC3R_STATIC AND NOT SLIC3R_STATIC_EXCLUDE_CURL AND UNIX AND NOT APPLE)
target_compile_definitions(libslic3r_gui PRIVATE OPENSSL_CERT_OVERRIDE)
endif ()

View file

@ -8,14 +8,23 @@
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree_fwd.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Time.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/FileParserError.hpp"
#include "libslic3r/Utils.hpp"
#include "../GUI/GUI.hpp"
#include "../GUI/GUI_App.hpp"
#include "../GUI/I18N.hpp"
#include "../GUI/MainFrame.hpp"
#include <wx/richmsgdlg.h>
#define SLIC3R_SNAPSHOTS_DIR "snapshots"
#define SLIC3R_SNAPSHOT_FILE "snapshot.ini"
@ -358,11 +367,12 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src
{
if (! boost::filesystem::is_directory(path_dst) &&
! boost::filesystem::create_directory(path_dst))
throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + path_dst.string());
throw Slic3r::RuntimeError(std::string("PrusaSlicer was unable to create a directory at ") + path_dst.string());
for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
if (Slic3r::is_ini_file(dir_entry))
boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists);
if (std::string error_message; copy_file(dir_entry.path().string(), (path_dst / dir_entry.path().filename()).string(), error_message, false) != SUCCESS)
throw Slic3r::RuntimeError(format("Failed copying \"%1%\" to \"%2%\": %3%", path_src.string(), path_dst.string(), error_message));
}
static void delete_existing_ini_files(const boost::filesystem::path &path)
@ -413,7 +423,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
++ it;
// Read the active config bundle, parse the config version.
PresetBundle bundle;
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly);
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
for (const auto &vp : bundle.vendors)
if (vp.second.id == cfg.name)
cfg.version.config_version = vp.second.config_version;
@ -433,14 +443,27 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
}
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
boost::filesystem::create_directory(snapshot_dir);
// Backup the presets.
for (const char *subdir : snapshot_subdirs)
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
m_snapshots.emplace_back(std::move(snapshot));
try {
boost::filesystem::create_directory(snapshot_dir);
// Backup the presets.
for (const char *subdir : snapshot_subdirs)
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
m_snapshots.emplace_back(std::move(snapshot));
} catch (...) {
if (boost::filesystem::is_directory(snapshot_dir)) {
try {
// Clean up partially copied snapshot.
boost::filesystem::remove_all(snapshot_dir);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed taking snapshot and failed removing the snapshot directory " << snapshot_dir;
}
}
throw;
}
return m_snapshots.back();
}
@ -551,6 +574,32 @@ SnapshotDB& SnapshotDB::singleton()
return instance;
}
const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
{
try {
return &SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
} catch (std::exception &err) {
show_error(static_cast<wxWindow*>(wxGetApp().mainframe),
_L("Taking a configuration snapshot failed.") + "\n\n" + from_u8(err.what()));
return nullptr;
}
}
bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message)
{
try {
SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
return true;
} catch (std::exception &err) {
wxRichMessageDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe),
_L("PrusaSlicer has encountered an error while taking a configuration snapshot.") + "\n\n" + from_u8(err.what()) + "\n\n" + from_u8(message),
_L("PrusaSlicer error"),
wxYES_NO);
dlg.SetYesNoLabels(_L("Continue"), _L("Abort"));
return dlg.ShowModal() == wxID_YES;
}
}
} // namespace Config
} // namespace GUI
} // namespace Slic3r

View file

@ -127,6 +127,13 @@ private:
std::vector<Snapshot> m_snapshots;
};
// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report an error and return nullptr.
const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment);
// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report "message", and present a "Continue" or "Abort" buttons to respond.
// Return true on success and on "Continue" to continue with the process (for example installation of presets).
bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message);
} // namespace Config
} // namespace GUI
} // namespace Slic3r

View file

@ -303,13 +303,16 @@ void GLVolume::SinkingContours::render()
void GLVolume::SinkingContours::update()
{
if (m_parent.is_sinking() && !m_parent.is_below_printbed()) {
int object_idx = m_parent.object_idx();
Model& model = GUI::wxGetApp().plater()->model();
if (0 <= object_idx && object_idx < (int)model.objects.size() && m_parent.is_sinking() && !m_parent.is_below_printbed()) {
const BoundingBoxf3& box = m_parent.transformed_convex_hull_bounding_box();
if (!m_old_box.size().isApprox(box.size()) || m_old_box.min.z() != box.min.z()) {
m_old_box = box;
m_shift = Vec3d::Zero();
const TriangleMesh& mesh = GUI::wxGetApp().plater()->model().objects[m_parent.object_idx()]->volumes[m_parent.volume_idx()]->mesh();
const TriangleMesh& mesh = model.objects[object_idx]->volumes[m_parent.volume_idx()]->mesh();
assert(mesh.has_shared_vertices());
m_model.reset();
@ -592,7 +595,7 @@ bool GLVolume::is_sinking() const
bool GLVolume::is_below_printbed() const
{
return transformed_convex_hull_bounding_box().max(2) < 0.0;
return transformed_convex_hull_bounding_box().max.z() < 0.0;
}
#if ENABLE_SINKING_CONTOURS
@ -843,12 +846,13 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
volume.first->set_render_color();
// render sinking contours of non-hovered volumes
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) {
shader->stop_using();
volume.first->render_sinking_contours();
shader->start_using();
}
if (m_show_sinking_contours)
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) {
shader->stop_using();
volume.first->render_sinking_contours();
shader->start_using();
}
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
@ -887,17 +891,18 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
}
for (GLVolumeWithIdAndZ& volume : to_render) {
// render sinking contours of hovered/displaced volumes
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
(volume.first->hover != GLVolume::HS_None || volume.first->force_sinking_contours)) {
shader->stop_using();
glsafe(::glDepthFunc(GL_ALWAYS));
volume.first->render_sinking_contours();
glsafe(::glDepthFunc(GL_LESS));
shader->start_using();
if (m_show_sinking_contours)
for (GLVolumeWithIdAndZ& volume : to_render) {
// render sinking contours of hovered/displaced volumes
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
(volume.first->hover != GLVolume::HS_None || volume.first->force_sinking_contours)) {
shader->stop_using();
glsafe(::glDepthFunc(GL_ALWAYS));
volume.first->render_sinking_contours();
glsafe(::glDepthFunc(GL_LESS));
shader->start_using();
}
}
}
#else
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));

View file

@ -539,6 +539,7 @@ private:
};
Slope m_slope;
bool m_show_sinking_contours = false;
public:
GLVolumePtrs volumes;
@ -608,6 +609,7 @@ public:
float get_slope_normal_z() const { return m_slope.normal_z; }
void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; }
void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); }
void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; }
// returns true if all the volumes are completely contained in the print volume
// returns the containment state in the given out_state, if non-null

View file

@ -195,7 +195,7 @@ void CopyrightsDialog::on_dpi_changed(const wxRect &suggested_rect)
void CopyrightsDialog::onLinkClicked(wxHtmlLinkEvent &event)
{
wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref());
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref());
event.Skip(false);
}
@ -344,7 +344,7 @@ void AboutDialog::on_dpi_changed(const wxRect &suggested_rect)
void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event)
{
wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref());
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref());
event.Skip(false);
}

View file

@ -154,54 +154,7 @@ void BackgroundSlicingProcess::process_fff()
if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
// let the gcode window to unmap the temporary .gcode file (m_temp_output_path)
// because the scripts may want to modify it
GUI::wxGetApp().plater()->stop_mapping_gcode_window();
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
run_post_process_scripts(m_temp_output_path, m_fff_print->full_print_config());
// let the gcode window to reload and remap the temporary .gcode file (m_temp_output_path)
GUI::wxGetApp().plater()->start_mapping_gcode_window();
//FIXME localize the messages
// Perform the final post-processing of the export path by applying the print statistics over the file name.
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
std::string error_message;
int copy_ret_val = CopyFileResult::SUCCESS;
try
{
copy_ret_val = copy_file(m_temp_output_path, export_path, error_message, m_export_path_on_removable_media);
}
catch (...)
{
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
}
switch (copy_ret_val) {
case CopyFileResult::SUCCESS: break; // no error
case CopyFileResult::FAIL_COPY_FILE:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"))) % error_message).str());
break;
case CopyFileResult::FAIL_FILES_DIFFERENT:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
break;
case CopyFileResult::FAIL_RENAMING:
throw Slic3r::ExportError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str());
break;
case CopyFileResult::FAIL_CHECK_ORIGIN_NOT_OPENED:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str());
break;
case CopyFileResult::FAIL_CHECK_TARGET_NOT_OPENED:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
break;
default:
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
BOOST_LOG_TRIVIAL(error) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
break;
}
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
finalize_gcode();
} else if (! m_upload_job.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
prepare_upload();
@ -621,8 +574,11 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn
// Some FFF status was invalidated, and the G-code was not exported yet.
// Let the G-code preview UI know that the final G-code preview is not valid.
// In addition, this early memory deallocation reduces memory footprint.
if (m_gcode_result != nullptr)
if (m_gcode_result != nullptr) {
//FIXME calling platter from here is not a staple of a good architecture.
GUI::wxGetApp().plater()->stop_mapping_gcode_window();
m_gcode_result->reset();
}
}
return invalidated;
}
@ -698,12 +654,73 @@ bool BackgroundSlicingProcess::invalidate_all_steps()
return m_step_state.invalidate_all([this](){ this->stop_internal(); });
}
// G-code is generated in m_temp_output_path.
// Optionally run a post-processing script on a copy of m_temp_output_path.
// Copy the final G-code to target location (possibly a SD card, if it is a removable media, then verify that the file was written without an error).
void BackgroundSlicingProcess::finalize_gcode()
{
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
// Perform the final post-processing of the export path by applying the print statistics over the file name.
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
std::string output_path = m_temp_output_path;
// Both output_path and export_path ar in-out parameters.
// If post processed, output_path will differ from m_temp_output_path as run_post_process_scripts() will make a copy of the G-code to not
// collide with the G-code viewer memory mapping of the unprocessed G-code. G-code viewer maps unprocessed G-code, because m_gcode_result
// is calculated for the unprocessed G-code and it references lines in the memory mapped G-code file by line numbers.
// export_path may be changed by the post-processing script as well if the post processing script decides so, see GH #6042.
bool post_processed = run_post_process_scripts(output_path, true, "File", export_path, m_fff_print->full_print_config());
auto remove_post_processed_temp_file = [post_processed, &output_path]() {
if (post_processed)
try {
boost::filesystem::remove(output_path);
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << "Failed to remove temp file " << output_path << ": " << ex.what();
}
};
//FIXME localize the messages
std::string error_message;
int copy_ret_val = CopyFileResult::SUCCESS;
try
{
copy_ret_val = copy_file(output_path, export_path, error_message, m_export_path_on_removable_media);
remove_post_processed_temp_file();
}
catch (...)
{
remove_post_processed_temp_file();
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
}
switch (copy_ret_val) {
case CopyFileResult::SUCCESS: break; // no error
case CopyFileResult::FAIL_COPY_FILE:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"))) % error_message).str());
break;
case CopyFileResult::FAIL_FILES_DIFFERENT:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
break;
case CopyFileResult::FAIL_RENAMING:
throw Slic3r::ExportError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str());
break;
case CopyFileResult::FAIL_CHECK_ORIGIN_NOT_OPENED:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % output_path % export_path).str());
break;
case CopyFileResult::FAIL_CHECK_TARGET_NOT_OPENED:
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
break;
default:
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
BOOST_LOG_TRIVIAL(error) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
break;
}
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
}
// A print host upload job has been scheduled, enqueue it to the printhost job queue
void BackgroundSlicingProcess::prepare_upload()
{
// A print host upload job has been scheduled, enqueue it to the printhost job queue
// XXX: is fs::path::string() right?
// Generate a unique temp path to which the gcode/zip file is copied/exported
boost::filesystem::path source_path = boost::filesystem::temp_directory_path()
/ boost::filesystem::unique_path("." SLIC3R_APP_KEY ".upload.%%%%-%%%%-%%%%-%%%%");
@ -711,11 +728,15 @@ void BackgroundSlicingProcess::prepare_upload()
if (m_print == m_fff_print) {
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
std::string error_message;
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS) {
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS)
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
}
run_post_process_scripts(source_path.string(), m_fff_print->full_print_config());
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
// Make a copy of the source path, as run_post_process_scripts() is allowed to change it when making a copy of the source file
// (not here, but when the final target is a file).
std::string source_path_str = source_path.string();
std::string output_name_str = m_upload_job.upload_data.upload_path.string();
if (run_post_process_scripts(source_path_str, false, m_upload_job.printhost->get_name(), output_name_str, m_fff_print->full_print_config()))
m_upload_job.upload_data.upload_path = output_name_str;
} else {
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());

View file

@ -216,9 +216,9 @@ private:
Print *m_fff_print = nullptr;
SLAPrint *m_sla_print = nullptr;
// Data structure, to which the G-code export writes its annotations.
GCodeProcessor::Result *m_gcode_result = nullptr;
GCodeProcessor::Result *m_gcode_result = nullptr;
// Callback function, used to write thumbnails into gcode.
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
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;
@ -262,6 +262,7 @@ private:
bool invalidate_all_steps();
// If the background processing stop was requested, throw CanceledException.
void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
void finalize_gcode();
void prepare_upload();
// To be executed at the background thread.
ThumbnailsList render_thumbnails(const ThumbnailsParams &params);

View file

@ -12,6 +12,46 @@
namespace Slic3r {
namespace GUI {
void ButtonsDescription::FillSizerWithTextColorDescriptions(wxSizer* sizer, wxWindow* parent, wxColourPickerCtrl** sys_colour, wxColourPickerCtrl** mod_colour)
{
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(3, 5, 5);
sizer->Add(grid_sizer, 0, wxEXPAND);
ScalableBitmap bmp_delete = ScalableBitmap(parent, "cross");
ScalableBitmap bmp_delete_focus = ScalableBitmap(parent, "cross_focus");
auto add_color = [grid_sizer, parent](wxColourPickerCtrl** color_picker, const wxColour& color, const wxColour& def_color, wxString label_text) {
//
auto sys_label = new wxStaticText(parent, wxID_ANY, label_text);
sys_label->SetForegroundColour(color);
*color_picker = new wxColourPickerCtrl(parent, wxID_ANY, color);
wxGetApp().UpdateDarkUI((*color_picker)->GetPickerCtrl(), true);
(*color_picker)->Bind(wxEVT_COLOURPICKER_CHANGED, [color_picker, sys_label](wxCommandEvent&) {
sys_label->SetForegroundColour((*color_picker)->GetColour());
sys_label->Refresh();
});
auto btn = new ScalableButton(parent, wxID_ANY, "undo");
btn->SetToolTip(_L("Revert color to default"));
btn->Bind(wxEVT_BUTTON, [sys_label, color_picker, def_color](wxEvent& event) {
(*color_picker)->SetColour(def_color);
sys_label->SetForegroundColour(def_color);
sys_label->Refresh();
});
parent->Bind(wxEVT_UPDATE_UI, [color_picker, def_color](wxUpdateUIEvent& evt) {
evt.Enable((*color_picker)->GetColour() != def_color);
}, btn->GetId());
grid_sizer->Add(*color_picker, 0, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(btn, 0, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(sys_label, 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
};
add_color(sys_colour, wxGetApp().get_label_clr_sys(), wxGetApp().get_label_default_clr_system(), _L("Value is the same as the system value"));
add_color(mod_colour, wxGetApp().get_label_clr_modified(),wxGetApp().get_label_default_clr_modified(), _L("Value was changed and is not equal to the system value or the last saved preset"));
}
ButtonsDescription::ButtonsDescription(wxWindow* parent, const std::vector<Entry> &entries) :
wxDialog(parent, wxID_ANY, _(L("Buttons And Text Colors Description")), wxDefaultPosition, wxDefaultSize),
m_entries(entries)
@ -35,50 +75,23 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, const std::vector<Entry
}
// Text color description
auto sys_label = new wxStaticText(this, wxID_ANY, _(L("Value is the same as the system value")));
sys_label->SetForegroundColour(wxGetApp().get_label_clr_sys());
auto sys_colour = new wxColourPickerCtrl(this, wxID_ANY, wxGetApp().get_label_clr_sys());
wxGetApp().UpdateDarkUI(sys_colour->GetPickerCtrl(), true);
sys_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([sys_colour, sys_label](wxCommandEvent e)
{
sys_label->SetForegroundColour(sys_colour->GetColour());
sys_label->Refresh();
}));
size_t t= 0;
while (t < 3) {
grid_sizer->Add(new wxStaticText(this, wxID_ANY, ""), -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
++t;
}
grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(sys_colour, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(sys_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
auto mod_label = new wxStaticText(this, wxID_ANY, _(L("Value was changed and is not equal to the system value or the last saved preset")));
mod_label->SetForegroundColour(wxGetApp().get_label_clr_modified());
auto mod_colour = new wxColourPickerCtrl(this, wxID_ANY, wxGetApp().get_label_clr_modified());
wxGetApp().UpdateDarkUI(mod_colour->GetPickerCtrl(), true);
mod_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([mod_colour, mod_label](wxCommandEvent e)
{
mod_label->SetForegroundColour(mod_colour->GetColour());
mod_label->Refresh();
}));
grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(mod_colour, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(mod_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
FillSizerWithTextColorDescriptions(sizer, this, &sys_colour, &mod_colour);
main_sizer->Add(sizer, 0, wxEXPAND | wxALL, 20);
auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL);
main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
wxGetApp().UpdateDlgDarkUI(this, true);
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn->Bind(wxEVT_BUTTON, [sys_colour, mod_colour, this](wxCommandEvent&) {
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
wxGetApp().set_label_clr_sys(sys_colour->GetColour());
wxGetApp().set_label_clr_modified(mod_colour->GetColour());
EndModal(wxID_OK);
});
wxGetApp().UpdateDarkUI(btn);
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(FindWindowById(wxID_CANCEL, this)));
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
}

View file

@ -5,12 +5,15 @@
#include <vector>
class ScalableBitmap;
class wxColourPickerCtrl;
namespace Slic3r {
namespace GUI {
class ButtonsDescription : public wxDialog
{
wxColourPickerCtrl* sys_colour{ nullptr };
wxColourPickerCtrl* mod_colour{ nullptr };
public:
struct Entry {
Entry(ScalableBitmap *bitmap, const std::string &symbol, const std::string &explanation) : bitmap(bitmap), symbol(symbol), explanation(explanation) {}
@ -23,6 +26,8 @@ public:
ButtonsDescription(wxWindow* parent, const std::vector<Entry> &entries);
~ButtonsDescription() {}
static void FillSizerWithTextColorDescriptions(wxSizer* sizer, wxWindow* parent, wxColourPickerCtrl** sys_colour, wxColourPickerCtrl** mod_colour);
private:
std::vector<Entry> m_entries;
};

View file

@ -268,7 +268,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field("gap_fill_speed", have_perimeters);
for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" })
toggle_field(el, has_top_solid_infill);
toggle_field(el, has_top_solid_infill || (has_spiral_vase && has_bottom_solid_infill));
bool have_default_acceleration = config->opt_float("default_acceleration") > 0;
for (auto el : { "perimeter_acceleration", "infill_acceleration",

View file

@ -65,7 +65,9 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu
this->is_prusa_bundle = ais_prusa_bundle;
std::string path_string = source_path.string();
auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem);
// Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(
path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
UNUSED(config_substitutions);
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
assert(config_substitutions.empty());
@ -492,15 +494,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent)
{
welcome_text->Hide();
cbox_reset->Hide();
#ifdef __linux__
if (!DesktopIntegrationDialog::is_integrated())
cbox_integrate->Show(true);
else
cbox_integrate->Hide();
#else
cbox_integrate->Hide();
#endif
cbox_integrate->Hide();
}
void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
@ -508,7 +502,7 @@ void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY;
welcome_text->Show(data_empty);
cbox_reset->Show(!data_empty);
#ifdef __linux__
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
if (!DesktopIntegrationDialog::is_integrated())
cbox_integrate->Show(true);
else
@ -2451,7 +2445,35 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo
return true;
}
void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
static std::set<std::string> get_new_added_presets(const std::map<std::string, std::string>& old_data, const std::map<std::string, std::string>& new_data)
{
auto get_aliases = [](const std::map<std::string, std::string>& data) {
std::set<std::string> old_aliases;
for (auto item : data) {
const std::string& name = item.first;
size_t pos = name.find("@");
old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1));
}
return old_aliases;
};
std::set<std::string> old_aliases = get_aliases(old_data);
std::set<std::string> new_aliases = get_aliases(new_data);
std::set<std::string> diff;
std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin()));
return diff;
}
static std::string get_first_added_preset(const std::map<std::string, std::string>& old_data, const std::map<std::string, std::string>& new_data)
{
std::set<std::string> diff = get_new_added_presets(old_data, new_data);
if (diff.empty())
return std::string();
return *diff.begin();
}
bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
{
const auto enabled_vendors = appconfig_new.vendors();
@ -2506,14 +2528,14 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
break;
}
if (snapshot) {
SnapshotDB::singleton().take_snapshot(*app_config, snapshot_reason);
}
if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Continue with applying configuration changes?")))
return false;
if (install_bundles.size() > 0) {
// Install bundles from resources.
// Don't create snapshot - we've already done that above if applicable.
updater->install_bundles_rsrc(std::move(install_bundles), false);
if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
return false;
} else {
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
}
@ -2523,13 +2545,61 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
preset_bundle->reset(true);
}
std::string preferred_model;
std::string preferred_variant;
const auto enabled_vendors_old = app_config->vendors();
auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old](const std::string& bundle_name, const Bundle& bundle, std::string& variant) {
const auto config = enabled_vendors.find(bundle_name);
if (config == enabled_vendors.end())
return std::string();
for (const auto& model : bundle.vendor_profile->models) {
if (const auto model_it = config->second.find(model.id);
model_it != config->second.end() && model_it->second.size() > 0) {
variant = *model_it->second.begin();
const auto config_old = enabled_vendors_old.find(bundle_name);
if (config_old == enabled_vendors_old.end())
return model.id;
const auto model_it_old = config_old->second.find(model.id);
if (model_it_old == config_old->second.end())
return model.id;
else if (model_it_old->second != model_it->second) {
for (const auto& var : model_it->second)
if (model_it_old->second.find(var) == model_it_old->second.end()) {
variant = var;
return model.id;
}
}
}
}
if (!variant.empty())
variant.clear();
return std::string();
};
// Prusa printers are considered first, then 3rd party.
if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant);
preferred_model.empty()) {
for (const auto& bundle : bundles) {
if (bundle.second.is_prusa_bundle) { continue; }
if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant);
!preferred_model.empty())
break;
}
}
std::string first_added_filament, first_added_sla_material;
auto apply_section = [this, app_config](const std::string& section_name, std::string& first_added_preset) {
if (appconfig_new.has_section(section_name)) {
// get first of new added preset names
const std::map<std::string, std::string>& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map<std::string, std::string>();
first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name));
app_config->set_section(section_name, appconfig_new.get_section(section_name));
}
};
apply_section(AppConfig::SECTION_FILAMENTS, first_added_filament);
apply_section(AppConfig::SECTION_MATERIALS, first_added_sla_material);
app_config->set_vendors(appconfig_new);
if (appconfig_new.has_section(AppConfig::SECTION_FILAMENTS)) {
app_config->set_section(AppConfig::SECTION_FILAMENTS, appconfig_new.get_section(AppConfig::SECTION_FILAMENTS));
}
if (appconfig_new.has_section(AppConfig::SECTION_MATERIALS)) {
app_config->set_section(AppConfig::SECTION_MATERIALS, appconfig_new.get_section(AppConfig::SECTION_MATERIALS));
}
app_config->set("version_check", page_update->version_check ? "1" : "0");
app_config->set("preset_update", page_update->preset_update ? "1" : "0");
app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
@ -2554,43 +2624,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
page_mode->serialize_mode(app_config);
std::string preferred_model;
// Figure out the default pre-selected printer based on the selections in the pickers.
// The default is the first selected printer model (one with at least 1 variant selected).
// The default is only applied by load_presets() if the user doesn't have a (visible) printer
// selected already.
// Prusa printers are considered first, then 3rd party.
const auto config_prusa = enabled_vendors.find("PrusaResearch");
if (config_prusa != enabled_vendors.end()) {
for (const auto &model : bundles.prusa_bundle().vendor_profile->models) {
const auto model_it = config_prusa->second.find(model.id);
if (model_it != config_prusa->second.end() && model_it->second.size() > 0) {
preferred_model = model.id;
break;
}
}
}
if (preferred_model.empty()) {
for (const auto &bundle : bundles) {
if (bundle.second.is_prusa_bundle) { continue; }
const auto config = enabled_vendors.find(bundle.first);
if (config == enabled_vendors.end()) { continue; }
for (const auto &model : bundle.second.vendor_profile->models) {
const auto model_it = config->second.find(model.id);
if (model_it != config->second.end() && model_it->second.size() > 0) {
preferred_model = model.id;
break;
}
}
}
}
// Reloading the configs after some modifications were done to PrusaSlicer.ini.
// Just perform the substitutions silently, as the substitutions were already presented to the user on application start-up
// and the Wizard shall not create any new values that would require substitution.
PresetsConfigSubstitutions substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent, preferred_model);
preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem,
{preferred_model, preferred_variant, first_added_filament, first_added_sla_material});
if (page_custom->custom_wanted()) {
page_firmware->apply_custom_config(*custom_config);
@ -2604,6 +2639,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
// Update the selections from the compatibilty.
preset_bundle->export_selections(*app_config);
return true;
}
void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add)
{
@ -2814,7 +2851,8 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page)
p->set_start_page(start_page);
if (ShowModal() == wxID_OK) {
p->apply_config(app.app_config, app.preset_bundle, app.preset_updater);
if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater))
return false;
app.app_config->set_legacy_datadir(false);
app.update_mode();
app.obj_manipul()->update_ui_from_settings();

View file

@ -611,7 +611,7 @@ struct ConfigWizard::priv
bool on_bnt_finish();
bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string());
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
// #ys_FIXME_alise
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
#ifdef __linux__

View file

@ -1,15 +1,19 @@
#ifdef __linux__
#include "DesktopIntegrationDialog.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "format.hpp"
#include "I18N.hpp"
#include "NotificationManager.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Platform.hpp"
#include "libslic3r/Config.hpp"
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <boost/dll/runtime_symbol_info.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <wx/filename.h>
#include <wx/stattext.h>
@ -17,9 +21,9 @@
namespace Slic3r {
namespace GUI {
namespace integrate_desktop_internal{
namespace {
// Disects path strings stored in system variable divided by ':' and adds into vector
static void resolve_path_from_var(const std::string& var, std::vector<std::string>& paths)
void resolve_path_from_var(const std::string& var, std::vector<std::string>& paths)
{
wxString wxdirs;
if (! wxGetEnv(boost::nowide::widen(var), &wxdirs) || wxdirs.empty() )
@ -34,7 +38,7 @@ static void resolve_path_from_var(const std::string& var, std::vector<std::strin
paths.push_back(dirs);
}
// Return true if directory in path p+dir_name exists
static bool contains_path_dir(const std::string& p, const std::string& dir_name)
bool contains_path_dir(const std::string& p, const std::string& dir_name)
{
if (p.empty() || dir_name.empty())
return false;
@ -47,7 +51,7 @@ static bool contains_path_dir(const std::string& p, const std::string& dir_name)
return false;
}
// Creates directory in path if not exists yet
static void create_dir(const boost::filesystem::path& path)
void create_dir(const boost::filesystem::path& path)
{
if (boost::filesystem::exists(path))
return;
@ -58,7 +62,7 @@ static void create_dir(const boost::filesystem::path& path)
BOOST_LOG_TRIVIAL(error)<< "create directory failed: " << ec.message();
}
// Starts at basic_path (excluded) and creates all directories in dir_path
static void create_path(const std::string& basic_path, const std::string& dir_path)
void create_path(const std::string& basic_path, const std::string& dir_path)
{
if (basic_path.empty() || dir_path.empty())
return;
@ -76,7 +80,7 @@ static void create_path(const std::string& basic_path, const std::string& dir_pa
create_dir(path);
}
// Calls our internal copy_file function to copy file at icon_path to dest_path
static bool copy_icon(const std::string& icon_path, const std::string& dest_path)
bool copy_icon(const std::string& icon_path, const std::string& dest_path)
{
BOOST_LOG_TRIVIAL(debug) <<"icon from "<< icon_path;
BOOST_LOG_TRIVIAL(debug) <<"icon to "<< dest_path;
@ -90,8 +94,8 @@ static bool copy_icon(const std::string& icon_path, const std::string& dest_path
return true;
}
// Creates new file filled with data.
static bool create_desktop_file(const std::string& path, const std::string& data)
{
bool create_desktop_file(const std::string& path, const std::string& data)
{
BOOST_LOG_TRIVIAL(debug) <<".desktop to "<< path;
std::ofstream output(path);
output << data;
@ -109,10 +113,6 @@ static bool create_desktop_file(const std::string& path, const std::string& data
// methods that actually do / undo desktop integration. Static to be accesible from anywhere.
bool DesktopIntegrationDialog::is_integrated()
{
const char *appimage_env = std::getenv("APPIMAGE");
if (!appimage_env)
return false;
const AppConfig *app_config = wxGetApp().app_config;
std::string path(app_config->get("desktop_integration_app_path"));
BOOST_LOG_TRIVIAL(debug) << "Desktop integration desktop file path: " << path;
@ -126,10 +126,6 @@ bool DesktopIntegrationDialog::is_integrated()
}
bool DesktopIntegrationDialog::integration_possible()
{
const char *appimage_env = std::getenv("APPIMAGE");
if (!appimage_env)
return false;
return true;
}
void DesktopIntegrationDialog::perform_desktop_integration()
@ -138,19 +134,31 @@ void DesktopIntegrationDialog::perform_desktop_integration()
// Path to appimage
const char *appimage_env = std::getenv("APPIMAGE");
std::string appimage_path;
std::string excutable_path;
if (appimage_env) {
try {
appimage_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
excutable_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
} catch (std::exception &) {
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - boost::filesystem::canonical did not return appimage path.";
show_error(nullptr, _L("Performing desktop integration failed - boost::filesystem::canonical did not return appimage path."));
return;
}
} else {
// not appimage - not performing
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - not Appimage executable.";
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail);
return;
// not appimage - find executable
excutable_path = boost::dll::program_location().string();
//excutable_path = wxStandardPaths::Get().GetExecutablePath().string();
BOOST_LOG_TRIVIAL(debug) << "non-appimage path to executable: " << excutable_path;
if (excutable_path.empty())
{
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - no executable found.";
show_error(nullptr, _L("Performing desktop integration failed - Could not find executable."));
return;
}
}
// Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path'
boost::replace_all(excutable_path, "'", "'\\''");
// Find directories icons and applications
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
@ -158,8 +166,8 @@ void DesktopIntegrationDialog::perform_desktop_integration()
// The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
std::vector<std::string>target_candidates;
integrate_desktop_internal::resolve_path_from_var("XDG_DATA_HOME", target_candidates);
integrate_desktop_internal::resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
resolve_path_from_var("XDG_DATA_HOME", target_candidates);
resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
AppConfig *app_config = wxGetApp().app_config;
// suffix string to create different desktop file for alpha, beta.
@ -186,7 +194,6 @@ void DesktopIntegrationDialog::perform_desktop_integration()
icon_theme_dirs = "/hicolor/96x96/apps";
}
std::string target_dir_icons;
std::string target_dir_desktop;
@ -194,24 +201,24 @@ void DesktopIntegrationDialog::perform_desktop_integration()
// iterate thru target_candidates to find icons folder
for (size_t i = 0; i < target_candidates.size(); ++i) {
// Copy icon PrusaSlicer.png from resources_dir()/icons to target_dir_icons/icons/
if (integrate_desktop_internal::contains_path_dir(target_candidates[i], "icons")) {
if (contains_path_dir(target_candidates[i], "icons")) {
target_dir_icons = target_candidates[i];
std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir());
std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
if (integrate_desktop_internal::copy_icon(icon_path, dest_path))
if (copy_icon(icon_path, dest_path))
break; // success
else
target_dir_icons.clear(); // copying failed
// if all failed - try creating default home folder
if (i == target_candidates.size() - 1) {
// create $HOME/.local/share
integrate_desktop_internal::create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs);
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs);
// copy icon
target_dir_icons = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir());
std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
if (!integrate_desktop_internal::contains_path_dir(target_dir_icons, "icons")
|| !integrate_desktop_internal::copy_icon(icon_path, dest_path)) {
if (!contains_path_dir(target_dir_icons, "icons")
|| !copy_icon(icon_path, dest_path)) {
// every attempt failed - icon wont be present
target_dir_icons.clear();
}
@ -228,7 +235,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
// iterate thru target_candidates to find applications folder
for (size_t i = 0; i < target_candidates.size(); ++i)
{
if (integrate_desktop_internal::contains_path_dir(target_candidates[i], "applications")) {
if (contains_path_dir(target_candidates[i], "applications")) {
target_dir_desktop = target_candidates[i];
// Write slicer desktop file
std::string desktop_file = GUI::format(
@ -236,33 +243,33 @@ void DesktopIntegrationDialog::perform_desktop_integration()
"Name=PrusaSlicer%1%\n"
"GenericName=3D Printing Software\n"
"Icon=PrusaSlicer%2%\n"
"Exec=%3% %%F\n"
"Exec=\'%3%\' %%F\n"
"Terminal=false\n"
"Type=Application\n"
"MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n"
"Categories=Graphics;3DGraphics;Engineering;\n"
"Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n"
"StartupNotify=false\n"
"StartupWMClass=prusa-slicer", name_suffix, version_suffix, appimage_path);
"StartupWMClass=prusa-slicer", name_suffix, version_suffix, excutable_path);
std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix);
if (integrate_desktop_internal::create_desktop_file(path, desktop_file)){
if (create_desktop_file(path, desktop_file)){
BOOST_LOG_TRIVIAL(debug) << "PrusaSlicer.desktop file installation success.";
break;
} else {
// write failed - try another path
BOOST_LOG_TRIVIAL(error) << "PrusaSlicer.desktop file installation failed.";
BOOST_LOG_TRIVIAL(debug) << "Attempt to PrusaSlicer.desktop file installation failed. failed path: " << target_candidates[i];
target_dir_desktop.clear();
}
// if all failed - try creating default home folder
if (i == target_candidates.size() - 1) {
// create $HOME/.local/share
integrate_desktop_internal::create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
// create desktop file
target_dir_desktop = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix);
if (integrate_desktop_internal::contains_path_dir(target_dir_desktop, "applications")) {
if (!integrate_desktop_internal::create_desktop_file(path, desktop_file)) {
if (contains_path_dir(target_dir_desktop, "applications")) {
if (!create_desktop_file(path, desktop_file)) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create desktop file";
return;
@ -278,7 +285,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
if(target_dir_desktop.empty()) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not find applications directory";
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail);
show_error(nullptr, _L("Performing desktop integration failed - could not find applications directory."));
return;
}
// save path to desktop file
@ -290,7 +297,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
{
std::string icon_path = GUI::format("%1%/icons/PrusaSlicer-gcodeviewer_192px.png",resources_dir());
std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer-gcodeviewer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
if (integrate_desktop_internal::copy_icon(icon_path, dest_path))
if (copy_icon(icon_path, dest_path))
// save path to icon
app_config->set("desktop_integration_icon_viewer_path", dest_path);
else
@ -303,32 +310,26 @@ void DesktopIntegrationDialog::perform_desktop_integration()
"Name=Prusa Gcode Viewer%1%\n"
"GenericName=3D Printing Software\n"
"Icon=PrusaSlicer-gcodeviewer%2%\n"
"Exec=%3% --gcodeviwer %%F\n"
"Exec=\'%3%\' --gcodeviwer %%F\n"
"Terminal=false\n"
"Type=Application\n"
"MimeType=text/x.gcode;\n"
"Categories=Graphics;3DGraphics;\n"
"Keywords=3D;Printing;Slicer;\n"
"StartupNotify=false", name_suffix, version_suffix, appimage_path);
"StartupNotify=false", name_suffix, version_suffix, excutable_path);
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerGcodeViewer%2%.desktop", target_dir_desktop, version_suffix);
if (integrate_desktop_internal::create_desktop_file(desktop_path, desktop_file))
if (create_desktop_file(desktop_path, desktop_file))
// save path to desktop file
app_config->set("desktop_integration_app_viewer_path", desktop_path);
else {
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could create gcode viewer desktop file";
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail);
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create Gcodeviewer desktop file";
show_error(nullptr, _L("Performing desktop integration failed - could not create Gcodeviewer desktop file. PrusaSlicer desktop file was probably created successfully."));
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
}
void DesktopIntegrationDialog::undo_desktop_intgration()
{
const char *appimage_env = std::getenv("APPIMAGE");
if (!appimage_env) {
BOOST_LOG_TRIVIAL(error) << "Undo desktop integration failed - not Appimage executable.";
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationFail);
return;
}
const AppConfig *app_config = wxGetApp().app_config;
// slicer .desktop
std::string path = std::string(app_config->get("desktop_integration_app_path"));

View file

@ -11,6 +11,7 @@
#include "GUI_Utils.hpp"
#include "MsgDialog.hpp"
#include "Tab.hpp"
#include "GUI_ObjectList.hpp"
#include <wx/button.h>
#include <wx/dialog.h>
@ -383,7 +384,11 @@ void Control::SetTicksValues(const Info& custom_gcode_per_print_z)
// Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one
post_ticks_changed_event();
if (custom_gcode_per_print_z.mode)
// init extruder sequence in respect to the extruders count
if (m_ticks.empty())
m_extruders_sequence.init(m_extruder_colors.size());
if (custom_gcode_per_print_z.mode && !custom_gcode_per_print_z.gcodes.empty())
m_ticks.mode = custom_gcode_per_print_z.mode;
Refresh();
@ -438,7 +443,7 @@ void Control::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, c
m_mode = !is_one_extruder_printed_model ? MultiExtruder :
only_extruder < 0 ? SingleExtruder :
MultiAsSingle;
if (!m_ticks.mode)
if (!m_ticks.mode || (m_ticks.empty() && m_ticks.mode != m_mode))
m_ticks.mode = m_mode;
m_only_extruder = only_extruder;
@ -545,7 +550,8 @@ bool Control::is_wipe_tower_layer(int tick) const
return false;
if (tick == 0 || (tick == (int)m_values.size() - 1 && m_values[tick] > m_values[tick - 1]))
return false;
if (m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1])
if ((m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) ||
(tick > 0 && m_values[tick] < m_values[tick - 1]) ) // if there is just one wiping on the layer
return true;
return false;
@ -1077,7 +1083,9 @@ void Control::draw_ruler(wxDC& dc)
{
if (m_values.empty())
return;
m_ruler.update(this->GetParent(), m_values, get_scroll_step());
// When "No sparce layer" is enabled, use m_layers_values for ruler update.
// Because of m_values has duplicate values in this case.
m_ruler.update(this->GetParent(), m_layers_values.empty() ? m_values : m_layers_values, get_scroll_step());
int height, width;
get_size(&width, &height);
@ -1588,7 +1596,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current
append_submenu(menu, change_extruder_menu, wxID_ANY, change_extruder_menu_name, _L("Use another extruder"),
active_extruders[1] > 0 ? "edit_uni" : "change_extruder",
[this]() {return m_mode == MultiAsSingle; }, GUI::wxGetApp().plater());
[this]() {return m_mode == MultiAsSingle && !GUI::wxGetApp().obj_list()->has_paint_on_segmentation(); }, GUI::wxGetApp().plater());
}
}
@ -2020,7 +2028,7 @@ void Control::show_cog_icon_context_menu()
append_menu_item(&menu, wxID_ANY, _L("Set extruder sequence for the entire print"), "",
[this](wxCommandEvent&) { edit_extruder_sequence(); }, "", &menu);
if (m_mode != MultiExtruder && m_draw_mode == dmRegular)
if (GUI::wxGetApp().is_editor() && m_mode != MultiExtruder && m_draw_mode == dmRegular)
append_menu_item(&menu, wxID_ANY, _L("Set auto color changes"), "",
[this](wxCommandEvent&) { auto_color_change(); }, "", &menu);

View file

@ -180,6 +180,13 @@ struct ExtrudersSequence
return;// last item can't be deleted
extruders.erase(extruders.begin() + pos);
}
void init(size_t extruders_count)
{
extruders.clear();
for (size_t extruder = 0; extruder < extruders_count; extruder++)
extruders.push_back(extruder);
}
};
class Control : public wxControl

View file

@ -2,6 +2,7 @@
#include "wxExtensions.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include "BitmapComboBox.hpp"
#include <wx/dc.h>
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
@ -296,7 +297,11 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
DataViewBitmapText data;
data << value;
#ifdef _WIN32
Slic3r::GUI::BitmapComboBox* c_editor = new Slic3r::GUI::BitmapComboBox(parent, wxID_ANY, wxEmptyString,
#else
auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString,
#endif
labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1),
0, nullptr , wxCB_READONLY);
@ -310,9 +315,11 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
// to avoid event propagation to other sidebar items
c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
evt.StopPropagation();
#ifdef __linux__
// FinishEditing grabs new selection and triggers config update. We better call
// it explicitly, automatic update on KILL_FOCUS didn't work on Linux.
this->FinishEditing();
#endif
});
return c_editor;

View file

@ -73,8 +73,6 @@ Field::~Field()
{
if (m_on_kill_focus)
m_on_kill_focus = nullptr;
if (m_on_set_focus)
m_on_set_focus = nullptr;
if (m_on_change)
m_on_change = nullptr;
if (m_back_to_initial_value)
@ -156,15 +154,6 @@ void Field::on_kill_focus()
m_on_kill_focus(m_opt_id);
}
void Field::on_set_focus(wxEvent& event)
{
// to allow the default behavior
event.Skip();
// call the registered function if it is available
if (m_on_set_focus!=nullptr)
m_on_set_focus(m_opt_id);
}
void Field::on_change_field()
{
// std::cerr << "calling Field::_on_change \n";
@ -500,20 +489,18 @@ void TextCtrl::BUILD() {
temp->SetToolTip(get_tooltip_text(text_value));
if (style == wxTE_PROCESS_ENTER) {
if (style & wxTE_PROCESS_ENTER) {
temp->Bind(wxEVT_TEXT_ENTER, ([this, temp](wxEvent& e)
{
#if !defined(__WXGTK__)
e.Skip();
temp->GetToolTip()->Enable(true);
#endif // __WXGTK__
bEnterPressed = true;
EnterPressed enter(this);
propagate_value();
}), temp->GetId());
}
temp->Bind(wxEVT_SET_FOCUS, ([this](wxEvent& e) { on_set_focus(e); }), temp->GetId());
temp->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent& event)
{
//! to allow the default handling
@ -530,26 +517,11 @@ void TextCtrl::BUILD() {
temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e)
{
e.Skip();
#ifdef __WXOSX__
// OSX issue: For some unknown reason wxEVT_KILL_FOCUS is emitted twice in a row in some cases
// (like when information dialog is shown during an update of the option value)
// Thus, suppress its second call
if (bKilledFocus)
return;
bKilledFocus = true;
#endif // __WXOSX__
#if !defined(__WXGTK__)
temp->GetToolTip()->Enable(true);
#endif // __WXGTK__
if (bEnterPressed)
bEnterPressed = false;
else
if (!bEnterPressed)
propagate_value();
#ifdef __WXOSX__
// After processing of KILL_FOCUS event we should to invalidate a bKilledFocus flag
bKilledFocus = false;
#endif // __WXOSX__
}), temp->GetId());
/*
// select all text using Ctrl+A
@ -851,7 +823,7 @@ void SpinCtrl::BUILD() {
bEnterPressed = true;
}), temp->GetId());
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e)
temp->Bind(wxEVT_TEXT, ([this, temp](wxCommandEvent e)
{
// # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
// # when it was changed from the text control, so the on_change callback
@ -861,20 +833,28 @@ void SpinCtrl::BUILD() {
long value;
const bool parsed = e.GetString().ToLong(&value);
tmp_value = parsed && value >= INT_MIN && value <= INT_MAX ? (int)value : UNDEF_VALUE;
if (!parsed || value < INT_MIN || value > INT_MAX)
tmp_value = UNDEF_VALUE;
else {
tmp_value = std::min(std::max((int)value, m_opt.min), m_opt.max);
#ifdef __WXOSX__
// Forcibly set the input value for SpinControl, since the value
// inserted from the keyboard or clipboard is not updated under OSX
if (tmp_value != UNDEF_VALUE) {
wxSpinCtrl* spin = static_cast<wxSpinCtrl*>(window);
spin->SetValue(tmp_value);
// Forcibly set the input value for SpinControl, since the value
// inserted from the keyboard or clipboard is not updated under OSX
temp->SetValue(tmp_value);
// But in SetValue() is executed m_text_ctrl->SelectAll(), so
// discard this selection and set insertion point to the end of string
spin->GetText()->SetInsertionPointEnd();
}
temp->GetText()->SetInsertionPointEnd();
#else
// update value for the control only if it was changed in respect to the Min/max values
if (tmp_value != (int)value) {
temp->SetValue(tmp_value);
// But after SetValue() cursor ison the first position
// so put it to the end of string
int pos = std::to_string(tmp_value).length();
temp->SetSelection(pos, pos);
}
#endif
}
}), temp->GetId());
temp->SetToolTip(get_tooltip_text(text_value));
@ -935,7 +915,7 @@ void Choice::BUILD() {
choice_ctrl* temp;
if (m_opt.gui_type != ConfigOptionDef::GUIType::undefined && m_opt.gui_type != ConfigOptionDef::GUIType::select_open) {
m_is_editable = true;
temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr, wxTE_PROCESS_ENTER);
}
else {
#ifdef __WXOSX__
@ -988,46 +968,57 @@ void Choice::BUILD() {
temp->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { on_change_field(); }, temp->GetId());
if (m_is_editable) {
temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) {
temp->Bind(wxEVT_KILL_FOCUS, [this](wxEvent& e) {
e.Skip();
if (m_opt.type == coStrings) {
on_change_field();
return;
}
if (!bEnterPressed)
propagate_value();
} );
if (is_defined_input_value<choice_ctrl>(window, m_opt.type)) {
switch (m_opt.type) {
case coFloatOrPercent:
{
std::string old_val = !m_value.empty() ? boost::any_cast<std::string>(m_value) : "";
if (old_val == boost::any_cast<std::string>(get_value()))
return;
break;
}
case coInt:
{
int old_val = !m_value.empty() ? boost::any_cast<int>(m_value) : 0;
if (old_val == boost::any_cast<int>(get_value()))
return;
break;
}
default:
{
double old_val = !m_value.empty() ? boost::any_cast<double>(m_value) : -99999;
if (fabs(old_val - boost::any_cast<double>(get_value())) <= 0.0001)
return;
}
}
on_change_field();
}
else
on_kill_focus();
}), temp->GetId());
temp->Bind(wxEVT_TEXT_ENTER, [this, temp](wxEvent& e) {
EnterPressed enter(this);
propagate_value();
} );
}
temp->SetToolTip(get_tooltip_text(temp->GetValue()));
}
void Choice::propagate_value()
{
if (m_opt.type == coStrings) {
on_change_field();
return;
}
if (is_defined_input_value<choice_ctrl>(window, m_opt.type)) {
switch (m_opt.type) {
case coFloatOrPercent:
{
std::string old_val = !m_value.empty() ? boost::any_cast<std::string>(m_value) : "";
if (old_val == boost::any_cast<std::string>(get_value()))
return;
break;
}
case coInt:
{
int old_val = !m_value.empty() ? boost::any_cast<int>(m_value) : 0;
if (old_val == boost::any_cast<int>(get_value()))
return;
break;
}
default:
{
double old_val = !m_value.empty() ? boost::any_cast<double>(m_value) : -99999;
if (fabs(old_val - boost::any_cast<double>(get_value())) <= 0.0001)
return;
}
}
on_change_field();
}
else
on_kill_focus();
}
void Choice::suppress_scroll()
{
m_suppress_scroll = true;
@ -1137,6 +1128,15 @@ void Choice::set_value(const boost::any& value, bool change_event)
}
else
field->SetSelection(idx);
if (!m_value.empty() && m_opt.opt_key == "fill_density") {
// If m_value was changed before, then update m_value here too to avoid case
// when control's value is already changed from the ConfigManipulation::update_print_fff_config(),
// but m_value doesn't respect it.
if (double val; text_value.ToDouble(&val))
m_value = val;
}
break;
}
case coEnum: {

View file

@ -52,10 +52,17 @@ protected:
//! in another case we can't unfocused control at all
void on_kill_focus();
/// Call the attached on_change method.
void on_set_focus(wxEvent& event);
/// Call the attached on_change method.
void on_change_field();
class EnterPressed {
public:
EnterPressed(Field* field) :
m_parent(field){ m_parent->set_enter_pressed(true); }
~EnterPressed() { m_parent->set_enter_pressed(false); }
private:
Field* m_parent;
};
public:
/// Call the attached m_back_to_initial_value method.
void on_back_to_initial_value();
@ -69,9 +76,6 @@ public:
/// Function object to store callback passed in from owning object.
t_kill_focus m_on_kill_focus {nullptr};
/// Function object to store callback passed in from owning object.
t_kill_focus m_on_set_focus {nullptr};
/// Function object to store callback passed in from owning object.
t_change m_on_change {nullptr};
@ -236,10 +240,6 @@ class TextCtrl : public Field {
void change_field_value(wxEvent& event);
#endif //__WXGTK__
#ifdef __WXOSX__
bool bKilledFocus = false;
#endif // __WXOSX__
public:
TextCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
@ -342,6 +342,7 @@ public:
class Choice : public Field {
using Field::Field;
public:
Choice(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
Choice(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
@ -349,6 +350,8 @@ public:
wxWindow* window{ nullptr };
void BUILD() override;
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
void propagate_value();
/* Under OSX: wxBitmapComboBox->GetWindowStyle() returns some weard value,
* so let use a flag, which has TRUE value for a control without wxCB_READONLY style

View file

@ -65,6 +65,8 @@ enum {
USB_PID_MMU_APP = 4,
USB_PID_CW1_BOOT = 7,
USB_PID_CW1_APP = 8,
USB_PID_CW1S_BOOT = 14,
USB_PID_CW1S_APP = 15,
};
// This enum discriminates the kind of information in EVT_AVRDUDE,
@ -308,7 +310,7 @@ void FirmwareDialog::priv::update_flash_enabled()
void FirmwareDialog::priv::load_hex_file(const wxString &path)
{
hex_file = HexFile(path.wx_str());
const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1;
const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1 || hex_file.device == HexFile::DEV_CW1S;
set_autodetect(autodetect);
}
@ -636,6 +638,10 @@ void FirmwareDialog::priv::perform_upload()
this->prepare_avr109(Avr109Pid(USB_PID_CW1_BOOT, USB_PID_CW1_APP));
break;
case HexFile::DEV_CW1S:
this->prepare_avr109(Avr109Pid(USB_PID_CW1S_BOOT, USB_PID_CW1S_APP));
break;
default:
this->prepare_mk2();
break;
@ -767,11 +773,10 @@ const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) {
switch (usb_pid.boot) {
case USB_PID_MMU_BOOT:
return "Original Prusa MMU 2.0 Control";
break;
case USB_PID_CW1_BOOT:
return "Original Prusa CW1";
break;
case USB_PID_CW1S_BOOT:
return "Original Prusa CW1S";
default: throw Slic3r::RuntimeError((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str());
}
}

View file

@ -2239,6 +2239,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera());
m_dirty |= mouse3d_controller_applied;
m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current();
if (gizmo != nullptr) m_dirty |= gizmo->update_items_state();
if (!m_dirty)
return;
@ -2780,11 +2782,10 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt)
void GLCanvas3D::on_render_timer(wxTimerEvent& evt)
{
// no need to do anything here
// right after this event is recieved, idle event is fired
//m_dirty = true;
//wxWakeUpIdle();
// no need to wake up idle
// right after this event, idle event is fired
// m_dirty = true;
// wxWakeUpIdle();
}
@ -2802,21 +2803,15 @@ void GLCanvas3D::schedule_extra_frame(int miliseconds)
return;
}
}
// Start timer
int64_t now = timestamp_now();
int remaining_time = m_render_timer.GetInterval();
// Timer is not running
if (! m_render_timer.IsRunning()) {
m_extra_frame_requested_delayed = miliseconds;
if (!m_render_timer.IsRunning()) {
m_render_timer.StartOnce(miliseconds);
m_render_timer_start = now;
// Timer is running - restart only if new period is shorter than remaning period
} else {
const int64_t remaining_time = (m_render_timer_start + m_extra_frame_requested_delayed) - now;
if (miliseconds + 20 < remaining_time) {
m_render_timer.Stop();
m_extra_frame_requested_delayed = miliseconds;
m_render_timer.StartOnce(miliseconds);
m_render_timer_start = now;
}
}
}
@ -3455,12 +3450,17 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
// stores current min_z of instances
std::map<std::pair<int, int>, double> min_zs;
if (!snapshot_type.empty()) {
for (int i = 0; i < static_cast<int>(m_model->objects.size()); ++i) {
const ModelObject* obj = m_model->objects[i];
for (int j = 0; j < static_cast<int>(obj->instances.size()); ++j) {
for (int i = 0; i < static_cast<int>(m_model->objects.size()); ++i) {
const ModelObject* obj = m_model->objects[i];
for (int j = 0; j < static_cast<int>(obj->instances.size()); ++j) {
if (snapshot_type.empty() && m_selection.get_object_idx() == i) {
// This means we are flattening this object. In that case pretend
// that it is not sinking (even if it is), so it is placed on bed
// later on (whatever is sinking will be left sinking).
min_zs[{ i, j }] = SINKING_Z_THRESHOLD;
} else
min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z();
}
}
}
@ -3502,7 +3502,7 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
ModelObject* m = m_model->objects[i.first];
const double shift_z = m->get_instance_min_z(i.second);
// leave sinking instances as sinking
if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) {
if (min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) {
const Vec3d shift(0.0, 0.0, -shift_z);
m_selection.translate(i.first, i.second, shift);
m->translate_instance(i.second, shift);
@ -4433,13 +4433,7 @@ bool GLCanvas3D::_init_main_toolbar()
}
// init arrow
BackgroundTexture::Metadata arrow_data;
arrow_data.filename = "toolbar_arrow.png";
// arrow_data.filename = "toolbar_arrow.svg";
//arrow_data.left = 16;
//arrow_data.top = 16;
//arrow_data.right = 16;
//arrow_data.bottom = 16;
arrow_data.filename = "toolbar_arrow.svg";
arrow_data.left = 0;
arrow_data.top = 0;
arrow_data.right = 0;
@ -5107,6 +5101,7 @@ void GLCanvas3D::_render_objects()
m_volumes.set_z_range(-FLT_MAX, FLT_MAX);
m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data());
m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances());
GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
if (shader != nullptr) {
@ -6374,7 +6369,7 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
case EWarning::ObjectOutside: text = _u8L("An object outside the print area was detected."); break;
case EWarning::ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = ErrorType::SLICING_ERROR; break;
case EWarning::SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = ErrorType::PLATER_ERROR; break;
case EWarning::SomethingNotShown: text = _u8L("Some objects are not visible."); break;
case EWarning::SomethingNotShown: text = _u8L("Some objects are not visible during editing."); break;
case EWarning::ObjectClashed:
text = _u8L("An object outside the print area was detected.\n"
"Resolve the current problem to continue slicing.");

View file

@ -463,15 +463,13 @@ private:
std::string m_sidebar_field;
// when true renders an extra frame by not resetting m_dirty to false
// see request_extra_frame()
bool m_extra_frame_requested;
int m_extra_frame_requested_delayed { std::numeric_limits<int>::max() };
bool m_extra_frame_requested;
bool m_event_handlers_bound{ false };
GLVolumeCollection m_volumes;
GCodeViewer m_gcode_viewer;
RenderTimer m_render_timer;
int64_t m_render_timer_start;
Selection m_selection;
const DynamicPrintConfig* m_config;

View file

@ -169,6 +169,8 @@ void GLModel::reset()
void GLModel::render() const
{
GLShaderProgram* shader = wxGetApp().get_current_shader();
for (const RenderData& data : m_render_data) {
if (data.vbo_id == 0 || data.ibo_id == 0)
continue;
@ -190,7 +192,6 @@ void GLModel::render() const
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
GLShaderProgram* shader = wxGetApp().get_current_shader();
if (shader != nullptr)
shader->set_uniform("uniform_color", data.color);
else

View file

@ -69,6 +69,8 @@ std::pair<bool, std::string> GLShadersManager::init()
);
// used to render variable layers heights in 3d editor
valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" });
// used to render highlight contour around selected triangles inside the multi-material gizmo
valid &= append_shader("mm_contour", { "mm_contour.vs", "mm_contour.fs" });
return { valid, error };
}

View file

@ -193,10 +193,9 @@ bool GLToolbar::init_arrow(const BackgroundTexture::Metadata& arrow_texture)
std::string path = resources_dir() + "/icons/";
bool res = false;
if (!arrow_texture.filename.empty())
res = m_arrow_texture.texture.load_from_file(path + arrow_texture.filename, false, GLTexture::SingleThreaded, false);
// res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, true, false, 100);
if (!arrow_texture.filename.empty()) {
res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, false, false, 1000);
}
if (res)
m_arrow_texture.metadata = arrow_texture;
@ -1176,26 +1175,29 @@ void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighte
float right = left + scaled_icons_size;
unsigned int tex_id = m_arrow_texture.texture.get_id();
// width and height of icon arrow is pointing to
float tex_width = (float)m_icons_texture.get_width();
float tex_height = (float)m_icons_texture.get_height();
// arrow width and height
float arr_tex_width = (float)m_arrow_texture.texture.get_width();
float arr_tex_height = (float)m_arrow_texture.texture.get_height();
if ((tex_id != 0) && (arr_tex_width > 0) && (arr_tex_height > 0)) {
float inv_tex_width = (arr_tex_width != 0.0f) ? 1.0f / arr_tex_width : 0.0f;
float inv_tex_height = (arr_tex_height != 0.0f) ? 1.0f / arr_tex_height : 0.0f;
if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) {
float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f;
float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f;
float internal_left = left + border - scaled_icons_size / 2; // add half scaled_icons_size for huge arrow
float internal_right = right - border + scaled_icons_size / 2;
float internal_left = left + border - scaled_icons_size * 1.5f; // add scaled_icons_size for huge arrow
float internal_right = right - border + scaled_icons_size * 1.5f;
float internal_top = top - border;
// bottom is not moving and should be calculated from arrow texture sides ratio
float arrow_sides_ratio = (float)m_arrow_texture.texture.get_height() / (float)m_arrow_texture.texture.get_width();
float internal_bottom = internal_top - (internal_right - internal_left) * arrow_sides_ratio;
float internal_bottom = internal_top - (internal_right - internal_left) * arrow_sides_ratio ;
float internal_left_uv = (float)m_arrow_texture.metadata.left * inv_tex_width;
float internal_right_uv = 1.0f - (float)m_arrow_texture.metadata.right * inv_tex_width;
float internal_top_uv = 1.0f - (float)m_arrow_texture.metadata.top * inv_tex_height;
float internal_bottom_uv = (float)m_arrow_texture.metadata.bottom * inv_tex_height;
GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } });
GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv } });
}
}

View file

@ -8,6 +8,7 @@
#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/any.hpp>
#if __APPLE__
@ -21,6 +22,7 @@
#include "AboutDialog.hpp"
#include "MsgDialog.hpp"
#include "format.hpp"
#include "libslic3r/Print.hpp"
@ -244,6 +246,127 @@ void warning_catcher(wxWindow* parent, const wxString& message)
msg.ShowModal();
}
static wxString bold(const wxString& str)
{
return wxString::Format("<b>%s</b>", str);
};
static wxString bold_string(const wxString& str)
{
return wxString::Format("<b>\"%s\"</b>", str);
};
static void add_config_substitutions(const ConfigSubstitutions& conf_substitutions, wxString& changes)
{
changes += "<table>";
for (const ConfigSubstitution& conf_substitution : conf_substitutions) {
wxString new_val;
const ConfigOptionDef* def = conf_substitution.opt_def;
if (!def)
continue;
switch (def->type) {
case coEnum:
{
const std::vector<std::string>& labels = def->enum_labels;
const std::vector<std::string>& values = def->enum_values;
int val = conf_substitution.new_value->getInt();
bool is_infill = def->opt_key == "top_fill_pattern" ||
def->opt_key == "bottom_fill_pattern" ||
def->opt_key == "fill_pattern";
// Each infill doesn't use all list of infill declared in PrintConfig.hpp.
// So we should "convert" val to the correct one
if (is_infill) {
for (const auto& key_val : *def->enum_keys_map)
if ((int)key_val.second == val) {
auto it = std::find(values.begin(), values.end(), key_val.first);
if (it == values.end())
break;
auto idx = it - values.begin();
new_val = wxString("\"") + values[idx] + "\"" + " (" + from_u8(_utf8(labels[idx])) + ")";
break;
}
if (new_val.IsEmpty()) {
assert(false);
new_val = _L("Undefined");
}
}
else
new_val = wxString("\"") + values[val] + "\"" + " (" + from_u8(_utf8(labels[val])) + ")";
break;
}
case coBool:
new_val = conf_substitution.new_value->getBool() ? "true" : "false";
break;
case coBools:
if (conf_substitution.new_value->nullable())
for (const char v : static_cast<const ConfigOptionBoolsNullable*>(conf_substitution.new_value.get())->values)
new_val += std::string(v == ConfigOptionBoolsNullable::nil_value() ? "nil" : v ? "true" : "false") + ", ";
else
for (const char v : static_cast<const ConfigOptionBools*>(conf_substitution.new_value.get())->values)
new_val += std::string(v ? "true" : "false") + ", ";
if (! new_val.empty())
new_val.erase(new_val.begin() + new_val.size() - 2, new_val.end());
break;
default:
assert(false);
}
changes += format_wxstr("<tr><td><b>\"%1%\" (%2%)</b></td><td>: ", def->opt_key, _(def->label)) +
format_wxstr(_L("%1% was substituted with %2%"), bold_string(conf_substitution.old_value), bold(new_val)) +
"</td></tr>";
}
changes += "</table>";
}
static wxString substitution_message(const wxString& changes)
{
return
_L("Most likely the configuration was produced by a newer version of PrusaSlicer or by some PrusaSlicer fork.") + " " +
_L("The following values were substituted:") + "\n" + changes + "\n\n" +
_L("Review the substitutions and adjust them if needed.");
}
void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions)
{
wxString changes;
auto preset_type_name = [](Preset::Type type) {
switch (type) {
case Preset::TYPE_PRINT: return _L("Print settings");
case Preset::TYPE_SLA_PRINT: return _L("SLA print settings");
case Preset::TYPE_FILAMENT: return _L("Filament");
case Preset::TYPE_SLA_MATERIAL: return _L("SLA material");
case Preset::TYPE_PRINTER: return _L("Printer");
case Preset::TYPE_PHYSICAL_PRINTER: return _L("Physical Printer");
default: assert(false); return wxString();
}
};
for (const PresetConfigSubstitutions& substitution : presets_config_substitutions) {
changes += "\n\n" + format_wxstr("%1% : %2%", preset_type_name(substitution.preset_type), bold_string(substitution.preset_name));
if (!substitution.preset_file.empty())
changes += format_wxstr(" (%1%)", substitution.preset_file);
add_config_substitutions(substitution.substitutions, changes);
}
InfoDialog msg(nullptr, _L("Configuration bundle was loaded, however some configuration values were not recognized."), substitution_message(changes));
msg.ShowModal();
}
void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename)
{
wxString changes = "\n";
add_config_substitutions(config_substitutions, changes);
InfoDialog msg(nullptr,
format_wxstr(_L("Configuration file \"%1%\" was loaded, however some configuration values were not recognized."), from_u8(filename)),
substitution_message(changes));
msg.ShowModal();
}
void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items)
{
if (comboCtrl == nullptr)

View file

@ -7,6 +7,7 @@ namespace boost::filesystem { class path; }
#include <wx/string.h>
#include "libslic3r/Config.hpp"
#include "libslic3r/Preset.hpp"
class wxWindow;
class wxMenuBar;
@ -48,6 +49,8 @@ void show_info(wxWindow* parent, const wxString& message, const wxString& title
void show_info(wxWindow* parent, const char* message, const char* title = nullptr);
inline void show_info(wxWindow* parent, const std::string& message,const std::string& title = std::string()) { show_info(parent, message.c_str(), title.c_str()); }
void warning_catcher(wxWindow* parent, const wxString& message);
void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions);
void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename);
// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items.
// Items data must be separated by '|', and contain the item name to be shown followed by its initial value (0 for false, 1 for true).

View file

@ -87,6 +87,11 @@
#include <boost/nowide/fstream.hpp>
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
// Needed for forcing menu icons back under gtk2 and gtk3
#if defined(__WXGTK20__) || defined(__WXGTK3__)
#include <gtk/gtk.h>
#endif
namespace Slic3r {
namespace GUI {
@ -435,7 +440,9 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
/* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG",
/* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1",
/* FT_SL1 */ "Masked SLA files (*.sl1, *.sl1s)|*.sl1;*.SL1;*.sl1s;*.SL1S",
// Workaround for OSX file picker, for some reason it always saves with the 1st extension.
/* FT_SL1S */ "Masked SLA files (*.sl1s, *.sl1)|*.sl1s;*.SL1S;*.sl1;*.SL1",
};
std::string out = defaults[file_type];
@ -626,12 +633,8 @@ void GUI_App::post_init()
this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str()));
}
else {
if (! this->init_params->preset_substitutions.empty()) {
// TODO: Add list of changes from all_substitutions
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
if (! this->init_params->preset_substitutions.empty())
show_substitutions_info(this->init_params->preset_substitutions);
#if 0
// Load the cummulative config over the currently active profiles.
@ -664,7 +667,7 @@ void GUI_App::post_init()
// show "Did you know" notification
if (app_config->get("show_hints") == "1" && ! is_gcode_viewer())
plater_->get_notification_manager()->push_hint_notification();
plater_->get_notification_manager()->push_hint_notification(true);
// The extra CallAfter() is needed because of Mac, where this is the only way
// to popup a modal dialog on start without screwing combo boxes.
@ -801,6 +804,14 @@ bool GUI_App::OnInit()
bool GUI_App::on_init_inner()
{
// Forcing back menu icons under gtk2 and gtk3. Solution is based on:
// https://docs.gtk.org/gtk3/class.Settings.html
// see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb
// TODO: Find workaround for GTK4
#if defined(__WXGTK20__) || defined(__WXGTK3__)
g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL);
#endif
// Verify resources path
const wxString resources_dir = from_u8(Slic3r::resources_dir());
wxCHECK_MSG(wxDirExists(resources_dir), false,
@ -916,7 +927,10 @@ bool GUI_App::on_init_inner()
// Suppress the '- default -' presets.
preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
try {
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
// Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only.
// If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force
// installation of a compatible system preset, thus nullifying the system preset substitutions.
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
} catch (const std::exception &ex) {
show_error(nullptr, ex.what());
}
@ -1029,17 +1043,22 @@ bool GUI_App::dark_mode()
#endif
}
const wxColour GUI_App::get_label_default_clr_system()
{
return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57);
}
const wxColour GUI_App::get_label_default_clr_modified()
{
return dark_mode() ? wxColour(253, 111, 40) : wxColour(252, 77, 1);
}
void GUI_App::init_label_colours()
{
m_color_label_modified = get_label_default_clr_modified();
m_color_label_sys = get_label_default_clr_system();
bool is_dark_mode = dark_mode();
if (is_dark_mode) {
m_color_label_modified = wxColour(253, 111, 40);
m_color_label_sys = wxColour(115, 220, 103);
}
else {
m_color_label_modified = wxColour(252, 77, 1);
m_color_label_sys = wxColour(26, 132, 57);
}
#ifdef _WIN32
m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT);
@ -1800,10 +1819,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots"));
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot"));
local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for updates"), _L("Check for configuration updates"));
#ifdef __linux__
if (DesktopIntegrationDialog::integration_possible())
local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));
#endif
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
//if (DesktopIntegrationDialog::integration_possible())
local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));
#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
local_menu->AppendSeparator();
}
local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots +
@ -1861,9 +1880,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
child->SetFont(normal_font());
if (dlg.ShowModal() == wxID_OK)
app_config->set("on_snapshot",
Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot(
*app_config, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id);
if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error(
*app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data());
snapshot != nullptr)
app_config->set("on_snapshot", snapshot->id);
}
break;
case ConfigMenuSnapshots:
@ -1874,19 +1894,24 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
dlg.ShowModal();
if (!dlg.snapshot_to_activate().empty()) {
if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) &&
! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "",
GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate())))
break;
try {
app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
// Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system
// presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users
// are known to be creative and mess with the config files in various ways.
if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
! all_substitutions.empty()) {
// TODO:
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
! all_substitutions.empty())
show_substitutions_info(all_substitutions);
// Load the currently selected preset into the GUI, update the preset selection box.
load_current_presets();
// update config wizard in respect to the new config
update_wizard_from_config();
} catch (std::exception &ex) {
GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what()));
}
@ -2059,7 +2084,7 @@ std::vector<std::pair<unsigned int, std::string>> GUI_App::get_selected_presets(
// to notify the user whether he is aware that some preset changes will be lost.
bool GUI_App::check_and_save_current_preset_changes(const wxString& header)
{
if (this->plater()->model().objects.empty() && has_current_preset_changes()) {
if (/*this->plater()->model().objects.empty() && */has_current_preset_changes()) {
UnsavedChangesDialog dlg(header);
if (wxGetApp().app_config->get("default_action_on_close_application") == "none" && dlg.ShowModal() == wxID_CANCEL)
return false;
@ -2138,6 +2163,17 @@ void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/)
}
}
void GUI_App::update_wizard_from_config()
{
if (!m_wizard)
return;
// If ConfigWizard was created before changing of the configuration,
// we have to destroy it to have possibility to create it again in respect to the new config's parameters
m_wizard->Reparent(nullptr);
m_wizard->Destroy();
m_wizard = nullptr;
}
bool GUI_App::OnExceptionInMainLoop()
{
generic_exception_handle();
@ -2291,14 +2327,20 @@ wxString GUI_App::current_language_code_safe() const
void GUI_App::open_web_page_localized(const std::string &http_address)
{
wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code_safe());
open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe());
}
bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page)
{
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
if (reason == ConfigWizard::RR_USER)
if (PresetUpdater::UpdateResult result = preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD);
result == PresetUpdater::R_ALL_CANCELED)
return false;
if (! m_wizard) {
wxBusyCursor wait;
m_wizard = new ConfigWizard(mainframe);
}
@ -2466,7 +2508,7 @@ void GUI_App::check_updates(const bool verbose)
{
PresetUpdater::UpdateResult updater_result;
try {
updater_result = preset_updater->config_update(app_config->orig_version(), verbose);
updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION);
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
mainframe->Close();
}
@ -2483,6 +2525,23 @@ void GUI_App::check_updates(const bool verbose)
}
}
bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/* = 0*/)
{
bool launch = true;
if (get_app_config()->get("suppress_hyperlinks").empty()) {
wxRichMessageDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
dialog.ShowCheckBox(_L("Remember my choice"));
int answer = dialog.ShowModal();
launch = answer == wxID_YES;
get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : "");
}
if (launch)
launch = get_app_config()->get("suppress_hyperlinks") != "1";
return launch && wxLaunchDefaultBrowser(url, flags);
}
// static method accepting a wxWindow object as first parameter
// void warning_catcher{
// my($self, $message_dialog) = @_;

View file

@ -66,7 +66,9 @@ enum FileType
FT_TEX,
FT_PNGZIP,
FT_SL1,
// Workaround for OSX file picker, for some reason it always saves with the 1st extension.
FT_SL1S,
FT_SIZE,
};
@ -177,6 +179,8 @@ public:
static unsigned get_colour_approx_luma(const wxColour &colour);
static bool dark_mode();
const wxColour get_label_default_clr_system();
const wxColour get_label_default_clr_modified();
void init_label_colours();
void update_label_colours_from_appconfig();
void update_label_colours();
@ -247,6 +251,7 @@ public:
bool check_print_host_queue();
bool checked_tab(Tab* tab);
void load_current_presets(bool check_printer_presets = true);
void update_wizard_from_config();
wxString current_language_code() const { return m_wxLocale->GetCanonicalName(); }
// Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US".
@ -256,7 +261,8 @@ public:
void open_preferences(size_t open_on_tab = 0);
virtual bool OnExceptionInMainLoop() override;
// Calls wxLaunchDefaultBrowser if user confirms in dialog.
bool open_browser_with_warning_dialog(const wxString& url, int flags = 0);
#ifdef __APPLE__
void OSXStoreOpenFiles(const wxArrayString &files) override;
// wxWidgets override to get an event on open files.

View file

@ -657,6 +657,15 @@ wxMenuItem* MenuFactory::append_menu_item_fix_through_netfabb(wxMenu* menu)
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix through the Netfabb"), "",
[](wxCommandEvent&) { obj_list()->fix_through_netfabb(); }, "", menu,
[]() {return plater()->can_fix_through_netfabb(); }, plater());
return menu_item;
}
wxMenuItem* MenuFactory::append_menu_item_simplify(wxMenu* menu)
{
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Simplify model"), "",
[](wxCommandEvent&) { obj_list()->simplify(); }, "", menu,
[]() {return plater()->can_simplify(); }, plater());
menu->AppendSeparator();
return menu_item;
@ -874,6 +883,7 @@ void MenuFactory::create_common_object_menu(wxMenu* menu)
append_menu_item_scale_selection_to_fit_print_volume(menu);
append_menu_item_fix_through_netfabb(menu);
append_menu_item_simplify(menu);
append_menu_items_mirror(menu);
}
@ -923,6 +933,7 @@ void MenuFactory::create_part_menu()
append_menu_item_replace_with_stl(menu);
append_menu_item_export_stl(menu);
append_menu_item_fix_through_netfabb(menu);
append_menu_item_simplify(menu);
append_menu_items_mirror(menu);
append_menu_item(menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual parts"),

View file

@ -92,6 +92,7 @@ private:
wxMenuItem* append_menu_item_printable(wxMenu* menu);
void append_menu_items_osx(wxMenu* menu);
wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu);
wxMenuItem* append_menu_item_simplify(wxMenu* menu);
void append_menu_item_export_stl(wxMenu* menu);
void append_menu_item_reload_from_disk(wxMenu* menu);
void append_menu_item_replace_with_stl(wxMenu* menu);

View file

@ -145,7 +145,7 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinus
auto temp = new wxStaticText(m_parent, wxID_ANY, _L("mm"));
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
temp->SetFont(wxGetApp().normal_font());
sizer->Add(temp, 0, wxLEFT, wxGetApp().em_unit());
sizer->Add(temp, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, wxGetApp().em_unit());
m_grid_sizer->Add(sizer);

View file

@ -1049,7 +1049,7 @@ void ObjectList::key_event(wxKeyEvent& event)
|| event.GetKeyCode() == WXK_BACK
#endif //__WXOSX__
) {
remove();
wxGetApp().plater()->remove_selected();
}
else if (event.GetKeyCode() == WXK_F5)
wxGetApp().plater()->reload_all_from_disk();
@ -1778,10 +1778,8 @@ void ObjectList::del_subobject_item(wxDataViewItem& item)
del_layers_from_object(obj_idx);
else if (type & itLayer && obj_idx != -1)
del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item));
else if (type & itInfo && obj_idx != -1) {
Unselect(item);
Select(parent);
}
else if (type & itInfo && obj_idx != -1)
del_info_item(obj_idx, m_objects_model->GetInfoItemType(item));
else if (idx == -1)
return;
else if (!del_subobject_from_object(obj_idx, idx, type))
@ -1795,6 +1793,52 @@ void ObjectList::del_subobject_item(wxDataViewItem& item)
update_info_items(obj_idx);
}
void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
{
Plater* plater = wxGetApp().plater();
GLCanvas3D* cnv = plater->canvas3D();
switch (type) {
case InfoItemType::CustomSupports:
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _L("Remove paint-on supports"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->supported_facets.clear();
break;
case InfoItemType::CustomSeam:
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _L("Remove paint-on seam"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->seam_facets.clear();
break;
case InfoItemType::MmuSegmentation:
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _L("Remove Multi Material painting"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->mmu_segmentation_facets.clear();
break;
case InfoItemType::Sinking:
Plater::TakeSnapshot(plater, _L("Shift objects to bed"));
(*m_objects)[obj_idx]->ensure_on_bed();
cnv->reload_scene(true, true);
break;
case InfoItemType::VariableLayerHeight:
Plater::TakeSnapshot(plater, _L("Remove variable layer height"));
(*m_objects)[obj_idx]->layer_height_profile.clear();
if (cnv->is_layers_editing_enabled())
//cnv->post_event(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING));
cnv->force_main_toolbar_left_action(cnv->get_main_toolbar_item_id("layersediting"));
break;
case InfoItemType::Undef : assert(false); break;
}
cnv->post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item)
{
const bool is_layer_settings = m_objects_model->GetItemType(parent_item) == itLayer;
@ -1876,16 +1920,15 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
if (vol->is_model_part())
++solid_cnt;
if (volume->is_model_part() && solid_cnt == 1) {
Slic3r::GUI::show_error(nullptr, _(L("From Object List You can't delete the last solid part from object.")));
Slic3r::GUI::show_error(nullptr, _L("From Object List You can't delete the last solid part from object."));
return false;
}
take_snapshot(_(L("Delete Subobject")));
take_snapshot(_L("Delete Subobject"));
object->delete_volume(idx);
if (object->volumes.size() == 1)
{
if (object->volumes.size() == 1) {
const auto last_volume = object->volumes[0];
if (!last_volume->config.empty()) {
object->config.apply(last_volume->config);
@ -1904,11 +1947,11 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
}
else if (type == itInstance) {
if (object->instances.size() == 1) {
Slic3r::GUI::show_error(nullptr, _(L("Last instance of an object cannot be deleted.")));
Slic3r::GUI::show_error(nullptr, _L("Last instance of an object cannot be deleted."));
return false;
}
take_snapshot(_(L("Delete Instance")));
take_snapshot(_L("Delete Instance"));
object->delete_instance(idx);
}
else
@ -2517,7 +2560,7 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D
}
void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/)
void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/, bool added_object/* = false*/)
{
const ModelObject* model_object = (*m_objects)[obj_idx];
wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx);
@ -2561,8 +2604,8 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
if (! shows && should_show) {
m_objects_model->AddInfoChild(item_obj, type);
Expand(item_obj);
wxGetApp().notification_manager()->push_updated_item_info_notification(type);
if (added_object)
wxGetApp().notification_manager()->push_updated_item_info_notification(type);
}
else if (shows && ! should_show) {
if (!selections)
@ -2594,7 +2637,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed)
model_object->config.has("extruder") ? model_object->config.extruder() : 0,
get_mesh_errors_count(obj_idx) > 0);
update_info_items(obj_idx);
update_info_items(obj_idx, nullptr, true);
// add volumes to the object
if (model_object->volumes.size() > 1) {
@ -2687,8 +2730,9 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
return;
m_prevent_list_events = true;
for (std::vector<ItemForDelete>::const_reverse_iterator item = items_for_delete.rbegin(); item != items_for_delete.rend(); ++item)
{
std::set<size_t> modified_objects_ids;
for (std::vector<ItemForDelete>::const_reverse_iterator item = items_for_delete.rbegin(); item != items_for_delete.rend(); ++item) {
if (!(item->type&(itObject | itVolume | itInstance)))
continue;
if (item->type&itObject) {
@ -2698,8 +2742,7 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
else {
if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type))
continue;
if (item->type&itVolume)
{
if (item->type&itVolume) {
m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx));
ModelObject* obj = object(item->obj_idx);
if (obj->volumes.size() == 1) {
@ -2717,7 +2760,14 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
else
m_objects_model->Delete(m_objects_model->GetItemByInstanceId(item->obj_idx, item->sub_obj_idx));
}
modified_objects_ids.insert(static_cast<size_t>(item->obj_idx));
}
for (size_t id : modified_objects_ids) {
update_info_items(id);
}
m_prevent_list_events = true;
part_selection_changed();
}
@ -3360,6 +3410,18 @@ void ObjectList::update_selections_on_canvas()
std::vector<unsigned int> idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx);
volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
}
else if (type == itInfo) {
// When selecting an info item, select one instance of the
// respective object - a gizmo may want to be opened.
int inst_idx = selection.get_instance_idx();
int scene_obj_idx = selection.get_object_idx();
mode = Selection::Instance;
// select first instance, unless an instance of the object is already selected
if (scene_obj_idx == -1 || inst_idx == -1 || scene_obj_idx != obj_idx)
inst_idx = 0;
std::vector<unsigned int> idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx);
volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
}
else
{
mode = Selection::Instance;
@ -3374,7 +3436,7 @@ void ObjectList::update_selections_on_canvas()
if (sel_cnt == 1) {
wxDataViewItem item = GetSelection();
if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer | itInfo))
if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer))
add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode);
else
add_to_selection(item, selection, instance_idx, mode);
@ -3857,6 +3919,7 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set
// update printable state for new volumes on canvas3D
wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object(new_obj_indx);
update_info_items(new_obj_indx);
}
void ObjectList::instances_to_separated_objects(const int obj_idx)
@ -3889,6 +3952,8 @@ void ObjectList::instances_to_separated_objects(const int obj_idx)
// update printable state for new volumes on canvas3D
wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(object_idxs);
for (size_t object : object_idxs)
update_info_items(object);
}
void ObjectList::split_instances()
@ -3953,6 +4018,29 @@ void ObjectList::fix_through_netfabb()
wxGetApp().plater()->fix_through_netfabb(obj_idx, vol_idx);
update_item_error_icon(obj_idx, vol_idx);
update_info_items(obj_idx);
}
void ObjectList::simplify()
{
auto plater = wxGetApp().plater();
GLGizmosManager& gizmos_mgr = plater->canvas3D()->get_gizmos_manager();
// Do not simplify when a gizmo is open. There might be issues with updates
// and what is worse, the snapshot time would refer to the internal stack.
auto current_type = gizmos_mgr.get_current_type();
if (current_type == GLGizmosManager::Simplify) {
// close first
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
}else if (current_type != GLGizmosManager::Undefined) {
plater->get_notification_manager()->push_notification(
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
NotificationManager::NotificationLevel::RegularNotification,
_u8L("ERROR: Please close all manipulators available from "
"the left toolbar before start simplify the mesh."));
return;
}
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
}
void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const
@ -3972,6 +4060,8 @@ void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) co
// unmark fixed item only
m_objects_model->DeleteWarningIcon(item);
}
else
m_objects_model->AddWarningIcon(item);
}
void ObjectList::msw_rescale()
@ -4240,5 +4330,10 @@ ModelObject* ObjectList::object(const int obj_idx) const
return (*m_objects)[obj_idx];
}
bool ObjectList::has_paint_on_segmentation()
{
return m_objects_model->HasInfoItem(InfoItemType::MmuSegmentation);
}
} //namespace GUI
} //namespace Slic3r

View file

@ -254,6 +254,7 @@ public:
void del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range);
void del_layers_from_object(const int obj_idx);
bool del_subobject_from_object(const int obj_idx, const int idx, const int type);
void del_info_item(const int obj_idx, InfoItemType type);
void split();
void merge(bool to_multipart_object);
void layers_editing();
@ -350,13 +351,14 @@ public:
void update_and_show_object_settings_item();
void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections);
void update_object_list_by_printer_technology();
void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr);
void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr, bool added_object = false);
void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx);
void instances_to_separated_objects(const int obj_idx);
void split_instances();
void rename_item();
void fix_through_netfabb();
void simplify();
void update_item_error_icon(const int obj_idx, int vol_idx) const ;
void copy_layers_to_clipboard();
@ -378,6 +380,7 @@ public:
void set_extruder_for_selected_items(const int extruder) const ;
wxDataViewItemArray reorder_volumes_and_get_selection(int obj_idx, std::function<bool(const ModelVolume*)> add_to_selection = nullptr);
void apply_volumes_order();
bool has_paint_on_segmentation();
private:
#ifdef __WXOSX__

View file

@ -709,9 +709,9 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
const ExPolygons& bottom = object->get_layer(0)->lslices;
double bottom_area = area(bottom);
// at least 30% of object's height have to be a solid
int i;
for (i = 1; i < int(0.3 * num_layers); ++ i) {
// at least 25% of object's height have to be a solid
int i, min_solid_height = int(0.25 * num_layers);
for (i = 1; i <= min_solid_height; ++ i) {
double cur_area = area(object->get_layer(i)->lslices);
if (cur_area != bottom_area && fabs(cur_area - bottom_area) > scale_(scale_(1))) {
// but due to the elephant foot compensation, the first layer may be slightly smaller than the others
@ -723,7 +723,7 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
break;
}
}
if (i < int(0.3 * num_layers))
if (i < min_solid_height)
continue;
// bottom layer have to be a biggest, so control relation between bottom layer and object size
@ -788,11 +788,11 @@ void Preview::update_layers_slider_mode()
object->config.option("extruder")->getInt() != extruder)
return false;
if (object->volumes.size() > 1)
for (ModelVolume* volume : object->volumes)
if (volume->config.has("extruder") &&
volume->config.option("extruder")->getInt() != extruder)
return false;
for (ModelVolume* volume : object->volumes)
if ((volume->config.has("extruder") &&
volume->config.option("extruder")->getInt() != extruder) ||
!volume->mmu_segmentation_facets.empty())
return false;
for (const auto& range : object->layer_config_ranges)
if (range.second.has("extruder") &&

View file

@ -85,6 +85,7 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u
, m_dragging(false)
, m_imgui(wxGetApp().imgui())
, m_first_input_window_render(true)
, m_dirty(false)
{
m_base_color = DEFAULT_BASE_COLOR;
m_drag_color = DEFAULT_DRAG_COLOR;
@ -154,6 +155,13 @@ void GLGizmoBase::update(const UpdateData& data)
on_update(data);
}
bool GLGizmoBase::update_items_state()
{
bool res = m_dirty;
m_dirty = false;
return res;
};
std::array<float, 4> GLGizmoBase::picking_color_component(unsigned int id) const
{
static const float INV_255 = 1.0f / 255.0f;
@ -209,6 +217,10 @@ std::string GLGizmoBase::format(float value, unsigned int decimals) const
return Slic3r::string_printf("%.*f", decimals, value);
}
void GLGizmoBase::set_dirty() {
m_dirty = true;
}
void GLGizmoBase::render_input_window(float x, float y, float bottom_limit)
{
on_render_input_window(x, y, bottom_limit);

Some files were not shown because too many files have changed in this diff Show more