Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_retract_acceleration

This commit is contained in:
enricoturri1966 2021-08-26 08:42:04 +02:00
commit 8f3468030a
87 changed files with 2018 additions and 850 deletions

View File

@ -77,7 +77,7 @@ IndentWidth: 4
IndentWrappedFunctionNames: false IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave JavaScriptQuotes: Leave
JavaScriptWrapImports: true JavaScriptWrapImports: true
KeepLineBreaksForNonEmptyLines: false #KeepLineBreaksForNonEmptyLines: false
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: '' MacroBlockBegin: ''
MacroBlockEnd: '' MacroBlockEnd: ''

3
.gitignore vendored
View File

@ -11,6 +11,9 @@ MANIFEST.bak
xs/MANIFEST.bak xs/MANIFEST.bak
xs/assertlib* xs/assertlib*
.init_bundle.ini .init_bundle.ini
.vs/*
local-lib local-lib
/src/TAGS /src/TAGS
/.vscode/ /.vscode/
build-linux/*
deps/build-linux/*

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)
@ -474,6 +481,7 @@ add_custom_target(gettext_make_pot
COMMAND xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --debug COMMAND xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --debug
-f "${L10N_DIR}/list.txt" -f "${L10N_DIR}/list.txt"
-o "${L10N_DIR}/PrusaSlicer.pot" -o "${L10N_DIR}/PrusaSlicer.pot"
COMMAND hintsToPot ${SLIC3R_RESOURCES_DIR} ${L10N_DIR}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
COMMENT "Generate pot file from strings in the source tree" COMMENT "Generate pot file from strings in the source tree"
) )
@ -546,6 +554,8 @@ endfunction()
add_subdirectory(src) add_subdirectory(src)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT PrusaSlicer_app_console) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT PrusaSlicer_app_console)
add_dependencies(gettext_make_pot hintsToPot)
# Perl bindings, currently only used for the unit / integration tests of libslic3r. # Perl bindings, currently only used for the unit / integration tests of libslic3r.
# Also runs the unit / integration tests. # Also runs the unit / integration tests.
#FIXME Port the tests into C++ to finally get rid of the Perl! #FIXME Port the tests into C++ to finally get rid of the Perl!

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)

View File

@ -49,7 +49,13 @@
# Algorithm shows hint only if ALL enabled tags are affirmative. (so never do enabled_tags = FFF; SLA;) # Algorithm shows hint only if ALL enabled tags are affirmative. (so never do enabled_tags = FFF; SLA;)
# Algorithm shows hint only if not in all disabled tags. # Algorithm shows hint only if not in all disabled tags.
# if there are both disabled and preferred, only preferred that are not in disabled are valid. # if there are both disabled and preferred, only preferred that are not in disabled are valid.
#
#
# Notifications shows in random order, already shown notifications are saved at cache/hints.cereal (as binary - human non-readable)
# You can affect random ordering by seting weigh
# weight = 5
# Weight must be larger or equal to 1. Default weight is 1.
# Weight defines probability as weight : sum_of_all_weights.
[hint:Fuzzy skin] [hint:Fuzzy skin]
text = Fuzzy skin\nDid you know that you can create rough fibre-like texture on the sides of your models using the<a>Fuzzy skin</a>feature? You can also use modifiers to apply fuzzy-skin only to a portion of your model. text = Fuzzy skin\nDid you know that you can create rough fibre-like texture on the sides of your models using the<a>Fuzzy skin</a>feature? You can also use modifiers to apply fuzzy-skin only to a portion of your model.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,21 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<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" <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"
viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve"> viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve">
<style type="text/css"> <style 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 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
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 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"
style="fill:#ed6b21;"/>
<g id="XMLID_1_"> <g id="XMLID_1_">
<g> <g>
</g> </g>
<g> <g>
<polyline class="st1" points="113.6,84.5 164.3,18.3 164.3,18.3 "/> <polyline class="st1" points="113.6,84.5 164.3,18.3 164.3,18.3 "/>
<polyline class="st1" points="216.4,84.5 164.3,18.3 164.3,18.3 "/> <polyline class="st1" points="216.4,84.5 164.3,18.3 164.3,18.3 "/>
</g> </g>
</g> </g>
<line class="st1" x1="164.3" y1="263.3" x2="164.3" y2="18.3"/> <line class="st1" x1="164.3" y1="263.3" x2="164.3" y2="18.3"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

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,14 @@
#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.x, v_position.y, v_position.z, 1.0);
// the following line leads to crash on some Intel graphics card
//gl_Position = gl_ModelViewProjectionMatrix * vec4(v_position, 1.0);
tex_coords = v_tex_coords; tex_coords = v_tex_coords;
} }

View File

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

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

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

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

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

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

View File

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 2.8.12)
project(imgui) 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

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

View File

@ -71,12 +71,16 @@ static ConstPrintObjectPtrs get_top_level_objects_with_brim(const Print &print,
Polygons islands; Polygons islands;
ConstPrintObjectPtrs island_to_object; ConstPrintObjectPtrs island_to_object;
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) { for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
const PrintObject *object = print.objects()[print_object_idx];
if (! object->has_brim())
continue;
Polygons islands_object; Polygons islands_object;
islands_object.reserve(bottom_layers_expolygons[print_object_idx].size()); islands_object.reserve(bottom_layers_expolygons[print_object_idx].size());
for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx]) for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx])
islands_object.emplace_back(ex_poly.contour); islands_object.emplace_back(ex_poly.contour);
const PrintObject *object = print.objects()[print_object_idx];
islands.reserve(islands.size() + object->instances().size() * islands_object.size()); islands.reserve(islands.size() + object->instances().size() * islands_object.size());
for (const PrintInstance &instance : object->instances()) for (const PrintInstance &instance : object->instances())
for (Polygon &poly : islands_object) { for (Polygon &poly : islands_object) {

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

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

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;
}; };
@ -956,9 +956,26 @@ void ModelObject::center_around_origin(bool include_modifiers)
void ModelObject::ensure_on_bed(bool allow_negative_z) void ModelObject::ensure_on_bed(bool allow_negative_z)
{ {
const double min_z = get_min_z(); double z_offset = 0.0;
if (!allow_negative_z || min_z > SINKING_Z_THRESHOLD)
translate_instances({ 0.0, 0.0, -min_z }); if (allow_negative_z) {
if (parts_count() == 1) {
const double min_z = get_min_z();
const double max_z = get_max_z();
if (min_z >= SINKING_Z_THRESHOLD || max_z < 0.0)
z_offset = -min_z;
}
else {
const double max_z = get_max_z();
if (max_z < SINKING_MIN_Z_THRESHOLD)
z_offset = SINKING_MIN_Z_THRESHOLD - max_z;
}
}
else
z_offset = -get_min_z();
if (z_offset != 0.0)
translate_instances(z_offset * Vec3d::UnitZ());
} }
void ModelObject::translate_instances(const Vec3d& vector) void ModelObject::translate_instances(const Vec3d& vector)
@ -1114,6 +1131,15 @@ size_t ModelObject::facets_count() const
return num; return num;
} }
size_t ModelObject::parts_count() const
{
size_t num = 0;
for (const ModelVolume* v : this->volumes)
if (v->is_model_part())
++num;
return num;
}
bool ModelObject::needed_repair() const bool ModelObject::needed_repair() const
{ {
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
@ -1429,6 +1455,19 @@ double ModelObject::get_min_z() const
} }
} }
double ModelObject::get_max_z() const
{
if (instances.empty())
return 0.0;
else {
double max_z = -DBL_MAX;
for (size_t i = 0; i < instances.size(); ++i) {
max_z = std::max(max_z, get_instance_max_z(i));
}
return max_z;
}
}
double ModelObject::get_instance_min_z(size_t instance_idx) const double ModelObject::get_instance_min_z(size_t instance_idx) const
{ {
double min_z = DBL_MAX; double min_z = DBL_MAX;
@ -1450,6 +1489,27 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const
return min_z + inst->get_offset(Z); return min_z + inst->get_offset(Z);
} }
double ModelObject::get_instance_max_z(size_t instance_idx) const
{
double max_z = -DBL_MAX;
const ModelInstance* inst = instances[instance_idx];
const Transform3d& mi = inst->get_matrix(true);
for (const ModelVolume* v : volumes) {
if (!v->is_model_part())
continue;
const Transform3d mv = mi * v->get_matrix();
const TriangleMesh& hull = v->get_convex_hull();
for (const stl_facet& facet : hull.stl.facet_start)
for (int i = 0; i < 3; ++i)
max_z = std::max(max_z, (mv * facet.vertex[i].cast<double>()).z());
}
return max_z + inst->get_offset(Z);
}
unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume) unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume)
{ {
unsigned int num_printable = 0; unsigned int num_printable = 0;

View File

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

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

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

View File

@ -371,7 +371,7 @@ public:
const Preset& get_edited_preset() const { return m_edited_preset; } const Preset& get_edited_preset() const { return m_edited_preset; }
// Return the last saved preset. // Return the last saved preset.
const Preset& get_saved_preset() const { return m_saved_preset; } // const Preset& get_saved_preset() const { return m_saved_preset; }
// Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist. // Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist.
PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const; PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const;
@ -395,7 +395,7 @@ public:
void discard_current_changes() { void discard_current_changes() {
m_presets[m_idx_selected].reset_dirty(); m_presets[m_idx_selected].reset_dirty();
m_edited_preset = m_presets[m_idx_selected]; m_edited_preset = m_presets[m_idx_selected];
update_saved_preset_from_current_preset(); // update_saved_preset_from_current_preset();
} }
// Return a preset by its name. If the preset is active, a temporary copy is returned. // Return a preset by its name. If the preset is active, a temporary copy is returned.
@ -463,7 +463,8 @@ public:
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); } 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(), &m_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;

View File

@ -1477,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

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

View File

@ -271,7 +271,11 @@ public:
// Centering offset of the sliced mesh from the scaled and rotated mesh of the model. // Centering offset of the sliced mesh from the scaled and rotated mesh of the model.
const Point& center_offset() const { return m_center_offset; } const Point& center_offset() const { return m_center_offset; }
bool has_brim() const { return this->config().brim_type != btNoBrim && this->config().brim_width.value > 0.; } bool has_brim() const {
return this->config().brim_type != btNoBrim
&& this->config().brim_width.value > 0.
&& ! this->has_raft();
}
// This is the *total* layer count (including support layers) // This is the *total* layer count (including support layers)
// this value is not supposed to be compared with Layer::id // this value is not supposed to be compared with Layer::id
@ -321,7 +325,7 @@ public:
bool has_raft() const { return m_config.raft_layers > 0; } bool has_raft() const { return m_config.raft_layers > 0; }
bool has_support_material() const { return this->has_support() || this->has_raft(); } bool has_support_material() const { return this->has_support() || this->has_raft(); }
// Checks if the model object is painted using the multi-material painting gizmo. // Checks if the model object is painted using the multi-material painting gizmo.
bool is_mm_painted() const { return this->model_object()->is_mm_painted(); }; bool is_mm_painted() const { return this->model_object()->is_mm_painted(); }
// returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions)
std::vector<unsigned int> object_extruders() const; std::vector<unsigned int> object_extruders() const;

View File

@ -216,22 +216,25 @@ static t_config_option_keys print_config_diffs(
const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; 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

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

View File

@ -535,7 +535,6 @@ bool PrintObject::invalidate_state_by_config_options(
steps.emplace_back(posPerimeters); steps.emplace_back(posPerimeters);
} else if ( } else if (
opt_key == "layer_height" opt_key == "layer_height"
|| opt_key == "first_layer_height"
|| opt_key == "mmu_segmented_region_max_width" || opt_key == "mmu_segmented_region_max_width"
|| opt_key == "raft_layers" || opt_key == "raft_layers"
|| opt_key == "raft_contact_distance" || opt_key == "raft_contact_distance"

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

@ -7,13 +7,14 @@
using namespace Slic3r; using namespace Slic3r;
// only private namespace not neccessary be in hpp // only private namespace not neccessary be in .hpp
namespace QuadricEdgeCollapse { namespace QuadricEdgeCollapse {
using Vertices = std::vector<stl_vertex>; using Vertices = std::vector<stl_vertex>;
using Triangle = stl_triangle_vertex_indices; using Triangle = stl_triangle_vertex_indices;
using Indices = std::vector<stl_triangle_vertex_indices>; using Indices = std::vector<stl_triangle_vertex_indices>;
using SymMat = SimplifyMesh::implementation::SymetricMatrix<double>; using SymMat = SimplifyMesh::implementation::SymetricMatrix<double>;
using ThrowOnCancel = std::function<void(void)>;
using StatusFn = std::function<void(int)>;
// smallest error caused by edges, identify smallest edge in triangle // smallest error caused by edges, identify smallest edge in triangle
struct Error struct Error
{ {
@ -74,12 +75,14 @@ namespace QuadricEdgeCollapse {
// calculate error for vertex and quadrics, triangle quadrics and triangle vertex give zero, only pozitive number // calculate error for vertex and quadrics, triangle quadrics and triangle vertex give zero, only pozitive number
double vertex_error(const SymMat &q, const Vec3d &vertex); double vertex_error(const SymMat &q, const Vec3d &vertex);
SymMat create_quadric(const Triangle &t, const Vec3d& n, const Vertices &vertices); SymMat create_quadric(const Triangle &t, const Vec3d& n, const Vertices &vertices);
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors> init(const indexed_triangle_set &its); std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors>
init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn);
std::optional<uint32_t> find_triangle_index1(uint32_t vi, const VertexInfo& v_info, std::optional<uint32_t> find_triangle_index1(uint32_t vi, const VertexInfo& v_info,
uint32_t ti, const EdgeInfos& e_infos, const Indices& indices); uint32_t ti, const EdgeInfos& e_infos, const Indices& indices);
bool is_flipped(const Vec3f &new_vertex, uint32_t ti0, uint32_t ti1, const VertexInfo& v_info, bool is_flipped(const Vec3f &new_vertex, uint32_t ti0, uint32_t ti1, const VertexInfo& v_info,
const TriangleInfos &t_infos, const EdgeInfos &e_infos, const indexed_triangle_set &its); const TriangleInfos &t_infos, const EdgeInfos &e_infos, const indexed_triangle_set &its);
bool degenerate(uint32_t vi, uint32_t ti0, uint32_t ti1, const VertexInfo &v_info,
const EdgeInfos &e_infos, const Indices &indices);
// find edge with smallest error in triangle // find edge with smallest error in triangle
Vec3d calculate_3errors(const Triangle &t, const Vertices &vertices, const VertexInfos &v_infos); Vec3d calculate_3errors(const Triangle &t, const Vertices &vertices, const VertexInfos &v_infos);
Error calculate_error(uint32_t ti, const Triangle& t,const Vertices &vertices, const VertexInfos& v_infos, unsigned char& min_index); Error calculate_error(uint32_t ti, const Triangle& t,const Vertices &vertices, const VertexInfos& v_infos, unsigned char& min_index);
@ -88,6 +91,14 @@ namespace QuadricEdgeCollapse {
uint32_t vi0, uint32_t vi1, uint32_t vi_top0, uint32_t vi0, uint32_t vi1, uint32_t vi_top0,
const Triangle &t1, CopyEdgeInfos& infos, EdgeInfos &e_infos1); const Triangle &t1, CopyEdgeInfos& infos, EdgeInfos &e_infos1);
void compact(const VertexInfos &v_infos, const TriangleInfos &t_infos, const EdgeInfos &e_infos, indexed_triangle_set &its); void compact(const VertexInfos &v_infos, const TriangleInfos &t_infos, const EdgeInfos &e_infos, indexed_triangle_set &its);
#ifndef NDEBUG
void store_surround(const char *obj_filename, size_t triangle_index, int depth, const indexed_triangle_set &its,
const VertexInfos &v_infos, const EdgeInfos &e_infos);
bool check_neighbors(const indexed_triangle_set &its, const TriangleInfos &t_infos,
const VertexInfos &v_infos, const EdgeInfos &e_infos);
#endif /* NDEBUG */
} // namespace QuadricEdgeCollapse } // namespace QuadricEdgeCollapse
using namespace QuadricEdgeCollapse; using namespace QuadricEdgeCollapse;
@ -97,7 +108,7 @@ void Slic3r::its_quadric_edge_collapse(
uint32_t triangle_count, uint32_t triangle_count,
float * max_error, float * max_error,
std::function<void(void)> throw_on_cancel, std::function<void(void)> throw_on_cancel,
std::function<void(int)> statusfn) std::function<void(int)> status_fn)
{ {
// constants --> may be move to config // constants --> may be move to config
const int status_init_size = 10; // in percents const int status_init_size = 10; // in percents
@ -108,15 +119,22 @@ void Slic3r::its_quadric_edge_collapse(
float maximal_error = (max_error == nullptr)? std::numeric_limits<float>::max() : *max_error; float maximal_error = (max_error == nullptr)? std::numeric_limits<float>::max() : *max_error;
if (maximal_error <= 0.f) return; if (maximal_error <= 0.f) return;
if (throw_on_cancel == nullptr) throw_on_cancel = []() {}; if (throw_on_cancel == nullptr) throw_on_cancel = []() {};
if (statusfn == nullptr) statusfn = [](int) {}; if (status_fn == nullptr) status_fn = [](int) {};
StatusFn init_status_fn = [&](int percent) {
status_fn(std::round((percent * status_init_size) / 100.));
};
TriangleInfos t_infos; // only normals with information about deleted triangle TriangleInfos t_infos; // only normals with information about deleted triangle
VertexInfos v_infos; VertexInfos v_infos;
EdgeInfos e_infos; EdgeInfos e_infos;
Errors errors; Errors errors;
std::tie(t_infos, v_infos, e_infos, errors) = init(its); std::tie(t_infos, v_infos, e_infos, errors) = init(its, throw_on_cancel, init_status_fn);
throw_on_cancel(); throw_on_cancel();
statusfn(status_init_size); status_fn(status_init_size);
//its_store_triangle(its, "triangle.obj", 1182);
//store_surround("triangle_surround1.obj", 1182, 1, its, v_infos, e_infos);
// convert from triangle index to mutable priority queue index // convert from triangle index to mutable priority queue index
std::vector<size_t> ti_2_mpqi(its.indices.size(), {0}); std::vector<size_t> ti_2_mpqi(its.indices.size(), {0});
@ -142,7 +160,7 @@ void Slic3r::its_quadric_edge_collapse(
(double) count_triangle_to_reduce; (double) count_triangle_to_reduce;
double status = status_init_size + (100 - status_init_size) * double status = status_init_size + (100 - status_init_size) *
(1. - reduced); (1. - reduced);
statusfn(static_cast<int>(std::round(status))); status_fn(static_cast<int>(std::round(status)));
}; };
// modulo for update status // modulo for update status
uint32_t status_mod = std::max(uint32_t(16), count_triangle_to_reduce / 100); uint32_t status_mod = std::max(uint32_t(16), count_triangle_to_reduce / 100);
@ -181,6 +199,8 @@ void Slic3r::its_quadric_edge_collapse(
find_triangle_index1(vi1, v_info0, ti0, e_infos, its.indices) : find_triangle_index1(vi1, v_info0, ti0, e_infos, its.indices) :
find_triangle_index1(vi0, v_info1, ti0, e_infos, its.indices) ; find_triangle_index1(vi0, v_info1, ti0, e_infos, its.indices) ;
if (!ti1_opt.has_value() || // edge has only one triangle if (!ti1_opt.has_value() || // edge has only one triangle
degenerate(vi0, ti0, *ti1_opt, v_info1, e_infos, its.indices) ||
degenerate(vi1, ti0, *ti1_opt, v_info0, e_infos, its.indices) ||
is_flipped(new_vertex0, ti0, *ti1_opt, v_info0, t_infos, e_infos, its) || is_flipped(new_vertex0, ti0, *ti1_opt, v_info0, t_infos, e_infos, its) ||
is_flipped(new_vertex0, ti0, *ti1_opt, v_info1, t_infos, e_infos, its)) { is_flipped(new_vertex0, ti0, *ti1_opt, v_info1, t_infos, e_infos, its)) {
// try other triangle's edge // try other triangle's edge
@ -236,8 +256,7 @@ void Slic3r::its_quadric_edge_collapse(
} }
v_info0.q = q; v_info0.q = q;
// fix neighbors // fix neighbors
// vertex index of triangle 0 which is not vi0 nor vi1 // vertex index of triangle 0 which is not vi0 nor vi1
uint32_t vi_top0 = t0[(t_info0.min_index + 2) % 3]; uint32_t vi_top0 = t0[(t_info0.min_index + 2) % 3];
const Triangle &t1 = its.indices[ti1]; const Triangle &t1 = its.indices[ti1];
@ -263,6 +282,7 @@ void Slic3r::its_quadric_edge_collapse(
t_info1.set_deleted(); t_info1.set_deleted();
// triangle counter decrementation // triangle counter decrementation
actual_triangle_count-=2; actual_triangle_count-=2;
assert(check_neighbors(its, t_infos, v_infos, e_infos));
} }
// compact triangle // compact triangle
@ -362,8 +382,16 @@ SymMat QuadricEdgeCollapse::create_quadric(const Triangle &t,
} }
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors> std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors>
QuadricEdgeCollapse::init(const indexed_triangle_set &its) QuadricEdgeCollapse::init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn)
{ {
// change speed of progress bargraph
const int status_normal_size = 25;
const int status_sum_quadric = 25;
const int status_set_offsets = 10;
const int status_calc_errors = 30;
const int status_create_refs = 10;
int status_offset = 0;
TriangleInfos t_infos(its.indices.size()); TriangleInfos t_infos(its.indices.size());
VertexInfos v_infos(its.vertices.size()); VertexInfos v_infos(its.vertices.size());
{ {
@ -377,8 +405,13 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its)
Vec3d normal = create_normal(t, its.vertices); Vec3d normal = create_normal(t, its.vertices);
t_info.n = normal.cast<float>(); t_info.n = normal.cast<float>();
triangle_quadrics[i] = create_quadric(t, normal, its.vertices); triangle_quadrics[i] = create_quadric(t, normal, its.vertices);
if (i % 1000000 == 0) {
throw_on_cancel();
status_fn(status_offset + (i * status_normal_size) / its.indices.size());
}
} }
}); // END parallel for }); // END parallel for
status_offset += status_normal_size;
// sum quadrics // sum quadrics
for (size_t i = 0; i < its.indices.size(); i++) { for (size_t i = 0; i < its.indices.size(); i++) {
@ -389,7 +422,12 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its)
v_info.q += q; v_info.q += q;
++v_info.count; // triangle count ++v_info.count; // triangle count
} }
if (i % 1000000 == 0) {
throw_on_cancel();
status_fn(status_offset + (i * status_sum_quadric) / its.indices.size());
}
} }
status_offset += status_sum_quadric;
} // remove triangle quadrics } // remove triangle quadrics
// set offseted starts // set offseted starts
@ -402,6 +440,10 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its)
} }
assert(its.indices.size() * 3 == triangle_start); assert(its.indices.size() * 3 == triangle_start);
status_offset += status_set_offsets;
throw_on_cancel();
status_fn(status_offset);
// calc error // calc error
Errors errors(its.indices.size()); Errors errors(its.indices.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()), tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
@ -410,8 +452,15 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its)
const Triangle &t = its.indices[i]; const Triangle &t = its.indices[i];
TriangleInfo & t_info = t_infos[i]; TriangleInfo & t_info = t_infos[i];
errors[i] = calculate_error(i, t, its.vertices, v_infos, t_info.min_index); errors[i] = calculate_error(i, t, its.vertices, v_infos, t_info.min_index);
if (i % 1000000 == 0) {
throw_on_cancel();
status_fn(status_offset + (i * status_calc_errors) / its.indices.size());
}
if (i % 1000000 == 0) throw_on_cancel();
} }
}); // END parallel for }); // END parallel for
status_offset += status_calc_errors;
// create reference // create reference
EdgeInfos e_infos(its.indices.size() * 3); EdgeInfos e_infos(its.indices.size() * 3);
@ -426,7 +475,14 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its)
e_info.edge = j; e_info.edge = j;
++v_info.count; ++v_info.count;
} }
if (i % 1000000 == 0) {
throw_on_cancel();
status_fn(status_offset + (i * status_create_refs) / its.indices.size());
}
} }
throw_on_cancel();
status_fn(100);
return {t_infos, v_infos, e_infos, errors}; return {t_infos, v_infos, e_infos, errors};
} }
@ -489,6 +545,28 @@ bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex,
return false; return false;
} }
bool QuadricEdgeCollapse::degenerate(uint32_t vi,
uint32_t ti0,
uint32_t ti1,
const VertexInfo &v_info,
const EdgeInfos & e_infos,
const Indices & indices)
{
// check surround triangle do not contain vertex index
// protect from creation of triangle with two same vertices inside
size_t v_info_end = v_info.start + v_info.count;
for (size_t ei = v_info.start; ei < v_info_end; ++ei) {
assert(ei < e_infos.size());
const EdgeInfo &e_info = e_infos[ei];
if (e_info.t_index == ti0) continue; // ti0 will be deleted
if (e_info.t_index == ti1) continue; // ti1 will be deleted
const Triangle &t = indices[e_info.t_index];
for (size_t i = 0; i < 3; ++i)
if (static_cast<uint32_t>(t[i]) == vi) return true;
}
return false;
}
Vec3d QuadricEdgeCollapse::calculate_3errors(const Triangle & t, Vec3d QuadricEdgeCollapse::calculate_3errors(const Triangle & t,
const Vertices & vertices, const Vertices & vertices,
const VertexInfos &v_infos) const VertexInfos &v_infos)
@ -653,3 +731,115 @@ void QuadricEdgeCollapse::compact(const VertexInfos & v_infos,
} }
its.indices.erase(its.indices.begin() + ti_new, its.indices.end()); its.indices.erase(its.indices.begin() + ti_new, its.indices.end());
} }
#ifndef NDEBUG
// store triangle surrounding to file
void QuadricEdgeCollapse::store_surround(const char *obj_filename,
size_t triangle_index,
int depth,
const indexed_triangle_set &its,
const VertexInfos & v_infos,
const EdgeInfos & e_infos)
{
std::set<size_t> triangles;
// triangle index, depth
using Item = std::pair<size_t, int>;
std::queue<Item> process;
process.push({triangle_index, depth});
while (!process.empty()) {
Item item = process.front();
process.pop();
size_t ti = item.first;
auto it = triangles.find(ti);
if (it != triangles.end()) continue;
triangles.insert(ti);
if (item.second == 0) continue;
const Vec3i &t = its.indices[ti];
for (size_t i = 0; i < 3; ++i) {
const auto &v_info = v_infos[t[i]];
for (size_t d = 0; d < v_info.count; ++d) {
size_t ei = v_info.start + d;
const auto &e_info = e_infos[ei];
auto it = triangles.find(e_info.t_index);
if (it != triangles.end()) continue;
process.push({e_info.t_index, item.second - 1});
}
}
}
std::vector<size_t> trs;
trs.reserve(triangles.size());
for (size_t ti : triangles) trs.push_back(ti);
its_store_triangles(its, obj_filename, trs);
// its_write_obj(its,"original.obj");
}
bool QuadricEdgeCollapse::check_neighbors(const indexed_triangle_set &its,
const TriangleInfos & t_infos,
const VertexInfos & v_infos,
const EdgeInfos & e_infos)
{
VertexInfos v_infos2(v_infos.size());
size_t count_indices = 0;
for (size_t ti = 0; ti < its.indices.size(); ti++) {
if (t_infos[ti].is_deleted()) continue;
++count_indices;
const Triangle &t = its.indices[ti];
for (size_t e = 0; e < 3; e++) {
VertexInfo &v_info = v_infos2[t[e]];
++v_info.count; // triangle count
}
}
uint32_t triangle_start = 0;
for (VertexInfo &v_info : v_infos2) {
v_info.start = triangle_start;
triangle_start += v_info.count;
// set filled vertex to zero
v_info.count = 0;
}
// create reference
EdgeInfos e_infos2(count_indices * 3);
for (size_t ti = 0; ti < its.indices.size(); ti++) {
if (t_infos[ti].is_deleted()) continue;
const Triangle &t = its.indices[ti];
for (size_t j = 0; j < 3; ++j) {
VertexInfo &v_info = v_infos2[t[j]];
size_t ei = v_info.start + v_info.count;
assert(ei < e_infos2.size());
EdgeInfo &e_info = e_infos2[ei];
e_info.t_index = ti;
e_info.edge = j;
++v_info.count;
}
}
for (size_t vi = 0; vi < its.vertices.size(); vi++) {
const VertexInfo &v_info = v_infos[vi];
if (v_info.is_deleted()) continue;
const VertexInfo &v_info2 = v_infos2[vi];
if (v_info.count != v_info2.count) { return false; }
EdgeInfos eis;
eis.reserve(v_info.count);
std::copy(e_infos.begin() + v_info.start,
e_infos.begin() + v_info.start + v_info.count,
std::back_inserter(eis));
auto compare = [](const EdgeInfo &ei1, const EdgeInfo &ei2) {
return ei1.t_index < ei2.t_index;
};
std::sort(eis.begin(), eis.end(), compare);
std::sort(e_infos2.begin() + v_info2.start,
e_infos2.begin() + v_info2.start + v_info2.count, compare);
for (size_t ei = 0; ei < v_info.count; ++ei) {
if (eis[ei].t_index != e_infos2[ei + v_info2.start].t_index) {
return false;
}
}
}
return true;
}
#endif /* NDEBUG */

View File

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

View File

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

View File

@ -4,7 +4,7 @@
#include <boost/container/small_vector.hpp> #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

@ -595,7 +595,7 @@ bool GLVolume::is_sinking() const
bool GLVolume::is_below_printbed() const bool GLVolume::is_below_printbed() const
{ {
return transformed_convex_hull_bounding_box().max(2) < 0.0; return transformed_convex_hull_bounding_box().max.z() < 0.0;
} }
#if ENABLE_SINKING_CONTOURS #if ENABLE_SINKING_CONTOURS

View File

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

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

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

@ -2239,6 +2239,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera());
m_dirty |= mouse3d_controller_applied; m_dirty |= mouse3d_controller_applied;
m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this); m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current();
if (gizmo != nullptr) m_dirty |= gizmo->update_items_state();
if (!m_dirty) if (!m_dirty)
return; return;
@ -2780,11 +2782,10 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt)
void GLCanvas3D::on_render_timer(wxTimerEvent& evt) void GLCanvas3D::on_render_timer(wxTimerEvent& evt)
{ {
// no need to do anything here // no need to wake up idle
// right after this event is recieved, idle event is fired // right after this event, idle event is fired
// m_dirty = true;
//m_dirty = true; // wxWakeUpIdle();
//wxWakeUpIdle();
} }
@ -2802,21 +2803,15 @@ void GLCanvas3D::schedule_extra_frame(int miliseconds)
return; return;
} }
} }
// Start timer int remaining_time = m_render_timer.GetInterval();
int64_t now = timestamp_now();
// Timer is not running // Timer is not running
if (! m_render_timer.IsRunning()) { if (!m_render_timer.IsRunning()) {
m_extra_frame_requested_delayed = miliseconds;
m_render_timer.StartOnce(miliseconds); m_render_timer.StartOnce(miliseconds);
m_render_timer_start = now;
// Timer is running - restart only if new period is shorter than remaning period // Timer is running - restart only if new period is shorter than remaning period
} else { } else {
const int64_t remaining_time = (m_render_timer_start + m_extra_frame_requested_delayed) - now;
if (miliseconds + 20 < remaining_time) { if (miliseconds + 20 < remaining_time) {
m_render_timer.Stop(); m_render_timer.Stop();
m_extra_frame_requested_delayed = miliseconds;
m_render_timer.StartOnce(miliseconds); m_render_timer.StartOnce(miliseconds);
m_render_timer_start = now;
} }
} }
} }
@ -4438,13 +4433,7 @@ bool GLCanvas3D::_init_main_toolbar()
} }
// init arrow // init arrow
BackgroundTexture::Metadata arrow_data; BackgroundTexture::Metadata arrow_data;
arrow_data.filename = "toolbar_arrow.png"; arrow_data.filename = "toolbar_arrow.svg";
// arrow_data.filename = "toolbar_arrow.svg";
//arrow_data.left = 16;
//arrow_data.top = 16;
//arrow_data.right = 16;
//arrow_data.bottom = 16;
arrow_data.left = 0; arrow_data.left = 0;
arrow_data.top = 0; arrow_data.top = 0;
arrow_data.right = 0; arrow_data.right = 0;

View File

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

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

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

@ -87,6 +87,11 @@
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
// Needed for forcing menu icons back under gtk2 and gtk3
#if defined(__WXGTK20__) || defined(__WXGTK3__)
#include <gtk/gtk.h>
#endif
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
@ -799,6 +804,14 @@ bool GUI_App::OnInit()
bool GUI_App::on_init_inner() bool GUI_App::on_init_inner()
{ {
// Forcing back menu icons under gtk2 and gtk3. Solution is based on:
// https://docs.gtk.org/gtk3/class.Settings.html
// see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb
// TODO: Find workaround for GTK4
#if defined(__WXGTK20__) || defined(__WXGTK3__)
g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL);
#endif
// Verify resources path // Verify resources path
const wxString resources_dir = from_u8(Slic3r::resources_dir()); const wxString resources_dir = from_u8(Slic3r::resources_dir());
wxCHECK_MSG(wxDirExists(resources_dir), false, wxCHECK_MSG(wxDirExists(resources_dir), false,
@ -1806,10 +1819,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); local_menu->Append(config_id_base + 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 +
@ -2314,7 +2327,7 @@ wxString GUI_App::current_language_code_safe() const
void GUI_App::open_web_page_localized(const std::string &http_address) void GUI_App::open_web_page_localized(const std::string &http_address)
{ {
wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code_safe()); open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe());
} }
bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page)
@ -2512,6 +2525,23 @@ void GUI_App::check_updates(const bool verbose)
} }
} }
bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/* = 0*/)
{
bool launch = true;
if (get_app_config()->get("suppress_hyperlinks").empty()) {
wxRichMessageDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
dialog.ShowCheckBox(_L("Remember my choice"));
int answer = dialog.ShowModal();
launch = answer == wxID_YES;
get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : "");
}
if (launch)
launch = get_app_config()->get("suppress_hyperlinks") != "1";
return launch && wxLaunchDefaultBrowser(url, flags);
}
// static method accepting a wxWindow object as first parameter // static method accepting a wxWindow object as first parameter
// void warning_catcher{ // void warning_catcher{
// my($self, $message_dialog) = @_; // my($self, $message_dialog) = @_;

View File

@ -261,7 +261,8 @@ public:
void open_preferences(size_t open_on_tab = 0); void open_preferences(size_t open_on_tab = 0);
virtual bool OnExceptionInMainLoop() override; virtual bool OnExceptionInMainLoop() override;
// Calls wxLaunchDefaultBrowser if user confirms in dialog.
bool open_browser_with_warning_dialog(const wxString& url, int flags = 0);
#ifdef __APPLE__ #ifdef __APPLE__
void OSXStoreOpenFiles(const wxArrayString &files) override; void OSXStoreOpenFiles(const wxArrayString &files) override;
// wxWidgets override to get an event on open files. // wxWidgets override to get an event on open files.

View File

@ -1049,7 +1049,7 @@ void ObjectList::key_event(wxKeyEvent& event)
|| event.GetKeyCode() == WXK_BACK || event.GetKeyCode() == WXK_BACK
#endif //__WXOSX__ #endif //__WXOSX__
) { ) {
remove(); wxGetApp().plater()->remove_selected();
} }
else if (event.GetKeyCode() == WXK_F5) else if (event.GetKeyCode() == WXK_F5)
wxGetApp().plater()->reload_all_from_disk(); wxGetApp().plater()->reload_all_from_disk();
@ -1920,16 +1920,15 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
if (vol->is_model_part()) if (vol->is_model_part())
++solid_cnt; ++solid_cnt;
if (volume->is_model_part() && solid_cnt == 1) { if (volume->is_model_part() && solid_cnt == 1) {
Slic3r::GUI::show_error(nullptr, _(L("From Object List You can't delete the last solid part from object."))); Slic3r::GUI::show_error(nullptr, _L("From Object List You can't delete the last solid part from object."));
return false; return false;
} }
take_snapshot(_(L("Delete Subobject"))); take_snapshot(_L("Delete Subobject"));
object->delete_volume(idx); object->delete_volume(idx);
if (object->volumes.size() == 1) if (object->volumes.size() == 1) {
{
const auto last_volume = object->volumes[0]; const auto last_volume = object->volumes[0];
if (!last_volume->config.empty()) { if (!last_volume->config.empty()) {
object->config.apply(last_volume->config); object->config.apply(last_volume->config);
@ -1948,11 +1947,11 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
} }
else if (type == itInstance) { else if (type == itInstance) {
if (object->instances.size() == 1) { if (object->instances.size() == 1) {
Slic3r::GUI::show_error(nullptr, _(L("Last instance of an object cannot be deleted."))); Slic3r::GUI::show_error(nullptr, _L("Last instance of an object cannot be deleted."));
return false; return false;
} }
take_snapshot(_(L("Delete Instance"))); take_snapshot(_L("Delete Instance"));
object->delete_instance(idx); object->delete_instance(idx);
} }
else else
@ -2731,8 +2730,9 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
return; return;
m_prevent_list_events = true; m_prevent_list_events = true;
for (std::vector<ItemForDelete>::const_reverse_iterator item = items_for_delete.rbegin(); item != items_for_delete.rend(); ++item)
{ std::set<size_t> modified_objects_ids;
for (std::vector<ItemForDelete>::const_reverse_iterator item = items_for_delete.rbegin(); item != items_for_delete.rend(); ++item) {
if (!(item->type&(itObject | itVolume | itInstance))) if (!(item->type&(itObject | itVolume | itInstance)))
continue; continue;
if (item->type&itObject) { if (item->type&itObject) {
@ -2742,8 +2742,7 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
else { else {
if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type)) if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type))
continue; continue;
if (item->type&itVolume) if (item->type&itVolume) {
{
m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx)); m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx));
ModelObject* obj = object(item->obj_idx); ModelObject* obj = object(item->obj_idx);
if (obj->volumes.size() == 1) { if (obj->volumes.size() == 1) {
@ -2761,7 +2760,14 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
else else
m_objects_model->Delete(m_objects_model->GetItemByInstanceId(item->obj_idx, item->sub_obj_idx)); m_objects_model->Delete(m_objects_model->GetItemByInstanceId(item->obj_idx, item->sub_obj_idx));
} }
modified_objects_ids.insert(static_cast<size_t>(item->obj_idx));
} }
for (size_t id : modified_objects_ids) {
update_info_items(id);
}
m_prevent_list_events = true; m_prevent_list_events = true;
part_selection_changed(); part_selection_changed();
} }
@ -3404,6 +3410,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 +3436,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);
@ -4005,7 +4023,23 @@ void ObjectList::fix_through_netfabb()
void ObjectList::simplify() void ObjectList::simplify()
{ {
GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); auto plater = wxGetApp().plater();
GLGizmosManager& gizmos_mgr = plater->canvas3D()->get_gizmos_manager();
// Do not simplify when a gizmo is open. There might be issues with updates
// and what is worse, the snapshot time would refer to the internal stack.
auto current_type = gizmos_mgr.get_current_type();
if (current_type == GLGizmosManager::Simplify) {
// close first
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
}else if (current_type != GLGizmosManager::Undefined) {
plater->get_notification_manager()->push_notification(
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
NotificationManager::NotificationLevel::RegularNotification,
_u8L("ERROR: Please close all manipulators available from "
"the left toolbar before start simplify the mesh."));
return;
}
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
} }

View File

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

View File

@ -154,6 +154,9 @@ public:
void update(const UpdateData& data); void update(const UpdateData& data);
// returns True when Gizmo changed its state
bool update_items_state();
void render() { m_tooltip.clear(); on_render(); } void render() { m_tooltip.clear(); on_render(); }
void render_for_picking() { on_render_for_picking(); } void render_for_picking() { on_render_for_picking(); }
void render_input_window(float x, float y, float bottom_limit); void render_input_window(float x, float y, float bottom_limit);
@ -187,6 +190,13 @@ protected:
void render_grabbers_for_picking(const BoundingBoxf3& box) const; void render_grabbers_for_picking(const BoundingBoxf3& box) const;
std::string format(float value, unsigned int decimals) const; std::string format(float value, unsigned int decimals) const;
// Mark gizmo as dirty to Re-Render when idle()
void set_dirty();
private:
// Flag for dirty visible state of Gizmo
// When True then need new rendering
bool m_dirty;
}; };
// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components // Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components

View File

@ -1,34 +1,38 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoSimplify.hpp" #include "GLGizmoSimplify.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/AppConfig.hpp" #include "libslic3r/AppConfig.hpp"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/QuadricEdgeCollapse.hpp" #include "libslic3r/QuadricEdgeCollapse.hpp"
#include <chrono>
#include <thread>
namespace Slic3r::GUI { namespace Slic3r::GUI {
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent, GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
const std::string &icon_filename, const std::string &icon_filename,
unsigned int sprite_id) unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, -1) : GLGizmoBase(parent, icon_filename, -1)
, state(State::settings) , m_state(State::settings)
, is_valid_result(false) , m_is_valid_result(false)
, progress(0) , m_exist_preview(false)
, volume(nullptr) , m_progress(0)
, obj_index(0) , m_volume(nullptr)
, need_reload(false) , m_obj_index(0)
, m_need_reload(false)
, tr_mesh_name(_u8L("Mesh name"))
, tr_triangles(_u8L("Triangles"))
, tr_preview(_u8L("Preview"))
, tr_detail_level(_u8L("Detail level"))
, tr_decimate_ratio(_u8L("Decimate ratio"))
{} {}
GLGizmoSimplify::~GLGizmoSimplify() { GLGizmoSimplify::~GLGizmoSimplify() {
state = State::canceling; m_state = State::canceling;
if (worker.joinable()) worker.join(); if (m_worker.joinable()) m_worker.join();
} }
bool GLGizmoSimplify::on_init() bool GLGizmoSimplify::on_init()
@ -38,7 +42,6 @@ bool GLGizmoSimplify::on_init()
return true; return true;
} }
std::string GLGizmoSimplify::on_get_name() const std::string GLGizmoSimplify::on_get_name() const
{ {
return (_L("Simplify")).ToUTF8().data(); return (_L("Simplify")).ToUTF8().data();
@ -49,14 +52,7 @@ void GLGizmoSimplify::on_render_for_picking() {}
void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit)
{ {
const int min_triangle_count = 4; // tetrahedron
const int max_char_in_name = 25;
create_gui_cfg(); create_gui_cfg();
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse;
m_imgui->begin(on_get_name(), flag);
const Selection &selection = m_parent.get_selection(); const Selection &selection = m_parent.get_selection();
int object_idx = selection.get_object_idx(); int object_idx = selection.get_object_idx();
ModelObject *obj = wxGetApp().plater()->model().objects[object_idx]; ModelObject *obj = wxGetApp().plater()->model().objects[object_idx];
@ -64,159 +60,196 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
// Check selection of new volume // Check selection of new volume
// Do not reselect object when processing // Do not reselect object when processing
if (act_volume != volume && state == State::settings) { if (act_volume != m_volume && m_state == State::settings) {
obj_index = object_idx; // to remember correct object bool change_window_position = (m_volume == nullptr);
volume = act_volume; // select different model
original_its = {}; if (m_volume != nullptr && m_original_its.has_value()) {
const TriangleMesh &tm = volume->mesh(); set_its(*m_original_its);
c.wanted_percent = 50.; // default value }
c.update_percent(tm.its.indices.size());
is_valid_result = false; m_obj_index = object_idx; // to remember correct object
// set window position m_volume = act_volume;
ImVec2 pos = ImGui::GetMousePos(); m_original_its = {};
pos.x -= gui_cfg->window_offset; m_configuration.decimate_ratio = 50.; // default value
pos.y -= gui_cfg->window_offset; m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size());
ImGui::SetWindowPos(pos, ImGuiCond_Always); m_is_valid_result = false;
m_exist_preview = false;
if (change_window_position) {
ImVec2 pos = ImGui::GetMousePos();
pos.x -= m_gui_cfg->window_offset_x;
pos.y -= m_gui_cfg->window_offset_y;
// minimal top left value
ImVec2 tl(m_gui_cfg->window_padding, m_gui_cfg->window_padding);
if (pos.x < tl.x) pos.x = tl.x;
if (pos.y < tl.y) pos.y = tl.y;
// maximal bottom right value
auto parent_size = m_parent.get_canvas_size();
ImVec2 br(
parent_size.get_width() - (2 * m_gui_cfg->window_offset_x + m_gui_cfg->window_padding),
parent_size.get_height() - (2 * m_gui_cfg->window_offset_y + m_gui_cfg->window_padding));
if (pos.x > br.x) pos.x = br.x;
if (pos.y > br.y) pos.y = br.y;
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
}
} }
size_t triangle_count = volume->mesh().its.indices.size(); int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
// already reduced mesh ImGuiWindowFlags_NoCollapse;
if (original_its.has_value()) m_imgui->begin(on_get_name(), flag);
triangle_count = original_its->indices.size();
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mesh name") + ":"); size_t triangle_count = m_volume->mesh().its.indices.size();
ImGui::SameLine(gui_cfg->top_left_width); // already reduced mesh
std::string name = volume->name; if (m_original_its.has_value())
if (name.length() > max_char_in_name) triangle_count = m_original_its->indices.size();
name = name.substr(0, max_char_in_name-3) + "...";
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_mesh_name + ":");
ImGui::SameLine(m_gui_cfg->top_left_width);
std::string name = m_volume->name;
if (name.length() > m_gui_cfg->max_char_in_name)
name = name.substr(0, m_gui_cfg->max_char_in_name - 3) + "...";
m_imgui->text(name); m_imgui->text(name);
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Triangles") + ":"); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":");
ImGui::SameLine(gui_cfg->top_left_width); ImGui::SameLine(m_gui_cfg->top_left_width);
m_imgui->text(std::to_string(triangle_count)); m_imgui->text(std::to_string(triangle_count));
/*
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_preview + ":");
ImGui::SameLine(m_gui_cfg->top_left_width);
if (m_exist_preview) {
m_imgui->text(std::to_string(m_volume->mesh().its.indices.size()));
} else {
m_imgui->text("---");
}*/
ImGui::Separator(); ImGui::Separator();
ImGui::Text(_L("Limit by triangles").c_str()); if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) {
ImGui::SameLine(gui_cfg->bottom_left_width); m_is_valid_result = false;
// First initialization + fix triangle count m_configuration.use_count = !m_configuration.use_count;
if (m_imgui->checkbox("##UseCount", c.use_count)) { }
if (!c.use_count) c.use_error = true; ImGui::SameLine();
is_valid_result = false; m_imgui->disabled_begin(m_configuration.use_count);
ImGui::Text("%s", tr_detail_level.c_str());
std::vector<std::string> reduce_captions = {
static_cast<std::string>(_u8L("Extra high")),
static_cast<std::string>(_u8L("High")),
static_cast<std::string>(_u8L("Medium")),
static_cast<std::string>(_u8L("Low")),
static_cast<std::string>(_u8L("Extra low"))
};
ImGui::SameLine(m_gui_cfg->bottom_left_width);
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
static int reduction = 2;
if(ImGui::SliderInt("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) {
m_is_valid_result = false;
if (reduction < 0) reduction = 0;
if (reduction > 4) reduction = 4;
switch (reduction) {
case 0: m_configuration.max_error = 1e-3f; break;
case 1: m_configuration.max_error = 1e-2f; break;
case 2: m_configuration.max_error = 0.1f; break;
case 3: m_configuration.max_error = 0.5f; break;
case 4: m_configuration.max_error = 1.f; break;
}
}
m_imgui->disabled_end(); // !use_count
if (ImGui::RadioButton("##use_count", m_configuration.use_count)) {
m_is_valid_result = false;
m_configuration.use_count = !m_configuration.use_count;
}
ImGui::SameLine();
// show preview result triangle count (percent)
if (m_need_reload && !m_configuration.use_count) {
m_configuration.wanted_count = static_cast<uint32_t>(m_volume->mesh().its.indices.size());
m_configuration.decimate_ratio =
(1.0f - (m_configuration.wanted_count / (float) triangle_count)) * 100.f;
} }
m_imgui->disabled_begin(!c.use_count); m_imgui->disabled_begin(!m_configuration.use_count);
ImGui::Text(_L("Triangle count").c_str()); ImGui::Text("%s", tr_decimate_ratio.c_str());
ImGui::SameLine(gui_cfg->bottom_left_width); ImGui::SameLine(m_gui_cfg->bottom_left_width);
int wanted_count = c.wanted_count; ImGui::SetNextItemWidth(m_gui_cfg->input_width);
ImGui::SetNextItemWidth(gui_cfg->input_width); const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%":
if (ImGui::SliderInt("##triangle_count", &wanted_count, min_triangle_count, triangle_count, "%d")) { ((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%");
c.wanted_count = static_cast<uint32_t>(wanted_count); if (ImGui::SliderFloat("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) {
if (c.wanted_count < min_triangle_count) m_is_valid_result = false;
c.wanted_count = min_triangle_count; if (m_configuration.decimate_ratio < 0.f)
if (c.wanted_count > triangle_count) m_configuration.decimate_ratio = 0.01f;
c.wanted_count = triangle_count; if (m_configuration.decimate_ratio > 100.f)
c.update_count(triangle_count); m_configuration.decimate_ratio = 100.f;
is_valid_result = false; m_configuration.fix_count_by_ratio(triangle_count);
} }
ImGui::Text(_L("Ratio").c_str());
ImGui::SameLine(gui_cfg->bottom_left_width);
ImGui::SetNextItemWidth(gui_cfg->input_small_width);
const char * precision = (c.wanted_percent > 10)? "%.0f": ((c.wanted_percent > 1)? "%.1f":"%.2f");
float step = (c.wanted_percent > 10)? 1.f: ((c.wanted_percent > 1)? 0.1f : 0.01f);
if (ImGui::InputFloat("%", &c.wanted_percent, step, 10*step, precision)) {
if (c.wanted_percent > 100.f) c.wanted_percent = 100.f;
c.update_percent(triangle_count);
if (c.wanted_count < min_triangle_count) {
c.wanted_count = min_triangle_count;
c.update_count(triangle_count);
}
is_valid_result = false;
}
m_imgui->disabled_end(); // use_count
ImGui::NewLine(); ImGui::NewLine();
ImGui::Text(_L("Limit by error").c_str()); ImGui::SameLine(m_gui_cfg->bottom_left_width);
ImGui::SameLine(gui_cfg->bottom_left_width); ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count);
if (m_imgui->checkbox("##UseError", c.use_error)) { m_imgui->disabled_end(); // use_count
if (!c.use_error) c.use_count = true;
is_valid_result = false;
}
m_imgui->disabled_begin(!c.use_error); if (m_state == State::settings) {
ImGui::Text(_L("Max. error").c_str());
ImGui::SameLine(gui_cfg->bottom_left_width);
ImGui::SetNextItemWidth(gui_cfg->input_small_width);
if (ImGui::InputFloat("##maxError", &c.max_error, 0.01f, .1f, "%.2f")) {
if (c.max_error < 0.f) c.max_error = 0.f;
is_valid_result = false;
}
m_imgui->disabled_end(); // use_error
if (state == State::settings) {
if (m_imgui->button(_L("Cancel"))) { if (m_imgui->button(_L("Cancel"))) {
if (original_its.has_value()) { if (m_original_its.has_value()) {
set_its(*original_its); set_its(*m_original_its);
state = State::close_on_end; m_state = State::close_on_end;
} else { } else {
close(); close();
} }
} }
ImGui::SameLine(gui_cfg->bottom_left_width); ImGui::SameLine(m_gui_cfg->bottom_left_width);
if (m_imgui->button(_L("Preview"))) { if (m_imgui->button(_L("Preview"))) {
state = State::simplifying; m_state = State::preview;
// simplify but not aply on mesh // simplify but not aply on mesh
process(); process();
} }
ImGui::SameLine(); ImGui::SameLine();
if (m_imgui->button(_L("Apply"))) { if (m_imgui->button(_L("Apply"))) {
if (!is_valid_result) { if (!m_is_valid_result) {
state = State::close_on_end; m_state = State::close_on_end;
process(); process();
} else { } else {
// use preview and close // use preview and close
if (m_exist_preview) {
// fix hollowing, sla support points, modifiers, ...
auto plater = wxGetApp().plater();
plater->changed_mesh(m_obj_index);
}
close(); close();
} }
} }
} else { } else {
m_imgui->disabled_begin(state == State::canceling); m_imgui->disabled_begin(m_state == State::canceling);
if (m_imgui->button(_L("Cancel"))) state = State::canceling; if (m_imgui->button(_L("Cancel"))) m_state = State::canceling;
m_imgui->disabled_end(); m_imgui->disabled_end();
ImGui::SameLine(gui_cfg->bottom_left_width); ImGui::SameLine(m_gui_cfg->bottom_left_width);
// draw progress bar // draw progress bar
char buf[32]; char buf[32];
sprintf(buf, L("Process %d / 100"), progress); sprintf(buf, L("Process %d / 100"), m_progress);
ImGui::ProgressBar(progress / 100., ImVec2(gui_cfg->input_width, 0.f), buf); ImGui::ProgressBar(m_progress / 100., ImVec2(m_gui_cfg->input_width, 0.f), buf);
} }
m_imgui->end(); m_imgui->end();
if (need_reload) { if (m_need_reload) {
need_reload = false; m_need_reload = false;
bool close_on_end = (m_state == State::close_on_end);
// Reload visualization of mesh - change VBO, FBO on GPU // Reload visualization of mesh - change VBO, FBO on GPU
m_parent.reload_scene(true); // deactivate gizmo?? m_parent.reload_scene(true);
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager(); // set m_state must be before close() !!!
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); m_state = State::settings;
if (close_on_end) {
if (state == State::close_on_end) {
// fix hollowing, sla support points, modifiers, ... // fix hollowing, sla support points, modifiers, ...
auto plater = wxGetApp().plater(); auto plater = wxGetApp().plater();
plater->changed_mesh(obj_index); // deactivate gizmo?? plater->changed_mesh(m_obj_index);
// changed_mesh cause close(); close();
//close();
} }
// change from simplifying | aply
state = State::settings;
// Fix warning icon in object list // Fix warning icon in object list
wxGetApp().obj_list()->update_item_error_icon(obj_index, -1); wxGetApp().obj_list()->update_item_error_icon(m_obj_index, -1);
} }
} }
void GLGizmoSimplify::close() { void GLGizmoSimplify::close() {
volume = nullptr;
// close gizmo == open it again // close gizmo == open it again
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager(); GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
@ -230,66 +263,60 @@ void GLGizmoSimplify::process()
const char* what() const throw() { return L("Model simplification has been canceled"); } const char* what() const throw() { return L("Model simplification has been canceled"); }
}; };
if (!original_its.has_value()) if (!m_original_its.has_value())
original_its = volume->mesh().its; // copy m_original_its = m_volume->mesh().its; // copy
auto plater = wxGetApp().plater(); auto plater = wxGetApp().plater();
plater->take_snapshot(_L("Simplify ") + volume->name); plater->take_snapshot(_L("Simplify ") + m_volume->name);
plater->clear_before_change_mesh(obj_index); plater->clear_before_change_mesh(m_obj_index);
progress = 0; m_progress = 0;
if (worker.joinable()) worker.join(); if (m_worker.joinable()) m_worker.join();
worker = std::thread([&]() { m_worker = std::thread([this]() {
// store original triangles // store original triangles
uint32_t triangle_count = (c.use_count) ? c.wanted_count : 0; uint32_t triangle_count = (m_configuration.use_count) ? m_configuration.wanted_count : 0;
float max_error = (c.use_error) ? c.max_error : std::numeric_limits<float>::max(); float max_error = (!m_configuration.use_count) ? m_configuration.max_error : std::numeric_limits<float>::max();
std::function<void(void)> throw_on_cancel = [&]() { std::function<void(void)> throw_on_cancel = [&]() {
if (state == State::canceling) { if (m_state == State::canceling) {
throw SimplifyCanceledException(); throw SimplifyCanceledException();
} }
};
std::function<void(int)> statusfn = [&](int percent) {
progress = percent;
m_parent.schedule_extra_frame(0);
}; };
indexed_triangle_set collapsed; std::function<void(int)> statusfn = [this](int percent) {
if (last_error.has_value()) { m_progress = percent;
// is chance to continue with last reduction
const indexed_triangle_set &its = volume->mesh().its; // check max 4fps
uint32_t last_triangle_count = static_cast<uint32_t>(its.indices.size()); static int64_t last = 0;
if ((!c.use_count || triangle_count <= last_triangle_count) && int64_t now = m_parent.timestamp_now();
(!c.use_error || c.max_error <= *last_error)) { if ((now - last) < 250) return;
collapsed = its; // small copy last = now;
} else {
collapsed = *original_its; // copy request_rerender();
} };
} else {
collapsed = *original_its; // copy indexed_triangle_set collapsed = *m_original_its; // copy
}
try { try {
its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn); its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn);
set_its(collapsed); set_its(collapsed);
is_valid_result = true; m_is_valid_result = true;
last_error = max_error; m_exist_preview = true;
} catch (SimplifyCanceledException &) { } catch (SimplifyCanceledException &) {
state = State::settings; // set state out of main thread
m_state = State::settings;
} }
// need to render last status fn // need to render last status fn to change bar graph to buttons
// without sleep it freezes until mouse move request_rerender();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
m_parent.schedule_extra_frame(0);
}); });
} }
void GLGizmoSimplify::set_its(indexed_triangle_set &its) { void GLGizmoSimplify::set_its(indexed_triangle_set &its) {
auto tm = std::make_unique<TriangleMesh>(its); auto tm = std::make_unique<TriangleMesh>(its);
tm->repair(); tm->repair();
volume->set_mesh(std::move(tm)); m_volume->set_mesh(std::move(tm));
volume->set_new_unique_id(); m_volume->set_new_unique_id();
volume->get_object()->invalidate_bounding_box(); m_volume->get_object()->invalidate_bounding_box();
need_reload = true; m_need_reload = true;
} }
bool GLGizmoSimplify::on_is_activable() const bool GLGizmoSimplify::on_is_activable() const
@ -297,26 +324,62 @@ bool GLGizmoSimplify::on_is_activable() const
return !m_parent.get_selection().is_empty(); return !m_parent.get_selection().is_empty();
} }
void GLGizmoSimplify::create_gui_cfg() { void GLGizmoSimplify::on_set_state()
if (gui_cfg.has_value()) return; {
// Closing gizmo. e.g. selecting another one
if (GLGizmoBase::m_state == GLGizmoBase::Off) {
// refuse outgoing during simlification
if (m_state != State::settings) {
GLGizmoBase::m_state = GLGizmoBase::On;
auto notification_manager = wxGetApp().plater()->get_notification_manager();
notification_manager->push_notification(
NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotification,
_u8L("ERROR: Wait until Simplification ends or Cancel process."));
return;
}
// revert preview
if (m_exist_preview) {
set_its(*m_original_its);
m_parent.reload_scene(true);
m_need_reload = false;
}
// invalidate selected model
m_volume = nullptr;
} else if (GLGizmoBase::m_state == GLGizmoBase::On) {
// when open by hyperlink it needs to show up
request_rerender();
}
}
void GLGizmoSimplify::create_gui_cfg() {
if (m_gui_cfg.has_value()) return;
int space_size = m_imgui->calc_text_size(":MM").x; int space_size = m_imgui->calc_text_size(":MM").x;
GuiCfg cfg; GuiCfg cfg;
cfg.top_left_width = std::max(m_imgui->calc_text_size(_L("Mesh name")).x, cfg.top_left_width = std::max(m_imgui->calc_text_size(tr_mesh_name).x,
m_imgui->calc_text_size(_L("Triangles")).x) m_imgui->calc_text_size(tr_triangles).x)
+ space_size; + space_size;
const float radio_size = ImGui::GetFrameHeight();
cfg.bottom_left_width = cfg.bottom_left_width =
std::max( std::max(m_imgui->calc_text_size(tr_detail_level).x,
std::max(m_imgui->calc_text_size(_L("Limit by triangles")).x, m_imgui->calc_text_size(tr_decimate_ratio).x) +
std::max(m_imgui->calc_text_size(_L("Triangle count")).x, space_size + radio_size;
m_imgui->calc_text_size(_L("Ratio")).x)),
std::max(m_imgui->calc_text_size(_L("Limit by error")).x, cfg.input_width = cfg.bottom_left_width * 1.5;
m_imgui->calc_text_size(_L("Max. error")).x)) + space_size; cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2;
cfg.input_width = cfg.bottom_left_width; cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5;
cfg.input_small_width = cfg.input_width * 0.8; m_gui_cfg = cfg;
cfg.window_offset = cfg.input_width; }
gui_cfg = cfg;
void GLGizmoSimplify::request_rerender() {
wxGetApp().plater()->CallAfter([this]() {
set_dirty();
m_parent.schedule_extra_frame(0);
});
} }
} // namespace Slic3r::GUI } // namespace Slic3r::GUI

View File

@ -1,35 +1,22 @@
#ifndef slic3r_GLGizmoSimplify_hpp_ #ifndef slic3r_GLGizmoSimplify_hpp_
#define slic3r_GLGizmoSimplify_hpp_ #define slic3r_GLGizmoSimplify_hpp_
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
// which overrides our localization "L" macro.
#include "GLGizmoBase.hpp" #include "GLGizmoBase.hpp"
#include "libslic3r/Model.hpp" #include "admesh/stl.h" // indexed_triangle_set
#include <thread> #include <thread>
#include <optional> #include <optional>
namespace Slic3r { namespace Slic3r {
class ModelVolume;
namespace GUI { namespace GUI {
class GLGizmoSimplify : public GLGizmoBase class GLGizmoSimplify : public GLGizmoBase
{ {
enum class State {
settings,
simplifying, // start processing
canceling, // canceled
successfull, // successful simplified
close_on_end
} state;
bool is_valid_result; // differ what to do in apply
int progress;
ModelVolume *volume;
size_t obj_index;
std::optional<indexed_triangle_set> original_its;
std::optional<float> last_error; // for use previous reduction
bool need_reload; // after simplify, glReload must be on main thread
std::thread worker;
public: public:
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoSimplify(); virtual ~GLGizmoSimplify();
@ -41,32 +28,51 @@ protected:
virtual void on_render_input_window(float x, float y, float bottom_limit) override; virtual void on_render_input_window(float x, float y, float bottom_limit) override;
virtual bool on_is_activable() const override; virtual bool on_is_activable() const override;
virtual bool on_is_selectable() const override { return false; } virtual bool on_is_selectable() const override { return false; }
virtual void on_set_state() override;
private: private:
void close(); void close();
void process(); void process();
void set_its(indexed_triangle_set &its); void set_its(indexed_triangle_set &its);
void create_gui_cfg();
void request_rerender();
std::atomic_bool m_is_valid_result; // differ what to do in apply
std::atomic_bool m_exist_preview; // set when process end
volatile int m_progress; // percent of done work
ModelVolume *m_volume; //
size_t m_obj_index;
std::optional<indexed_triangle_set> m_original_its;
volatile bool m_need_reload; // after simplify, glReload must be on main thread
std::thread m_worker;
enum class State {
settings,
preview, // simplify to show preview
close_on_end, // simplify with close on end
canceling // after button click, before canceled
};
volatile State m_state;
struct Configuration struct Configuration
{ {
bool use_count = true; bool use_count = false;
// minimal triangle count // minimal triangle count
float wanted_percent = 50.f; float decimate_ratio = 50.f; // in percent
uint32_t wanted_count = 0; // initialize by percents uint32_t wanted_count = 0; // initialize by percents
bool use_error = false;
// maximal quadric error // maximal quadric error
float max_error = 1.; float max_error = 1.;
void update_count(size_t triangle_count) void fix_count_by_ratio(size_t triangle_count)
{
wanted_percent = (float) wanted_count / triangle_count * 100.f;
}
void update_percent(size_t triangle_count)
{ {
wanted_count = static_cast<uint32_t>( wanted_count = static_cast<uint32_t>(
std::round(triangle_count * wanted_percent / 100.f)); std::round(triangle_count * (100.f-decimate_ratio) / 100.f));
} }
} c; } m_configuration;
// This configs holds GUI layout size given by translated texts. // This configs holds GUI layout size given by translated texts.
// etc. When language changes, GUI is recreated and this class constructed again, // etc. When language changes, GUI is recreated and this class constructed again,
@ -76,11 +82,21 @@ private:
int top_left_width = 100; int top_left_width = 100;
int bottom_left_width = 100; int bottom_left_width = 100;
int input_width = 100; int input_width = 100;
int input_small_width = 80; int window_offset_x = 100;
int window_offset = 100; int window_offset_y = 100;
int window_padding = 0;
// trunc model name when longer
size_t max_char_in_name = 30;
}; };
std::optional<GuiCfg> gui_cfg; std::optional<GuiCfg> m_gui_cfg;
void create_gui_cfg();
// translations used for calc window size
const std::string tr_mesh_name;
const std::string tr_triangles;
const std::string tr_preview;
const std::string tr_detail_level;
const std::string tr_decimate_ratio;
}; };
} // namespace GUI } // namespace GUI

View File

@ -132,8 +132,7 @@ bool GLGizmosManager::init_arrow(const BackgroundTexture::Metadata& arrow_textur
bool res = false; bool res = false;
if (!arrow_texture.filename.empty()) if (!arrow_texture.filename.empty())
res = m_arrow_texture.texture.load_from_file(path + arrow_texture.filename, false, GLTexture::SingleThreaded, false); res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, false, false, 1000);
// res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, true, false, 100);
if (res) if (res)
m_arrow_texture.metadata = arrow_texture; m_arrow_texture.metadata = arrow_texture;
@ -1019,7 +1018,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 * 2.2f * arrow_sides_ratio, zoomed_top_y - zoomed_icons_size * 1.6f , zoomed_top_y + zoomed_icons_size * 0.6f, { { 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"
@ -13,6 +14,31 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/property_tree/ini_parser.hpp> #include <boost/property_tree/ini_parser.hpp>
#include <map> #include <map>
#include <cereal/archives/binary.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#define HINTS_CEREAL_VERSION 1
// structure for writing used hints into binary file with version
struct HintsCerealData
{
std::vector<std::string> my_data;
// cereal will supply the version automatically when loading or saving
// The version number comes from the CEREAL_CLASS_VERSION macro
template<class Archive>
void serialize(Archive& ar, std::uint32_t const version)
{
// You can choose different behaviors depending on the version
// This is useful if you need to support older variants of your codebase
// interacting with newer ones
if (version > HINTS_CEREAL_VERSION)
throw Slic3r::IOError("Version of hints.cereal is higher than current version.");
else
ar(my_data);
}
};
// version of used hints binary file
CEREAL_CLASS_VERSION(HintsCerealData, HINTS_CEREAL_VERSION);
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
@ -30,6 +56,41 @@ inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, f
else else
ImGui::PushStyleColor(idx, col); ImGui::PushStyleColor(idx, col);
} }
void write_used_binary(const std::vector<std::string>& ids)
{
boost::filesystem::ofstream file((boost::filesystem::path(data_dir()) / "cache" / "hints.cereal"), std::ios::binary);
cereal::BinaryOutputArchive archive(file);
HintsCerealData cd { ids };
try
{
archive(cd);
}
catch (const std::exception& ex)
{
BOOST_LOG_TRIVIAL(error) << "Failed to write to hints.cereal. " << ex.what();
}
}
void read_used_binary(std::vector<std::string>& ids)
{
boost::filesystem::ifstream file((boost::filesystem::path(data_dir()) / "cache" / "hints.cereal"));
cereal::BinaryInputArchive archive(file);
HintsCerealData cd;
try
{
archive(cd);
}
catch (const std::exception& ex)
{
BOOST_LOG_TRIVIAL(error) << "Failed to load to hints.cereal. " << ex.what();
return;
}
ids = cd.my_data;
}
enum TagCheckResult enum TagCheckResult
{ {
TagCheckAffirmative, TagCheckAffirmative,
@ -175,20 +236,19 @@ bool tags_check(const std::string& disabled_tags, const std::string& enabled_tag
} }
void launch_browser_if_allowed(const std::string& url) void launch_browser_if_allowed(const std::string& url)
{ {
if (wxGetApp().app_config->get("suppress_hyperlinks") != "1") wxGetApp().open_browser_with_warning_dialog(url);
wxLaunchDefaultBrowser(url);
} }
} //namespace } //namespace
HintDatabase::~HintDatabase()
{
if (m_initialized) {
write_used_binary(m_used_ids);
}
}
void HintDatabase::init() void HintDatabase::init()
{ {
load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini")); load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini"));
const AppConfig* app_config = wxGetApp().app_config;
m_hint_id = std::atoi(app_config->get("last_hint").c_str());
m_initialized = true; m_initialized = true;
} }
void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
{ {
@ -209,15 +269,22 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
for (const auto& data : section.second) { for (const auto& data : section.second) {
dict.emplace(data.first, data.second.data()); dict.emplace(data.first, data.second.data());
} }
// unique id string [hint:id] (trim "hint:")
//unescaping and translating all texts and saving all data common for all hint types std::string id_string = section.first.substr(5);
id_string = std::to_string(std::hash<std::string>{}(id_string));
// unescaping and translating all texts and saving all data common for all hint types
std::string fulltext; std::string fulltext;
std::string text1; std::string text1;
std::string hypertext_text; std::string hypertext_text;
std::string follow_text; std::string follow_text;
// tags
std::string disabled_tags; std::string disabled_tags;
std::string enabled_tags; std::string enabled_tags;
// optional link to documentation (accessed from button)
std::string documentation_link; std::string documentation_link;
// randomized weighted order variables
size_t weight = 1;
bool was_displayed = is_used(id_string);
//unescape text1 //unescape text1
unescape_string_cstyle(_utf8(dict["text"]), fulltext); unescape_string_cstyle(_utf8(dict["text"]), fulltext);
// replace <b> and </b> for imgui markers // replace <b> and </b> for imgui markers
@ -275,52 +342,59 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
documentation_link = dict["documentation_link"]; documentation_link = dict["documentation_link"];
} }
if (dict.find("weight") != dict.end()) {
weight = (size_t)std::max(1, std::atoi(dict["weight"].c_str()));
}
// create HintData // create HintData
if (dict.find("hypertext_type") != dict.end()) { if (dict.find("hypertext_type") != dict.end()) {
//link to internet //link to internet
if(dict["hypertext_type"] == "link") { if(dict["hypertext_type"] == "link") {
std::string hypertext_link = dict["hypertext_link"]; std::string hypertext_link = dict["hypertext_link"];
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [hypertext_link]() { launch_browser_if_allowed(hypertext_link); } }; HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [hypertext_link]() { launch_browser_if_allowed(hypertext_link); } };
m_loaded_hints.emplace_back(hint_data); m_loaded_hints.emplace_back(hint_data);
// highlight settings // highlight settings
} else if (dict["hypertext_type"] == "settings") { } else if (dict["hypertext_type"] == "settings") {
std::string opt = dict["hypertext_settings_opt"]; std::string opt = dict["hypertext_settings_opt"];
Preset::Type type = static_cast<Preset::Type>(std::atoi(dict["hypertext_settings_type"].c_str())); Preset::Type type = static_cast<Preset::Type>(std::atoi(dict["hypertext_settings_type"].c_str()));
std::wstring category = boost::nowide::widen(dict["hypertext_settings_category"]); std::wstring category = boost::nowide::widen(dict["hypertext_settings_category"]);
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } }; HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } };
m_loaded_hints.emplace_back(hint_data); m_loaded_hints.emplace_back(hint_data);
// open preferences // open preferences
} else if(dict["hypertext_type"] == "preferences") { } else if(dict["hypertext_type"] == "preferences") {
int page = static_cast<Preset::Type>(std::atoi(dict["hypertext_preferences_page"].c_str())); int page = static_cast<Preset::Type>(std::atoi(dict["hypertext_preferences_page"].c_str()));
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page]() { wxGetApp().open_preferences(page); } }; HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page]() { wxGetApp().open_preferences(page); } };
m_loaded_hints.emplace_back(hint_data); m_loaded_hints.emplace_back(hint_data);
} else if (dict["hypertext_type"] == "plater") { } else if (dict["hypertext_type"] == "plater") {
std::string item = dict["hypertext_plater_item"]; std::string item = dict["hypertext_plater_item"];
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } }; HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } };
m_loaded_hints.emplace_back(hint_data); m_loaded_hints.emplace_back(hint_data);
} else if (dict["hypertext_type"] == "gizmo") { } else if (dict["hypertext_type"] == "gizmo") {
std::string item = dict["hypertext_gizmo_item"]; std::string item = dict["hypertext_gizmo_item"];
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_gizmo(item); } }; HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_gizmo(item); } };
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{ id_string, text1, weight, was_displayed, 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 {
// plain text without hypertext // plain text without hypertext
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link }; HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link };
m_loaded_hints.emplace_back(hint_data); m_loaded_hints.emplace_back(hint_data);
} }
} }
} }
} }
HintData* HintDatabase::get_hint(bool up) HintData* HintDatabase::get_hint(bool new_hint/* = true*/)
{ {
if (! m_initialized) { if (! m_initialized) {
init(); init();
//return false; new_hint = true;
} }
if (m_loaded_hints.empty()) if (m_loaded_hints.empty())
{ {
@ -328,23 +402,97 @@ HintData* HintDatabase::get_hint(bool up)
return nullptr; return nullptr;
} }
// shift id try
m_hint_id = (up ? m_hint_id + 1 : m_hint_id ); {
m_hint_id %= m_loaded_hints.size(); if (new_hint)
m_hint_id = get_next();
}
catch (const std::exception&)
{
return nullptr;
}
AppConfig* app_config = wxGetApp().app_config;
app_config->set("last_hint", std::to_string(m_hint_id));
//data = &m_loaded_hints[m_hint_id];
/*
data.text = m_loaded_hints[m_hint_id].text;
data.hypertext = m_loaded_hints[m_hint_id].hypertext;
data.follow_text = m_loaded_hints[m_hint_id].follow_text;
data.callback = m_loaded_hints[m_hint_id].callback;
*/
return &m_loaded_hints[m_hint_id]; return &m_loaded_hints[m_hint_id];
} }
size_t HintDatabase::get_next()
{
if (!m_sorted_hints)
{
auto compare_wieght = [](const HintData& a, const HintData& b){ return a.weight < b.weight; };
std::sort(m_loaded_hints.begin(), m_loaded_hints.end(), compare_wieght);
m_sorted_hints = true;
srand(time(NULL));
}
std::vector<size_t> candidates; // index in m_loaded_hints
// total weight
size_t total_weight = 0;
for (size_t i = 0; i < m_loaded_hints.size(); i++) {
if (!m_loaded_hints[i].was_displayed && tags_check(m_loaded_hints[i].disabled_tags, m_loaded_hints[i].enabled_tags)) {
candidates.emplace_back(i);
total_weight += m_loaded_hints[i].weight;
}
}
// all were shown
if (total_weight == 0) {
clear_used();
for (size_t i = 0; i < m_loaded_hints.size(); i++) {
m_loaded_hints[i].was_displayed = false;
if (tags_check(m_loaded_hints[i].disabled_tags, m_loaded_hints[i].enabled_tags)) {
candidates.emplace_back(i);
total_weight += m_loaded_hints[i].weight;
}
}
}
if (total_weight == 0) {
BOOST_LOG_TRIVIAL(error) << "Hint notification random number generator failed. No suitable hint was found.";
throw std::exception();
}
size_t random_number = rand() % total_weight + 1;
size_t current_weight = 0;
for (size_t i = 0; i < candidates.size(); i++) {
current_weight += m_loaded_hints[candidates[i]].weight;
if (random_number <= current_weight) {
set_used(m_loaded_hints[candidates[i]].id_string);
m_loaded_hints[candidates[i]].was_displayed = true;
return candidates[i];
}
}
BOOST_LOG_TRIVIAL(error) << "Hint notification random number generator failed.";
throw std::exception();
}
bool HintDatabase::is_used(const std::string& id)
{
// load used ids from file
if (!m_used_ids_loaded) {
read_used_binary(m_used_ids);
m_used_ids_loaded = true;
}
// check if id is in used
for (const std::string& used_id : m_used_ids) {
if (used_id == id)
{
return true;
}
}
return false;
}
void HintDatabase::set_used(const std::string& id)
{
// check needed?
if (!is_used(id))
{
m_used_ids.emplace_back(id);
}
}
void HintDatabase::clear_used()
{
m_used_ids.clear();
}
void NotificationManager::HintNotification::count_spaces() void NotificationManager::HintNotification::count_spaces()
{ {
//determine line width //determine line width
@ -840,23 +988,12 @@ void NotificationManager::HintNotification::open_documentation()
launch_browser_if_allowed(m_documentation_link); launch_browser_if_allowed(m_documentation_link);
} }
} }
void NotificationManager::HintNotification::retrieve_data(int recursion_counter) void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true*/)
{ {
HintData* hint_data = HintDatabase::get_instance().get_hint(recursion_counter >= 0 ? true : false); HintData* hint_data = HintDatabase::get_instance().get_hint(new_hint);
if (hint_data == nullptr) if (hint_data == nullptr)
close(); close();
if (hint_data != nullptr && !tags_check(hint_data->disabled_tags, hint_data->enabled_tags))
{
// Content for different user - retrieve another
size_t count = HintDatabase::get_instance().get_count();
if ((int)count < recursion_counter) {
BOOST_LOG_TRIVIAL(error) << "Hint notification failed to load data due to recursion counter.";
} else {
retrieve_data(recursion_counter + 1);
}
return;
}
if(hint_data != nullptr) if(hint_data != nullptr)
{ {
NotificationData nd { NotificationType::DidYouKnowHint, NotificationData nd { NotificationType::DidYouKnowHint,

View File

@ -9,7 +9,10 @@ namespace GUI {
// Database of hints updatable // Database of hints updatable
struct HintData struct HintData
{ {
std::string id_string;
std::string text; std::string text;
size_t weight;
bool was_displayed;
std::string hypertext; std::string hypertext;
std::string follow_text; std::string follow_text;
std::string disabled_tags; std::string disabled_tags;
@ -33,11 +36,12 @@ private:
: m_hint_id(0) : m_hint_id(0)
{} {}
public: public:
~HintDatabase();
HintDatabase(HintDatabase const&) = delete; HintDatabase(HintDatabase const&) = delete;
void operator=(HintDatabase const&) = delete; void operator=(HintDatabase const&) = delete;
// return true if HintData filled; // return true if HintData filled;
HintData* get_hint(bool up = true); HintData* get_hint(bool new_hint = true);
size_t get_count() { size_t get_count() {
if (!m_initialized) if (!m_initialized)
return 0; return 0;
@ -46,10 +50,17 @@ public:
private: private:
void init(); void init();
void load_hints_from_file(const boost::filesystem::path& path); void load_hints_from_file(const boost::filesystem::path& path);
bool is_used(const std::string& id);
void set_used(const std::string& id);
void clear_used();
// Returns position in m_loaded_hints with next hint chosed randomly with weights
size_t get_next();
size_t m_hint_id; size_t m_hint_id;
bool m_initialized { false }; bool m_initialized { false };
std::vector<HintData> m_loaded_hints; std::vector<HintData> m_loaded_hints;
bool m_sorted_hints { false };
std::vector<std::string> m_used_ids;
bool m_used_ids_loaded { false };
}; };
// Notification class - shows current Hint ("Did you know") // Notification class - shows current Hint ("Did you know")
class NotificationManager::HintNotification : public NotificationManager::PopNotification class NotificationManager::HintNotification : public NotificationManager::PopNotification
@ -58,10 +69,10 @@ public:
HintNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool new_hint) HintNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool new_hint)
: PopNotification(n, id_provider, evt_handler) : PopNotification(n, id_provider, evt_handler)
{ {
retrieve_data(new_hint ? 0 : -1); retrieve_data(new_hint);
} }
virtual void init() override; virtual void init() override;
void open_next() { retrieve_data(0); } void open_next() { retrieve_data(); }
protected: protected:
virtual void set_next_window_size(ImGuiWrapper& imgui) override; virtual void set_next_window_size(ImGuiWrapper& imgui) override;
virtual void count_spaces() override; virtual void count_spaces() override;
@ -87,7 +98,7 @@ protected:
const float win_size_x, const float win_size_y, const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y); const float win_pos_x, const float win_pos_y);
// recursion counter -1 tells to retrieve same hint as last time // recursion counter -1 tells to retrieve same hint as last time
void retrieve_data(int recursion_counter = 0); void retrieve_data(bool new_hint = true);
void open_documentation(); void open_documentation();
bool m_has_hint_data { false }; bool m_has_hint_data { false };

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

@ -147,26 +147,28 @@ void FillBedJob::finalize()
size_t inst_cnt = model_object->instances.size(); size_t inst_cnt = model_object->instances.size();
for (ArrangePolygon &ap : m_selected) { int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0, [](int s, auto &ap) {
if (ap.bed_idx != arrangement::UNARRANGED && (ap.priority != 0 || ap.bed_idx == 0)) return s + int(ap.priority == 0 && ap.bed_idx == 0);
ap.apply(); });
}
model_object->ensure_on_bed(); if (added_cnt > 0) {
for (ArrangePolygon &ap : m_selected) {
if (ap.bed_idx != arrangement::UNARRANGED && (ap.priority != 0 || ap.bed_idx == 0))
ap.apply();
}
m_plater->update(); model_object->ensure_on_bed();
int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0, m_plater->update();
[](int s, auto &ap) {
return s + int(ap.priority == 0 && ap.bed_idx == 0);
});
// FIXME: somebody explain why this is needed for increase_object_instances // FIXME: somebody explain why this is needed for increase_object_instances
if (inst_cnt == 1) added_cnt++; if (inst_cnt == 1) added_cnt++;
if (added_cnt > 0)
m_plater->sidebar() m_plater->sidebar()
.obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt)); .obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt));
}
Job::finalize();
} }
}} // namespace Slic3r::GUI }} // namespace Slic3r::GUI

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

@ -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

@ -618,8 +618,11 @@ void MainFrame::update_title()
// Don't try to remove the extension, it would remove part of the file name after the last dot! // Don't try to remove the extension, it would remove part of the file name after the last dot!
wxString project = from_path(into_path(m_plater->get_project_filename()).filename()); wxString project = from_path(into_path(m_plater->get_project_filename()).filename());
wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : ""; wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : "";
if (!dirty_marker.empty() || !project.empty()) if (!dirty_marker.empty() || !project.empty()) {
if (!dirty_marker.empty() && project.empty())
project = _("Untitled");
title = dirty_marker + project + " - "; title = dirty_marker + project + " - ";
}
} }
std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID; std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID;
@ -1054,7 +1057,7 @@ static wxMenu* generate_help_menu()
append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"), append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"),
[](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); }); [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); });
append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"), append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"),
[](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); }); [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); });
//# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{ //# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{
//# wxTheApp->check_version(1); //# wxTheApp->check_version(1);
//# }); //# });
@ -1064,14 +1067,14 @@ static wxMenu* generate_help_menu()
[](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); }); [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); });
// append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("%s &Manual"), SLIC3R_APP_NAME), // append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("%s &Manual"), SLIC3R_APP_NAME),
// wxString::Format(_L("Open the %s manual in your browser"), SLIC3R_APP_NAME), // wxString::Format(_L("Open the %s manual in your browser"), SLIC3R_APP_NAME),
// [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://manual.slic3r.org/"); }); // [this](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("http://manual.slic3r.org/"); });
helpMenu->AppendSeparator(); helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"), append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"),
[](wxCommandEvent&) { wxGetApp().system_info(); }); [](wxCommandEvent&) { wxGetApp().system_info(); });
append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"), append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"),
[](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); [](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); });
append_menu_item(helpMenu, wxID_ANY, _L("Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME), append_menu_item(helpMenu, wxID_ANY, _L("Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME),
[](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); }); [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/slic3r/issues/new"); });
if (wxGetApp().is_editor()) if (wxGetApp().is_editor())
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"), append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"),
[](wxCommandEvent&) { Slic3r::GUI::about(); }); [](wxCommandEvent&) { Slic3r::GUI::about(); });

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

@ -42,12 +42,12 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat
} }
}, },
{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
{NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10, {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10,
_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

@ -96,7 +96,9 @@ enum class NotificationType
DidYouKnowHint, DidYouKnowHint,
// Shows when ObjectList::update_info_items finds information that should be stressed to the user // Shows when ObjectList::update_info_items finds information that should be stressed to the user
// Might contain logo taken from gizmos // Might contain logo taken from gizmos
UpdatedItemsInfo UpdatedItemsInfo,
// Give user advice to simplify object with big amount of triangles
SimplifySuggestion
}; };
class NotificationManager class NotificationManager
@ -391,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;
@ -434,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;
@ -537,7 +540,13 @@ private:
// Timestamp of last rendering // Timestamp of last rendering
int64_t m_last_render { 0LL }; int64_t m_last_render { 0LL };
// Notification types that can be shown multiple types at once (compared by text) // Notification types that can be shown multiple types at once (compared by text)
const std::vector<NotificationType> m_multiple_types = { NotificationType::CustomNotification, NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload, NotificationType::UpdatedItemsInfo }; const std::vector<NotificationType> m_multiple_types = {
NotificationType::CustomNotification,
NotificationType::PlaterWarning,
NotificationType::ProgressBar,
NotificationType::PrintHostUpload,
NotificationType::SimplifySuggestion
};
//prepared (basic) notifications //prepared (basic) notifications
static const NotificationData basic_notifications[]; static const NotificationData basic_notifications[];
}; };

View File

@ -396,6 +396,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
m_optgroup->append_line(cafile_hint); m_optgroup->append_line(cafile_hint);
} }
else { else {
Line line{ "", "" }; Line line{ "", "" };
line.full_width = 1; line.full_width = 1;
@ -411,7 +412,6 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
sizer->Add(txt, 1, wxEXPAND); sizer->Add(txt, 1, wxEXPAND);
return sizer; return sizer;
}; };
m_optgroup->append_line(line); m_optgroup->append_line(line);
} }
@ -421,6 +421,12 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
m_optgroup->append_single_option_line(option); m_optgroup->append_single_option_line(option);
} }
#ifdef WIN32
option = m_optgroup->get_option("printhost_ssl_ignore_revoke");
option.opt.width = Field::def_width_wider();
m_optgroup->append_single_option_line(option);
#endif
m_optgroup->activate(); m_optgroup->activate();
Field* printhost_field = m_optgroup->get_field("print_host"); Field* printhost_field = m_optgroup->get_field("print_host");

View File

@ -1658,6 +1658,7 @@ struct Plater::priv
void deselect_all(); void deselect_all();
void remove(size_t obj_idx); void remove(size_t obj_idx);
void delete_object_from_model(size_t obj_idx); void delete_object_from_model(size_t obj_idx);
void delete_all_objects_from_model();
void reset(); void reset();
void mirror(Axis axis); void mirror(Axis axis);
void split_object(); void split_object();
@ -1720,7 +1721,7 @@ struct Plater::priv
void replace_with_stl(); void replace_with_stl();
void reload_all_from_disk(); void reload_all_from_disk();
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
void create_simplify_notification(const std::vector<size_t>& obj_ids);
void set_current_panel(wxPanel* panel); void set_current_panel(wxPanel* panel);
void on_select_preset(wxCommandEvent&); void on_select_preset(wxCommandEvent&);
@ -1944,7 +1945,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
// 3DScene/Toolbar: // 3DScene/Toolbar:
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { delete_all_objects_from_model(); });
// view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
@ -2334,7 +2336,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 +2364,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,6 +2428,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
if (one_by_one) { if (one_by_one) {
if (type_3mf && !is_project_file)
model.center_instances_around_point(bed_shape_bb().center());
auto loaded_idxs = load_model_objects(model.objects, is_project_file); auto loaded_idxs = load_model_objects(model.objects, 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 {
@ -2474,6 +2478,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
view3D->get_canvas3d()->update_gizmos_on_off_state(); view3D->get_canvas3d()->update_gizmos_on_off_state();
} }
create_simplify_notification(obj_idxs);
return obj_idxs; return obj_idxs;
} }
@ -2751,6 +2757,32 @@ void Plater::priv::delete_object_from_model(size_t obj_idx)
object_list_changed(); object_list_changed();
} }
void Plater::priv::delete_all_objects_from_model()
{
Plater::TakeSnapshot snapshot(q, _L("Delete All Objects"));
if (view3D->is_layers_editing_enabled())
view3D->enable_layers_editing(false);
reset_gcode_toolpaths();
gcode_result.reset();
view3D->get_canvas3d()->reset_sequential_print_clearance();
// Stop and reset the Print content.
background_process.reset();
model.clear_objects();
update();
// Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
sidebar->obj_list()->delete_all_objects_from_list();
object_list_changed();
// The hiding of the slicing results, if shown, is not taken care by the background process, so we do it here
sidebar->show_sliced_info_sizer(false);
model.custom_gcode_per_print_z.gcodes.clear();
}
void Plater::priv::reset() void Plater::priv::reset()
{ {
Plater::TakeSnapshot snapshot(q, _L("Reset Project")); Plater::TakeSnapshot snapshot(q, _L("Reset Project"));
@ -2942,6 +2974,9 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
if (view3D->is_layers_editing_enabled()) if (view3D->is_layers_editing_enabled())
view3D->get_wxglcanvas()->Refresh(); view3D->get_wxglcanvas()->Refresh();
if (background_process.empty())
view3D->get_canvas3d()->reset_sequential_print_clearance();
if (invalidated == Print::APPLY_STATUS_INVALIDATED) { if (invalidated == Print::APPLY_STATUS_INVALIDATED) {
// Some previously calculated data on the Print was invalidated. // Some previously calculated data on the Print was invalidated.
// Hide the slicing results, as the current slicing status is no more valid. // Hide the slicing results, as the current slicing status is no more valid.
@ -3507,6 +3542,53 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
q->SetFocus(); q->SetFocus();
} }
void Plater::priv::create_simplify_notification(const std::vector<size_t>& obj_ids) {
const uint32_t triangles_to_suggest_simplify = 1000000;
std::vector<size_t> big_ids;
big_ids.reserve(obj_ids.size());
std::copy_if(obj_ids.begin(), obj_ids.end(), std::back_inserter(big_ids),
[this, triangles_to_suggest_simplify](size_t object_id) {
if (object_id >= model.objects.size()) return false; // out of object index
ModelVolumePtrs& volumes = model.objects[object_id]->volumes;
if (volumes.size() != 1) return false; // not only one volume
size_t triangle_count = volumes.front()->mesh().its.indices.size();
if (triangle_count < triangles_to_suggest_simplify) return false; // small volume
return true;
});
if (big_ids.empty()) return;
for (size_t object_id : big_ids) {
std::string t = _u8L(
"Processing model '@object_name' with more than 1M triangles "
"could be slow. It is highly recommend to reduce "
"amount of triangles.");
t.replace(t.find("@object_name"), sizeof("@object_name") - 1,
model.objects[object_id]->name);
std::stringstream text;
text << _u8L("WARNING:") << "\n" << t << "\n";
std::string hypertext = _u8L("Simplify model");
std::function<bool(wxEvtHandler *)> open_simplify = [object_id](wxEvtHandler *) {
auto plater = wxGetApp().plater();
if (object_id >= plater->model().objects.size()) return true;
Selection &selection = plater->canvas3D()->get_selection();
selection.clear();
selection.add_object((unsigned int) object_id);
auto &manager = plater->canvas3D()->get_gizmos_manager();
manager.open_gizmo(GLGizmosManager::EType::Simplify);
return true;
};
notification_manager->push_notification(
NotificationType::SimplifySuggestion,
NotificationManager::NotificationLevel::WarningNotification,
text.str(), hypertext, open_simplify);
}
}
void Plater::priv::set_current_panel(wxPanel* panel) void Plater::priv::set_current_panel(wxPanel* panel)
{ {
if (std::find(panels.begin(), panels.end(), panel) == panels.end()) if (std::find(panels.begin(), panels.end(), panel) == panels.end())
@ -4283,6 +4365,12 @@ bool Plater::priv::can_fix_through_netfabb() const
bool Plater::priv::can_simplify() const bool Plater::priv::can_simplify() const
{ {
// is object for simplification selected
if (get_selected_object_idx() < 0) return false;
// is already opened?
if (q->canvas3D()->get_gizmos_manager().get_current_type() ==
GLGizmosManager::EType::Simplify)
return false;
return true; return true;
} }
@ -4701,10 +4789,8 @@ void Plater::load_project(const wxString& filename)
std::vector<fs::path> input_paths; std::vector<fs::path> input_paths;
input_paths.push_back(into_path(filename)); input_paths.push_back(into_path(filename));
std::vector<size_t> res = load_files(input_paths); if (! load_files(input_paths).empty()) {
// At least one file was loaded.
// if res is empty no data has been loaded
if (!res.empty()) {
p->set_project_filename(filename); p->set_project_filename(filename);
reset_project_dirty_initial_presets(); reset_project_dirty_initial_presets();
update_project_dirty_from_presets(); update_project_dirty_from_presets();
@ -4739,8 +4825,7 @@ void Plater::add_model(bool imperial_units/* = false*/)
} }
Plater::TakeSnapshot snapshot(this, snapshot_label); Plater::TakeSnapshot snapshot(this, snapshot_label);
std::vector<size_t> res = load_files(paths, true, false, imperial_units); if (! load_files(paths, true, false, imperial_units).empty())
if (!res.empty())
wxGetApp().mainframe->update_title(); wxGetApp().mainframe->update_title();
} }
@ -4874,7 +4959,7 @@ ProjectDropDialog::ProjectDropDialog(const std::string& filename)
main_sizer->Add(new wxStaticText(this, wxID_ANY, main_sizer->Add(new wxStaticText(this, wxID_ANY,
_L("Select an action to apply to the file") + ": " + from_u8(filename)), 0, wxEXPAND | wxALL, 10); _L("Select an action to apply to the file") + ": " + from_u8(filename)), 0, wxEXPAND | wxALL, 10);
int action = std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), m_action = std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)) - 1; static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)) - 1;
wxStaticBox* action_stb = new wxStaticBox(this, wxID_ANY, _L("Action")); wxStaticBox* action_stb = new wxStaticBox(this, wxID_ANY, _L("Action"));
@ -4885,7 +4970,7 @@ ProjectDropDialog::ProjectDropDialog(const std::string& filename)
int id = 0; int id = 0;
for (const wxString& label : choices) { for (const wxString& label : choices) {
wxRadioButton* btn = new wxRadioButton(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0); wxRadioButton* btn = new wxRadioButton(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0);
btn->SetValue(id == action); btn->SetValue(id == m_action);
btn->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { m_action = id; }); btn->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { m_action = id; });
stb_sizer->Add(btn, 0, wxEXPAND | wxTOP, 5); stb_sizer->Add(btn, 0, wxEXPAND | wxTOP, 5);
id++; id++;
@ -5847,6 +5932,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;
@ -6169,8 +6255,7 @@ void Plater::changed_object(int obj_idx)
if (obj_idx < 0) if (obj_idx < 0)
return; return;
// recenter and re - align to Z = 0 // recenter and re - align to Z = 0
auto model_object = p->model.objects[obj_idx]; p->model.objects[obj_idx]->ensure_on_bed(p->printer_technology != ptSLA);
model_object->ensure_on_bed(this->p->printer_technology != ptSLA);
if (this->p->printer_technology == ptSLA) { if (this->p->printer_technology == ptSLA) {
// Update the SLAPrint from the current Model, so that the reload_scene() // Update the SLAPrint from the current Model, so that the reload_scene()
// pulls the correct data, update the 3D scene. // pulls the correct data, update the 3D scene.

View File

@ -28,7 +28,7 @@ static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stac
const size_t active_snapshot_time = stack.active_snapshot_time(); const size_t active_snapshot_time = stack.active_snapshot_time();
const auto it = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot_time)); const auto it = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot_time));
const int idx = it - snapshots.begin() - 1; const int idx = it - snapshots.begin() - 1;
const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && (size_t)idx < snapshots.size() - 1) ? const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && idx < int(snapshots.size()) - 1) ?
&snapshots[idx] : nullptr; &snapshots[idx] : nullptr;
assert(ret != nullptr); assert(ret != nullptr);
@ -195,8 +195,7 @@ void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type)
void ProjectDirtyStateManager::update_from_presets() void ProjectDirtyStateManager::update_from_presets()
{ {
m_state.presets = false; m_state.presets = false;
std::vector<std::pair<unsigned int, std::string>> selected_presets = wxGetApp().get_selected_presets(); for (const auto& [type, name] : wxGetApp().get_selected_presets()) {
for (const auto& [type, name] : selected_presets) {
m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name; m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
} }
m_state.presets |= wxGetApp().has_unsaved_preset_changes(); m_state.presets |= wxGetApp().has_unsaved_preset_changes();
@ -214,6 +213,7 @@ void ProjectDirtyStateManager::reset_after_save()
m_last_save.main = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0; m_last_save.main = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0;
} }
else { else {
// Gizmo is active with its own Undo / Redo stack (for example the SLA support point editing gizmo).
const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack);
if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) { if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) {
if (m_state.gizmos.current) if (m_state.gizmos.current)
@ -231,8 +231,7 @@ void ProjectDirtyStateManager::reset_after_save()
void ProjectDirtyStateManager::reset_initial_presets() void ProjectDirtyStateManager::reset_initial_presets()
{ {
m_initial_presets = std::array<std::string, Preset::TYPE_COUNT>(); m_initial_presets = std::array<std::string, Preset::TYPE_COUNT>();
std::vector<std::pair<unsigned int, std::string>> selected_presets = wxGetApp().get_selected_presets(); for (const auto& [type, name] : wxGetApp().get_selected_presets()) {
for (const auto& [type, name] : selected_presets) {
m_initial_presets[type] = name; m_initial_presets[type] = name;
} }
} }

View File

@ -683,7 +683,8 @@ void Selection::translate(const Vec3d& displacement, bool local)
synchronize_unselected_volumes(); synchronize_unselected_volumes();
#endif // !DISABLE_INSTANCES_SYNCH #endif // !DISABLE_INSTANCES_SYNCH
this->set_bounding_boxes_dirty(); ensure_not_below_bed();
set_bounding_boxes_dirty();
} }
// Rotate an object around one of the axes. Only one rotation component is expected to be changing. // Rotate an object around one of the axes. Only one rotation component is expected to be changing.
@ -1148,6 +1149,7 @@ void Selection::erase()
} }
wxGetApp().obj_list()->delete_from_model_and_list(items); wxGetApp().obj_list()->delete_from_model_and_list(items);
ensure_not_below_bed();
} }
} }
@ -1712,7 +1714,7 @@ void Selection::calc_unscaled_instance_bounding_box() const
if (volume.is_modifier) if (volume.is_modifier)
continue; continue;
Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix();
trafo.translation()(2) += volume.get_sla_shift_z(); trafo.translation().z() += volume.get_sla_shift_z();
unscaled_instance_bounding_box->merge(volume.transformed_convex_hull_bounding_box(trafo)); unscaled_instance_bounding_box->merge(volume.transformed_convex_hull_bounding_box(trafo));
} }
} }
@ -1729,7 +1731,7 @@ void Selection::calc_scaled_instance_bounding_box() const
if (volume.is_modifier) if (volume.is_modifier)
continue; continue;
Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix(); Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix();
trafo.translation()(2) += volume.get_sla_shift_z(); trafo.translation().z() += volume.get_sla_shift_z();
scaled_instance_bounding_box->merge(volume.transformed_convex_hull_bounding_box(trafo)); scaled_instance_bounding_box->merge(volume.transformed_convex_hull_bounding_box(trafo));
} }
} }
@ -2134,6 +2136,44 @@ void Selection::ensure_on_bed()
} }
} }
void Selection::ensure_not_below_bed()
{
typedef std::map<std::pair<int, int>, double> InstancesToZMap;
InstancesToZMap instances_max_z;
for (size_t i = 0; i < m_volumes->size(); ++i) {
GLVolume* volume = (*m_volumes)[i];
if (!volume->is_wipe_tower && !volume->is_modifier) {
const double max_z = volume->transformed_convex_hull_bounding_box().max.z();
std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx());
InstancesToZMap::iterator it = instances_max_z.find(instance);
if (it == instances_max_z.end())
it = instances_max_z.insert(InstancesToZMap::value_type(instance, -DBL_MAX)).first;
it->second = std::max(it->second, max_z);
}
}
if (is_any_volume()) {
for (unsigned int i : m_list) {
GLVolume& volume = *(*m_volumes)[i];
std::pair<int, int> instance = std::make_pair(volume.object_idx(), volume.instance_idx());
InstancesToZMap::iterator it = instances_max_z.find(instance);
double z_shift = SINKING_MIN_Z_THRESHOLD - it->second;
if (it != instances_max_z.end() && z_shift > 0.0)
volume.set_volume_offset(Z, volume.get_volume_offset(Z) + z_shift);
}
}
else {
for (GLVolume* volume : *m_volumes) {
std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx());
InstancesToZMap::iterator it = instances_max_z.find(instance);
if (it != instances_max_z.end() && it->second < SINKING_MIN_Z_THRESHOLD)
volume->set_instance_offset(Z, volume->get_instance_offset(Z) + SINKING_MIN_Z_THRESHOLD - it->second);
}
}
}
bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const
{ {
struct SameInstance struct SameInstance

View File

@ -385,6 +385,7 @@ public:
private: private:
void ensure_on_bed(); void ensure_on_bed();
void ensure_not_below_bed();
bool is_from_fully_selected_instance(unsigned int volume_idx) const; bool is_from_fully_selected_instance(unsigned int volume_idx) const;
void paste_volumes_from_clipboard(); void paste_volumes_from_clipboard();

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();

View File

@ -491,6 +491,18 @@ Http& Http::form_add_file(const std::string &name, const fs::path &path, const s
return *this; return *this;
} }
#ifdef WIN32
// Tells libcurl to ignore certificate revocation checks in case of missing or offline distribution points for those SSL backends where such behavior is present.
// This option is only supported for Schannel (the native Windows SSL library).
Http& Http::ssl_revoke_best_effort(bool set)
{
if(p && set){
::curl_easy_setopt(p->curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
}
return *this;
}
#endif // WIN32
Http& Http::set_post_body(const fs::path &path) Http& Http::set_post_body(const fs::path &path)
{ {
if (p) { p->set_post_body(path);} if (p) { p->set_post_body(path);}

View File

@ -80,6 +80,12 @@ public:
// Same as above except also override the file's filename with a custom one // Same as above except also override the file's filename with a custom one
Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename); Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename);
#ifdef WIN32
// Tells libcurl to ignore certificate revocation checks in case of missing or offline distribution points for those SSL backends where such behavior is present.
// This option is only supported for Schannel (the native Windows SSL library).
Http& ssl_revoke_best_effort(bool set);
#endif // WIN32
// Set the file contents as a POST request body. // Set the file contents as a POST request body.
// The data is used verbatim, it is not additionally encoded in any way. // The data is used verbatim, it is not additionally encoded in any way.
// This can be used for hosts which do not support multipart requests. // This can be used for hosts which do not support multipart requests.

View File

@ -23,9 +23,10 @@ namespace pt = boost::property_tree;
namespace Slic3r { namespace Slic3r {
OctoPrint::OctoPrint(DynamicPrintConfig *config) : OctoPrint::OctoPrint(DynamicPrintConfig *config) :
host(config->opt_string("print_host")), m_host(config->opt_string("print_host")),
apikey(config->opt_string("printhost_apikey")), m_apikey(config->opt_string("printhost_apikey")),
cafile(config->opt_string("printhost_cafile")) m_cafile(config->opt_string("printhost_cafile")),
m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke"))
{} {}
const char* OctoPrint::get_name() const { return "OctoPrint"; } const char* OctoPrint::get_name() const { return "OctoPrint"; }
@ -73,6 +74,9 @@ bool OctoPrint::test(wxString &msg) const
msg = "Could not parse server response"; msg = "Could not parse server response";
} }
}) })
#ifdef WIN32
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
#endif
.perform_sync(); .perform_sync();
return res; return res;
@ -137,6 +141,9 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
res = false; res = false;
} }
}) })
#ifdef WIN32
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
#endif
.perform_sync(); .perform_sync();
return res; return res;
@ -149,31 +156,31 @@ bool OctoPrint::validate_version_text(const boost::optional<std::string> &versio
void OctoPrint::set_auth(Http &http) const void OctoPrint::set_auth(Http &http) const
{ {
http.header("X-Api-Key", apikey); http.header("X-Api-Key", m_apikey);
if (! cafile.empty()) { if (!m_cafile.empty()) {
http.ca_file(cafile); http.ca_file(m_cafile);
} }
} }
std::string OctoPrint::make_url(const std::string &path) const std::string OctoPrint::make_url(const std::string &path) const
{ {
if (host.find("http://") == 0 || host.find("https://") == 0) { if (m_host.find("http://") == 0 || m_host.find("https://") == 0) {
if (host.back() == '/') { if (m_host.back() == '/') {
return (boost::format("%1%%2%") % host % path).str(); return (boost::format("%1%%2%") % m_host % path).str();
} else { } else {
return (boost::format("%1%/%2%") % host % path).str(); return (boost::format("%1%/%2%") % m_host % path).str();
} }
} else { } else {
return (boost::format("http://%1%/%2%") % host % path).str(); return (boost::format("http://%1%/%2%") % m_host % path).str();
} }
} }
SL1Host::SL1Host(DynamicPrintConfig *config) : SL1Host::SL1Host(DynamicPrintConfig *config) :
OctoPrint(config), OctoPrint(config),
authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value), m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
username(config->opt_string("printhost_user")), m_username(config->opt_string("printhost_user")),
password(config->opt_string("printhost_password")) m_password(config->opt_string("printhost_password"))
{ {
} }
@ -199,12 +206,12 @@ bool SL1Host::validate_version_text(const boost::optional<std::string> &version_
void SL1Host::set_auth(Http &http) const void SL1Host::set_auth(Http &http) const
{ {
switch (authorization_type) { switch (m_authorization_type) {
case atKeyPassword: case atKeyPassword:
http.header("X-Api-Key", get_apikey()); http.header("X-Api-Key", get_apikey());
break; break;
case atUserPassword: case atUserPassword:
http.auth_digest(username, password); http.auth_digest(m_username, m_password);
break; break;
} }
@ -216,9 +223,9 @@ void SL1Host::set_auth(Http &http) const
// PrusaLink // PrusaLink
PrusaLink::PrusaLink(DynamicPrintConfig* config) : PrusaLink::PrusaLink(DynamicPrintConfig* config) :
OctoPrint(config), OctoPrint(config),
authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value), m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
username(config->opt_string("printhost_user")), m_username(config->opt_string("printhost_user")),
password(config->opt_string("printhost_password")) m_password(config->opt_string("printhost_password"))
{ {
} }
@ -243,12 +250,12 @@ bool PrusaLink::validate_version_text(const boost::optional<std::string>& versio
void PrusaLink::set_auth(Http& http) const void PrusaLink::set_auth(Http& http) const
{ {
switch (authorization_type) { switch (m_authorization_type) {
case atKeyPassword: case atKeyPassword:
http.header("X-Api-Key", get_apikey()); http.header("X-Api-Key", get_apikey());
break; break;
case atUserPassword: case atUserPassword:
http.auth_digest(username, password); http.auth_digest(m_username, m_password);
break; break;
} }

View File

@ -29,17 +29,18 @@ public:
bool has_auto_discovery() const override { return true; } bool has_auto_discovery() const override { return true; }
bool can_test() const override { return true; } bool can_test() const override { return true; }
bool can_start_print() const override { return true; } bool can_start_print() const override { return true; }
std::string get_host() const override { return host; } std::string get_host() const override { return m_host; }
const std::string& get_apikey() const { return apikey; } const std::string& get_apikey() const { return m_apikey; }
const std::string& get_cafile() const { return cafile; } const std::string& get_cafile() const { return m_cafile; }
protected: protected:
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const; virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
private: private:
std::string host; std::string m_host;
std::string apikey; std::string m_apikey;
std::string cafile; std::string m_cafile;
bool m_ssl_revoke_best_effort;
virtual void set_auth(Http &http) const; virtual void set_auth(Http &http) const;
std::string make_url(const std::string &path) const; std::string make_url(const std::string &path) const;
@ -64,10 +65,10 @@ private:
void set_auth(Http &http) const override; void set_auth(Http &http) const override;
// Host authorization type. // Host authorization type.
AuthorizationType authorization_type; AuthorizationType m_authorization_type;
// username and password for HTTP Digest Authentization (RFC RFC2617) // username and password for HTTP Digest Authentization (RFC RFC2617)
std::string username; std::string m_username;
std::string password; std::string m_password;
}; };
class PrusaLink : public OctoPrint class PrusaLink : public OctoPrint
@ -89,10 +90,10 @@ private:
void set_auth(Http& http) const override; void set_auth(Http& http) const override;
// Host authorization type. // Host authorization type.
AuthorizationType authorization_type; AuthorizationType m_authorization_type;
// username and password for HTTP Digest Authentization (RFC RFC2617) // username and password for HTTP Digest Authentization (RFC RFC2617)
std::string username; std::string m_username;
std::string password; std::string m_password;
}; };
} }

View File

@ -0,0 +1,46 @@
v 39.349007 -54.069000 -199.819000
v 39.489006 -54.029007 -199.815002
v 39.419006 -53.993011 -199.769012
v 39.629005 -53.975006 -199.815002
v 39.639008 -53.947006 -199.805023
v 39.651001 -53.919006 -199.795013
v 39.807007 -53.863007 -199.796997
v 39.729004 -53.891006 -199.796997
v 39.727005 -53.935013 -199.813019
v 39.767006 -53.899002 -199.805023
v 39.871002 -53.835007 -199.801025
v 39.443001 -53.829010 -199.878998
v 39.523003 -53.965012 -199.827026
v 39.807007 -53.863007 -199.796997
v 39.833008 -53.723007 -199.723022
v 39.759003 -53.822998 -199.822998
v 39.867004 -53.845001 -199.805023
v 39.937004 -53.805008 -199.805023
f 1 2 3
f 4 5 2
f 2 6 3
f 7 8 4
f 9 10 4
f 10 9 11
f 12 2 1
f 13 6 4
f 13 4 2
f 8 7 9
f 6 9 4
f 6 14 15
f 16 14 6
f 17 18 9
f 3 6 15
f 12 16 6
f 12 6 13
f 12 13 2
f 5 4 8
f 6 8 9
f 5 6 2
f 6 5 8
f 17 9 7
f 7 11 17
f 18 11 9
f 11 18 17
f 10 7 4
f 7 10 11

View File

@ -165,29 +165,51 @@ std::vector<Vec3f> its_sample_surface(const indexed_triangle_set &its,
#include "libslic3r/AABBTreeIndirect.hpp" #include "libslic3r/AABBTreeIndirect.hpp"
// return Average abs distance to original struct CompareConfig
float compare(const indexed_triangle_set &original, {
const indexed_triangle_set &simplified, float max_distance = 3.f;
double sample_per_mm2) float max_average_distance = 2.f;
};
bool is_similar(const indexed_triangle_set &from,
const indexed_triangle_set &to,
const CompareConfig &cfg)
{ {
// create ABBTree // create ABBTree
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
original.vertices, original.indices); from.vertices, from.indices);
float sum_distance = 0.f;
float max_distance = 0.f;
unsigned int init = 0; auto collect_distances = [&](const Vec3f &surface_point) {
std::mt19937 rnd(init);
auto samples = its_sample_surface(simplified, sample_per_mm2, rnd);
float sumDistance = 0;
for (const Vec3f &sample : samples) {
size_t hit_idx; size_t hit_idx;
Vec3f hit_point; Vec3f hit_point;
float distance2 = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( float distance2 =
original.vertices, original.indices, tree, sample, hit_idx, AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
hit_point); from.vertices, from.indices, tree, surface_point, hit_idx, hit_point);
sumDistance += sqrt(distance2); float distance = sqrt(distance2);
if (max_distance < distance) max_distance = distance;
sum_distance += distance;
};
for (const Vec3f &vertex : to.vertices) {
collect_distances(vertex);
} }
return sumDistance / samples.size();
for (const Vec3i &t : to.indices) {
Vec3f center(0,0,0);
for (size_t i = 0; i < 3; ++i) {
center += to.vertices[t[i]] / 3;
}
collect_distances(center);
}
size_t count = to.vertices.size() + to.indices.size();
float avg_distance = sum_distance / count;
if (avg_distance > cfg.max_average_distance ||
max_distance > cfg.max_distance)
return false;
return true;
} }
TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]") TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]")
@ -226,8 +248,12 @@ TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]")
(v[i] > v4[i] && v[i] < v2[i]); (v[i] > v4[i] && v[i] < v2[i]);
CHECK(is_between); CHECK(is_between);
} }
float avg_distance = compare(its_, its, 10); CompareConfig cfg;
CHECK(avg_distance < 8e-3f); cfg.max_average_distance = 0.014f;
cfg.max_distance = 0.75f;
CHECK(is_similar(its, its_, cfg));
CHECK(is_similar(its_, its, cfg));
} }
#include "test_utils.hpp" #include "test_utils.hpp"
@ -244,6 +270,21 @@ TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]")
CHECK(its.indices.size() <= wanted_count); CHECK(its.indices.size() <= wanted_count);
double volume = its_volume(its); double volume = its_volume(its);
CHECK(fabs(original_volume - volume) < 33.); CHECK(fabs(original_volume - volume) < 33.);
float avg_distance = compare(mesh.its, its, 10);
CHECK(avg_distance < 0.022f); // 0.02022 | 0.0199614074 CompareConfig cfg;
} cfg.max_average_distance = 0.043f;
cfg.max_distance = 0.32f;
CHECK(is_similar(mesh.its, its, cfg));
CHECK(is_similar(its, mesh.its, cfg));
}
TEST_CASE("Simplify trouble case", "[its]")
{
TriangleMesh tm = load_model("simplification.obj");
REQUIRE_FALSE(tm.empty());
float max_error = std::numeric_limits<float>::max();
uint32_t wanted_count = 8;
its_quadric_edge_collapse(tm.its, wanted_count, &max_error);
CHECK(tm.its.indices.size() <= 8);
}