mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-22 16:21:24 -06:00
Fixed conflicts after merge with master
This commit is contained in:
commit
39ec1a6318
192 changed files with 7204 additions and 2296 deletions
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
12
src/hints/CMakeLists.txt
Normal 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
84
src/hints/HintsToPot.cpp
Normal 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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
project(libigl)
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(libigl)
|
||||
|
||||
add_library(libigl INTERFACE)
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -178,6 +178,8 @@ add_library(libslic3r STATIC
|
|||
PrintRegion.cpp
|
||||
PNGReadWrite.hpp
|
||||
PNGReadWrite.cpp
|
||||
QuadricEdgeCollapse.cpp
|
||||
QuadricEdgeCollapse.hpp
|
||||
Semver.cpp
|
||||
ShortestPath.cpp
|
||||
ShortestPath.hpp
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()};
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()});
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = [§ion, &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 = [§ion, &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())
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ¤t_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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
845
src/libslic3r/QuadricEdgeCollapse.cpp
Normal file
845
src/libslic3r/QuadricEdgeCollapse.cpp
Normal 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 */
|
28
src/libslic3r/QuadricEdgeCollapse.hpp
Normal file
28
src/libslic3r/QuadricEdgeCollapse.hpp
Normal 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
|
|
@ -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 ¶ms;
|
||||
|
||||
// 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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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
|
||||
|
|
|
@ -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 ¶ms = {});
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -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.;
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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 = """; break;
|
||||
case '\'': replacement = "'"; break;
|
||||
case '&': replacement = "&"; break;
|
||||
case '<': replacement = "<"; break;
|
||||
case '>': replacement = ">"; break;
|
||||
case '<': replacement = is_marked ? "<" :"<"; break;
|
||||
case '>': replacement = is_marked ? ">" :">"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 2.8.12)
|
||||
project(miniz)
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
|
||||
add_library(miniz INTERFACE)
|
||||
|
||||
|
|
12
src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.desktop
Executable file
12
src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.desktop
Executable 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
|
62
src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.metainfo.xml
Executable file
62
src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.metainfo.xml
Executable 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>
|
|
@ -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
|
||||
|
|
|
@ -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 ()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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 ¶ms);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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 } });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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) = @_;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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") &&
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue