Merge branch 'master' into fs_QuadricEdgeCollapse

This commit is contained in:
Filip Sykala 2021-08-23 11:33:14 +02:00
commit 89819c1c22
57 changed files with 797 additions and 415 deletions

View file

@ -3,6 +3,7 @@ project(PrusaSlicer)
include("version.inc") include("version.inc")
include(GNUInstallDirs) include(GNUInstallDirs)
include(CMakeDependentOption)
set(SLIC3R_RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/resources") set(SLIC3R_RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/resources")
file(TO_NATIVE_PATH "${SLIC3R_RESOURCES_DIR}" SLIC3R_RESOURCES_DIR_WIN) file(TO_NATIVE_PATH "${SLIC3R_RESOURCES_DIR}" SLIC3R_RESOURCES_DIR_WIN)
@ -32,6 +33,8 @@ option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1)
option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1) option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1)
option(SLIC3R_PERL_XS "Compile XS Perl module and enable Perl unit and integration tests" 0) option(SLIC3R_PERL_XS "Compile XS Perl module and enable Perl unit and integration tests" 0)
option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0) option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0)
# If SLIC3R_FHS is 1 -> SLIC3R_DESKTOP_INTEGRATION is always 0, othrewise variable.
CMAKE_DEPENDENT_OPTION(SLIC3R_DESKTOP_INTEGRATION "Allow perfoming desktop integration during runtime" 1 "NOT SLIC3R_FHS" 0)
set(OPENVDB_FIND_MODULE_PATH "" CACHE PATH "Path to OpenVDB installation's find modules.") set(OPENVDB_FIND_MODULE_PATH "" CACHE PATH "Path to OpenVDB installation's find modules.")
@ -71,6 +74,10 @@ if (SLIC3R_GUI)
add_definitions(-DSLIC3R_GUI) add_definitions(-DSLIC3R_GUI)
endif () endif ()
if(SLIC3R_DESKTOP_INTEGRATION)
add_definitions(-DSLIC3R_DESKTOP_INTEGRATION)
endif ()
if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set(IS_CLANG_CL TRUE) set(IS_CLANG_CL TRUE)
@ -398,7 +405,7 @@ else()
target_link_libraries(libcurl INTERFACE crypt32) target_link_libraries(libcurl INTERFACE crypt32)
endif() endif()
if (SLIC3R_STATIC) if (SLIC3R_STATIC AND NOT SLIC3R_STATIC_EXCLUDE_CURL)
if (NOT APPLE) if (NOT APPLE)
# libcurl is always linked dynamically to the system libcurl on OSX. # libcurl is always linked dynamically to the system libcurl on OSX.
# On other systems, libcurl is linked statically if SLIC3R_STATIC is set. # On other systems, libcurl is linked statically if SLIC3R_STATIC is set.
@ -449,13 +456,13 @@ set(OpenGL_GL_PREFERENCE "LEGACY")
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
# Find glew or use bundled version # Find glew or use bundled version
if (SLIC3R_STATIC) if (SLIC3R_STATIC AND NOT SLIC3R_STATIC_EXCLUDE_GLEW)
set(GLEW_USE_STATIC_LIBS ON) set(GLEW_USE_STATIC_LIBS ON)
set(GLEW_VERBOSE ON) set(GLEW_VERBOSE ON)
endif() endif()
find_package(GLEW) find_package(GLEW)
if (NOT GLEW_FOUND) if (NOT TARGET GLEW::GLEW)
message(STATUS "GLEW not found, using bundled version.") message(STATUS "GLEW not found, using bundled version.")
add_library(glew STATIC ${LIBDIR}/glew/src/glew.c) add_library(glew STATIC ${LIBDIR}/glew/src/glew.c)
set(GLEW_FOUND TRUE) set(GLEW_FOUND TRUE)

View file

@ -56,4 +56,4 @@ FIND_PATH(DBUS_ARCH_INCLUDE_DIR
SET(DBUS_INCLUDE_DIRS ${DBUS_INCLUDE_DIR} ${DBUS_ARCH_INCLUDE_DIR}) SET(DBUS_INCLUDE_DIRS ${DBUS_INCLUDE_DIR} ${DBUS_ARCH_INCLUDE_DIR})
INCLUDE(FindPackageHandleStandardArgs) INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(DBUS REQUIRED_VARS DBUS_INCLUDE_DIRS DBUS_LIBRARIES) FIND_PACKAGE_HANDLE_STANDARD_ARGS(DBus REQUIRED_VARS DBUS_INCLUDE_DIRS DBUS_LIBRARIES)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Before After
Before After

View file

@ -1,21 +1,76 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <svg
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" xmlns:dc="http://purl.org/dc/elements/1.1/"
viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve"> xmlns:cc="http://creativecommons.org/ns#"
<style type="text/css"> xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="toolbar_arrow.svg"
xml:space="preserve"
style="enable-background:new 0 0 330 330;"
viewBox="0 0 330 330"
y="0px"
x="0px"
id="Layer_1"
version="1.1"><metadata
id="metadata21"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs19" /><sodipodi:namedview
inkscape:current-layer="Layer_1"
inkscape:window-maximized="1"
inkscape:window-y="-8"
inkscape:window-x="-8"
inkscape:cy="165"
inkscape:cx="165"
inkscape:zoom="3.0545455"
showgrid="false"
id="namedview17"
inkscape:window-height="1377"
inkscape:window-width="2560"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<style
id="style2"
type="text/css">
.st0{display:none;} .st0{display:none;}
.st1{fill:none;stroke:#ED6B21;stroke-width:17.0079;stroke-linecap:round;stroke-miterlimit:10;} .st1{fill:none;stroke:#ED6B21;stroke-width:17.0079;stroke-linecap:round;stroke-miterlimit:10;}
</style> </style>
<path id="XMLID_28_" class="st0" d="M180,315V51.2l49.4,49.4c5.9,5.9,15.4,5.9,21.2,0c5.9-5.9,5.9-15.4,0-21.2l-75-75 <path
c-5.9-5.9-15.4-5.9-21.2,0l-75,75C76.5,82.3,75,86.2,75,90s1.5,7.7,4.4,10.6c5.9,5.9,15.4,5.9,21.2,0L150,51.2V315 d="M180,315V51.2l49.4,49.4c5.9,5.9,15.4,5.9,21.2,0c5.9-5.9,5.9-15.4,0-21.2l-75-75 c-5.9-5.9-15.4-5.9-21.2,0l-75,75C76.5,82.3,75,86.2,75,90s1.5,7.7,4.4,10.6c5.9,5.9,15.4,5.9,21.2,0L150,51.2V315 c0,8.3,6.7,15,15,15S180,323.3,180,315z"
c0,8.3,6.7,15,15,15S180,323.3,180,315z"/> class="st0"
<g id="XMLID_1_"> id="XMLID_28_" />
<g> <g
id="XMLID_1_">
<g
id="g5">
</g> </g>
<g> <g
<polyline class="st1" points="113.6,84.5 164.3,18.3 164.3,18.3 "/> id="g11">
<polyline class="st1" points="216.4,84.5 164.3,18.3 164.3,18.3 "/> <polyline
id="polyline7"
points="113.6,84.5 164.3,18.3 164.3,18.3 "
class="st1" />
<polyline
id="polyline9"
points="216.4,84.5 164.3,18.3 164.3,18.3 "
class="st1" />
</g> </g>
</g> </g>
<line class="st1" x1="164.3" y1="263.3" x2="164.3" y2="18.3"/> <line
id="line14"
y2="18.3"
x2="164.3"
y1="263.3"
x1="164.3"
class="st1" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

View file

@ -1,4 +1,5 @@
min_slic3r_version = 2.4.0-alpha0 min_slic3r_version = 2.4.0-alpha0
1.4.0-alpha7 Updated brim_offset value. Updated Prusa MINI end g-code. Added Filamentworld filament profiles.
1.4.0-alpha6 Added nozzle priming after M600. Added nozzle diameter checks for 0.8 nozzle printer profiles. Updated FW version. Increased number of top solid infill layers (0.2 layer height). 1.4.0-alpha6 Added nozzle priming after M600. Added nozzle diameter checks for 0.8 nozzle printer profiles. Updated FW version. Increased number of top solid infill layers (0.2 layer height).
1.4.0-alpha5 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S). 1.4.0-alpha5 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S).
1.4.0-alpha4 Decreased Area Fill (SL1S). 1.4.0-alpha4 Decreased Area Fill (SL1S).

View file

@ -5,7 +5,7 @@
name = Prusa Research name = Prusa Research
# Configuration version of this file. Config file will only be installed, if the config_version differs. # Configuration version of this file. Config file will only be installed, if the config_version differs.
# This means, the server may force the PrusaSlicer configuration to be downgraded. # This means, the server may force the PrusaSlicer configuration to be downgraded.
config_version = 1.4.0-alpha6 config_version = 1.4.0-alpha7
# Where to get the updates from? # Where to get the updates from?
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/
changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1%
@ -144,6 +144,7 @@ bridge_angle = 0
bridge_flow_ratio = 1 bridge_flow_ratio = 1
bridge_speed = 25 bridge_speed = 25
brim_width = 0 brim_width = 0
brim_offset = 0.1
clip_multipart_objects = 1 clip_multipart_objects = 1
compatible_printers = compatible_printers =
complete_objects = 0 complete_objects = 0
@ -2464,6 +2465,76 @@ inherits = addnorth Textura
filament_retract_length = nil filament_retract_length = nil
compatible_printers_condition = printer_model=="MK2SMM" compatible_printers_condition = printer_model=="MK2SMM"
[filament:Filamentworld ABS]
inherits = *ABSC*
filament_vendor = Filamentworld
filament_cost = 24.9
filament_density = 1.04
temperature = 230
bed_temperature = 95
first_layer_temperature = 240
first_layer_bed_temperature = 105
max_fan_speed = 20
min_fan_speed = 10
min_print_speed = 20
disable_fan_first_layers = 3
fan_below_layer_time = 60
slowdown_below_layer_time = 15
bridge_fan_speed = 20
compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
[filament:Filamentworld ABS @MINI]
inherits = Filamentworld ABS
first_layer_bed_temperature = 100
min_fan_speed = 15
fan_below_layer_time = 60
compatible_printers_condition = printer_model=="MINI"
[filament:Filamentworld PETG]
inherits = *PET*
filament_vendor = Filamentworld
filament_cost = 34.9
filament_density = 1.27
bed_temperature = 70
first_layer_bed_temperature = 85
first_layer_temperature = 240
temperature = 235
fan_always_on = 1
min_fan_speed = 25
max_fan_speed = 55
bridge_fan_speed = 55
slowdown_below_layer_time = 20
min_print_speed = 20
fan_below_layer_time = 35
disable_fan_first_layers = 2
full_fan_speed_layer = 0
filament_retract_length = 1.4
filament_max_volumetric_speed = 8
filament_spool_weight = 0
[filament:Filamentworld PETG @MINI]
inherits = Filamentworld PETG
filament_retract_length = nil
filament_retract_lift = nil
filament_retract_speed = 40
filament_deretract_speed = 25
filament_max_volumetric_speed = 7
compatible_printers_condition = printer_model=="MINI"
[filament:Filamentworld PLA]
inherits = *PLA*
filament_vendor = Filamentworld
filament_cost = 24.9
filament_density = 1.24
temperature = 205
bed_temperature = 55
first_layer_temperature = 215
first_layer_bed_temperature = 60
full_fan_speed_layer = 3
slowdown_below_layer_time = 10
filament_spool_weight = 0
min_print_speed = 20
[filament:Filament PM PETG] [filament:Filament PM PETG]
inherits = *PET* inherits = *PET*
renamed_from = "Plasty Mladec PETG" renamed_from = "Plasty Mladec PETG"
@ -3097,6 +3168,7 @@ filament_loading_speed_start = 19
filament_minimal_purge_on_wipe_tower = 15 filament_minimal_purge_on_wipe_tower = 15
filament_unloading_speed_start = 100 filament_unloading_speed_start = 100
full_fan_speed_layer = 4 full_fan_speed_layer = 4
filament_max_volumetric_speed = 13
[filament:Generic PLA @MMU2] [filament:Generic PLA @MMU2]
inherits = *PLA MMU2* inherits = *PLA MMU2*
@ -6447,7 +6519,7 @@ retract_layer_change = 1
silent_mode = 0 silent_mode = 0
remaining_times = 1 remaining_times = 1
start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM204 T1250 ; set travel acceleration\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM204 T[machine_max_acceleration_travel] ; restore travel acceleration\nM104 S[first_layer_temperature] ; set extruder temp\nG92 E0\nG1 Y-2 X179 F2400\nG1 Z3 F720\nM109 S[first_layer_temperature] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110 E8 F900\nG1 X40 E10 F700\nG92 E0\n\nM221 S95 ; set flow start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM204 T1250 ; set travel acceleration\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM204 T[machine_max_acceleration_travel] ; restore travel acceleration\nM104 S[first_layer_temperature] ; set extruder temp\nG92 E0\nG1 Y-2 X179 F2400\nG1 Z3 F720\nM109 S[first_layer_temperature] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110 E8 F900\nG1 X40 E10 F700\nG92 E0\n\nM221 S95 ; set flow
end_gcode = G1 E-1 F2100 ; retract\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+2, max_print_height)}{endif} F720 ; Move print head up\nG1 X178 Y178 F4200 ; park print head\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} F720 ; Move print head further up\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM221 S100 ; reset flow\nM900 K0 ; reset LA\nM84 ; disable motors end_gcode = G1 E-1 F2100 ; retract\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+2, max_print_height)} F720 ; Move print head up{endif}\nG1 X178 Y178 F4200 ; park print head\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)} F720 ; Move print head further up{endif}\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM221 S100 ; reset flow\nM900 K0 ; reset LA\nM84 ; disable motors
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MINI\n printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MINI\n
extruder_colour = extruder_colour =
color_change_gcode = M600\nG1 E0.8 F1500 ; prime after color change color_change_gcode = M600\nG1 E0.8 F1500 ; prime after color change

View file

@ -1,12 +1,12 @@
#version 110 #version 110
attribute vec4 v_position; attribute vec3 v_position;
attribute vec2 v_tex_coords; attribute vec2 v_tex_coords;
varying vec2 tex_coords; varying vec2 tex_coords;
void main() void main()
{ {
gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * v_position; gl_Position = gl_ModelViewProjectionMatrix * vec4(v_position, 1.0);
tex_coords = v_tex_coords; tex_coords = v_tex_coords;
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -426,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 ConfigBase::diff(const ConfigBase &other) const
{ {
t_config_option_keys diff; t_config_option_keys diff;
@ -439,6 +451,7 @@ t_config_option_keys ConfigBase::diff(const ConfigBase &other) const
return diff; 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 ConfigBase::equal(const ConfigBase &other) const
{ {
t_config_option_keys equal; t_config_option_keys equal;
@ -1190,6 +1203,65 @@ t_config_option_keys StaticConfig::keys() const
return keys; 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> #include <cereal/types/polymorphic.hpp>

View file

@ -1893,8 +1893,8 @@ public:
// The configuration definition is static: It does not carry the actual configuration values, // The configuration definition is static: It does not carry the actual configuration values,
// but it carries the defaults of the configuration values. // but it carries the defaults of the configuration values.
ConfigBase() {} ConfigBase() = default;
~ConfigBase() override {} ~ConfigBase() override = default;
// Virtual overridables: // Virtual overridables:
public: public:
@ -1953,8 +1953,11 @@ public:
// An UnknownOptionException is thrown in case some option keys are not defined by this->def(), // 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. // 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); 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; 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; t_config_option_keys equal(const ConfigBase &other) const;
std::string opt_serialize(const t_config_option_key &opt_key) const; std::string opt_serialize(const t_config_option_key &opt_key) const;
@ -2022,12 +2025,12 @@ private:
class DynamicConfig : public virtual ConfigBase class DynamicConfig : public virtual ConfigBase
{ {
public: public:
DynamicConfig() {} DynamicConfig() = default;
DynamicConfig(const DynamicConfig &rhs) { *this = rhs; } DynamicConfig(const DynamicConfig &rhs) { *this = rhs; }
DynamicConfig(DynamicConfig &&rhs) noexcept : options(std::move(rhs.options)) { rhs.options.clear(); } 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, const t_config_option_keys &keys);
explicit DynamicConfig(const ConfigBase& rhs) : DynamicConfig(rhs, rhs.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. // Copy a content of one DynamicConfig to another DynamicConfig.
// If rhs.def() is not null, then it has to be equal to this->def(). // If rhs.def() is not null, then it has to be equal to this->def().
@ -2144,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; } 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); } 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); } std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }

View file

@ -595,7 +595,7 @@ namespace Slic3r {
mz_zip_archive_file_stat stat; 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 // 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) { for (mz_uint i = 0; i < num_entries; ++i) {
@ -1408,6 +1408,13 @@ namespace Slic3r {
m_model->delete_object(model_object); 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 // applies instances' matrices
for (Instance& instance : m_instances) { for (Instance& instance : m_instances) {
if (instance.instance != nullptr && instance.instance->get_object() != nullptr) if (instance.instance != nullptr && instance.instance->get_object() != nullptr)

View file

@ -424,7 +424,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
ModelObject* object = new ModelObject(this); ModelObject* object = new ModelObject(this);
object->input_file = this->objects.front()->input_file; 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? //FIXME copy the config etc?
unsigned int extruder_counter = 0; unsigned int extruder_counter = 0;
@ -439,7 +439,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
int counter = 1; int counter = 1;
auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) { auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) {
assert(new_v != nullptr); 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)); new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
return new_v; return new_v;
}; };

View file

@ -24,6 +24,7 @@ public:
PlaceholderParser(const DynamicConfig *external_config = nullptr); 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. // 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. // This contains keys, which are found in rhs, but not in m_config.
std::vector<std::string> config_diff(const DynamicPrintConfig &rhs); std::vector<std::string> config_diff(const DynamicPrintConfig &rhs);

View file

@ -1194,21 +1194,38 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
return diff; 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> PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/)
{ {
std::vector<std::string> changed; std::vector<std::string> changed;
if (edited != nullptr && reference != nullptr) { if (edited != nullptr && reference != nullptr) {
// Only compares options existing in both configs.
changed = deep_compare ? changed = deep_compare ?
deep_diff(edited->config, reference->config) : deep_diff(edited->config, reference->config) :
reference->config.diff(edited->config); reference->config.diff(edited->config);
// The "compatible_printers" option key is handled differently from the others: // 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. // 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. // 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)) if (reference->config.has(opt_key) != edited->config.has(opt_key))
changed.emplace_back(opt_key); changed.emplace_back(opt_key);
}
} }
return changed; return changed;
} }
@ -1385,12 +1402,16 @@ const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConf
return this->default_preset((opt_printer_technology == nullptr || opt_printer_technology->value == ptFFF) ? 0 : 1); 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; } if (model_id.empty()) { return nullptr; }
const auto it = std::find_if(cbegin(), cend(), [&](const Preset &preset) { 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; return it != cend() ? &*it : nullptr;

View file

@ -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;}); } 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. // 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. // 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 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); } { 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); } { 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. // 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(), &this->get_saved_preset()); }
// Compare the content of get_saved_preset() with get_edited_preset() configs, return the list of keys where they differ. // 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 // std::vector<std::string> saved_dirty_options() const
{ return dirty_options(&this->get_edited_preset(), &this->get_saved_preset(), deep_compare); } // { return dirty_options(&this->get_edited_preset(), &this->get_saved_preset(), /* deep_compare */ false); }
// Copy edited preset into saved preset. // Copy edited preset into saved preset.
void update_saved_preset_from_current_preset() { m_saved_preset = m_edited_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); size_t update_compatible_internal(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, PresetSelectCompatibleType unselect_if_incompatible);
public: 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: private:
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER. // Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
Preset::Type m_type; Preset::Type m_type;
@ -592,7 +595,7 @@ public:
const Preset& default_preset_for(const DynamicPrintConfig &config) const override; 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: private:
PrinterPresetCollection() = default; PrinterPresetCollection() = default;

View file

@ -188,7 +188,8 @@ 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. // First load the vendor specific system presets.
PresetsConfigSubstitutions substitutions; PresetsConfigSubstitutions substitutions;
@ -239,7 +240,8 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
if (! errors_cummulative.empty()) if (! errors_cummulative.empty())
throw Slic3r::RuntimeError(errors_cummulative); 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; return substitutions;
} }
@ -441,7 +443,7 @@ void PresetBundle::load_installed_sla_materials(AppConfig &config)
// Load selections (current print, current filaments, current printer) from config.ini // Load selections (current print, current filaments, current printer) from config.ini
// This is done on application start up or after updates are applied. // 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. // Update visibility of presets based on application vendor / model / variant configuration.
this->load_installed_printers(config); this->load_installed_printers(config);
@ -464,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). // 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 *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( 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 : preferred_printer->name :
initial_printer_profile_name, initial_printer_profile_name,
true); 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. // 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); prints.select_preset_by_name_strict(initial_print_profile_name);
filaments.select_preset_by_name_strict(initial_filament_profile_name); filaments.select_preset_by_name_strict(initial_filament_profile_name);
@ -1467,7 +1477,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
if (! active_print.empty()) if (! active_print.empty())
prints.select_preset_by_name(active_print, true); prints.select_preset_by_name(active_print, true);
if (! active_sla_print.empty()) 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()) if (! active_sla_material.empty())
sla_materials.select_preset_by_name(active_sla_material, true); sla_materials.select_preset_by_name(active_sla_material, true);
if (! active_printer.empty()) if (! active_printer.empty())

View file

@ -25,9 +25,18 @@ public:
void setup_directories(); 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 ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
// Load selections (current print, current filaments, current printer) from config.ini // 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 // Export selections (current print, current filaments, current printer) into config.ini
void export_selections(AppConfig &config); void export_selections(AppConfig &config);
@ -153,7 +162,7 @@ private:
// Load selections (current print, current filaments, current printer) from config.ini // Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up. // 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. // 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. // and the external config is just referenced, not stored into user profile directory.

View file

@ -216,22 +216,25 @@ static t_config_option_keys print_config_diffs(
const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; 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()) { if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) {
// An extruder retract override is available at some of the filament presets. // 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(); auto opt_copy = opt_new->clone();
opt_copy->apply_override(opt_new_filament); opt_copy->apply_override(opt_new_filament);
if (*opt_old == *opt_copy) bool changed = *opt_old != *opt_copy;
delete opt_copy; if (changed)
else {
filament_overrides.set_key_value(opt_key, opt_copy);
print_diff.emplace_back(opt_key); 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) } else if (*opt_new != *opt_old)
print_diff.emplace_back(opt_key); print_diff.emplace_back(opt_key);
} }
return print_diff; 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. // Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser.
static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig &current_full_config, const DynamicPrintConfig &new_full_config) static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig &current_full_config, const DynamicPrintConfig &new_full_config)
@ -812,7 +815,7 @@ static PrintObjectRegions* generate_print_object_regions(
layer_ranges_regions.push_back({ range.layer_height_range, range.config }); 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)); 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; std::vector<PrintRegion*> region_set;
@ -928,6 +931,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
bool num_extruders_changed = false; bool num_extruders_changed = false;
if (! full_config_diff.empty()) { if (! full_config_diff.empty()) {
update_apply_status(this->invalidate_step(psGCodeExport)); update_apply_status(this->invalidate_step(psGCodeExport));
m_placeholder_parser.clear_config();
// Set the profile aliases for the PrintBase::output_filename() // 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("print_preset", new_full_config.option("print_settings_id")->clone());
m_placeholder_parser.set("filament_preset", new_full_config.option("filament_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. // 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); m_config.apply_only(new_full_config, print_diff, true);
//FIXME use move semantics once ConfigBase supports it. //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); m_config.apply(filament_overrides);
// Handle changes to object config defaults // Handle changes to object config defaults
m_default_object_config.apply_only(new_full_config, object_diff, true); 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_default_region_config.apply_only(new_full_config, region_diff, true);
m_full_print_config = std::move(new_full_config); m_full_print_config = std::move(new_full_config);
if (num_extruders != m_config.nozzle_diameter.size()) { if (num_extruders != m_config.nozzle_diameter.size()) {
num_extruders = m_config.nozzle_diameter.size(); num_extruders = m_config.nozzle_diameter.size();
num_extruders_changed = true; 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. // 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. // 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) || 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) || 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); 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()); 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(); print_object_regions->ref_cnt_inc();
} }
std::vector<unsigned int> painting_extruders; 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()) { 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! //FIXME be more specific! Don't enumerate extruders that are not used for painting!
painting_extruders.assign(num_extruders, 0); painting_extruders.assign(num_extruders, 0);

View file

@ -167,8 +167,9 @@ static std::vector<VolumeSlices> slice_volumes_inner(
params_base.mode_below = params_base.mode; 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 size_t num_extruders = print_config.nozzle_diameter.size();
const auto extra_offset = is_mm_painted ? 0.f : std::max(0.f, float(print_object_config.xy_size_compensation.value)); 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) for (const ModelVolume *model_volume : model_volumes)
if (model_volume_needs_slicing(*model_volume)) { if (model_volume_needs_slicing(*model_volume)) {
@ -723,6 +724,7 @@ void PrintObject::slice_volumes()
// Is any ModelVolume MMU painted? // Is any ModelVolume MMU painted?
if (const auto& volumes = this->model_object()->volumes; 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()) { 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 // If XY Size compensation is also enabled, notify the user that XY Size compensation
@ -743,8 +745,9 @@ void PrintObject::slice_volumes()
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin"; 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. // 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 size_t num_extruders = print->config().nozzle_diameter.size();
const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ? 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. // Only enable Elephant foot compensation if printing directly on the print bed.
float(scale_(m_config.elefant_foot_compensation.value)) : float(scale_(m_config.elefant_foot_compensation.value)) :
0.f; 0.f;

View file

@ -283,64 +283,61 @@ std::array<double, N> find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn)
} // namespace } // namespace
// Assemble the mesh with the correct transformation to be used in rotation
// optimization.
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); template<unsigned MAX_ITER>
struct RotfinderBoilerplate {
static constexpr unsigned MAX_TRIES = MAX_ITER;
return mesh; int status = 0;
} TriangleMesh mesh;
unsigned max_tries;
const RotOptimizeParams &params;
// Assemble the mesh with the correct transformation to be used in rotation
// optimization.
static TriangleMesh get_mesh_to_rotate(const ModelObject &mo)
{
TriangleMesh mesh = mo.raw_mesh();
mesh.require_shared_vertices();
ModelInstance *mi = mo.instances[0];
auto rotation = Vec3d::Zero();
auto offset = Vec3d::Zero();
Transform3d trafo_instance =
Geometry::assemble_transform(offset, rotation,
mi->get_scaling_factor(),
mi->get_mirror());
mesh.transform(trafo_instance);
return mesh;
}
RotfinderBoilerplate(const ModelObject &mo, const RotOptimizeParams &p)
: mesh{get_mesh_to_rotate(mo)}
, params{p}
, max_tries(p.accuracy() * MAX_TRIES)
{
}
void statusfn() { params.statuscb()(++status * 100.0 / max_tries); }
bool stopcond() { return ! params.statuscb()(-1); }
};
Vec2d find_best_misalignment_rotation(const ModelObject & mo, Vec2d find_best_misalignment_rotation(const ModelObject & mo,
const RotOptimizeParams &params) const RotOptimizeParams &params)
{ {
static constexpr unsigned MAX_TRIES = 1000; RotfinderBoilerplate<1000> bp{mo, params};
// return value
XYRotation rot;
// We will use only one instance of this converted mesh to examine different
// rotations
TriangleMesh mesh = get_mesh_to_rotate(mo);
// 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);
};
// Preparing the optimizer. // Preparing the optimizer.
size_t gridsize = std::sqrt(max_tries); size_t gridsize = std::sqrt(bp.max_tries);
opt::Optimizer<opt::AlgBruteForce> solver(opt::StopCriteria{} opt::Optimizer<opt::AlgBruteForce> solver(
.max_iterations(max_tries) opt::StopCriteria{}.max_iterations(bp.max_tries)
.stop_condition(stopcond), .stop_condition([&bp] { return bp.stopcond(); }),
gridsize); gridsize
);
// We are searching rotations around only two axes x, y. Thus the // We are searching rotations around only two axes x, y. Thus the
// problem becomes a 2 dimensional optimization task. // problem becomes a 2 dimensional optimization task.
@ -348,48 +345,19 @@ Vec2d find_best_misalignment_rotation(const ModelObject & mo,
auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} }); auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} });
auto result = solver.to_max().optimize( auto result = solver.to_max().optimize(
[&mesh, &statusfn] (const XYRotation &rot) [&bp] (const XYRotation &rot)
{ {
statusfn(); bp.statusfn();
return get_misalginment_score(mesh, to_transform3f(rot)); return get_misalginment_score(bp.mesh, to_transform3f(rot));
}, opt::initvals({0., 0.}), bounds); }, opt::initvals({0., 0.}), bounds);
rot = result.optimum; return {result.optimum[0], result.optimum[1]};
return {rot[0], rot[1]};
} }
Vec2d find_least_supports_rotation(const ModelObject & mo, Vec2d find_least_supports_rotation(const ModelObject & mo,
const RotOptimizeParams &params) const RotOptimizeParams &params)
{ {
static const unsigned MAX_TRIES = 1000; RotfinderBoilerplate<1000> bp{mo, params};
// return value
XYRotation rot;
// We will use only one instance of this converted mesh to examine different
// rotations
TriangleMesh mesh = get_mesh_to_rotate(mo);
// 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);
};
SLAPrintObjectConfig pocfg; SLAPrintObjectConfig pocfg;
if (params.print_config()) if (params.print_config())
@ -397,31 +365,35 @@ Vec2d find_least_supports_rotation(const ModelObject & mo,
pocfg.apply(mo.config.get()); pocfg.apply(mo.config.get());
XYRotation rot;
// Different search methods have to be used depending on the model elevation // Different search methods have to be used depending on the model elevation
if (is_on_floor(pocfg)) { if (is_on_floor(pocfg)) {
std::vector<XYRotation> inputs = get_chull_rotations(mesh, max_tries); std::vector<XYRotation> inputs = get_chull_rotations(bp.mesh, bp.max_tries);
max_tries = inputs.size(); bp.max_tries = inputs.size();
// If the model can be placed on the bed directly, we only need to // If the model can be placed on the bed directly, we only need to
// check the 3D convex hull face rotations. // check the 3D convex hull face rotations.
auto objfn = [&mesh, &statusfn](const XYRotation &rot) { auto objfn = [&bp](const XYRotation &rot) {
statusfn(); bp.statusfn();
Transform3f tr = to_transform3f(rot); 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 { } else {
// Preparing the optimizer. // Preparing the optimizer.
size_t gridsize = std::sqrt(max_tries); // 2D grid has gridsize^2 calls size_t gridsize = std::sqrt(bp.max_tries); // 2D grid has gridsize^2 calls
opt::Optimizer<opt::AlgBruteForce> solver(opt::StopCriteria{} opt::Optimizer<opt::AlgBruteForce> solver(
.max_iterations(max_tries) opt::StopCriteria{}.max_iterations(bp.max_tries)
.stop_condition(stopcond), .stop_condition([&bp] { return bp.stopcond(); }),
gridsize); gridsize
);
// We are searching rotations around only two axes x, y. Thus the // We are searching rotations around only two axes x, y. Thus the
// problem becomes a 2 dimensional optimization task. // problem becomes a 2 dimensional optimization task.
@ -429,10 +401,10 @@ Vec2d find_least_supports_rotation(const ModelObject & mo,
auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} }); auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} });
auto result = solver.to_min().optimize( auto result = solver.to_min().optimize(
[&mesh, &statusfn] (const XYRotation &rot) [&bp] (const XYRotation &rot)
{ {
statusfn(); bp.statusfn();
return get_supportedness_score(mesh, to_transform3f(rot)); return get_supportedness_score(bp.mesh, to_transform3f(rot));
}, opt::initvals({0., 0.}), bounds); }, opt::initvals({0., 0.}), bounds);
// Save the result // Save the result
@ -442,4 +414,66 @@ Vec2d find_least_supports_rotation(const ModelObject & mo,
return {rot[0], rot[1]}; return {rot[0], rot[1]};
} }
inline BoundingBoxf3 bounding_box_with_tr(const indexed_triangle_set &its,
const Transform3f &tr)
{
if (its.vertices.empty())
return {};
Vec3f bmin = tr * its.vertices.front(), bmax = tr * its.vertices.front();
for (const Vec3f &p : its.vertices) {
Vec3f pp = tr * p;
bmin = pp.cwiseMin(bmin);
bmax = pp.cwiseMax(bmax);
}
return {bmin.cast<double>(), bmax.cast<double>()};
}
Vec2d find_min_z_height_rotation(const ModelObject &mo,
const RotOptimizeParams &params)
{
RotfinderBoilerplate<1000> bp{mo, params};
TriangleMesh chull = bp.mesh.convex_hull_3d();
chull.require_shared_vertices();
auto inputs = reserve_vector<XYRotation>(chull.its.indices.size());
auto rotcmp = [](const XYRotation &r1, const XYRotation &r2) {
double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y];
return std::abs(xdiff) < EPSILON ? ydiff < 0. : xdiff < 0.;
};
auto eqcmp = [](const XYRotation &r1, const XYRotation &r2) {
double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y];
return std::abs(xdiff) < EPSILON && std::abs(ydiff) < EPSILON;
};
for (size_t fi = 0; fi < chull.its.indices.size(); ++fi) {
Facestats fc{get_triangle_vertices(chull, fi)};
auto q = Eigen::Quaternionf{}.FromTwoVectors(fc.normal, DOWN);
XYRotation rot = from_transform3f(Transform3f::Identity() * q);
auto it = std::lower_bound(inputs.begin(), inputs.end(), rot, rotcmp);
if (it == inputs.end() || !eqcmp(*it, rot))
inputs.insert(it, rot);
}
inputs.shrink_to_fit();
bp.max_tries = inputs.size();
auto objfn = [&bp, &chull](const XYRotation &rot) {
bp.statusfn();
Transform3f tr = to_transform3f(rot);
return bounding_box_with_tr(chull.its, tr).size().z();
};
XYRotation rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), [&bp] {
return bp.stopcond();
});
return {rot[0], rot[1]};
}
}} // namespace Slic3r::sla }} // namespace Slic3r::sla

View file

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

View file

@ -4,7 +4,7 @@
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#ifndef NDEBUG #ifndef NDEBUG
#define EXPENSIVE_DEBUG_CHECKS // #define EXPENSIVE_DEBUG_CHECKS
#endif // NDEBUG #endif // NDEBUG
namespace Slic3r { namespace Slic3r {

View file

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

View file

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

View file

@ -268,7 +268,7 @@ if(APPLE)
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY}) target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY})
endif() 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) target_compile_definitions(libslic3r_gui PRIVATE OPENSSL_CERT_OVERRIDE)
endif () endif ()

View file

@ -306,7 +306,7 @@ void GLVolume::SinkingContours::update()
int object_idx = m_parent.object_idx(); int object_idx = m_parent.object_idx();
Model& model = GUI::wxGetApp().plater()->model(); Model& model = GUI::wxGetApp().plater()->model();
if (0 <= object_idx && object_idx < model.objects.size() && m_parent.is_sinking() && !m_parent.is_below_printbed()) { 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(); 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()) { if (!m_old_box.size().isApprox(box.size()) || m_old_box.min.z() != box.min.z()) {
m_old_box = box; m_old_box = box;

View file

@ -12,6 +12,46 @@
namespace Slic3r { namespace Slic3r {
namespace GUI { 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) : ButtonsDescription::ButtonsDescription(wxWindow* parent, const std::vector<Entry> &entries) :
wxDialog(parent, wxID_ANY, _(L("Buttons And Text Colors Description")), wxDefaultPosition, wxDefaultSize), wxDialog(parent, wxID_ANY, _(L("Buttons And Text Colors Description")), wxDefaultPosition, wxDefaultSize),
m_entries(entries) m_entries(entries)
@ -35,50 +75,23 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, const std::vector<Entry
} }
// Text color description // Text color description
auto sys_label = new wxStaticText(this, wxID_ANY, _(L("Value is the same as the system value"))); wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sys_label->SetForegroundColour(wxGetApp().get_label_clr_sys()); FillSizerWithTextColorDescriptions(sizer, this, &sys_colour, &mod_colour);
auto sys_colour = new wxColourPickerCtrl(this, wxID_ANY, wxGetApp().get_label_clr_sys()); main_sizer->Add(sizer, 0, wxEXPAND | wxALL, 20);
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);
auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL); auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL);
main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
wxGetApp().UpdateDlgDarkUI(this, true);
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this)); 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_sys(sys_colour->GetColour());
wxGetApp().set_label_clr_modified(mod_colour->GetColour()); wxGetApp().set_label_clr_modified(mod_colour->GetColour());
EndModal(wxID_OK); EndModal(wxID_OK);
}); });
wxGetApp().UpdateDarkUI(btn);
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(FindWindowById(wxID_CANCEL, this)));
SetSizer(main_sizer); SetSizer(main_sizer);
main_sizer->SetSizeHints(this); main_sizer->SetSizeHints(this);
} }

View file

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

View file

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

View file

@ -494,15 +494,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent)
{ {
welcome_text->Hide(); welcome_text->Hide();
cbox_reset->Hide(); cbox_reset->Hide();
#ifdef __linux__ cbox_integrate->Hide();
if (!DesktopIntegrationDialog::is_integrated())
cbox_integrate->Show(true);
else
cbox_integrate->Hide();
#else
cbox_integrate->Hide();
#endif
} }
void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
@ -510,7 +502,7 @@ void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY; const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY;
welcome_text->Show(data_empty); welcome_text->Show(data_empty);
cbox_reset->Show(!data_empty); cbox_reset->Show(!data_empty);
#ifdef __linux__ #if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
if (!DesktopIntegrationDialog::is_integrated()) if (!DesktopIntegrationDialog::is_integrated())
cbox_integrate->Show(true); cbox_integrate->Show(true);
else else
@ -2453,6 +2445,34 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo
return true; return true;
} }
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) bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
{ {
const auto enabled_vendors = appconfig_new.vendors(); const auto enabled_vendors = appconfig_new.vendors();
@ -2525,13 +2545,61 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
preset_bundle->reset(true); 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); 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("version_check", page_update->version_check ? "1" : "0");
app_config->set("preset_update", page_update->preset_update ? "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"); app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
@ -2556,44 +2624,8 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
page_mode->serialize_mode(app_config); page_mode->serialize_mode(app_config);
std::string preferred_model; preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem,
{preferred_model, preferred_variant, first_added_filament, first_added_sla_material});
// 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.
// Throw on substitutions in system profiles, as the system profiles provided over the air should be compatible with this PrusaSlicer version.
preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, preferred_model);
if (page_custom->custom_wanted()) { if (page_custom->custom_wanted()) {
page_firmware->apply_custom_config(*custom_config); page_firmware->apply_custom_config(*custom_config);

View file

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

View file

@ -2028,7 +2028,7 @@ void Control::show_cog_icon_context_menu()
append_menu_item(&menu, wxID_ANY, _L("Set extruder sequence for the entire print"), "", append_menu_item(&menu, wxID_ANY, _L("Set extruder sequence for the entire print"), "",
[this](wxCommandEvent&) { edit_extruder_sequence(); }, "", &menu); [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"), "", append_menu_item(&menu, wxID_ANY, _L("Set auto color changes"), "",
[this](wxCommandEvent&) { auto_color_change(); }, "", &menu); [this](wxCommandEvent&) { auto_color_change(); }, "", &menu);

View file

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

View file

@ -1195,7 +1195,7 @@ void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighte
float internal_top_uv = 1.0f - (float)m_arrow_texture.metadata.top * inv_tex_height; 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; float internal_bottom_uv = (float)m_arrow_texture.metadata.bottom * inv_tex_height;
GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv } });
} }
} }

View file

@ -1030,17 +1030,22 @@ bool GUI_App::dark_mode()
#endif #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() 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(); 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 #ifdef _WIN32
m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); 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); m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT);
@ -1801,10 +1806,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 + 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 + 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")); local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for updates"), _L("Check for configuration updates"));
#ifdef __linux__ #if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
if (DesktopIntegrationDialog::integration_possible()) //if (DesktopIntegrationDialog::integration_possible())
local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration")); local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));
#endif #endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
local_menu->AppendSeparator(); local_menu->AppendSeparator();
} }
local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots + local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots +

View file

@ -179,6 +179,8 @@ public:
static unsigned get_colour_approx_luma(const wxColour &colour); static unsigned get_colour_approx_luma(const wxColour &colour);
static bool dark_mode(); static bool dark_mode();
const wxColour get_label_default_clr_system();
const wxColour get_label_default_clr_modified();
void init_label_colours(); void init_label_colours();
void update_label_colours_from_appconfig(); void update_label_colours_from_appconfig();
void update_label_colours(); void update_label_colours();

View file

@ -3404,6 +3404,18 @@ void ObjectList::update_selections_on_canvas()
std::vector<unsigned int> idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx); 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()); 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 else
{ {
mode = Selection::Instance; mode = Selection::Instance;
@ -3418,7 +3430,7 @@ void ObjectList::update_selections_on_canvas()
if (sel_cnt == 1) { if (sel_cnt == 1) {
wxDataViewItem item = GetSelection(); 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); add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode);
else else
add_to_selection(item, selection, instance_idx, mode); add_to_selection(item, selection, instance_idx, mode);

View file

@ -1019,7 +1019,7 @@ void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_t
float arrow_sides_ratio = (float)m_arrow_texture.texture.get_height() / (float)m_arrow_texture.texture.get_width(); float arrow_sides_ratio = (float)m_arrow_texture.texture.get_height() / (float)m_arrow_texture.texture.get_width();
GLTexture::render_sub_texture(tex_id, zoomed_top_x + zoomed_icons_size * 1.2f, zoomed_top_x + zoomed_icons_size * 1.2f + zoomed_icons_size * arrow_sides_ratio, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { internal_left_uv, internal_top_uv }, { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv } }); GLTexture::render_sub_texture(tex_id, zoomed_top_x + zoomed_icons_size * 1.2f, zoomed_top_x + zoomed_icons_size * 1.2f + zoomed_icons_size * arrow_sides_ratio, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { internal_left_uv, internal_bottom_uv }, { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, internal_bottom_uv } });
break; break;
} }
zoomed_top_y -= zoomed_stride_y; zoomed_top_y -= zoomed_stride_y;

View file

@ -3,6 +3,7 @@
#include "format.hpp" #include "format.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "GUI_ObjectList.hpp" #include "GUI_ObjectList.hpp"
#include "GLCanvas3D.hpp"
#include "libslic3r/AppConfig.hpp" #include "libslic3r/AppConfig.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "libslic3r/Config.hpp" #include "libslic3r/Config.hpp"
@ -305,7 +306,10 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
m_loaded_hints.emplace_back(hint_data); m_loaded_hints.emplace_back(hint_data);
} }
else if (dict["hypertext_type"] == "gallery") { else if (dict["hypertext_type"] == "gallery") {
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, []() { wxGetApp().obj_list()->load_shape_object_from_gallery(); } }; HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, []() {
// Deselect all objects, otherwise gallery wont show.
wxGetApp().plater()->canvas3D()->deselect_all();
wxGetApp().obj_list()->load_shape_object_from_gallery(); } };
m_loaded_hints.emplace_back(hint_data); m_loaded_hints.emplace_back(hint_data);
} }
} else { } else {

View file

@ -35,7 +35,9 @@ protected:
void prepare() override; void prepare() override;
void on_exception(const std::exception_ptr &) override; void on_exception(const std::exception_ptr &) override;
void process() override;
public: public:
ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater) ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: PlaterJob{std::move(pri), plater} : PlaterJob{std::move(pri), plater}
@ -46,8 +48,6 @@ public:
return int(m_selected.size() + m_unprintable.size()); return int(m_selected.size() + m_unprintable.size());
} }
void process() override;
void finalize() override; void finalize() override;
}; };

View file

@ -24,6 +24,7 @@ class FillBedJob : public PlaterJob
protected: protected:
void prepare() override; void prepare() override;
void process() override;
public: public:
FillBedJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater) FillBedJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
@ -35,8 +36,6 @@ public:
return m_status_range; return m_status_range;
} }
void process() override;
void finalize() override; void finalize() override;
}; };

View file

@ -49,11 +49,20 @@ protected:
// Launched just before start(), a job can use it to prepare internals // Launched just before start(), a job can use it to prepare internals
virtual void prepare() {} virtual void prepare() {}
// The method where the actual work of the job should be defined.
virtual void process() = 0;
// Launched when the job is finished. It refreshes the 3Dscene by def. // Launched when the job is finished. It refreshes the 3Dscene by def.
virtual void finalize() { m_finalized = true; } virtual void finalize() { m_finalized = true; }
virtual void on_exception(const std::exception_ptr &) {} // Exceptions occuring in process() are redirected from the worker thread
// into the main (UI) thread. This method is called from the main thread and
// can be overriden to handle these exceptions.
virtual void on_exception(const std::exception_ptr &eptr)
{
if (eptr) std::rethrow_exception(eptr);
}
public: public:
Job(std::shared_ptr<ProgressIndicator> pri); Job(std::shared_ptr<ProgressIndicator> pri);
@ -65,8 +74,6 @@ public:
Job &operator=(const Job &) = delete; Job &operator=(const Job &) = delete;
Job &operator=(Job &&) = delete; Job &operator=(Job &&) = delete;
virtual void process() = 0;
void start(); void start();
// To wait for the running job and join the threads. False is // To wait for the running job and join the threads. False is

View file

@ -27,9 +27,9 @@ class RotoptimizeJob : public PlaterJob
"structures.\nNote that this method will try to find the best surface of the object " "structures.\nNote that this method will try to find the best surface of the object "
"for touching the print bed if no elevation is set.")}, "for touching the print bed if no elevation is set.")},
// Just a min area bounding box that is done for all methods anyway. // Just a min area bounding box that is done for all methods anyway.
{L("Smallest bounding box (Z axis only)"), {L("Lowest Z height"),
nullptr, sla::find_min_z_height_rotation,
L("Rotate the object only in Z axis to have the smallest bounding box.")}}; L("Rotate the model to have the lowest z height for faster print time.")}};
size_t m_method_id = 0; size_t m_method_id = 0;
float m_accuracy = 0.75; float m_accuracy = 0.75;
@ -48,14 +48,14 @@ class RotoptimizeJob : public PlaterJob
protected: protected:
void prepare() override; void prepare() override;
void process() override;
public: public:
RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater) RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: PlaterJob{std::move(pri), plater} : PlaterJob{std::move(pri), plater}
{} {}
void process() override;
void finalize() override; void finalize() override;
static constexpr size_t get_methods_count() { return std::size(Methods); } static constexpr size_t get_methods_count() { return std::size(Methods); }

View file

@ -10,18 +10,16 @@ class SLAImportJob : public PlaterJob {
std::unique_ptr<priv> p; std::unique_ptr<priv> p;
protected:
void prepare() override;
void process() override;
void finalize() override;
public: public:
SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater); SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater);
~SLAImportJob(); ~SLAImportJob();
void process() override;
void reset(); void reset();
protected:
void prepare() override;
void finalize() override;
}; };
}} // namespace Slic3r::GUI }} // namespace Slic3r::GUI

View file

@ -4,6 +4,7 @@
#include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/TriangleMeshSlicer.hpp"
#include "libslic3r/ClipperUtils.hpp" #include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Model.hpp"
#include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Camera.hpp"
@ -225,7 +226,7 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
// Also, remove anything below the bed (sinking objects). // Also, remove anything below the bed (sinking objects).
for (i=0; i<hits.size(); ++i) { for (i=0; i<hits.size(); ++i) {
Vec3d transformed_hit = trafo * hits[i].position(); Vec3d transformed_hit = trafo * hits[i].position();
if (transformed_hit.z() >= 0. && if (transformed_hit.z() >= SINKING_Z_THRESHOLD &&
(! clipping_plane || ! clipping_plane->is_point_clipped(transformed_hit))) (! clipping_plane || ! clipping_plane->is_point_clipped(transformed_hit)))
break; break;
} }

View file

@ -47,7 +47,7 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat
_u8L("You have just added a G-code for color change, but its value is empty.\n" _u8L("You have just added a G-code for color change, but its value is empty.\n"
"To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
{NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotification, 10, {NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotification, 10,
_u8L("This model doesn't allow to automatically add the color changes") }, _u8L("No color change event was added to the print. The print does not look like a sign.") },
{NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, {NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10,
_u8L("Desktop integration was successful.") }, _u8L("Desktop integration was successful.") },
{NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotification, 10, {NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotification, 10,

View file

@ -393,7 +393,7 @@ private:
{ {
public: public:
ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { set_percentage(percentage); } ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { }
virtual void set_percentage(float percent) { m_percentage = percent; } virtual void set_percentage(float percent) { m_percentage = percent; }
protected: protected:
virtual void init() override; virtual void init() override;
@ -436,6 +436,7 @@ private:
, m_file_size(filesize) , m_file_size(filesize)
{ {
m_has_cancel_button = true; m_has_cancel_button = true;
set_percentage(percentage);
} }
static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return /*"[" + std::to_string(id) + "] " + */filename + " -> " + host; } static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return /*"[" + std::to_string(id) + "] " + */filename + " -> " + host; }
void set_percentage(float percent) override; void set_percentage(float percent) override;

View file

@ -1643,7 +1643,7 @@ struct Plater::priv
BoundingBox scaled_bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const;
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool used_inches = false); std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool used_inches = false);
std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false); std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool force_center_on_bed = false);
wxString get_export_file(GUI::FileType file_type); wxString get_export_file(GUI::FileType file_type);
@ -2334,7 +2334,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
else { else {
model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion)); model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion));
for (auto obj : model.objects) for (auto obj : model.objects)
if (obj->name.empty()) if (obj->name.empty() ||
obj->name.find_first_of("/") != std::string::npos) // When file is imported from Fusion360 the path containes "/" instead of "\\" (see https://github.com/prusa3d/PrusaSlicer/issues/6803)
// But read_from_file doesn't support that direction separator and as a result object name containes full path
obj->name = fs::path(obj->input_file).filename().string(); obj->name = fs::path(obj->input_file).filename().string();
} }
} catch (const ConfigurationError &e) { } catch (const ConfigurationError &e) {
@ -2360,25 +2362,23 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// Convert even if the object is big. // Convert even if the object is big.
convert_from_imperial_units(model, false); convert_from_imperial_units(model, false);
else if (model.looks_like_saved_in_meters()) { else if (model.looks_like_saved_in_meters()) {
//wxMessageDialog msg_dlg(q, format_wxstr(_L_PLURAL(
MessageDialog msg_dlg(q, format_wxstr(_L_PLURAL( MessageDialog msg_dlg(q, format_wxstr(_L_PLURAL(
"The object in file %s looks like saved in meters.\n" "The dimensions of the object from file %s seem to be defined in meters.\n"
"Should I consider it as a saved in meters and convert it?", "The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
"Some objects in file %s look like saved in meters.\n" "The dimensions of some objects from file %s seem to be defined in meters.\n"
"Should I consider them as a saved in meters and convert them?", model.objects.size()), from_path(filename)) + "\n", "The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
_L("The object appears to be saved in meters"), wxICON_WARNING | wxYES | wxNO); _L("The object is too small"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES) if (msg_dlg.ShowModal() == wxID_YES)
//FIXME up-scale only the small parts? //FIXME up-scale only the small parts?
model.convert_from_meters(true); model.convert_from_meters(true);
} }
else if (model.looks_like_imperial_units()) { else if (model.looks_like_imperial_units()) {
//wxMessageDialog msg_dlg(q, format_wxstr(_L_PLURAL(
MessageDialog msg_dlg(q, format_wxstr(_L_PLURAL( MessageDialog msg_dlg(q, format_wxstr(_L_PLURAL(
"The object in file %s looks like saved in inches.\n" "The dimensions of the object from file %s seem to be defined in inches.\n"
"Should I consider it as a saved in inches and convert it?", "The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
"Some objects in file %s look like saved in inches.\n" "The dimensions of some objects from file %s seem to be defined in inches.\n"
"Should I consider them as a saved in inches and convert them?", model.objects.size()), from_path(filename)) + "\n", "The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
_L("The object appears to be saved in inches"), wxICON_WARNING | wxYES | wxNO); _L("The object is too small"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES) if (msg_dlg.ShowModal() == wxID_YES)
//FIXME up-scale only the small parts? //FIXME up-scale only the small parts?
convert_from_imperial_units(model, true); convert_from_imperial_units(model, true);
@ -2426,7 +2426,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
if (one_by_one) { if (one_by_one) {
auto loaded_idxs = load_model_objects(model.objects, is_project_file); auto loaded_idxs = load_model_objects(model.objects, is_project_file, !is_project_file);
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
} else { } else {
// This must be an .stl or .obj file, which may contain a maximum of one volume. // This must be an .stl or .obj file, which may contain a maximum of one volume.
@ -2481,7 +2481,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// #define AUTOPLACEMENT_ON_LOAD // #define AUTOPLACEMENT_ON_LOAD
std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z) std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z, bool force_center_on_bed)
{ {
const BoundingBoxf bed_shape = bed_shape_bb(); const BoundingBoxf bed_shape = bed_shape_bb();
const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast<double>(), 1.0) - 2.0 * Vec3d::Ones(); const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast<double>(), 1.0) - 2.0 * Vec3d::Ones();
@ -2541,6 +2541,9 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode
object->ensure_on_bed(allow_negative_z); object->ensure_on_bed(allow_negative_z);
} }
if (force_center_on_bed)
model.center_instances_around_point(bed_shape.center());
#ifdef AUTOPLACEMENT_ON_LOAD #ifdef AUTOPLACEMENT_ON_LOAD
// FIXME distance should be a config value ///////////////////////////////// // FIXME distance should be a config value /////////////////////////////////
auto min_obj_distance = static_cast<coord_t>(6/SCALING_FACTOR); auto min_obj_distance = static_cast<coord_t>(6/SCALING_FACTOR);
@ -5902,6 +5905,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
p->sidebar->update_searcher(); p->sidebar->update_searcher();
p->sidebar->show_sliced_info_sizer(false); p->sidebar->show_sliced_info_sizer(false);
p->reset_gcode_toolpaths(); p->reset_gcode_toolpaths();
p->view3D->get_canvas3d()->reset_sequential_print_clearance();
} }
else if (opt_key == "bed_shape" || opt_key == "bed_custom_texture" || opt_key == "bed_custom_model") { else if (opt_key == "bed_shape" || opt_key == "bed_custom_texture" || opt_key == "bed_custom_model") {
bed_shape_changed = true; bed_shape_changed = true;

View file

@ -7,6 +7,7 @@
#include "libslic3r/AppConfig.hpp" #include "libslic3r/AppConfig.hpp"
#include <wx/notebook.h> #include <wx/notebook.h>
#include "Notebook.hpp" #include "Notebook.hpp"
#include "ButtonsDescription.hpp"
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
@ -395,7 +396,8 @@ void PreferencesDialog::build(size_t selected_tab)
auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK); this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK);
wxGetApp().UpdateDlgDarkUI(this, true); for (int id : {wxID_OK, wxID_CANCEL})
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(FindWindowById(id, this)));
sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM | wxTOP, 10); sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM | wxTOP, 10);
@ -638,32 +640,7 @@ void PreferencesDialog::create_settings_text_color_widget()
if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT);
wxSizer* sizer = new wxStaticBoxSizer(stb, wxVERTICAL); wxSizer* sizer = new wxStaticBoxSizer(stb, wxVERTICAL);
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(2, 5, 5); ButtonsDescription::FillSizerWithTextColorDescriptions(sizer, parent, &m_sys_colour, &m_mod_colour);
sizer->Add(grid_sizer, 0, wxEXPAND);
auto sys_label = new wxStaticText(parent, wxID_ANY, _L("Value is the same as the system value"));
sys_label->SetForegroundColour(wxGetApp().get_label_clr_sys());
m_sys_colour = new wxColourPickerCtrl(parent, wxID_ANY, wxGetApp().get_label_clr_sys());
wxGetApp().UpdateDarkUI(m_sys_colour->GetPickerCtrl(), true);
m_sys_colour->Bind(wxEVT_COLOURPICKER_CHANGED, [this, sys_label](wxCommandEvent&) {
sys_label->SetForegroundColour(m_sys_colour->GetColour());
sys_label->Refresh();
});
grid_sizer->Add(m_sys_colour, 0, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(sys_label, 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
auto mod_label = new wxStaticText(parent, 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());
m_mod_colour = new wxColourPickerCtrl(parent, wxID_ANY, wxGetApp().get_label_clr_modified());
wxGetApp().UpdateDarkUI(m_mod_colour->GetPickerCtrl(), true);
m_mod_colour->Bind(wxEVT_COLOURPICKER_CHANGED, [this, mod_label](wxCommandEvent&) {
mod_label->SetForegroundColour(m_mod_colour->GetColour());
mod_label->Refresh();
});
grid_sizer->Add(m_mod_colour, 0, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(mod_label, 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());
} }

View file

@ -634,6 +634,12 @@ PlaterPresetComboBox::~PlaterPresetComboBox()
edit_btn->Destroy(); edit_btn->Destroy();
} }
static void run_wizard(ConfigWizard::StartPage sp)
{
if (wxGetApp().check_and_save_current_preset_changes())
wxGetApp().run_wizard(ConfigWizard::RR_USER, sp);
}
void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt) void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt)
{ {
auto selected_item = evt.GetSelection(); auto selected_item = evt.GetSelection();
@ -653,7 +659,7 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt)
case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break;
default: break; default: break;
} }
wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); wxTheApp->CallAfter([sp]() { run_wizard(sp); });
} }
return; return;
} }
@ -685,7 +691,7 @@ void PlaterPresetComboBox::show_add_menu()
append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "", append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "",
[](wxCommandEvent&) { [](wxCommandEvent&) {
wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); wxTheApp->CallAfter([]() { run_wizard(ConfigWizard::SP_PRINTERS); });
}, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); }, "edit_uni", menu, []() { return true; }, wxGetApp().plater());
append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "", append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "",
@ -715,7 +721,7 @@ void PlaterPresetComboBox::show_edit_menu()
else else
append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "", append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "",
[](wxCommandEvent&) { [](wxCommandEvent&) {
wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); wxTheApp->CallAfter([]() { run_wizard(ConfigWizard::SP_PRINTERS); });
}, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); }, "edit_uni", menu, []() { return true; }, wxGetApp().plater());
append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "", append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "",
@ -918,7 +924,7 @@ void TabPresetComboBox::OnSelect(wxCommandEvent &evt)
this->SetSelection(m_last_selected); this->SetSelection(m_last_selected);
if (marker == LABEL_ITEM_WIZARD_PRINTERS) if (marker == LABEL_ITEM_WIZARD_PRINTERS)
wxTheApp->CallAfter([this]() { wxTheApp->CallAfter([this]() {
wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); run_wizard(ConfigWizard::SP_PRINTERS);
// update combobox if its parent is a PhysicalPrinterDialog // update combobox if its parent is a PhysicalPrinterDialog
PhysicalPrinterDialog* parent = dynamic_cast<PhysicalPrinterDialog*>(this->GetParent()); PhysicalPrinterDialog* parent = dynamic_cast<PhysicalPrinterDialog*>(this->GetParent());

View file

@ -1518,11 +1518,11 @@ void TabPrint::build()
optgroup->append_single_option_line("support_material_auto", category_path + "auto-generated-supports"); optgroup->append_single_option_line("support_material_auto", category_path + "auto-generated-supports");
optgroup->append_single_option_line("support_material_threshold", category_path + "overhang-threshold"); optgroup->append_single_option_line("support_material_threshold", category_path + "overhang-threshold");
optgroup->append_single_option_line("support_material_enforce_layers", category_path + "enforce-support-for-the-first"); optgroup->append_single_option_line("support_material_enforce_layers", category_path + "enforce-support-for-the-first");
optgroup->append_single_option_line("raft_first_layer_density", category_path + "raft-first-layer-density");
optgroup->append_single_option_line("raft_first_layer_expansion", category_path + "raft-first-layer-expansion");
optgroup = page->new_optgroup(L("Raft")); optgroup = page->new_optgroup(L("Raft"));
optgroup->append_single_option_line("raft_layers", category_path + "raft-layers"); optgroup->append_single_option_line("raft_layers", category_path + "raft-layers");
optgroup->append_single_option_line("raft_first_layer_density", category_path + "raft-first-layer-density");
optgroup->append_single_option_line("raft_first_layer_expansion", category_path + "raft-first-layer-expansion");
optgroup->append_single_option_line("raft_contact_distance"); optgroup->append_single_option_line("raft_contact_distance");
optgroup->append_single_option_line("raft_expansion"); optgroup->append_single_option_line("raft_expansion");
@ -1747,14 +1747,14 @@ bool Tab::validate_custom_gcode(const wxString& title, const std::string& gcode)
std::vector<std::string> tags; std::vector<std::string> tags;
bool invalid = GCodeProcessor::contains_reserved_tags(gcode, 5, tags); bool invalid = GCodeProcessor::contains_reserved_tags(gcode, 5, tags);
if (invalid) { if (invalid) {
wxString reports = _L_PLURAL("The following line", "The following lines", tags.size()); std::string lines = ":\n";
reports += ":\n"; for (const std::string& keyword : tags)
for (const std::string& keyword : tags) { lines += ";" + keyword + "\n";
reports += ";" + keyword + "\n"; wxString reports = format_wxstr(
} _L_PLURAL("The following line %s contains reserved keywords.\nPlease remove it, as it may cause problems in G-code visualization and printing time estimation.",
reports += _L("contain reserved keywords.") + "\n"; "The following lines %s contain reserved keywords.\nPlease remove them, as they may cause problems in G-code visualization and printing time estimation.",
reports += _L("Please remove them, as they may cause problems in g-code visualization and printing time estimation."); tags.size()),
lines);
//wxMessageDialog dialog(wxGetApp().mainframe, reports, _L("Found reserved keywords in") + " " + _(title), wxICON_WARNING | wxOK); //wxMessageDialog dialog(wxGetApp().mainframe, reports, _L("Found reserved keywords in") + " " + _(title), wxICON_WARNING | wxOK);
MessageDialog dialog(wxGetApp().mainframe, reports, _L("Found reserved keywords in") + " " + _(title), wxICON_WARNING | wxOK); MessageDialog dialog(wxGetApp().mainframe, reports, _L("Found reserved keywords in") + " " + _(title), wxICON_WARNING | wxOK);
dialog.ShowModal(); dialog.ShowModal();