Merge remote-tracking branch 'remotes/origin/master' into lh_adaptive_infill_hooks

This commit is contained in:
Vojtech Bubnik 2020-11-03 15:07:38 +01:00
commit 414fdaefc5
228 changed files with 30930 additions and 13115 deletions

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.8)
cmake_minimum_required(VERSION 3.13)
project(PrusaSlicer-native)
add_subdirectory(build-utils)
@ -204,11 +204,11 @@ if (WIN32)
VERBATIM
)
endif ()
# This has to be a separate target due to the windows command line lenght limits
add_custom_target(PrusaSlicerDllsCopy ALL DEPENDS PrusaSlicer)
prusaslicer_copy_dlls(PrusaSlicerDllsCopy)
else ()
if (APPLE)
# On OSX, the name of the binary matches the name of the Application.
@ -247,4 +247,7 @@ if (WIN32)
endif ()
else ()
install(TARGETS PrusaSlicer RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
# Install the symlink for gcodeviewer
install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink prusa-slicer prusa-gcodeviewer WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})")
endif ()

View file

@ -45,23 +45,21 @@
#include "libslic3r/Format/OBJ.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Thread.hpp"
#include "PrusaSlicer.hpp"
#ifdef SLIC3R_GUI
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/InstanceCheck.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_Init.hpp"
#endif /* SLIC3R_GUI */
using namespace Slic3r;
int CLI::run(int argc, char **argv)
{
// Mark the main thread for the debugger and for runtime checks.
set_current_thread_name("slic3r_main");
#ifdef __WXGTK__
// On Linux, wxGTK has no support for Wayland, and the app crashes on
// startup if gtk3 is used. This env var has to be set explicitly to
@ -154,6 +152,15 @@ int CLI::run(int argc, char **argv)
// Read input file(s) if any.
#if ENABLE_GCODE_VIEWER
for (const std::string& file : m_input_files) {
std::string ext = boost::filesystem::path(file).extension().string();
if (ext == ".gcode" || ext == ".g") {
if (boost::filesystem::exists(file)) {
start_as_gcodeviewer = true;
break;
}
}
}
if (!start_as_gcodeviewer) {
#endif // ENABLE_GCODE_VIEWER
for (const std::string& file : m_input_files) {
@ -509,9 +516,12 @@ int CLI::run(int argc, char **argv)
outfile_final = sla_print.print_statistics().finalize_output_path(outfile);
sla_archive.export_print(outfile_final, sla_print);
}
if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final)) {
boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl;
return 1;
if (outfile != outfile_final) {
if (Slic3r::rename_file(outfile, outfile_final)) {
boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl;
return 1;
}
outfile = outfile_final;
}
boost::nowide::cout << "Slicing result exported to " << outfile << std::endl;
} catch (const std::exception &ex) {
@ -562,66 +572,20 @@ int CLI::run(int argc, char **argv)
if (start_gui) {
#ifdef SLIC3R_GUI
// #ifdef USE_WX
#if ENABLE_GCODE_VIEWER
GUI::GUI_App* gui = new GUI::GUI_App(start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor);
#else
GUI::GUI_App *gui = new GUI::GUI_App();
#endif // ENABLE_GCODE_VIEWER
bool gui_single_instance_setting = gui->app_config->get("single_instance") == "1";
if (Slic3r::instance_check(argc, argv, gui_single_instance_setting)) {
//TODO: do we have delete gui and other stuff?
return -1;
}
// gui->autosave = m_config.opt_string("autosave");
GUI::GUI_App::SetInstance(gui);
#if ENABLE_GCODE_VIEWER
gui->CallAfter([gui, this, &load_configs, start_as_gcodeviewer] {
#else
gui->CallAfter([gui, this, &load_configs] {
#endif // ENABLE_GCODE_VIEWER
if (!gui->initialized()) {
return;
}
#if ENABLE_GCODE_VIEWER
if (start_as_gcodeviewer) {
if (!m_input_files.empty())
gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str()));
} else {
#endif // ENABLE_GCODE_VIEWER_AS
#if 0
// Load the cummulative config over the currently active profiles.
//FIXME if multiple configs are loaded, only the last one will have an effect.
// We need to decide what to do about loading of separate presets (just print preset, just filament preset etc).
// As of now only the full configs are supported here.
if (!m_print_config.empty())
gui->mainframe->load_config(m_print_config);
#endif
if (!load_configs.empty())
// Load the last config to give it a name at the UI. The name of the preset may be later
// changed by loading an AMF or 3MF.
//FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
gui->mainframe->load_config_file(load_configs.back());
// If loading a 3MF file, the config is loaded from the last one.
if (!m_input_files.empty())
gui->plater()->load_files(m_input_files, true, true);
if (!m_extra_config.empty())
gui->mainframe->load_config(m_extra_config);
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
});
int result = wxEntry(argc, argv);
return result;
#else /* SLIC3R_GUI */
Slic3r::GUI::GUI_InitParams params;
params.argc = argc;
params.argv = argv;
params.load_configs = load_configs;
params.extra_config = std::move(m_extra_config);
params.input_files = std::move(m_input_files);
params.start_as_gcodeviewer = start_as_gcodeviewer;
return Slic3r::GUI::GUI_Run(params);
#else // SLIC3R_GUI
// No GUI support. Just print out a help.
this->print_help(false);
// If started without a parameter, consider it to be OK, otherwise report an error code (no action etc).
return (argc == 0) ? 0 : 1;
#endif /* SLIC3R_GUI */
#endif // SLIC3R_GUI
}
return 0;
@ -646,7 +610,7 @@ bool CLI::setup(int argc, char **argv)
#ifdef __APPLE__
// The application is packed in the .dmg archive as 'Slic3r.app/Contents/MacOS/Slic3r'
// The resources are packed to 'Slic3r.app/Contents/Resources'
boost::filesystem::path path_resources = path_to_binary.parent_path() / "../Resources";
boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../Resources";
#elif defined _WIN32
// The application is packed in the .zip archive in the root,
// The resources are packed to 'resources'
@ -660,7 +624,7 @@ bool CLI::setup(int argc, char **argv)
// The application is packed in the .tar.bz archive (or in AppImage) as 'bin/slic3r',
// The resources are packed to 'resources'
// Path from Slic3r binary to resources:
boost::filesystem::path path_resources = path_to_binary.parent_path() / "../resources";
boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../resources";
#endif
set_resources_dir(path_resources.string());

View file

@ -28,7 +28,7 @@
#include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/detail/endian.hpp>
#include <boost/predef/other/endian.h>
#include "stl.h"
@ -36,9 +36,9 @@
#error "SEEK_SET not defined"
#endif
#ifndef BOOST_LITTLE_ENDIAN
#if BOOST_ENDIAN_BIG_BYTE
extern void stl_internal_reverse_quads(char *buf, size_t cnt);
#endif /* BOOST_LITTLE_ENDIAN */
#endif /* BOOST_ENDIAN_BIG_BYTE */
static FILE* stl_open_count_facets(stl_file *stl, const char *file)
{
@ -89,10 +89,10 @@ static FILE* stl_open_count_facets(stl_file *stl, const char *file)
// Read the int following the header. This should contain # of facets.
uint32_t header_num_facets;
bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, fp) != 0;
#ifndef BOOST_LITTLE_ENDIAN
#if BOOST_ENDIAN_BIG_BYTE
// Convert from little endian to big endian.
stl_internal_reverse_quads((char*)&header_num_facets, 4);
#endif /* BOOST_LITTLE_ENDIAN */
#endif /* BOOST_ENDIAN_BIG_BYTE */
if (! header_num_faces_read || num_facets != header_num_facets)
BOOST_LOG_TRIVIAL(info) << "stl_open_count_facets: Warning: File size doesn't match number of facets in the header: " << file;
}
@ -158,10 +158,10 @@ static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first)
// Read a single facet from a binary .STL file. We assume little-endian architecture!
if (fread(&facet, 1, SIZEOF_STL_FACET, fp) != SIZEOF_STL_FACET)
return false;
#ifndef BOOST_LITTLE_ENDIAN
#if BOOST_ENDIAN_BIG_BYTE
// Convert the loaded little endian data to big endian.
stl_internal_reverse_quads((char*)&facet, 48);
#endif /* BOOST_LITTLE_ENDIAN */
#endif /* BOOST_ENDIAN_BIG_BYTE */
} else {
// Read a single facet from an ASCII .STL file
// skip solid/endsolid

View file

@ -42,10 +42,16 @@
#include <linux/hidraw.h>
#include <linux/version.h>
#include <linux/input.h>
#include <libudev.h>
#include "hidapi.h"
// Declare udev structures needed in this module. They are passed by pointers
// to udev functions and not used directly.
struct udev_device;
struct udev_list_entry;
struct udev_enumerate;
struct udev;
typedef const char* (*hid_wrapper_udev_device_get_devnode_type)(struct udev_device *udev_device);
typedef struct udev_device* (*hid_wrapper_udev_device_get_parent_with_subsystem_devtype_type)(struct udev_device *udev_device, const char *subsystem, const char *devtype);
typedef const char* (*hid_wrapper_udev_device_get_sysattr_value_type)(struct udev_device *udev_device, const char *sysattr);

View file

@ -2,6 +2,7 @@
#include "libslic3r/Utils.hpp"
#include "AppConfig.hpp"
#include "Exception.hpp"
#include "Thread.hpp"
#include <utility>
#include <vector>
@ -37,56 +38,89 @@ void AppConfig::reset()
// Override missing or keys with their defaults.
void AppConfig::set_defaults()
{
// Reset the empty fields to defaults.
if (get("autocenter").empty())
set("autocenter", "0");
// Disable background processing by default as it is not stable.
if (get("background_processing").empty())
set("background_processing", "0");
// If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
// By default, Prusa has the controller hidden.
if (get("no_controller").empty())
set("no_controller", "1");
// If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available.
if (get("no_defaults").empty())
set("no_defaults", "1");
if (get("show_incompatible_presets").empty())
set("show_incompatible_presets", "0");
#if ENABLE_GCODE_VIEWER
if (m_mode == EAppMode::Editor) {
#endif // ENABLE_GCODE_VIEWER
// Reset the empty fields to defaults.
if (get("autocenter").empty())
set("autocenter", "0");
// Disable background processing by default as it is not stable.
if (get("background_processing").empty())
set("background_processing", "0");
// If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
// By default, Prusa has the controller hidden.
if (get("no_controller").empty())
set("no_controller", "1");
// If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available.
if (get("no_defaults").empty())
set("no_defaults", "1");
if (get("show_incompatible_presets").empty())
set("show_incompatible_presets", "0");
if (get("version_check").empty())
set("version_check", "1");
if (get("preset_update").empty())
set("preset_update", "1");
if (get("version_check").empty())
set("version_check", "1");
if (get("preset_update").empty())
set("preset_update", "1");
if (get("export_sources_full_pathnames").empty())
set("export_sources_full_pathnames", "0");
if (get("export_sources_full_pathnames").empty())
set("export_sources_full_pathnames", "0");
// remove old 'use_legacy_opengl' parameter from this config, if present
if (!get("use_legacy_opengl").empty())
erase("", "use_legacy_opengl");
// remove old 'use_legacy_opengl' parameter from this config, if present
if (!get("use_legacy_opengl").empty())
erase("", "use_legacy_opengl");
#ifdef __APPLE__
if (get("use_retina_opengl").empty())
set("use_retina_opengl", "1");
if (get("use_retina_opengl").empty())
set("use_retina_opengl", "1");
#endif
if (get("single_instance").empty())
set("single_instance", "0");
if (get("single_instance").empty())
set("single_instance",
#ifdef __APPLE__
"1"
#else // __APPLE__
"0"
#endif // __APPLE__
);
if (get("remember_output_path").empty())
set("remember_output_path", "1");
if (get("remember_output_path").empty())
set("remember_output_path", "1");
if (get("remember_output_path_removable").empty())
set("remember_output_path_removable", "1");
if (get("remember_output_path_removable").empty())
set("remember_output_path_removable", "1");
if (get("use_custom_toolbar_size").empty())
set("use_custom_toolbar_size", "0");
if (get("use_custom_toolbar_size").empty())
set("use_custom_toolbar_size", "0");
if (get("custom_toolbar_size").empty())
set("custom_toolbar_size", "100");
if (get("custom_toolbar_size").empty())
set("custom_toolbar_size", "100");
if (get("auto_toolbar_size").empty())
set("auto_toolbar_size", "100");
if (get("auto_toolbar_size").empty())
set("auto_toolbar_size", "100");
#if !ENABLE_GCODE_VIEWER
if (get("use_perspective_camera").empty())
set("use_perspective_camera", "1");
if (get("use_free_camera").empty())
set("use_free_camera", "0");
if (get("reverse_mouse_wheel_zoom").empty())
set("reverse_mouse_wheel_zoom", "0");
#endif // !ENABLE_GCODE_VIEWER
#if ENABLE_ENVIRONMENT_MAP
if (get("use_environment_map").empty())
set("use_environment_map", "0");
#endif // ENABLE_ENVIRONMENT_MAP
if (get("use_inches").empty())
set("use_inches", "0");
#if ENABLE_GCODE_VIEWER
}
if (get("seq_top_layer_only").empty())
set("seq_top_layer_only", "1");
if (get("use_perspective_camera").empty())
set("use_perspective_camera", "1");
@ -94,17 +128,19 @@ void AppConfig::set_defaults()
if (get("use_free_camera").empty())
set("use_free_camera", "0");
#if ENABLE_ENVIRONMENT_MAP
if (get("use_environment_map").empty())
set("use_environment_map", "0");
#endif // ENABLE_ENVIRONMENT_MAP
if (get("use_inches").empty())
set("use_inches", "0");
if (get("reverse_mouse_wheel_zoom").empty())
set("reverse_mouse_wheel_zoom", "0");
#endif // ENABLE_GCODE_VIEWER
if (get("show_splash_screen").empty())
set("show_splash_screen", "1");
if (get("default_action_on_close_application").empty())
set("default_action_on_close_application", "none"); // , "discard" or "save"
if (get("default_action_on_select_preset").empty())
set("default_action_on_select_preset", "none"); // , "transfer", "discard" or "save"
// Remove legacy window positions/sizes
erase("", "main_frame_maximized");
erase("", "main_frame_pos");
@ -175,6 +211,20 @@ std::string AppConfig::load()
m_legacy_datadir = ini_ver < Semver(1, 40, 0);
}
// Legacy conversion
if (m_mode == EAppMode::Editor) {
// Convert [extras] "physical_printer" to [presets] "physical_printer",
// remove the [extras] section if it becomes empty.
if (auto it_section = m_storage.find("extras"); it_section != m_storage.end()) {
if (auto it_physical_printer = it_section->second.find("physical_printer"); it_physical_printer != it_section->second.end()) {
m_storage["presets"]["physical_printer"] = it_physical_printer->second;
it_section->second.erase(it_physical_printer);
}
if (it_section->second.empty())
m_storage.erase(it_section);
}
}
// Override missing or keys with their defaults.
this->set_defaults();
m_dirty = false;
@ -183,10 +233,12 @@ std::string AppConfig::load()
void AppConfig::save()
{
#if ENABLE_GCODE_VIEWER
if (!m_save_enabled)
return;
#endif // ENABLE_GCODE_VIEWER
{
// Returns "undefined" if the thread naming functionality is not supported by the operating system.
std::optional<std::string> current_thread_name = get_current_thread_name();
if (current_thread_name && *current_thread_name != "slic3r_main")
throw CriticalException("Calling AppConfig::save() from a worker thread!");
}
// The config is first written to a file with a PID suffix and then moved
// to avoid race conditions with multiple instances of Slic3r
@ -195,7 +247,14 @@ void AppConfig::save()
boost::nowide::ofstream c;
c.open(path_pid, std::ios::out | std::ios::trunc);
#if ENABLE_GCODE_VIEWER
if (m_mode == EAppMode::Editor)
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
else
c << "# " << Slic3r::header_gcodeviewer_generated() << std::endl;
#else
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
#endif // ENABLE_GCODE_VIEWER
// Make sure the "no" category is written first.
for (const std::pair<std::string, std::string> &kvp : m_storage[""])
c << kvp.first << " = " << kvp.second << std::endl;
@ -389,13 +448,22 @@ void AppConfig::reset_selections()
it->second.erase("sla_print");
it->second.erase("sla_material");
it->second.erase("printer");
it->second.erase("physical_printer");
m_dirty = true;
}
}
std::string AppConfig::config_path()
{
return (boost::filesystem::path(Slic3r::data_dir()) / (SLIC3R_APP_KEY ".ini")).make_preferred().string();
#if ENABLE_GCODE_VIEWER
std::string path = (m_mode == EAppMode::Editor) ?
(boost::filesystem::path(Slic3r::data_dir()) / (SLIC3R_APP_KEY ".ini")).make_preferred().string() :
(boost::filesystem::path(Slic3r::data_dir()) / (GCODEVIEWER_APP_KEY ".ini")).make_preferred().string();
return path;
#else
return (boost::filesystem::path(Slic3r::data_dir()) / (SLIC3R_APP_KEY ".ini")).make_preferred().string();
#endif // ENABLE_GCODE_VIEWER
}
std::string AppConfig::version_check_url() const
@ -406,7 +474,11 @@ std::string AppConfig::version_check_url() const
bool AppConfig::exists()
{
#if ENABLE_GCODE_VIEWER
return boost::filesystem::exists(config_path());
#else
return boost::filesystem::exists(AppConfig::config_path());
#endif // ENABLE_GCODE_VIEWER
}
}; // namespace Slic3r

View file

@ -15,11 +15,21 @@ namespace Slic3r {
class AppConfig
{
public:
#if ENABLE_GCODE_VIEWER
enum class EAppMode : unsigned char
{
Editor,
GCodeViewer
};
explicit AppConfig(EAppMode mode) :
#else
AppConfig() :
#endif // ENABLE_GCODE_VIEWER
m_dirty(false),
m_orig_version(Semver::invalid()),
#if ENABLE_GCODE_VIEWER
m_save_enabled(true),
m_mode(mode),
#endif // ENABLE_GCODE_VIEWER
m_legacy_datadir(false)
{
@ -125,22 +135,30 @@ public:
void reset_selections();
// Get the default config path from Slic3r::data_dir().
#if ENABLE_GCODE_VIEWER
std::string config_path();
#else
static std::string config_path();
#endif // ENABLE_GCODE_VIEWER
// Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating)
bool legacy_datadir() const { return m_legacy_datadir; }
void set_legacy_datadir(bool value) { m_legacy_datadir = value; }
bool legacy_datadir() const { return m_legacy_datadir; }
void set_legacy_datadir(bool value) { m_legacy_datadir = value; }
// Get the Slic3r version check url.
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
std::string version_check_url() const;
std::string version_check_url() const;
// Returns the original Slic3r version found in the ini file before it was overwritten
// by the current version
Semver orig_version() const { return m_orig_version; }
Semver orig_version() const { return m_orig_version; }
// Does the config file exist?
#if ENABLE_GCODE_VIEWER
bool exists();
#else
static bool exists();
#endif // ENABLE_GCODE_VIEWER
std::vector<std::string> get_recent_projects() const;
void set_recent_projects(const std::vector<std::string>& recent_projects);
@ -160,10 +178,6 @@ public:
bool get_mouse_device_swap_yz(const std::string& name, bool& swap) const
{ return get_3dmouse_device_numeric_value(name, "swap_yz", swap); }
#if ENABLE_GCODE_VIEWER
void enable_save(bool enable) { m_save_enabled = enable; }
#endif // ENABLE_GCODE_VIEWER
static const std::string SECTION_FILAMENTS;
static const std::string SECTION_MATERIALS;
@ -182,6 +196,10 @@ private:
return true;
}
#if ENABLE_GCODE_VIEWER
EAppMode m_mode { EAppMode::Editor };
#endif // ENABLE_GCODE_VIEWER
// Map of section, name -> value
std::map<std::string, std::map<std::string, std::string>> m_storage;
// Map of enabled vendors / models / variants
@ -190,10 +208,6 @@ private:
bool m_dirty;
// Original version found in the ini file before it was overwritten
Semver m_orig_version;
#if ENABLE_GCODE_VIEWER
// Whether or not calls to save() should take effect
bool m_save_enabled;
#endif // ENABLE_GCODE_VIEWER
// Whether the existing version is before system profiles & configuration updating
bool m_legacy_datadir;
};

View file

@ -1,5 +1,5 @@
project(libslic3r)
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.13)
include(PrecompiledHeader)
@ -62,8 +62,6 @@ add_library(libslic3r STATIC
Fill/FillRectilinear.hpp
Fill/FillRectilinear2.cpp
Fill/FillRectilinear2.hpp
Fill/FillRectilinear3.cpp
Fill/FillRectilinear3.hpp
Flow.cpp
Flow.hpp
format.hpp
@ -81,8 +79,6 @@ add_library(libslic3r STATIC
Format/STL.hpp
Format/SL1.hpp
Format/SL1.cpp
GCode/Analyzer.cpp
GCode/Analyzer.hpp
GCode/ThumbnailData.cpp
GCode/ThumbnailData.hpp
GCode/CoolingBuffer.cpp
@ -111,8 +107,6 @@ add_library(libslic3r STATIC
GCodeReader.hpp
# GCodeSender.cpp
# GCodeSender.hpp
GCodeTimeEstimator.cpp
GCodeTimeEstimator.hpp
GCodeWriter.cpp
GCodeWriter.hpp
Geometry.cpp
@ -201,6 +195,8 @@ add_library(libslic3r STATIC
Utils.hpp
Time.cpp
Time.hpp
Thread.cpp
Thread.hpp
TriangleSelector.cpp
TriangleSelector.hpp
MTUtils.hpp

View file

@ -1414,6 +1414,8 @@ public:
bool multiline = false;
// For text input: If true, the GUI text box spans the complete page width.
bool full_width = false;
// For text input: If true, the GUI formats text as code (fixed-width)
bool is_code = false;
// Not editable. Currently only used for the display of the number of threads.
bool readonly = false;
// Height of a multiline GUI text box.

View file

@ -19,6 +19,7 @@ SLIC3R_DERIVE_EXCEPTION(InvalidArgument, LogicError);
SLIC3R_DERIVE_EXCEPTION(OutOfRange, LogicError);
SLIC3R_DERIVE_EXCEPTION(IOError, CriticalException);
SLIC3R_DERIVE_EXCEPTION(FileIOError, IOError);
SLIC3R_DERIVE_EXCEPTION(HostNetworkError, IOError);
// Runtime exception produced by Slicer. Such exception cancels the slicing process and it shall be shown in notifications.
SLIC3R_DERIVE_EXCEPTION(SlicingError, Exception);
#undef SLIC3R_DERIVE_EXCEPTION

View file

@ -331,7 +331,7 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
return "";
}
ExtrusionRole ExtrusionEntity::string_to_role(const std::string& role)
ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role)
{
if (role == L("Perimeter"))
return erPerimeter;

View file

@ -6,6 +6,7 @@
#include "Polyline.hpp"
#include <assert.h>
#include <string_view>
namespace Slic3r {
@ -106,7 +107,7 @@ public:
virtual double total_volume() const = 0;
static std::string role_to_string(ExtrusionRole role);
static ExtrusionRole string_to_role(const std::string& role);
static ExtrusionRole string_to_role(const std::string_view role);
};
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;

View file

@ -535,7 +535,7 @@ void Layer::make_ironing()
fill_params.density = 1.;
// fill_params.dont_connect = true;
fill_params.dont_connect = false;
fill_params.monotonous = true;
fill_params.monotonic = true;
for (size_t i = 0; i < by_extruder.size(); ++ i) {
// Find span of regions equivalent to the ironing operation.
@ -579,7 +579,7 @@ void Layer::make_ironing()
// Save into layer.
ExtrusionEntityCollection *eec = nullptr;
ironing_params.layerm->fills.entities.push_back(eec = new ExtrusionEntityCollection());
// Don't sort the ironing infill lines as they are monotonously ordered.
// Don't sort the ironing infill lines as they are monotonicly ordered.
eec->no_sort = true;
extrusion_entities_append_paths(
eec->entities, std::move(polylines),

View file

@ -228,7 +228,7 @@ static const std::array<Vec3d, 8> child_centers {
};
// Traversal order of octree children cells for three infill directions,
// so that a single line will be discretized in a strictly monotonous order.
// so that a single line will be discretized in a strictly monotonic order.
static constexpr std::array<std::array<int, 8>, 3> child_traversal_order {
std::array<int, 8>{ 2, 3, 0, 1, 6, 7, 4, 5 },
std::array<int, 8>{ 4, 0, 6, 2, 5, 1, 7, 3 },

View file

@ -15,7 +15,6 @@
#include "FillPlanePath.hpp"
#include "FillRectilinear.hpp"
#include "FillRectilinear2.hpp"
#include "FillRectilinear3.hpp"
#include "FillAdaptive.hpp"
namespace Slic3r {
@ -28,7 +27,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ip3DHoneycomb: return new Fill3DHoneycomb();
case ipGyroid: return new FillGyroid();
case ipRectilinear: return new FillRectilinear2();
case ipMonotonous: return new FillMonotonous();
case ipMonotonic: return new FillMonotonic();
case ipLine: return new FillLine();
case ipGrid: return new FillGrid2();
case ipTriangles: return new FillTriangles();

View file

@ -43,8 +43,8 @@ struct FillParams
// Don't adjust spacing to fill the space evenly.
bool dont_adjust { true };
// Monotonous infill - strictly left to right for better surface quality of top infills.
bool monotonous { false };
// Monotonic infill - strictly left to right for better surface quality of top infills.
bool monotonic { false };
// For Honeycomb.
// we were requested to complete each loop;

View file

@ -154,7 +154,9 @@ struct SegmentIntersection
// Vertical link, up.
Up,
// Vertical link, down.
Down
Down,
// Phony intersection point has no link.
Phony,
};
enum class LinkQuality : uint8_t {
@ -353,6 +355,25 @@ struct SegmentedIntersectionLine
std::vector<SegmentIntersection> intersections;
};
static SegmentIntersection phony_outer_intersection(SegmentIntersection::SegmentIntersectionType type, coord_t pos)
{
assert(type == SegmentIntersection::OUTER_LOW || type == SegmentIntersection::OUTER_HIGH);
SegmentIntersection out;
// Invalid contour & segment.
out.iContour = std::numeric_limits<size_t>::max();
out.iSegment = std::numeric_limits<size_t>::max();
out.pos_p = pos;
out.type = type;
// Invalid prev / next.
out.prev_on_contour = -1;
out.next_on_contour = -1;
out.prev_on_contour_type = SegmentIntersection::LinkType::Phony;
out.next_on_contour_type = SegmentIntersection::LinkType::Phony;
out.prev_on_contour_quality = SegmentIntersection::LinkQuality::Invalid;
out.next_on_contour_quality = SegmentIntersection::LinkQuality::Invalid;
return out;
}
// A container maintaining an expolygon with its inner offsetted polygon.
// The purpose of the inner offsetted polygon is to provide segments to connect the infill lines.
struct ExPolygonWithOffset
@ -889,6 +910,60 @@ static std::vector<SegmentedIntersectionLine> slice_region_by_vertical_lines(con
return segs;
}
#ifndef NDEBUG
bool validate_segment_intersection_connectivity(const std::vector<SegmentedIntersectionLine> &segs)
{
// Validate the connectivity.
for (size_t i_vline = 0; i_vline + 1 < segs.size(); ++ i_vline) {
const SegmentedIntersectionLine &il_left = segs[i_vline];
const SegmentedIntersectionLine &il_right = segs[i_vline + 1];
for (const SegmentIntersection &it : il_left.intersections) {
if (it.has_right_horizontal()) {
const SegmentIntersection &it_right = il_right.intersections[it.right_horizontal()];
// For a right link there is a symmetric left link.
assert(it.iContour == it_right.iContour);
assert(it.type == it_right.type);
assert(it_right.has_left_horizontal());
assert(it_right.left_horizontal() == int(&it - il_left.intersections.data()));
}
}
for (const SegmentIntersection &it : il_right.intersections) {
if (it.has_left_horizontal()) {
const SegmentIntersection &it_left = il_left.intersections[it.left_horizontal()];
// For a right link there is a symmetric left link.
assert(it.iContour == it_left.iContour);
assert(it.type == it_left.type);
assert(it_left.has_right_horizontal());
assert(it_left.right_horizontal() == int(&it - il_right.intersections.data()));
}
}
}
for (size_t i_vline = 0; i_vline < segs.size(); ++ i_vline) {
const SegmentedIntersectionLine &il = segs[i_vline];
for (const SegmentIntersection &it : il.intersections) {
auto i_it = int(&it - il.intersections.data());
if (it.has_left_vertical_up()) {
assert(il.intersections[it.left_vertical_up()].left_vertical_down() == i_it);
assert(il.intersections[it.left_vertical_up()].prev_on_contour_quality == it.prev_on_contour_quality);
}
if (it.has_left_vertical_down()) {
assert(il.intersections[it.left_vertical_down()].left_vertical_up() == i_it);
assert(il.intersections[it.left_vertical_down()].prev_on_contour_quality == it.prev_on_contour_quality);
}
if (it.has_right_vertical_up()) {
assert(il.intersections[it.right_vertical_up()].right_vertical_down() == i_it);
assert(il.intersections[it.right_vertical_up()].next_on_contour_quality == it.next_on_contour_quality);
}
if (it.has_right_vertical_down()) {
assert(il.intersections[it.right_vertical_down()].right_vertical_up() == i_it);
assert(il.intersections[it.right_vertical_down()].next_on_contour_quality == it.next_on_contour_quality);
}
}
}
return true;
}
#endif /* NDEBUG */
// Connect each contour / vertical line intersection point with another two contour / vertical line intersection points.
// (fill in SegmentIntersection::{prev_on_contour, prev_on_contour_vertical, next_on_contour, next_on_contour_vertical}.
// These contour points are either on the same vertical line, or on the vertical line left / right to the current one.
@ -1055,55 +1130,104 @@ static void connect_segment_intersections_by_contours(
}
}
#ifndef NDEBUG
// Validate the connectivity.
for (size_t i_vline = 0; i_vline + 1 < segs.size(); ++ i_vline) {
const SegmentedIntersectionLine &il_left = segs[i_vline];
const SegmentedIntersectionLine &il_right = segs[i_vline + 1];
for (const SegmentIntersection &it : il_left.intersections) {
if (it.has_right_horizontal()) {
const SegmentIntersection &it_right = il_right.intersections[it.right_horizontal()];
// For a right link there is a symmetric left link.
assert(it.iContour == it_right.iContour);
assert(it.type == it_right.type);
assert(it_right.has_left_horizontal());
assert(it_right.left_horizontal() == int(&it - il_left.intersections.data()));
assert(validate_segment_intersection_connectivity(segs));
}
static void pinch_contours_insert_phony_outer_intersections(std::vector<SegmentedIntersectionLine> &segs)
{
// Keep the vector outside the loops, so they will not be reallocated.
// Where to insert new outer points.
std::vector<size_t> insert_after;
// Mapping of indices of current intersection line after inserting new outer points.
std::vector<int32_t> map;
std::vector<SegmentIntersection> temp_intersections;
for (size_t i_vline = 1; i_vline < segs.size(); ++ i_vline) {
SegmentedIntersectionLine &il = segs[i_vline];
assert(il.intersections.empty() || il.intersections.size() >= 2);
if (! il.intersections.empty()) {
assert(il.intersections.front().type == SegmentIntersection::OUTER_LOW);
assert(il.intersections.back().type == SegmentIntersection::OUTER_HIGH);
auto end = il.intersections.end() - 1;
insert_after.clear();
for (auto it = il.intersections.begin() + 1; it != end;) {
if (it->type == SegmentIntersection::OUTER_HIGH) {
++ it;
assert(it->type == SegmentIntersection::OUTER_LOW);
++ it;
} else {
auto lo = it;
assert(lo->type == SegmentIntersection::INNER_LOW);
auto hi = ++ it;
assert(hi->type == SegmentIntersection::INNER_HIGH);
auto lo2 = ++ it;
if (lo2->type == SegmentIntersection::INNER_LOW) {
// INNER_HIGH followed by INNER_LOW. The outer contour may have squeezed the inner contour into two separate loops.
// In that case one shall insert a phony OUTER_HIGH / OUTER_LOW pair.
int up = hi->vertical_up();
int dn = lo2->vertical_down();
#ifndef _NDEBUG
assert(up == -1 || up > 0);
assert(dn == -1 || dn >= 0);
assert((up == -1 && dn == -1) || (dn + 1 == up));
#endif // _NDEBUG
bool pinched = dn + 1 != up;
if (pinched) {
// hi is not connected with its inner contour to lo2.
// Insert a phony OUTER_HIGH / OUTER_LOW pair.
#if 0
static int pinch_idx = 0;
printf("Pinched %d\n", pinch_idx++);
#endif
insert_after.emplace_back(hi - il.intersections.begin());
}
}
}
}
}
for (const SegmentIntersection &it : il_right.intersections) {
if (it.has_left_horizontal()) {
const SegmentIntersection &it_left = il_left.intersections[it.left_horizontal()];
// For a right link there is a symmetric left link.
assert(it.iContour == it_left.iContour);
assert(it.type == it_left.type);
assert(it_left.has_right_horizontal());
assert(it_left.right_horizontal() == int(&it - il_right.intersections.data()));
if (! insert_after.empty()) {
// Insert phony OUTER_HIGH / OUTER_LOW pairs, adjust indices pointing to intersection points on this contour.
map.clear();
{
size_t i = 0;
temp_intersections.clear();
for (size_t idx_inset_after : insert_after) {
for (; i <= idx_inset_after; ++ i) {
map.emplace_back(temp_intersections.size());
temp_intersections.emplace_back(il.intersections[i]);
}
coord_t pos = (temp_intersections.back().pos() + il.intersections[i].pos()) / 2;
temp_intersections.emplace_back(phony_outer_intersection(SegmentIntersection::OUTER_HIGH, pos));
temp_intersections.emplace_back(phony_outer_intersection(SegmentIntersection::OUTER_LOW, pos));
}
for (; i < il.intersections.size(); ++ i) {
map.emplace_back(temp_intersections.size());
temp_intersections.emplace_back(il.intersections[i]);
}
temp_intersections.swap(il.intersections);
}
// Reindex references on current intersection line.
for (SegmentIntersection &ip : il.intersections) {
if (ip.has_left_vertical())
ip.prev_on_contour = map[ip.prev_on_contour];
if (ip.has_right_vertical())
ip.next_on_contour = map[ip.next_on_contour];
}
// Reindex references on previous intersection line.
for (SegmentIntersection &ip : segs[i_vline - 1].intersections)
if (ip.has_right_horizontal())
ip.next_on_contour = map[ip.next_on_contour];
if (i_vline < segs.size()) {
// Reindex references on next intersection line.
for (SegmentIntersection &ip : segs[i_vline + 1].intersections)
if (ip.has_left_horizontal())
ip.prev_on_contour = map[ip.prev_on_contour];
}
}
}
}
for (size_t i_vline = 0; i_vline < segs.size(); ++ i_vline) {
const SegmentedIntersectionLine &il = segs[i_vline];
for (const SegmentIntersection &it : il.intersections) {
auto i_it = int(&it - il.intersections.data());
if (it.has_left_vertical_up()) {
assert(il.intersections[it.left_vertical_up()].left_vertical_down() == i_it);
assert(il.intersections[it.left_vertical_up()].prev_on_contour_quality == it.prev_on_contour_quality);
}
if (it.has_left_vertical_down()) {
assert(il.intersections[it.left_vertical_down()].left_vertical_up() == i_it);
assert(il.intersections[it.left_vertical_down()].prev_on_contour_quality == it.prev_on_contour_quality);
}
if (it.has_right_vertical_up()) {
assert(il.intersections[it.right_vertical_up()].right_vertical_down() == i_it);
assert(il.intersections[it.right_vertical_up()].next_on_contour_quality == it.next_on_contour_quality);
}
if (it.has_right_vertical_down()) {
assert(il.intersections[it.right_vertical_down()].right_vertical_up() == i_it);
assert(il.intersections[it.right_vertical_down()].next_on_contour_quality == it.next_on_contour_quality);
}
}
}
#endif /* NDEBUG */
assert(validate_segment_intersection_connectivity(segs));
}
// Find the last INNER_HIGH intersection starting with INNER_LOW, that is followed by OUTER_HIGH intersection.
@ -1387,7 +1511,7 @@ static void traverse_graph_generate_polylines(
}
}
struct MonotonousRegion
struct MonotonicRegion
{
struct Boundary {
int vline;
@ -1412,13 +1536,13 @@ struct MonotonousRegion
#if NDEBUG
// Left regions are used to track whether all regions left to this one have already been printed.
boost::container::small_vector<MonotonousRegion*, 4> left_neighbors;
boost::container::small_vector<MonotonicRegion*, 4> left_neighbors;
// Right regions are held to pick a next region to be extruded using the "Ant colony" heuristics.
boost::container::small_vector<MonotonousRegion*, 4> right_neighbors;
boost::container::small_vector<MonotonicRegion*, 4> right_neighbors;
#else
// For debugging, use the normal vector as it is better supported by debug visualizers.
std::vector<MonotonousRegion*> left_neighbors;
std::vector<MonotonousRegion*> right_neighbors;
std::vector<MonotonicRegion*> left_neighbors;
std::vector<MonotonicRegion*> right_neighbors;
#endif
};
@ -1429,9 +1553,9 @@ struct AntPath
float pheromone { 0 }; // <0, 1>
};
struct MonotonousRegionLink
struct MonotonicRegionLink
{
MonotonousRegion *region;
MonotonicRegion *region;
bool flipped;
// Distance of right side of this region to left side of the next region, if the "flipped" flag of this region and the next region
// is applied as defined.
@ -1447,7 +1571,7 @@ class AntPathMatrix
{
public:
AntPathMatrix(
const std::vector<MonotonousRegion> &regions,
const std::vector<MonotonicRegion> &regions,
const ExPolygonWithOffset &poly_with_offset,
const std::vector<SegmentedIntersectionLine> &segs,
const float initial_pheromone) :
@ -1463,7 +1587,7 @@ public:
ap.pheromone = initial_pheromone;
}
AntPath& operator()(const MonotonousRegion &region_from, bool flipped_from, const MonotonousRegion &region_to, bool flipped_to)
AntPath& operator()(const MonotonicRegion &region_from, bool flipped_from, const MonotonicRegion &region_to, bool flipped_to)
{
int row = 2 * int(&region_from - m_regions.data()) + flipped_from;
int col = 2 * int(&region_to - m_regions.data()) + flipped_to;
@ -1490,16 +1614,16 @@ public:
return path;
}
AntPath& operator()(const MonotonousRegionLink &region_from, const MonotonousRegion &region_to, bool flipped_to)
AntPath& operator()(const MonotonicRegionLink &region_from, const MonotonicRegion &region_to, bool flipped_to)
{ return (*this)(*region_from.region, region_from.flipped, region_to, flipped_to); }
AntPath& operator()(const MonotonousRegion &region_from, bool flipped_from, const MonotonousRegionLink &region_to)
AntPath& operator()(const MonotonicRegion &region_from, bool flipped_from, const MonotonicRegionLink &region_to)
{ return (*this)(region_from, flipped_from, *region_to.region, region_to.flipped); }
AntPath& operator()(const MonotonousRegionLink &region_from, const MonotonousRegionLink &region_to)
AntPath& operator()(const MonotonicRegionLink &region_from, const MonotonicRegionLink &region_to)
{ return (*this)(*region_from.region, region_from.flipped, *region_to.region, region_to.flipped); }
private:
// Source regions, used for addressing and updating m_matrix.
const std::vector<MonotonousRegion> &m_regions;
const std::vector<MonotonicRegion> &m_regions;
// To calculate the intersection points and contour lengths.
const ExPolygonWithOffset &m_poly_with_offset;
const std::vector<SegmentedIntersectionLine> &m_segs;
@ -1652,9 +1776,9 @@ static std::pair<SegmentIntersection*, SegmentIntersection*> right_overlap(std::
return start_end.first == nullptr ? start_end : right_overlap(*start_end.first, *start_end.second, vline_this, vline_right);
}
static std::vector<MonotonousRegion> generate_montonous_regions(std::vector<SegmentedIntersectionLine> &segs)
static std::vector<MonotonicRegion> generate_montonous_regions(std::vector<SegmentedIntersectionLine> &segs)
{
std::vector<MonotonousRegion> monotonous_regions;
std::vector<MonotonicRegion> monotonic_regions;
#ifndef NDEBUG
#define SLIC3R_DEBUG_MONOTONOUS_REGIONS
@ -1685,11 +1809,11 @@ static std::vector<MonotonousRegion> generate_montonous_regions(std::vector<Segm
SegmentIntersection *start = &vline_seed.intersections[i_intersection_seed];
SegmentIntersection *end = &end_of_vertical_run(vline_seed, *start);
if (! start->consumed_vertical_up) {
// Draw a new monotonous region starting with this segment.
// Draw a new monotonic region starting with this segment.
// while there is only a single right neighbor
int i_vline = i_vline_seed;
std::pair<SegmentIntersection*, SegmentIntersection*> left(start, end);
MonotonousRegion region;
MonotonicRegion region;
region.left.vline = i_vline;
region.left.low = int(left.first - vline_seed.intersections.data());
region.left.high = int(left.second - vline_seed.intersections.data());
@ -1722,19 +1846,19 @@ static std::vector<MonotonousRegion> generate_montonous_regions(std::vector<Segm
}
// Even number of lines makes the infill zig-zag to exit on the other side of the region than where it starts.
region.flips = (num_lines & 1) != 0;
monotonous_regions.emplace_back(region);
monotonic_regions.emplace_back(region);
}
i_intersection_seed = int(end - vline_seed.intersections.data()) + 1;
}
}
return monotonous_regions;
return monotonic_regions;
}
// Traverse path, calculate length of the draw for the purpose of optimization.
// This function is very similar to polylines_from_paths() in the way how it traverses the path, but
// polylines_from_paths() emits a path, while this function just calculates the path length.
static float montonous_region_path_length(const MonotonousRegion &region, bool dir, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs)
static float montonous_region_path_length(const MonotonicRegion &region, bool dir, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs)
{
// From the initial point (i_vline, i_intersection), follow a path.
int i_intersection = region.left_intersection_point(dir);
@ -1822,15 +1946,15 @@ static float montonous_region_path_length(const MonotonousRegion &region, bool d
return unscale<float>(total_length);
}
static void connect_monotonous_regions(std::vector<MonotonousRegion> &regions, const ExPolygonWithOffset &poly_with_offset, std::vector<SegmentedIntersectionLine> &segs)
static void connect_monotonic_regions(std::vector<MonotonicRegion> &regions, const ExPolygonWithOffset &poly_with_offset, std::vector<SegmentedIntersectionLine> &segs)
{
// Map from low intersection to left / right side of a monotonous region.
using MapType = std::pair<SegmentIntersection*, MonotonousRegion*>;
// Map from low intersection to left / right side of a monotonic region.
using MapType = std::pair<SegmentIntersection*, MonotonicRegion*>;
std::vector<MapType> map_intersection_to_region_start;
std::vector<MapType> map_intersection_to_region_end;
map_intersection_to_region_start.reserve(regions.size());
map_intersection_to_region_end.reserve(regions.size());
for (MonotonousRegion &region : regions) {
for (MonotonicRegion &region : regions) {
map_intersection_to_region_start.emplace_back(&segs[region.left.vline].intersections[region.left.low], &region);
map_intersection_to_region_end.emplace_back(&segs[region.right.vline].intersections[region.right.low], &region);
}
@ -1840,7 +1964,7 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> &regions, c
std::sort(map_intersection_to_region_end.begin(), map_intersection_to_region_end.end(), intersections_lower);
// Scatter links to neighboring regions.
for (MonotonousRegion &region : regions) {
for (MonotonicRegion &region : regions) {
if (region.left.vline > 0) {
auto &vline = segs[region.left.vline];
auto &vline_left = segs[region.left.vline - 1];
@ -1884,17 +2008,17 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> &regions, c
// Sometimes a segment may indicate that it connects to a segment on the other side while the other does not.
// This may be a valid case if one side contains runs of OUTER_LOW, INNER_LOW, {INNER_HIGH, INNER_LOW}*, INNER_HIGH, OUTER_HIGH,
// where the part in the middle does not connect to the other side, but it will be extruded through.
for (MonotonousRegion &region : regions) {
for (MonotonicRegion &region : regions) {
std::sort(region.left_neighbors.begin(), region.left_neighbors.end());
std::sort(region.right_neighbors.begin(), region.right_neighbors.end());
}
for (MonotonousRegion &region : regions) {
for (MonotonousRegion *neighbor : region.left_neighbors) {
for (MonotonicRegion &region : regions) {
for (MonotonicRegion *neighbor : region.left_neighbors) {
auto it = std::lower_bound(neighbor->right_neighbors.begin(), neighbor->right_neighbors.end(), &region);
if (it == neighbor->right_neighbors.end() || *it != &region)
neighbor->right_neighbors.insert(it, &region);
}
for (MonotonousRegion *neighbor : region.right_neighbors) {
for (MonotonicRegion *neighbor : region.right_neighbors) {
auto it = std::lower_bound(neighbor->left_neighbors.begin(), neighbor->left_neighbors.end(), &region);
if (it == neighbor->left_neighbors.end() || *it != &region)
neighbor->left_neighbors.insert(it, &region);
@ -1903,12 +2027,12 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> &regions, c
#ifndef NDEBUG
// Verify symmetry of the left_neighbors / right_neighbors.
for (MonotonousRegion &region : regions) {
for (MonotonousRegion *neighbor : region.left_neighbors) {
for (MonotonicRegion &region : regions) {
for (MonotonicRegion *neighbor : region.left_neighbors) {
assert(std::count(region.left_neighbors.begin(), region.left_neighbors.end(), neighbor) == 1);
assert(std::find(neighbor->right_neighbors.begin(), neighbor->right_neighbors.end(), &region) != neighbor->right_neighbors.end());
}
for (MonotonousRegion *neighbor : region.right_neighbors) {
for (MonotonicRegion *neighbor : region.right_neighbors) {
assert(std::count(region.right_neighbors.begin(), region.right_neighbors.end(), neighbor) == 1);
assert(std::find(neighbor->left_neighbors.begin(), neighbor->left_neighbors.end(), &region) != neighbor->left_neighbors.end());
}
@ -1916,7 +2040,7 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> &regions, c
#endif /* NDEBUG */
// Fill in sum length of connecting lines of a region. This length is used for optimizing the infill path for minimum length.
for (MonotonousRegion &region : regions) {
for (MonotonicRegion &region : regions) {
region.len1 = montonous_region_path_length(region, false, poly_with_offset, segs);
region.len2 = montonous_region_path_length(region, true, poly_with_offset, segs);
// Subtract the smaller length from the longer one, so we will optimize just with the positive difference of the two.
@ -1934,7 +2058,7 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> &regions, c
// https://www.chalmers.se/en/departments/math/research/research-groups/optimization/OptimizationMasterTheses/MScThesis-RaadSalman-final.pdf
// Algorithm 6.1 Lexicographic Path Preserving 3-opt
// Optimize path while maintaining the ordering constraints.
void monotonous_3_opt(std::vector<MonotonousRegionLink> &path, const std::vector<SegmentedIntersectionLine> &segs)
void monotonic_3_opt(std::vector<MonotonicRegionLink> &path, const std::vector<SegmentedIntersectionLine> &segs)
{
// When doing the 3-opt path preserving flips, one has to fulfill two constraints:
//
@ -1949,7 +2073,7 @@ void monotonous_3_opt(std::vector<MonotonousRegionLink> &path, const std::vector
// then the precedence constraint verification is amortized inside the O(n^3) loop. Now which is better for our task?
//
// It is beneficial to also try flipping of the infill zig-zags, for which a prefix sum of both flipped and non-flipped paths over
// MonotonousRegionLinks may be utilized, however updating the prefix sum has a linear complexity, the same complexity as doing the 3-opt
// MonotonicRegionLinks may be utilized, however updating the prefix sum has a linear complexity, the same complexity as doing the 3-opt
// exchange by copying the pieces.
}
@ -1962,17 +2086,17 @@ inline void print_ant(const std::string& fmt, TArgs&&... args) {
#endif
}
// Find a run through monotonous infill blocks using an 'Ant colony" optimization method.
// Find a run through monotonic infill blocks using an 'Ant colony" optimization method.
// http://www.scholarpedia.org/article/Ant_colony_optimization
static std::vector<MonotonousRegionLink> chain_monotonous_regions(
std::vector<MonotonousRegion> &regions, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs, std::mt19937_64 &rng)
static std::vector<MonotonicRegionLink> chain_monotonic_regions(
std::vector<MonotonicRegion> &regions, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs, std::mt19937_64 &rng)
{
// Number of left neighbors (regions that this region depends on, this region cannot be printed before the regions left of it are printed) + self.
std::vector<int32_t> left_neighbors_unprocessed(regions.size(), 1);
// Queue of regions, which have their left neighbors already printed.
std::vector<MonotonousRegion*> queue;
std::vector<MonotonicRegion*> queue;
queue.reserve(regions.size());
for (MonotonousRegion &region : regions)
for (MonotonicRegion &region : regions)
if (region.left_neighbors.empty())
queue.emplace_back(&region);
else
@ -1981,13 +2105,13 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
auto left_neighbors_unprocessed_initial = left_neighbors_unprocessed;
auto queue_initial = queue;
std::vector<MonotonousRegionLink> path, best_path;
std::vector<MonotonicRegionLink> path, best_path;
path.reserve(regions.size());
best_path.reserve(regions.size());
float best_path_length = std::numeric_limits<float>::max();
struct NextCandidate {
MonotonousRegion *region;
MonotonicRegion *region;
AntPath *link;
AntPath *link_flipped;
float probability;
@ -2002,22 +2126,22 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
[&regions, &left_neighbors_unprocessed, &path, &queue]() {
std::vector<unsigned char> regions_processed(regions.size(), false);
std::vector<unsigned char> regions_in_queue(regions.size(), false);
for (const MonotonousRegion *region : queue) {
for (const MonotonicRegion *region : queue) {
// This region is not processed yet, his predecessors are processed.
assert(left_neighbors_unprocessed[region - regions.data()] == 1);
regions_in_queue[region - regions.data()] = true;
}
for (const MonotonousRegionLink &link : path) {
for (const MonotonicRegionLink &link : path) {
assert(left_neighbors_unprocessed[link.region - regions.data()] == 0);
regions_processed[link.region - regions.data()] = true;
}
for (size_t i = 0; i < regions_processed.size(); ++ i) {
assert(! regions_processed[i] || ! regions_in_queue[i]);
const MonotonousRegion &region = regions[i];
const MonotonicRegion &region = regions[i];
if (regions_processed[i] || regions_in_queue[i]) {
assert(left_neighbors_unprocessed[i] == (regions_in_queue[i] ? 1 : 0));
// All left neighbors should be processed already.
for (const MonotonousRegion *left : region.left_neighbors) {
for (const MonotonicRegion *left : region.left_neighbors) {
assert(regions_processed[left - regions.data()]);
assert(left_neighbors_unprocessed[left - regions.data()] == 0);
}
@ -2026,7 +2150,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
assert(left_neighbors_unprocessed[i] > 1);
size_t num_predecessors_unprocessed = 0;
bool has_left_last_on_path = false;
for (const MonotonousRegion* left : region.left_neighbors) {
for (const MonotonicRegion* left : region.left_neighbors) {
size_t iprev = left - regions.data();
if (regions_processed[iprev]) {
assert(left_neighbors_unprocessed[iprev] == 0);
@ -2058,7 +2182,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
// After how many rounds without an improvement to exit?
constexpr int num_rounds_no_change_exit = 8;
// With how many ants each of the run will be performed?
const int num_ants = std::min<int>(regions.size(), 10);
const int num_ants = std::min(int(regions.size()), 10);
// Base (initial) pheromone level. This value will be adjusted based on the length of the first greedy path found.
float pheromone_initial_deposit = 0.5f;
// Evaporation rate of pheromones.
@ -2080,18 +2204,18 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
left_neighbors_unprocessed = left_neighbors_unprocessed_initial;
assert(validate_unprocessed());
// Pick the last of the queue.
MonotonousRegionLink path_end { queue.back(), false };
MonotonicRegionLink path_end { queue.back(), false };
queue.pop_back();
-- left_neighbors_unprocessed[path_end.region - regions.data()];
float total_length = path_end.region->length(false);
while (! queue.empty() || ! path_end.region->right_neighbors.empty()) {
// Chain.
MonotonousRegion &region = *path_end.region;
MonotonicRegion &region = *path_end.region;
bool dir = path_end.flipped;
NextCandidate next_candidate;
next_candidate.probability = 0;
for (MonotonousRegion *next : region.right_neighbors) {
for (MonotonicRegion *next : region.right_neighbors) {
int &unprocessed = left_neighbors_unprocessed[next - regions.data()];
assert(unprocessed > 1);
if (left_neighbors_unprocessed[next - regions.data()] == 2) {
@ -2106,7 +2230,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
}
bool from_queue = next_candidate.probability == 0;
if (from_queue) {
for (MonotonousRegion *next : queue) {
for (MonotonicRegion *next : queue) {
AntPath &path1 = path_matrix(region, dir, *next, false);
AntPath &path2 = path_matrix(region, dir, *next, true);
if (path1.visibility > next_candidate.probability)
@ -2116,7 +2240,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
}
}
// Move the other right neighbors with satisified constraints to the queue.
for (MonotonousRegion *next : region.right_neighbors)
for (MonotonicRegion *next : region.right_neighbors)
if (-- left_neighbors_unprocessed[next - regions.data()] == 1 && next_candidate.region != next)
queue.emplace_back(next);
if (from_queue) {
@ -2127,7 +2251,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
queue.pop_back();
}
// Extend the path.
MonotonousRegion *next_region = next_candidate.region;
MonotonicRegion *next_region = next_candidate.region;
bool next_dir = next_candidate.dir;
total_length += next_region->length(next_dir) + path_matrix(*path_end.region, path_end.flipped, *next_region, next_dir).length;
path_end = { next_region, next_dir };
@ -2136,11 +2260,11 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
}
// Set an initial pheromone value to 10% of the greedy path's value.
pheromone_initial_deposit = 0.1 / total_length;
pheromone_initial_deposit = 0.1f / total_length;
path_matrix.update_inital_pheromone(pheromone_initial_deposit);
}
// Probability (unnormalized) of traversing a link between two monotonous regions.
// Probability (unnormalized) of traversing a link between two monotonic regions.
auto path_probability = [pheromone_alpha, pheromone_beta](AntPath &path) {
return pow(path.pheromone, pheromone_alpha) * pow(path.visibility, pheromone_beta);
};
@ -2163,10 +2287,10 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
left_neighbors_unprocessed = left_neighbors_unprocessed_initial;
assert(validate_unprocessed());
// Pick randomly the first from the queue at random orientation.
//FIXME picking the 1st monotonous region should likely be done based on accumulated pheromone level as well,
// but the inefficiency caused by the random pick of the 1st monotonous region is likely insignificant.
//FIXME picking the 1st monotonic region should likely be done based on accumulated pheromone level as well,
// but the inefficiency caused by the random pick of the 1st monotonic region is likely insignificant.
int first_idx = std::uniform_int_distribution<>(0, int(queue.size()) - 1)(rng);
path.emplace_back(MonotonousRegionLink{ queue[first_idx], rng() > rng.max() / 2 });
path.emplace_back(MonotonicRegionLink{ queue[first_idx], rng() > rng.max() / 2 });
*(queue.begin() + first_idx) = std::move(queue.back());
queue.pop_back();
-- left_neighbors_unprocessed[path.back().region - regions.data()];
@ -2182,12 +2306,12 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
while (! queue.empty() || ! path.back().region->right_neighbors.empty()) {
// Chain.
MonotonousRegion &region = *path.back().region;
MonotonicRegion &region = *path.back().region;
bool dir = path.back().flipped;
// Sort by distance to pt.
next_candidates.clear();
next_candidates.reserve(region.right_neighbors.size() * 2);
for (MonotonousRegion *next : region.right_neighbors) {
for (MonotonicRegion *next : region.right_neighbors) {
int &unprocessed = left_neighbors_unprocessed[next - regions.data()];
assert(unprocessed > 1);
if (-- unprocessed == 1) {
@ -2204,7 +2328,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
//FIXME add the queue items to the candidates? These are valid moves as well.
if (num_direct_neighbors == 0) {
// Add the queue candidates.
for (MonotonousRegion *next : queue) {
for (MonotonicRegion *next : queue) {
assert(left_neighbors_unprocessed[next - regions.data()] == 1);
AntPath &path1 = path_matrix(region, dir, *next, false);
AntPath &path1_flipped = path_matrix(region, ! dir, *next, true);
@ -2247,11 +2371,11 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
queue.pop_back();
}
// Extend the path.
MonotonousRegion *next_region = take_path->region;
MonotonicRegion *next_region = take_path->region;
bool next_dir = take_path->dir;
path.back().next = take_path->link;
path.back().next_flipped = take_path->link_flipped;
path.emplace_back(MonotonousRegionLink{ next_region, next_dir });
path.emplace_back(MonotonicRegionLink{ next_region, next_dir });
assert(left_neighbors_unprocessed[next_region - regions.data()] == 1);
left_neighbors_unprocessed[next_region - regions.data()] = 0;
print_ant("\tRegion (%1%:%2%,%3%) (%4%:%5%,%6%) length to prev %7%",
@ -2279,14 +2403,14 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
}
// Perform 3-opt local optimization of the path.
monotonous_3_opt(path, segs);
monotonic_3_opt(path, segs);
// Measure path length.
assert(! path.empty());
float path_length = std::accumulate(path.begin(), path.end() - 1,
path.back().region->length(path.back().flipped),
[&path_matrix](const float l, const MonotonousRegionLink &r) {
const MonotonousRegionLink &next = *(&r + 1);
[&path_matrix](const float l, const MonotonicRegionLink &r) {
const MonotonicRegionLink &next = *(&r + 1);
return l + r.region->length(r.flipped) + path_matrix(*r.region, r.flipped, *next.region, next.flipped).length;
});
// Save the shortest path.
@ -2309,7 +2433,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
// Reinforce the path pheromones with the best path.
float total_cost = best_path_length + float(EPSILON);
for (size_t i = 0; i + 1 < path.size(); ++ i) {
MonotonousRegionLink &link = path[i];
MonotonicRegionLink &link = path[i];
link.next->pheromone = (1.f - pheromone_evaporation) * link.next->pheromone + pheromone_evaporation / total_cost;
}
@ -2324,7 +2448,7 @@ end:
}
// Traverse path, produce polylines.
static void polylines_from_paths(const std::vector<MonotonousRegionLink> &path, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs, Polylines &polylines_out)
static void polylines_from_paths(const std::vector<MonotonicRegionLink> &path, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs, Polylines &polylines_out)
{
Polyline *polyline = nullptr;
auto finish_polyline = [&polyline, &polylines_out]() {
@ -2334,14 +2458,26 @@ static void polylines_from_paths(const std::vector<MonotonousRegionLink> &path,
// Handle nearly zero length edges.
if (polyline->points.size() <= 1 ||
(polyline->points.size() == 2 &&
std::abs(polyline->points.front()(0) - polyline->points.back()(0)) < SCALED_EPSILON &&
std::abs(polyline->points.front()(1) - polyline->points.back()(1)) < SCALED_EPSILON))
std::abs(polyline->points.front().x() - polyline->points.back().x()) < SCALED_EPSILON &&
std::abs(polyline->points.front().y() - polyline->points.back().y()) < SCALED_EPSILON))
polylines_out.pop_back();
else if (polylines_out.size() >= 2) {
assert(polyline->points.size() >= 2);
// Merge the two last polylines. An extrusion may have been split by an introduction of phony outer points on intersection lines
// to cope with pinching of inner offset contours.
Polyline &pl_prev = polylines_out[polylines_out.size() - 2];
if (std::abs(polyline->points.front().x() - pl_prev.points.back().x()) < SCALED_EPSILON &&
std::abs(polyline->points.front().y() - pl_prev.points.back().y()) < SCALED_EPSILON) {
pl_prev.points.back() = (pl_prev.points.back() + polyline->points.front()) / 2;
pl_prev.points.insert(pl_prev.points.end(), polyline->points.begin() + 1, polyline->points.end());
polylines_out.pop_back();
}
}
polyline = nullptr;
};
for (const MonotonousRegionLink &path_segment : path) {
MonotonousRegion &region = *path_segment.region;
for (const MonotonicRegionLink &path_segment : path) {
MonotonicRegion &region = *path_segment.region;
bool dir = path_segment.flipped;
// From the initial point (i_vline, i_intersection), follow a path.
@ -2350,8 +2486,8 @@ static void polylines_from_paths(const std::vector<MonotonousRegionLink> &path,
if (polyline != nullptr && &path_segment != path.data()) {
// Connect previous path segment with the new one.
const MonotonousRegionLink &path_segment_prev = *(&path_segment - 1);
const MonotonousRegion &region_prev = *path_segment_prev.region;
const MonotonicRegionLink &path_segment_prev = *(&path_segment - 1);
const MonotonicRegion &region_prev = *path_segment_prev.region;
bool dir_prev = path_segment_prev.flipped;
int i_vline_prev = region_prev.right.vline;
const SegmentedIntersectionLine &vline_prev = segs[i_vline_prev];
@ -2456,7 +2592,7 @@ static void polylines_from_paths(const std::vector<MonotonousRegionLink> &path,
if (polyline != nullptr) {
// Finish the current vertical line,
const MonotonousRegion &region = *path.back().region;
const MonotonicRegion &region = *path.back().region;
const SegmentedIntersectionLine &vline = segs[region.right.vline];
const SegmentIntersection *ip = &vline.intersections[region.right_intersection_point(path.back().flipped)];
assert(ip->is_inner());
@ -2489,7 +2625,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP
surface->expolygon,
- rotate_vector.first,
float(scale_(this->overlap - (0.5 - INFILL_OVERLAP_OVER_SPACING) * this->spacing)),
float(scale_(this->overlap - 0.5 * this->spacing)));
float(scale_(this->overlap - 0.5f * this->spacing)));
if (poly_with_offset.n_contours_inner == 0) {
// Not a single infill line fits.
//FIXME maybe one shall trigger the gap fill here?
@ -2558,14 +2694,18 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP
svg.Close();
#endif /* SLIC3R_DEBUG */
//FIXME this is a hack to get the monotonous infill rolling. We likely want a smarter switch, likely based on user decison.
bool monotonous_infill = params.monotonous; // || params.density > 0.99;
if (monotonous_infill) {
std::vector<MonotonousRegion> regions = generate_montonous_regions(segs);
connect_monotonous_regions(regions, poly_with_offset, segs);
//FIXME this is a hack to get the monotonic infill rolling. We likely want a smarter switch, likely based on user decison.
bool monotonic_infill = params.monotonic; // || params.density > 0.99;
if (monotonic_infill) {
// Sometimes the outer contour pinches the inner contour from both sides along a single vertical line.
// This situation is not handled correctly by generate_montonous_regions().
// Insert phony OUTER_HIGH / OUTER_LOW pairs at the position where the contour is pinched.
pinch_contours_insert_phony_outer_intersections(segs);
std::vector<MonotonicRegion> regions = generate_montonous_regions(segs);
connect_monotonic_regions(regions, poly_with_offset, segs);
if (! regions.empty()) {
std::mt19937_64 rng;
std::vector<MonotonousRegionLink> path = chain_monotonous_regions(regions, poly_with_offset, segs, rng);
std::vector<MonotonicRegionLink> path = chain_monotonic_regions(regions, poly_with_offset, segs, rng);
polylines_from_paths(path, poly_with_offset, segs, polylines_out);
}
} else
@ -2616,13 +2756,13 @@ Polylines FillRectilinear2::fill_surface(const Surface *surface, const FillParam
return polylines_out;
}
Polylines FillMonotonous::fill_surface(const Surface *surface, const FillParams &params)
Polylines FillMonotonic::fill_surface(const Surface *surface, const FillParams &params)
{
FillParams params2 = params;
params2.monotonous = true;
params2.monotonic = true;
Polylines polylines_out;
if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out)) {
printf("FillMonotonous::fill_surface() failed to fill a region.\n");
printf("FillMonotonic::fill_surface() failed to fill a region.\n");
}
return polylines_out;
}

View file

@ -20,11 +20,11 @@ protected:
bool fill_surface_by_lines(const Surface *surface, const FillParams &params, float angleBase, float pattern_shift, Polylines &polylines_out);
};
class FillMonotonous : public FillRectilinear2
class FillMonotonic : public FillRectilinear2
{
public:
virtual Fill* clone() const { return new FillMonotonous(*this); };
virtual ~FillMonotonous() = default;
virtual Fill* clone() const { return new FillMonotonic(*this); };
virtual ~FillMonotonic() = default;
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
virtual bool no_sort() const { return true; }
};

File diff suppressed because it is too large Load diff

View file

@ -1,83 +0,0 @@
#ifndef slic3r_FillRectilinear3_hpp_
#define slic3r_FillRectilinear3_hpp_
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class Surface;
class FillRectilinear3 : public Fill
{
public:
virtual Fill* clone() const { return new FillRectilinear3(*this); };
virtual ~FillRectilinear3() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
struct FillDirParams
{
FillDirParams(coordf_t spacing, double angle, coordf_t pattern_shift = 0.f) :
spacing(spacing), angle(angle), pattern_shift(pattern_shift) {}
coordf_t spacing;
double angle;
coordf_t pattern_shift;
};
protected:
bool fill_surface_by_lines(const Surface *surface, const FillParams &params, std::vector<FillDirParams> &fill_dir_params, Polylines &polylines_out);
};
class FillGrid3 : public FillRectilinear3
{
public:
virtual Fill* clone() const { return new FillGrid3(*this); };
virtual ~FillGrid3() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
};
class FillTriangles3 : public FillRectilinear3
{
public:
virtual Fill* clone() const { return new FillTriangles3(*this); };
virtual ~FillTriangles3() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
};
class FillStars3 : public FillRectilinear3
{
public:
virtual Fill* clone() const { return new FillStars3(*this); };
virtual ~FillStars3() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
};
class FillCubic3 : public FillRectilinear3
{
public:
virtual Fill* clone() const { return new FillCubic3(*this); };
virtual ~FillCubic3() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
};
}; // namespace Slic3r
#endif // slic3r_FillRectilinear3_hpp_

View file

@ -1899,9 +1899,9 @@ namespace Slic3r {
assert(index < geometry.custom_supports.size());
assert(index < geometry.custom_seam.size());
if (! geometry.custom_supports[index].empty())
volume->m_supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
volume->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
if (! geometry.custom_seam[index].empty())
volume->m_seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
}
@ -2417,11 +2417,11 @@ namespace Slic3r {
stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" ";
}
std::string custom_supports_data_string = volume->m_supported_facets.get_triangle_as_string(i);
std::string custom_supports_data_string = volume->supported_facets.get_triangle_as_string(i);
if (! custom_supports_data_string.empty())
stream << CUSTOM_SUPPORTS_ATTR << "=\"" << custom_supports_data_string << "\" ";
std::string custom_seam_data_string = volume->m_seam_facets.get_triangle_as_string(i);
std::string custom_seam_data_string = volume->seam_facets.get_triangle_as_string(i);
if (! custom_seam_data_string.empty())
stream << CUSTOM_SEAM_ATTR << "=\"" << custom_seam_data_string << "\" ";

View file

@ -383,6 +383,7 @@ void fill_slicerconf(ConfMap &m, const SLAPrint &print)
static constexpr auto banned_keys = {
"compatible_printers"sv,
"compatible_prints"sv,
//FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed.
"print_host"sv,
"printhost_apikey"sv,
"printhost_cafile"sv

View file

@ -158,7 +158,7 @@ namespace Slic3r {
polygons_per_layer[i * 2] = union_(polys);
}
});
for (size_t i = 0; i < cnt / 2; ++i)
for (size_t i = 1; i < cnt / 2; ++i)
polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]);
if (cnt & 1)
polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]);
@ -819,48 +819,54 @@ namespace DoExport {
// this->print_machine_envelope(file, print);
// shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor.
if (config.gcode_flavor.value == gcfMarlin) {
normal_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values[0]);
normal_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values[0]);
normal_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values[0]);
normal_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values[0]);
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values[0]);
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values[0]);
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values[0]);
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values[0]);
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values[0]);
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values[0]);
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values[0]);
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values[0]);
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values[0]);
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values[0]);
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values[0]);
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values[0]);
if (config.machine_limits_usage.value != MachineLimitsUsage::Ignore) {
normal_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values[0]);
normal_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values[0]);
normal_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values[0]);
normal_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values[0]);
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values[0]);
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values[0]);
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values[0]);
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values[0]);
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values[0]);
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values[0]);
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values[0]);
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values[0]);
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values[0]);
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values[0]);
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values[0]);
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values[0]);
}
if (silent_time_estimator_enabled)
{
silent_time_estimator.reset();
silent_time_estimator.set_dialect(config.gcode_flavor);
silent_time_estimator.set_extrusion_axis(config.get_extrusion_axis()[0]);
/* "Stealth mode" values can be just a copy of "normal mode" values
* (when they aren't input for a printer preset).
* Thus, use back value from values, instead of second one, which could be absent
*/
silent_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values.back());
silent_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values.back());
silent_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values.back());
silent_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values.back());
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values.back());
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values.back());
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values.back());
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values.back());
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values.back());
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values.back());
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values.back());
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values.back());
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values.back());
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values.back());
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values.back());
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values.back());
if (config.machine_limits_usage.value != MachineLimitsUsage::Ignore) {
/* "Stealth mode" values can be just a copy of "normal mode" values
* (when they aren't input for a printer preset).
* Thus, use back value from values, instead of second one, which could be absent
*/
silent_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values.back());
silent_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values.back());
silent_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values.back());
silent_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values.back());
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values.back());
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values.back());
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values.back());
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values.back());
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values.back());
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values.back());
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values.back());
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values.back());
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values.back());
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values.back());
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values.back());
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values.back());
}
if (config.single_extruder_multi_material) {
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
// are considered to be active for the single extruder multi-material printers only.
@ -1082,14 +1088,14 @@ namespace DoExport {
++ dst.second;
};
print_statistics.filament_stats.insert(std::pair<size_t, float>{extruder.id(), (float)used_filament});
append(out_filament_used_mm, "%.1lf", used_filament);
append(out_filament_used_cm3, "%.1lf", extruded_volume * 0.001);
append(out_filament_used_mm, "%.2lf", used_filament);
append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001);
if (filament_weight > 0.) {
print_statistics.total_weight = print_statistics.total_weight + filament_weight;
append(out_filament_used_g, "%.1lf", filament_weight);
append(out_filament_used_g, "%.2lf", filament_weight);
if (filament_cost > 0.) {
print_statistics.total_cost = print_statistics.total_cost + filament_cost;
append(out_filament_cost, "%.1lf", filament_cost);
append(out_filament_cost, "%.2lf", filament_cost);
}
}
print_statistics.total_used_filament += used_filament;
@ -1598,8 +1604,8 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Modifies
print.m_print_statistics));
_write(file, "\n");
_write_format(file, "; total filament used [g] = %.1lf\n", print.m_print_statistics.total_weight);
_write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
_write_format(file, "; total filament used [g] = %.2lf\n", print.m_print_statistics.total_weight);
_write_format(file, "; total filament cost = %.2lf\n", print.m_print_statistics.total_cost);
if (print.m_print_statistics.total_toolchanges > 0)
_write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
#if ENABLE_GCODE_VIEWER
@ -1613,7 +1619,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Append full config.
_write(file, "\n");
{
std::string full_config = "";
std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
_write(file, full_config);
@ -1636,9 +1642,9 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std
}
}
// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code.
// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the custom G-code.
// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out.
static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, int &temp_out)
static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, const bool include_g10, int &temp_out)
{
temp_out = -1;
if (gcode.empty())
@ -1649,17 +1655,23 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
while (*ptr != 0) {
// Skip whitespaces.
for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
if (*ptr == 'M') {
// Line starts with 'M'. It is a machine command.
if (*ptr == 'M' || // Line starts with 'M'. It is a machine command.
(*ptr == 'G' && include_g10)) { // Only check for G10 if requested
bool is_gcode = *ptr == 'G';
++ ptr;
// Parse the M code value.
// Parse the M or G code value.
char *endptr = nullptr;
int mcode = int(strtol(ptr, &endptr, 10));
if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) {
// M104/M109 or M140/M190 found.
int mgcode = int(strtol(ptr, &endptr, 10));
if (endptr != nullptr && endptr != ptr &&
is_gcode ?
// G10 found
mgcode == 10 :
// M104/M109 or M140/M190 found.
(mgcode == mcode_set_temp_dont_wait || mgcode == mcode_set_temp_and_wait)) {
ptr = endptr;
// Let the caller know that the custom G-code sets the temperature.
temp_set_by_gcode = true;
if (! is_gcode)
// Let the caller know that the custom M-code sets the temperature.
temp_set_by_gcode = true;
// Now try to parse the temperature value.
// While not at the end of the line:
while (strchr(";\r\n\0", *ptr) == nullptr) {
@ -1674,6 +1686,10 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
if (endptr > ptr) {
ptr = endptr;
temp_out = temp_parsed;
// Let the caller know that the custom G-code sets the temperature
// Only do this after successfully parsing temperature since G10
// can be used for other reasons
temp_set_by_gcode = true;
}
} else {
// Skip this word.
@ -1694,7 +1710,7 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
// Do not process this piece of G-code by the time estimator, it already knows the values through another sources.
void GCode::print_machine_envelope(FILE *file, Print &print)
{
if (print.config().gcode_flavor.value == gcfMarlin) {
if (print.config().gcode_flavor.value == gcfMarlin && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) {
fprintf(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n",
int(print.config().machine_max_acceleration_x.values.front() + 0.5),
int(print.config().machine_max_acceleration_y.values.front() + 0.5),
@ -1730,7 +1746,7 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s
int temp = print.config().first_layer_bed_temperature.get_at(first_printing_extruder_id);
// Is the bed temperature set by the provided custom G-code?
int temp_by_gcode = -1;
bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode);
bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, false, temp_by_gcode);
if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000)
temp = temp_by_gcode;
// Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if
@ -1744,11 +1760,13 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s
// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
// M104 - Set Extruder Temperature
// M109 - Set Extruder Temperature and Wait
// RepRapFirmware: G10 Sxx
void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
{
// Is the bed temperature set by the provided custom G-code?
int temp_by_gcode = -1;
if (custom_gcode_sets_temperature(gcode, 104, 109, temp_by_gcode)) {
int temp_by_gcode = -1;
bool include_g10 = print.config().gcode_flavor == gcfRepRapFirmware;
if (custom_gcode_sets_temperature(gcode, 104, 109, include_g10, temp_by_gcode)) {
// Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code.
int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
if (temp_by_gcode >= 0 && temp_by_gcode < 1000)
@ -2457,6 +2475,7 @@ void GCode::append_full_config(const Print &print, std::string &str)
static constexpr auto banned_keys = {
"compatible_printers"sv,
"compatible_prints"sv,
//FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed.
"print_host"sv,
"printhost_apikey"sv,
"printhost_cafile"sv

File diff suppressed because it is too large Load diff

View file

@ -1,309 +0,0 @@
#ifndef slic3r_GCode_Analyzer_hpp_
#define slic3r_GCode_Analyzer_hpp_
#if !ENABLE_GCODE_VIEWER
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "../ExtrusionEntity.hpp"
#include "../Point.hpp"
#include "../GCodeReader.hpp"
namespace Slic3r {
class GCodePreviewData;
class GCodeAnalyzer
{
public:
static const std::string Extrusion_Role_Tag;
static const std::string Mm3_Per_Mm_Tag;
static const std::string Width_Tag;
static const std::string Height_Tag;
static const std::string Color_Change_Tag;
static const std::string Pause_Print_Tag;
static const std::string Custom_Code_Tag;
static const std::string End_Pause_Print_Or_Custom_Code_Tag;
static const float Default_mm3_per_mm;
static const float Default_Width;
static const float Default_Height;
enum EUnits : unsigned char
{
Millimeters,
Inches
};
enum EAxis : unsigned char
{
X,
Y,
Z,
E,
Num_Axis
};
enum EPositioningType : unsigned char
{
Absolute,
Relative
};
struct Metadata
{
ExtrusionRole extrusion_role;
unsigned int extruder_id;
float mm3_per_mm;
float width; // mm
float height; // mm
float feedrate; // mm/s
float fan_speed; // percentage
unsigned int cp_color_id;
Metadata();
Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, float mm3_per_mm, float width, float height, float feedrate, float fan_speed, unsigned int cp_color_id = 0);
bool operator != (const Metadata& other) const;
};
struct GCodeMove
{
enum EType : unsigned char
{
Noop,
Retract,
Unretract,
Tool_change,
Move,
Extrude,
Num_Types
};
EType type;
Metadata data;
Vec3f start_position;
Vec3f end_position;
float delta_extruder;
GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, float mm3_per_mm, float width, float height, float feedrate, const Vec3f& start_position, const Vec3f& end_position, float delta_extruder, float fan_speed, unsigned int cp_color_id = 0);
GCodeMove(EType type, const Metadata& data, const Vec3f& start_position, const Vec3f& end_position, float delta_extruder);
};
typedef std::vector<GCodeMove> GCodeMovesList;
typedef std::map<GCodeMove::EType, GCodeMovesList> TypeToMovesMap;
typedef std::map<unsigned int, Vec2d> ExtruderOffsetsMap;
typedef std::map<unsigned int, unsigned int> ExtruderToColorMap;
private:
struct State
{
EUnits units;
EPositioningType global_positioning_type;
EPositioningType e_local_positioning_type;
Metadata data;
Vec3f start_position = Vec3f::Zero();
float cached_position[5];
float start_extrusion;
float position[Num_Axis];
float origin[Num_Axis];
unsigned int cp_color_counter = 0;
};
private:
State m_state;
GCodeReader m_parser;
TypeToMovesMap m_moves_map;
ExtruderOffsetsMap m_extruder_offsets;
unsigned int m_extruders_count;
GCodeFlavor m_gcode_flavor;
ExtruderToColorMap m_extruder_color;
// The output of process_layer()
std::string m_process_output;
public:
GCodeAnalyzer() { reset(); }
void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets) { m_extruder_offsets = extruder_offsets; }
void set_extruders_count(unsigned int count);
void set_extrusion_axis(char axis) { m_parser.set_extrusion_axis(axis); }
void set_gcode_flavor(const GCodeFlavor& flavor) { m_gcode_flavor = flavor; }
// Reinitialize the analyzer
void reset();
// Adds the gcode contained in the given string to the analysis and returns it after removing the workcodes
const std::string& process_gcode(const std::string& gcode);
// Calculates all data needed for gcode visualization
// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
void calc_gcode_preview_data(GCodePreviewData& preview_data, std::function<void()> cancel_callback = std::function<void()>());
// Return an estimate of the memory consumed by the time estimator.
size_t memory_used() const;
static bool is_valid_extrusion_role(ExtrusionRole role);
private:
// Processes the given gcode line
void _process_gcode_line(GCodeReader& reader, const GCodeReader::GCodeLine& line);
// Move
void _processG1(const GCodeReader::GCodeLine& line);
// Retract
void _processG10(const GCodeReader::GCodeLine& line);
// Unretract
void _processG11(const GCodeReader::GCodeLine& line);
// Firmware controlled Retract
void _processG22(const GCodeReader::GCodeLine& line);
// Firmware controlled Unretract
void _processG23(const GCodeReader::GCodeLine& line);
// Set to Absolute Positioning
void _processG90(const GCodeReader::GCodeLine& line);
// Set to Relative Positioning
void _processG91(const GCodeReader::GCodeLine& line);
// Set Position
void _processG92(const GCodeReader::GCodeLine& line);
// Set extruder to absolute mode
void _processM82(const GCodeReader::GCodeLine& line);
// Set extruder to relative mode
void _processM83(const GCodeReader::GCodeLine& line);
// Set fan speed
void _processM106(const GCodeReader::GCodeLine& line);
// Disable fan
void _processM107(const GCodeReader::GCodeLine& line);
// Set tool (MakerWare and Sailfish flavor)
void _processM108orM135(const GCodeReader::GCodeLine& line);
// Recall stored home offsets
void _processM132(const GCodeReader::GCodeLine& line);
// Repetier: Store x, y and z position
void _processM401(const GCodeReader::GCodeLine& line);
// Repetier: Go to stored position
void _processM402(const GCodeReader::GCodeLine& line);
// Processes T line (Select Tool)
void _processT(const std::string& command);
void _processT(const GCodeReader::GCodeLine& line);
// Processes the tags
// Returns true if any tag has been processed
bool _process_tags(const GCodeReader::GCodeLine& line);
// Processes extrusion role tag
void _process_extrusion_role_tag(const std::string& comment, size_t pos);
// Processes mm3_per_mm tag
void _process_mm3_per_mm_tag(const std::string& comment, size_t pos);
// Processes width tag
void _process_width_tag(const std::string& comment, size_t pos);
// Processes height tag
void _process_height_tag(const std::string& comment, size_t pos);
// Processes color change tag
void _process_color_change_tag(unsigned extruder);
// Processes pause print and custom gcode tag
void _process_pause_print_or_custom_code_tag();
// Processes new layer tag
void _process_end_pause_print_or_custom_code_tag();
void _set_units(EUnits units);
EUnits _get_units() const;
void _set_global_positioning_type(EPositioningType type);
EPositioningType _get_global_positioning_type() const;
void _set_e_local_positioning_type(EPositioningType type);
EPositioningType _get_e_local_positioning_type() const;
void _set_extrusion_role(ExtrusionRole extrusion_role);
ExtrusionRole _get_extrusion_role() const;
void _set_extruder_id(unsigned int id);
unsigned int _get_extruder_id() const;
void _set_cp_color_id(unsigned int id);
unsigned int _get_cp_color_id() const;
void _set_mm3_per_mm(float value);
float _get_mm3_per_mm() const;
void _set_width(float width);
float _get_width() const;
void _set_height(float height);
float _get_height() const;
void _set_feedrate(float feedrate_mm_sec);
float _get_feedrate() const;
void _set_fan_speed(float fan_speed_percentage);
float _get_fan_speed() const;
void _set_axis_position(EAxis axis, float position);
float _get_axis_position(EAxis axis) const;
void _set_axis_origin(EAxis axis, float position);
float _get_axis_origin(EAxis axis) const;
// Sets axes position to zero
void _reset_axes_position();
// Sets origin position to zero
void _reset_axes_origin();
void _set_start_position(const Vec3f& position);
const Vec3f& _get_start_position() const;
void _set_cached_position(unsigned char axis, float position);
float _get_cached_position(unsigned char axis) const;
void _reset_cached_position();
void _set_start_extrusion(float extrusion);
float _get_start_extrusion() const;
float _get_delta_extrusion() const;
// Returns current xyz position (from m_state.position[])
Vec3f _get_end_position() const;
// Adds a new move with the given data
void _store_move(GCodeMove::EType type);
// Checks if the given int is a valid extrusion role (contained into enum ExtrusionRole)
bool _is_valid_extrusion_role(int value) const;
// All the following methods throw CanceledException through print->throw_if_canceled() (sent by the caller as callback).
void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
void _calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
void _calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
};
} // namespace Slic3r
#endif // !ENABLE_GCODE_VIEWER
#endif /* slic3r_GCode_Analyzer_hpp_ */

View file

@ -10,6 +10,11 @@
#include <float.h>
#include <assert.h>
#if __has_include(<charconv>)
#include <charconv>
#include <utility>
#endif
#if ENABLE_GCODE_VIEWER
#include <chrono>
@ -170,7 +175,7 @@ void GCodeProcessor::TimeMachine::reset()
prev.reset();
gcode_time.reset();
blocks = std::vector<TimeBlock>();
g1_times_cache = std::vector<float>();
g1_times_cache = std::vector<G1LinesCacheItem>();
std::fill(moves_time.begin(), moves_time.end(), 0.0f);
std::fill(roles_time.begin(), roles_time.end(), 0.0f);
layers_time = std::vector<float>();
@ -292,7 +297,7 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks)
}
layers_time[block.layer_id - 1] += block_time;
}
g1_times_cache.push_back(time);
g1_times_cache.push_back({ block.g1_line_id, time });
}
if (keep_last_n_blocks)
@ -358,7 +363,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
std::string ret;
if (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag) {
if (export_remaining_time_enabled && (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag)) {
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = machines[i];
if (machine.enabled) {
@ -371,10 +376,11 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
else if (line == Estimated_Printing_Time_Placeholder_Tag) {
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = machines[i];
if (machine.enabled) {
PrintEstimatedTimeStatistics::ETimeMode mode = static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i);
if (mode == PrintEstimatedTimeStatistics::ETimeMode::Normal || machine.enabled) {
char buf[128];
sprintf(buf, "; estimated printing time (%s mode) = %s\n",
(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal) ? "normal" : "silent",
(mode == PrintEstimatedTimeStatistics::ETimeMode::Normal) ? "normal" : "silent",
get_time_dhms(machine.time).c_str());
ret += buf;
}
@ -394,18 +400,30 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
return false;
};
// Iterators for the normal and silent cached time estimate entry recently processed, used by process_line_G1.
auto g1_times_cache_it = Slic3r::reserve_vector<std::vector<TimeMachine::G1LinesCacheItem>::const_iterator>(machines.size());
for (const auto& machine : machines)
g1_times_cache_it.emplace_back(machine.g1_times_cache.begin());
// add lines M73 to exported gcode
auto process_line_G1 = [&]() {
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = machines[i];
if (machine.enabled && g1_lines_counter < machine.g1_times_cache.size()) {
float elapsed_time = machine.g1_times_cache[g1_lines_counter];
std::pair<int, int> to_export = { int(::roundf(100.0f * elapsed_time / machine.time)),
time_in_minutes(machine.time - elapsed_time) };
if (last_exported[i] != to_export) {
export_line += format_line_M73(machine.line_m73_mask.c_str(),
to_export.first, to_export.second);
last_exported[i] = to_export;
if (export_remaining_time_enabled) {
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = machines[i];
if (machine.enabled) {
// Skip all machine.g1_times_cache below g1_lines_counter.
auto& it = g1_times_cache_it[i];
while (it != machine.g1_times_cache.end() && it->id < g1_lines_counter)
++it;
if (it != machine.g1_times_cache.end() && it->id == g1_lines_counter) {
float elapsed_time = it->elapsed_time;
std::pair<int, int> to_export = { int(100.0f * elapsed_time / machine.time),
time_in_minutes(machine.time - elapsed_time) };
if (last_exported[i] != to_export) {
export_line += format_line_M73(machine.line_m73_mask.c_str(),
to_export.first, to_export.second);
last_exported[i] = to_export;
}
}
}
}
}
@ -466,6 +484,8 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProcessor::Producers = {
{ EProducer::PrusaSlicer, "PrusaSlicer" },
{ EProducer::Slic3rPE, "Slic3r Prusa Edition" },
{ EProducer::Slic3r, "Slic3r" },
{ EProducer::Cura, "Cura_SteamEngine" },
{ EProducer::Simplify3D, "Simplify3D" },
{ EProducer::CraftWare, "CraftWare" },
@ -505,7 +525,9 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
m_filament_diameters[i] = static_cast<float>(config.filament_diameter.values[i]);
}
m_time_processor.machine_limits = reinterpret_cast<const MachineEnvelopeConfig&>(config);
if (config.machine_limits_usage.value != MachineLimitsUsage::Ignore)
m_time_processor.machine_limits = reinterpret_cast<const MachineEnvelopeConfig&>(config);
// Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
// are considered to be active for the single extruder multi-material printers only.
@ -687,7 +709,7 @@ void GCodeProcessor::reset()
m_global_positioning_type = EPositioningType::Absolute;
m_e_local_positioning_type = EPositioningType::Absolute;
m_extruder_offsets = std::vector<Vec3f>(Min_Extruder_Count, Vec3f::Zero());
m_flavor = gcfRepRap;
m_flavor = gcfRepRapSprinter;
m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f };
m_end_position = { 0.0f, 0.0f, 0.0f, 0.0f };
@ -709,6 +731,7 @@ void GCodeProcessor::reset()
m_filament_diameters = std::vector<float>(Min_Extruder_Count, 1.75f);
m_extruded_last_z = 0.0f;
m_g1_line_id = 0;
m_layer_id = 0;
m_cp_color.reset();
@ -739,9 +762,9 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
// parse the gcode file to detect its producer
if (m_producers_enabled) {
m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
std::string cmd = line.cmd();
const std::string_view cmd = line.cmd();
if (cmd.length() == 0) {
std::string comment = line.comment();
const std::string_view comment = line.comment();
if (comment.length() > 1 && detect_producer(comment))
m_parser.quit_parsing_file();
}
@ -749,7 +772,7 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
// if the gcode was produced by PrusaSlicer,
// extract the config from it
if (m_producer == EProducer::PrusaSlicer) {
if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) {
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config.load_from_gcode_file(filename);
@ -785,8 +808,7 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
update_estimated_times_stats();
// post-process to add M73 lines into the gcode
if (m_time_processor.export_remaining_time_enabled)
m_time_processor.post_process(filename);
m_time_processor.post_process(filename);
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
std::cout << "\n";
@ -865,7 +887,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line)
// update start position
m_start_position = m_end_position;
std::string cmd = line.cmd();
const std::string_view cmd = line.cmd();
if (cmd.length() > 1) {
// process command lines
switch (::toupper(cmd[0]))
@ -923,122 +945,151 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line)
}
}
else {
std::string comment = line.comment();
if (comment.length() > 1)
// process tags embedded into comments
process_tags(comment);
const std::string &comment = line.raw();
if (comment.length() > 2 && comment.front() == ';')
// Process tags embedded into comments. Tag comments always start at the start of a line
// with a comment and continue with a tag without any whitespace separator.
process_tags(comment.substr(1));
}
}
void GCodeProcessor::process_tags(const std::string& comment)
static inline bool starts_with(const std::string_view comment, const std::string_view tag)
{
// producers tags
if (m_producers_enabled) {
if (m_producer != EProducer::Unknown) {
if (process_producers_tags(comment))
return;
size_t tag_len = tag.size();
return comment.size() >= tag_len && comment.substr(0, tag_len) == tag;
}
#if __has_include(<charconv>)
template <typename T, typename = void>
struct is_from_chars_convertible : std::false_type {};
template <typename T>
struct is_from_chars_convertible<T, std::void_t<decltype(std::from_chars(std::declval<const char*>(), std::declval<const char*>(), std::declval<T&>()))>> : std::true_type {};
#endif
// Returns true if the number was parsed correctly into out and the number spanned the whole input string.
template<typename T>
[[nodiscard]] static inline bool parse_number(const std::string_view sv, T &out)
{
// https://www.bfilipek.com/2019/07/detect-overload-from-chars.html#example-stdfromchars
#if __has_include(<charconv>)
// Visual Studio 19 supports from_chars all right.
// OSX compiler that we use only implements std::from_chars just for ints.
// GCC that we compile on does not provide <charconv> at all.
if constexpr (is_from_chars_convertible<T>::value) {
auto str_end = sv.data() + sv.size();
auto [end_ptr, error_code] = std::from_chars(sv.data(), str_end, out);
return error_code == std::errc() && end_ptr == str_end;
}
else
#endif
{
// Legacy conversion, which is costly due to having to make a copy of the string before conversion.
try {
assert(sv.size() < 1024);
assert(sv.data() != nullptr);
std::string str { sv };
size_t read = 0;
if constexpr (std::is_same_v<T, int>)
out = std::stoi(str, &read);
else if constexpr (std::is_same_v<T, long>)
out = std::stol(str, &read);
else if constexpr (std::is_same_v<T, float>)
out = std::stof(str, &read);
else if constexpr (std::is_same_v<T, double>)
out = std::stod(str, &read);
return str.size() == read;
} catch (...) {
return false;
}
}
}
void GCodeProcessor::process_tags(const std::string_view comment)
{
// producers tags
if (m_producers_enabled && process_producers_tags(comment))
return;
// extrusion role tag
size_t pos = comment.find(Extrusion_Role_Tag);
if (pos != comment.npos) {
m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(pos + Extrusion_Role_Tag.length()));
if (starts_with(comment, Extrusion_Role_Tag)) {
m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(Extrusion_Role_Tag.length()));
return;
}
if (!m_producers_enabled || m_producer == EProducer::PrusaSlicer) {
if ((!m_producers_enabled || m_producer == EProducer::PrusaSlicer) &&
starts_with(comment, Height_Tag)) {
// height tag
pos = comment.find(Height_Tag);
if (pos != comment.npos) {
try {
m_height = std::stof(comment.substr(pos + Height_Tag.length()));
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
}
return;
}
if (! parse_number(comment.substr(Height_Tag.size()), m_height))
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
return;
}
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
// width tag
pos = comment.find(Width_Tag);
if (pos != comment.npos) {
try {
m_width_compare.last_tag_value = std::stof(comment.substr(pos + Width_Tag.length()));
}
catch (...) {
if (starts_with(comment, Width_Tag)) {
if (! parse_number(comment.substr(Width_Tag.size()), m_width_compare.last_tag_value))
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
}
return;
}
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
// color change tag
pos = comment.find(Color_Change_Tag);
if (pos != comment.npos) {
pos = comment.find_last_of(",T");
try {
unsigned char extruder_id = (pos == comment.npos) ? 0 : static_cast<unsigned char>(std::stoi(comment.substr(pos + 1)));
m_extruder_colors[extruder_id] = static_cast<unsigned char>(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview
++m_cp_color.counter;
if (m_cp_color.counter == UCHAR_MAX)
m_cp_color.counter = 0;
if (m_extruder_id == extruder_id) {
m_cp_color.current = m_extruder_colors[extruder_id];
store_move_vertex(EMoveType::Color_change);
if (starts_with(comment, Color_Change_Tag)) {
unsigned char extruder_id = 0;
if (starts_with(comment.substr(Color_Change_Tag.size()), ",T")) {
int eid;
if (! parse_number(comment.substr(Color_Change_Tag.size() + 2), eid) || eid < 0 || eid > 255) {
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ").";
return;
}
extruder_id = static_cast<unsigned char>(eid);
}
process_custom_gcode_time(CustomGCode::ColorChange);
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ").";
m_extruder_colors[extruder_id] = static_cast<unsigned char>(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview
++m_cp_color.counter;
if (m_cp_color.counter == UCHAR_MAX)
m_cp_color.counter = 0;
if (m_extruder_id == extruder_id) {
m_cp_color.current = m_extruder_colors[extruder_id];
store_move_vertex(EMoveType::Color_change);
}
process_custom_gcode_time(CustomGCode::ColorChange);
return;
}
// pause print tag
pos = comment.find(Pause_Print_Tag);
if (pos != comment.npos) {
if (comment == Pause_Print_Tag) {
store_move_vertex(EMoveType::Pause_Print);
process_custom_gcode_time(CustomGCode::PausePrint);
return;
}
// custom code tag
pos = comment.find(Custom_Code_Tag);
if (pos != comment.npos) {
if (comment == Custom_Code_Tag) {
store_move_vertex(EMoveType::Custom_GCode);
return;
}
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
// mm3_per_mm print tag
pos = comment.find(Mm3_Per_Mm_Tag);
if (pos != comment.npos) {
try {
m_mm3_per_mm_compare.last_tag_value = std::stof(comment.substr(pos + Mm3_Per_Mm_Tag.length()));
}
catch (...) {
if (starts_with(comment, Mm3_Per_Mm_Tag)) {
if (! parse_number(comment.substr(Mm3_Per_Mm_Tag.size()), m_mm3_per_mm_compare.last_tag_value))
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ").";
}
return;
}
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
// layer change tag
pos = comment.find(Layer_Change_Tag);
if (pos != comment.npos) {
if (comment == Layer_Change_Tag) {
++m_layer_id;
return;
}
}
bool GCodeProcessor::process_producers_tags(const std::string& comment)
bool GCodeProcessor::process_producers_tags(const std::string_view comment)
{
switch (m_producer)
{
@ -1051,18 +1102,18 @@ bool GCodeProcessor::process_producers_tags(const std::string& comment)
}
}
bool GCodeProcessor::process_prusaslicer_tags(const std::string& comment)
bool GCodeProcessor::process_prusaslicer_tags(const std::string_view comment)
{
return false;
}
bool GCodeProcessor::process_cura_tags(const std::string& comment)
bool GCodeProcessor::process_cura_tags(const std::string_view comment)
{
// TYPE -> extrusion role
std::string tag = "TYPE:";
size_t pos = comment.find(tag);
if (pos != comment.npos) {
std::string type = comment.substr(pos + tag.length());
const std::string_view type = comment.substr(pos + tag.length());
if (type == "SKIRT")
m_extrusion_role = erSkirt;
else if (type == "WALL-OUTER")
@ -1091,7 +1142,7 @@ bool GCodeProcessor::process_cura_tags(const std::string& comment)
tag = "FLAVOR:";
pos = comment.find(tag);
if (pos != comment.npos) {
std::string flavor = comment.substr(pos + tag.length());
const std::string_view flavor = comment.substr(pos + tag.length());
if (flavor == "BFB")
m_flavor = gcfMarlin; // << ???????????????????????
else if (flavor == "Mach3")
@ -1107,7 +1158,7 @@ bool GCodeProcessor::process_cura_tags(const std::string& comment)
else if (flavor == "Repetier")
m_flavor = gcfRepetier;
else if (flavor == "RepRap")
m_flavor = gcfRepRap;
m_flavor = gcfRepRapFirmware;
else if (flavor == "Marlin")
m_flavor = gcfMarlin;
else
@ -1119,7 +1170,7 @@ bool GCodeProcessor::process_cura_tags(const std::string& comment)
return false;
}
bool GCodeProcessor::process_simplify3d_tags(const std::string& comment)
bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment)
{
// extrusion roles
@ -1207,7 +1258,7 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment)
std::string tag = " tool";
pos = comment.find(tag);
if (pos == 0) {
std::string data = comment.substr(pos + tag.length());
const std::string_view data = comment.substr(pos + tag.length());
std::string h_tag = "H";
size_t h_start = data.find(h_tag);
size_t h_end = data.find_first_of(' ', h_start);
@ -1215,20 +1266,12 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment)
size_t w_start = data.find(w_tag);
size_t w_end = data.find_first_of(' ', w_start);
if (h_start != data.npos) {
try {
m_height_compare.last_tag_value = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end));
}
catch (...) {
if (! parse_number(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end), m_height_compare.last_tag_value))
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
}
}
if (w_start != data.npos) {
try {
m_width_compare.last_tag_value = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end));
}
catch (...) {
if (! parse_number(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end), m_width_compare.last_tag_value))
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
}
}
return true;
@ -1238,13 +1281,13 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment)
return false;
}
bool GCodeProcessor::process_craftware_tags(const std::string& comment)
bool GCodeProcessor::process_craftware_tags(const std::string_view comment)
{
// segType -> extrusion role
std::string tag = "segType:";
size_t pos = comment.find(tag);
if (pos != comment.npos) {
std::string type = comment.substr(pos + tag.length());
const std::string_view type = comment.substr(pos + tag.length());
if (type == "Skirt")
m_extrusion_role = erSkirt;
else if (type == "Perimeter")
@ -1278,13 +1321,13 @@ bool GCodeProcessor::process_craftware_tags(const std::string& comment)
return false;
}
bool GCodeProcessor::process_ideamaker_tags(const std::string& comment)
bool GCodeProcessor::process_ideamaker_tags(const std::string_view comment)
{
// TYPE -> extrusion role
std::string tag = "TYPE:";
size_t pos = comment.find(tag);
if (pos != comment.npos) {
std::string type = comment.substr(pos + tag.length());
const std::string_view type = comment.substr(pos + tag.length());
if (type == "RAFT")
m_extrusion_role = erSkirt;
else if (type == "WALL-OUTER")
@ -1313,12 +1356,8 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment)
tag = "WIDTH:";
pos = comment.find(tag);
if (pos != comment.npos) {
try {
m_width_compare.last_tag_value = std::stof(comment.substr(pos + tag.length()));
}
catch (...) {
if (! parse_number(comment.substr(pos + tag.length()), m_width_compare.last_tag_value))
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
}
return true;
}
@ -1326,12 +1365,8 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment)
tag = "HEIGHT:";
pos = comment.find(tag);
if (pos != comment.npos) {
try {
m_height_compare.last_tag_value = std::stof(comment.substr(pos + tag.length()));
}
catch (...) {
if (! parse_number(comment.substr(pos + tag.length()), m_height_compare.last_tag_value))
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
}
return true;
}
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
@ -1339,7 +1374,7 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment)
return false;
}
bool GCodeProcessor::detect_producer(const std::string& comment)
bool GCodeProcessor::detect_producer(const std::string_view comment)
{
for (const auto& [id, search_string] : Producers) {
size_t pos = comment.find(search_string);
@ -1391,6 +1426,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
return type;
};
++m_g1_line_id;
// enable processing of lines M201/M203/M204/M205
m_time_processor.machine_envelope_processing_enabled = true;
@ -1454,7 +1491,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height;
// clamp width to avoid artifacts which may arise from wrong values of m_height
m_width = std::min(m_width, 4.0f * m_height);
m_width = std::min(m_width, 1.0f);
// m_width = std::min(m_width, 4.0f * m_height);
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
m_width_compare.update(m_width, m_extrusion_role);
@ -1495,6 +1533,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
block.move_type = type;
block.role = m_extrusion_role;
block.distance = distance;
block.g1_line_id = m_g1_line_id;
block.layer_id = m_layer_id;
// calculates block cruise feedrate
@ -1798,7 +1837,7 @@ void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line)
return;
// see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration
float factor = (m_flavor != gcfRepRap && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
float factor = ((m_flavor != gcfRepRapSprinter && m_flavor != gcfRepRapFirmware) && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
if (line.has_x())
@ -1991,11 +2030,14 @@ void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line)
process_T(line.cmd());
}
void GCodeProcessor::process_T(const std::string& command)
void GCodeProcessor::process_T(const std::string_view command)
{
if (command.length() > 1) {
try {
unsigned char id = static_cast<unsigned char>(std::stoi(command.substr(1)));
int eid;
if (! parse_number(command.substr(1), eid) || eid < 0 || eid > 255) {
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ").";
} else {
unsigned char id = static_cast<unsigned char>(eid);
if (m_extruder_id != id) {
unsigned char extruders_count = static_cast<unsigned char>(m_extruder_offsets.size());
if (id >= extruders_count)
@ -2017,9 +2059,6 @@ void GCodeProcessor::process_T(const std::string& command)
store_move_vertex(EMoveType::Tool_change);
}
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ").";
}
}
}

View file

@ -11,6 +11,7 @@
#include <array>
#include <vector>
#include <string>
#include <string_view>
namespace Slic3r {
@ -145,6 +146,7 @@ namespace Slic3r {
EMoveType move_type{ EMoveType::Noop };
ExtrusionRole role{ erNone };
unsigned int g1_line_id{ 0 };
unsigned int layer_id{ 0 };
float distance{ 0.0f }; // mm
float acceleration{ 0.0f }; // mm/s^2
@ -182,6 +184,12 @@ namespace Slic3r {
void reset();
};
struct G1LinesCacheItem
{
unsigned int id;
float elapsed_time;
};
bool enabled;
float acceleration; // mm/s^2
// hard limit for the acceleration, to which the firmware will clamp.
@ -193,7 +201,7 @@ namespace Slic3r {
State prev;
CustomGCodeTime gcode_time;
std::vector<TimeBlock> blocks;
std::vector<float> g1_times_cache;
std::vector<G1LinesCacheItem> g1_times_cache;
std::array<float, static_cast<size_t>(EMoveType::Count)> moves_time;
std::array<float, static_cast<size_t>(ExtrusionRole::erCount)> roles_time;
std::vector<float> layers_time;
@ -305,10 +313,12 @@ namespace Slic3r {
{}
void update(float value, ExtrusionRole role) {
++count;
if (last_tag_value != 0.0f) {
if (std::abs(value - last_tag_value) / last_tag_value > threshold)
errors.push_back({ value, last_tag_value, role });
if (role != erCustom) {
++count;
if (last_tag_value != 0.0f) {
if (std::abs(value - last_tag_value) / last_tag_value > threshold)
errors.push_back({ value, last_tag_value, role });
}
}
}
@ -374,6 +384,7 @@ namespace Slic3r {
ExtruderColors m_extruder_colors;
std::vector<float> m_filament_diameters;
float m_extruded_last_z;
unsigned int m_g1_line_id;
unsigned int m_layer_id;
CpColor m_cp_color;
@ -381,6 +392,8 @@ namespace Slic3r {
{
Unknown,
PrusaSlicer,
Slic3rPE,
Slic3r,
Cura,
Simplify3D,
CraftWare,
@ -434,15 +447,15 @@ namespace Slic3r {
void process_gcode_line(const GCodeReader::GCodeLine& line);
// Process tags embedded into comments
void process_tags(const std::string& comment);
bool process_producers_tags(const std::string& comment);
bool process_prusaslicer_tags(const std::string& comment);
bool process_cura_tags(const std::string& comment);
bool process_simplify3d_tags(const std::string& comment);
bool process_craftware_tags(const std::string& comment);
bool process_ideamaker_tags(const std::string& comment);
void process_tags(const std::string_view comment);
bool process_producers_tags(const std::string_view comment);
bool process_prusaslicer_tags(const std::string_view comment);
bool process_cura_tags(const std::string_view comment);
bool process_simplify3d_tags(const std::string_view comment);
bool process_craftware_tags(const std::string_view comment);
bool process_ideamaker_tags(const std::string_view comment);
bool detect_producer(const std::string& comment);
bool detect_producer(const std::string_view comment);
// Move
void process_G0(const GCodeReader::GCodeLine& line);
@ -528,7 +541,7 @@ namespace Slic3r {
// Processes T line (Select Tool)
void process_T(const GCodeReader::GCodeLine& line);
void process_T(const std::string& command);
void process_T(const std::string_view command);
void store_move_vertex(EMoveType type);

View file

@ -165,9 +165,10 @@ static inline float parse_float(const char *&line)
return result;
};
#define EXTRUSION_ROLE_TAG ";_EXTRUSION_ROLE:"
bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLine &buf)
{
static constexpr const char *EXTRUSION_ROLE_TAG = ";_EXTRUSION_ROLE:";
if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) {
line += strlen(EXTRUSION_ROLE_TAG);
int role = atoi(line);

View file

@ -1,4 +1,3 @@
#include "Analyzer.hpp"
#include "PreviewData.hpp"
#include <I18N.hpp>
#include "Utils.hpp"

View file

@ -126,7 +126,7 @@ public:
}
WipeTowerWriter& disable_linear_advance() {
m_gcode += (m_gcode_flavor == gcfRepRap
m_gcode += (m_gcode_flavor == gcfRepRapSprinter || m_gcode_flavor == gcfRepRapFirmware
? (std::string("M572 D") + std::to_string(m_current_tool) + " S0\n")
: std::string("M900 K0\n"));
return *this;
@ -386,7 +386,7 @@ public:
// Set digital trimpot motor
WipeTowerWriter& set_extruder_trimpot(int current)
{
if (m_gcode_flavor == gcfRepRap)
if (m_gcode_flavor == gcfRepRapSprinter || m_gcode_flavor == gcfRepRapFirmware)
m_gcode += "M906 E";
else
m_gcode += "M907 E";

View file

@ -6,6 +6,7 @@
#include <cstdlib>
#include <functional>
#include <string>
#include <string_view>
#include "PrintConfig.hpp"
namespace Slic3r {
@ -17,13 +18,13 @@ public:
GCodeLine() { reset(); }
void reset() { m_mask = 0; memset(m_axis, 0, sizeof(m_axis)); m_raw.clear(); }
const std::string& raw() const { return m_raw; }
const std::string cmd() const {
const std::string& raw() const { return m_raw; }
const std::string_view cmd() const {
const char *cmd = GCodeReader::skip_whitespaces(m_raw.c_str());
return std::string(cmd, GCodeReader::skip_word(cmd));
return std::string_view(cmd, GCodeReader::skip_word(cmd) - cmd);
}
const std::string comment() const
{ size_t pos = m_raw.find(';'); return (pos == std::string::npos) ? "" : m_raw.substr(pos + 1); }
const std::string_view comment() const
{ size_t pos = m_raw.find(';'); return (pos == std::string::npos) ? std::string_view() : std::string_view(m_raw).substr(pos + 1); }
bool has(Axis axis) const { return (m_mask & (1 << int(axis))) != 0; }
float value(Axis axis) const { return m_axis[axis]; }

File diff suppressed because it is too large Load diff

View file

@ -1,488 +0,0 @@
#ifndef slic3r_GCodeTimeEstimator_hpp_
#define slic3r_GCodeTimeEstimator_hpp_
#include "libslic3r.h"
#include "PrintConfig.hpp"
#include "GCodeReader.hpp"
#include "CustomGCode.hpp"
#if !ENABLE_GCODE_VIEWER
#define ENABLE_MOVE_STATS 0
namespace Slic3r {
//
// Some of the algorithms used by class GCodeTimeEstimator were inpired by
// Cura Engine's class TimeEstimateCalculator
// https://github.com/Ultimaker/CuraEngine/blob/master/src/timeEstimate.h
//
class GCodeTimeEstimator
{
public:
static const std::string Normal_First_M73_Output_Placeholder_Tag;
static const std::string Silent_First_M73_Output_Placeholder_Tag;
static const std::string Normal_Last_M73_Output_Placeholder_Tag;
static const std::string Silent_Last_M73_Output_Placeholder_Tag;
static const std::string Color_Change_Tag;
static const std::string Pause_Print_Tag;
enum EMode : unsigned char
{
Normal,
Silent
};
enum EUnits : unsigned char
{
Millimeters,
Inches
};
enum EAxis : unsigned char
{
X,
Y,
Z,
E,
Num_Axis
};
enum EPositioningType : unsigned char
{
Absolute,
Relative
};
private:
struct Axis
{
float position; // mm
float origin; // mm
float max_feedrate; // mm/s
float max_acceleration; // mm/s^2
float max_jerk; // mm/s
};
struct Feedrates
{
float feedrate; // mm/s
float axis_feedrate[Num_Axis]; // mm/s
float abs_axis_feedrate[Num_Axis]; // mm/s
float safe_feedrate; // mm/s
void reset();
};
struct State
{
GCodeFlavor dialect;
EUnits units;
EPositioningType global_positioning_type;
EPositioningType e_local_positioning_type;
Axis axis[Num_Axis];
float feedrate; // mm/s
float acceleration; // mm/s^2
// hard limit for the acceleration, to which the firmware will clamp.
float max_acceleration; // mm/s^2
float retract_acceleration; // mm/s^2
float minimum_feedrate; // mm/s
float minimum_travel_feedrate; // mm/s
float extrude_factor_override_percentage;
// Additional load / unload times for a filament exchange sequence.
std::vector<float> filament_load_times;
std::vector<float> filament_unload_times;
unsigned int g1_line_id;
// extruder_id is currently used to correctly calculate filament load / unload times
// into the total print time. This is currently only really used by the MK3 MMU2:
// Extruder id (-1) means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit.
static const unsigned int extruder_id_unloaded = (unsigned int)-1;
unsigned int extruder_id;
};
public:
struct Block
{
#if ENABLE_MOVE_STATS
enum EMoveType : unsigned char
{
Noop,
Retract,
Unretract,
Tool_change,
Move,
Extrude,
Num_Types
};
#endif // ENABLE_MOVE_STATS
struct FeedrateProfile
{
float entry; // mm/s
float cruise; // mm/s
float exit; // mm/s
};
struct Trapezoid
{
float accelerate_until; // mm
float decelerate_after; // mm
float cruise_feedrate; // mm/sec
float acceleration_time(float entry_feedrate, float acceleration) const;
float cruise_time() const;
float deceleration_time(float distance, float acceleration) const;
float cruise_distance() const;
// This function gives the time needed to accelerate from an initial speed to reach a final distance.
static float acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration);
// This function gives the final speed while accelerating at the given constant acceleration from the given initial speed along the given distance.
static float speed_from_distance(float initial_feedrate, float distance, float acceleration);
};
struct Flags
{
bool recalculate;
bool nominal_length;
};
#if ENABLE_MOVE_STATS
EMoveType move_type;
#endif // ENABLE_MOVE_STATS
Flags flags;
float distance; // mm
float acceleration; // mm/s^2
float max_entry_speed; // mm/s
float safe_feedrate; // mm/s
FeedrateProfile feedrate;
Trapezoid trapezoid;
// Ordnary index of this G1 line in the file.
int g1_line_id { -1 };
// Returns the time spent accelerating toward cruise speed, in seconds
float acceleration_time() const;
// Returns the time spent at cruise speed, in seconds
float cruise_time() const;
// Returns the time spent decelerating from cruise speed, in seconds
float deceleration_time() const;
// Returns the distance covered at cruise speed, in mm
float cruise_distance() const;
// Calculates this block's trapezoid
void calculate_trapezoid();
// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the
// acceleration within the allotted distance.
static float max_allowable_speed(float acceleration, float target_velocity, float distance);
// Calculates the distance (not time) it takes to accelerate from initial_rate to target_rate using the given acceleration:
static float estimate_acceleration_distance(float initial_rate, float target_rate, float acceleration);
// This function gives you the point at which you must start braking (at the rate of -acceleration) if
// you started at speed initial_rate and accelerated until this point and want to end at the final_rate after
// a total travel of distance. This can be used to compute the intersection point between acceleration and
// deceleration in the cases where the trapezoid has no plateau (i.e. never reaches maximum speed)
static float intersection_distance(float initial_rate, float final_rate, float acceleration, float distance);
};
typedef std::vector<Block> BlocksList;
#if ENABLE_MOVE_STATS
struct MoveStats
{
unsigned int count;
float time;
MoveStats();
};
typedef std::map<Block::EMoveType, MoveStats> MovesStatsMap;
#endif // ENABLE_MOVE_STATS
public:
typedef std::pair<int, float> G1LineIdTime;
typedef std::vector<G1LineIdTime> G1LineIdsTimes;
struct PostProcessData
{
const G1LineIdsTimes& g1_times;
float time;
};
private:
EMode m_mode;
GCodeReader m_parser;
State m_state;
Feedrates m_curr;
Feedrates m_prev;
BlocksList m_blocks;
// Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks.
// Let's be conservative and plan for newer boards with more memory.
static constexpr size_t planner_queue_size = 64;
// The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added.
// We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate.
static constexpr size_t planner_refresh_if_larger = planner_queue_size * 4;
// Map from g1 line id to its elapsed time from the start of the print.
G1LineIdsTimes m_g1_times;
float m_time; // s
// data to calculate custom code times
bool m_needs_custom_gcode_times;
std::vector<std::pair<CustomGCode::Type, float>> m_custom_gcode_times;
float m_custom_gcode_time_cache;
#if ENABLE_MOVE_STATS
MovesStatsMap _moves_stats;
#endif // ENABLE_MOVE_STATS
public:
explicit GCodeTimeEstimator(EMode mode);
// Adds the given gcode line
void add_gcode_line(const std::string& gcode_line);
void add_gcode_block(const char *ptr);
void add_gcode_block(const std::string &str) { this->add_gcode_block(str.c_str()); }
// Calculates the time estimate from the gcode lines added using add_gcode_line() or add_gcode_block()
// start_from_beginning:
// if set to true all blocks will be used to calculate the time estimate,
// if set to false only the blocks not yet processed will be used and the calculated time will be added to the current calculated time
void calculate_time(bool start_from_beginning);
// Calculates the time estimate from the given gcode in string format
//void calculate_time_from_text(const std::string& gcode);
// Calculates the time estimate from the gcode contained in the file with the given filename
//void calculate_time_from_file(const std::string& file);
// Calculates the time estimate from the gcode contained in given list of gcode lines
//void calculate_time_from_lines(const std::vector<std::string>& gcode_lines);
// Process the gcode contained in the file with the given filename,
// replacing placeholders with correspondent new lines M73
// placing new lines M73 (containing the remaining time) where needed (in dependence of the given interval in seconds)
// and removing working tags (as those used for color changes)
// if normal_mode == nullptr no M73 line will be added for normal mode
// if silent_mode == nullptr no M73 line will be added for silent mode
static bool post_process(const std::string& filename, float interval_sec, const PostProcessData* const normal_mode, const PostProcessData* const silent_mode);
// Set current position on the given axis with the given value
void set_axis_position(EAxis axis, float position);
// Set current origin on the given axis with the given value
void set_axis_origin(EAxis axis, float position);
void set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec);
void set_axis_max_acceleration(EAxis axis, float acceleration);
void set_axis_max_jerk(EAxis axis, float jerk);
// Returns current position on the given axis
float get_axis_position(EAxis axis) const;
// Returns current origin on the given axis
float get_axis_origin(EAxis axis) const;
float get_axis_max_feedrate(EAxis axis) const;
float get_axis_max_acceleration(EAxis axis) const;
float get_axis_max_jerk(EAxis axis) const;
void set_feedrate(float feedrate_mm_sec);
float get_feedrate() const;
void set_acceleration(float acceleration_mm_sec2);
float get_acceleration() const;
// Maximum acceleration for the machine. The firmware simulator will clamp the M204 Sxxx to this maximum.
void set_max_acceleration(float acceleration_mm_sec2);
float get_max_acceleration() const;
void set_retract_acceleration(float acceleration_mm_sec2);
float get_retract_acceleration() const;
void set_minimum_feedrate(float feedrate_mm_sec);
float get_minimum_feedrate() const;
void set_minimum_travel_feedrate(float feedrate_mm_sec);
float get_minimum_travel_feedrate() const;
void set_filament_load_times(const std::vector<double> &filament_load_times);
void set_filament_unload_times(const std::vector<double> &filament_unload_times);
float get_filament_load_time(unsigned int id_extruder);
float get_filament_unload_time(unsigned int id_extruder);
void set_extrude_factor_override_percentage(float percentage);
float get_extrude_factor_override_percentage() const;
void set_dialect(GCodeFlavor dialect);
GCodeFlavor get_dialect() const;
void set_units(EUnits units);
EUnits get_units() const;
void set_global_positioning_type(EPositioningType type);
EPositioningType get_global_positioning_type() const;
void set_e_local_positioning_type(EPositioningType type);
EPositioningType get_e_local_positioning_type() const;
int get_g1_line_id() const;
void increment_g1_line_id();
void reset_g1_line_id();
void set_extrusion_axis(char axis) { m_parser.set_extrusion_axis(axis); }
void set_extruder_id(unsigned int id);
unsigned int get_extruder_id() const;
void reset_extruder_id();
void set_default();
// Call this method before to start adding lines using add_gcode_line() when reusing an instance of GCodeTimeEstimator
void reset();
// Returns the estimated time, in seconds
float get_time() const;
// Returns the estimated time, in format DDd HHh MMm SSs
std::string get_time_dhms() const;
// Returns the estimated time, in format DDd HHh MMm
std::string get_time_dhm() const;
// Returns the estimated time, in minutes (integer)
std::string get_time_minutes() const;
// Returns the estimated time, in seconds, for each custom gcode
std::vector<std::pair<CustomGCode::Type, float>> get_custom_gcode_times() const;
// Returns the estimated time, in format DDd HHh MMm SSs, for each color
// If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)"
std::vector<std::string> get_color_times_dhms(bool include_remaining) const;
// Returns the estimated time, in minutes (integer), for each color
// If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)"
std::vector<std::string> get_color_times_minutes(bool include_remaining) const;
// Returns the estimated time, in format DDd HHh MMm, for each custom_gcode
// If include_remaining==true the strings will be formatted as: "time for custom_gcode (remaining time at color start)"
std::vector<std::pair<CustomGCode::Type, std::string>> get_custom_gcode_times_dhm(bool include_remaining) const;
// Return an estimate of the memory consumed by the time estimator.
size_t memory_used() const;
PostProcessData get_post_process_data() const { return PostProcessData{ m_g1_times, m_time }; }
private:
void _reset();
void _reset_time();
void _reset_blocks();
// Calculates the time estimate
void _calculate_time(size_t keep_last_n_blocks);
// Processes the given gcode line
void _process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line);
// Move
void _processG1(const GCodeReader::GCodeLine& line);
// Dwell
void _processG4(const GCodeReader::GCodeLine& line);
// Set Units to Inches
void _processG20(const GCodeReader::GCodeLine& line);
// Set Units to Millimeters
void _processG21(const GCodeReader::GCodeLine& line);
// Move to Origin (Home)
void _processG28(const GCodeReader::GCodeLine& line);
// Set to Absolute Positioning
void _processG90(const GCodeReader::GCodeLine& line);
// Set to Relative Positioning
void _processG91(const GCodeReader::GCodeLine& line);
// Set Position
void _processG92(const GCodeReader::GCodeLine& line);
// Sleep or Conditional stop
void _processM1(const GCodeReader::GCodeLine& line);
// Set extruder to absolute mode
void _processM82(const GCodeReader::GCodeLine& line);
// Set extruder to relative mode
void _processM83(const GCodeReader::GCodeLine& line);
// Set Extruder Temperature and Wait
void _processM109(const GCodeReader::GCodeLine& line);
// Set max printing acceleration
void _processM201(const GCodeReader::GCodeLine& line);
// Set maximum feedrate
void _processM203(const GCodeReader::GCodeLine& line);
// Set default acceleration
void _processM204(const GCodeReader::GCodeLine& line);
// Advanced settings
void _processM205(const GCodeReader::GCodeLine& line);
// Set extrude factor override percentage
void _processM221(const GCodeReader::GCodeLine& line);
// Set allowable instantaneous speed change
void _processM566(const GCodeReader::GCodeLine& line);
// Unload the current filament into the MK3 MMU2 unit at the end of print.
void _processM702(const GCodeReader::GCodeLine& line);
// Processes T line (Select Tool)
void _processT(const GCodeReader::GCodeLine& line);
// Processes the tags
// Returns true if any tag has been processed
bool _process_tags(const GCodeReader::GCodeLine& line);
// Processes ColorChangeTag and PausePrintTag
void _process_custom_gcode_tag(CustomGCode::Type code);
// Simulates firmware st_synchronize() call
void _simulate_st_synchronize(float additional_time);
void _forward_pass();
void _reverse_pass();
void _planner_forward_pass_kernel(Block& prev, Block& curr);
void _planner_reverse_pass_kernel(Block& curr, Block& next);
void _recalculate_trapezoids();
// Returns the given time is seconds in format DDd HHh MMm SSs
static std::string _get_time_dhms(float time_in_secs);
// Returns the given time is minutes in format DDd HHh MMm
static std::string _get_time_dhm(float time_in_secs);
// Returns the given, in minutes (integer)
static std::string _get_time_minutes(float time_in_secs);
#if ENABLE_MOVE_STATS
void _log_moves_stats() const;
#endif // ENABLE_MOVE_STATS
};
} /* namespace Slic3r */
#endif // !ENABLE_GCODE_VIEWER
#endif /* slic3r_GCodeTimeEstimator_hpp_ */

View file

@ -20,7 +20,7 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
this->config.apply(print_config, true);
m_extrusion_axis = this->config.get_extrusion_axis();
m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
m_max_acceleration = std::lrint((print_config.gcode_flavor.value == gcfMarlin) ?
m_max_acceleration = std::lrint((print_config.gcode_flavor.value == gcfMarlin && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ?
print_config.machine_max_acceleration_extruding.values.front() : 0);
}
@ -46,7 +46,13 @@ std::string GCodeWriter::preamble()
gcode << "G21 ; set units to millimeters\n";
gcode << "G90 ; use absolute coordinates\n";
}
if (FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfMarlin) || FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepetier) || FLAVOR_IS(gcfSmoothie)) {
if (FLAVOR_IS(gcfRepRapSprinter) ||
FLAVOR_IS(gcfRepRapFirmware) ||
FLAVOR_IS(gcfMarlin) ||
FLAVOR_IS(gcfTeacup) ||
FLAVOR_IS(gcfRepetier) ||
FLAVOR_IS(gcfSmoothie))
{
if (this->config.use_relative_e_distances) {
gcode << "M83 ; use relative distances for extrusion\n";
} else {
@ -72,11 +78,15 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in
return "";
std::string code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) {
code = "M109";
comment = "set temperature and wait for it to be reached";
} else {
code = "M104";
if (FLAVOR_IS(gcfRepRapFirmware)) { // M104 is deprecated on RepRapFirmware
code = "G10";
} else {
code = "M104";
}
comment = "set temperature";
}
@ -88,14 +98,17 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in
gcode << "S";
}
gcode << temperature;
if (tool != -1 &&
( (this->multiple_extruders && ! m_single_extruder_multi_material) ||
FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) ) {
gcode << " T" << tool;
bool multiple_tools = this->multiple_extruders && ! m_single_extruder_multi_material;
if (tool != -1 && (multiple_tools || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) ) {
if (FLAVOR_IS(gcfRepRapFirmware)) {
gcode << " P" << tool;
} else {
gcode << " T" << tool;
}
}
gcode << " ; " << comment << "\n";
if (FLAVOR_IS(gcfTeacup) && wait)
if ((FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepRapFirmware)) && wait)
gcode << "M116 ; wait for temperature to be reached\n";
return gcode.str();

View file

@ -12,7 +12,6 @@
#include <CGAL/Exact_integer.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
#include <CGAL/Polygon_mesh_processing/repair_polygon_soup.h>
#include <CGAL/Polygon_mesh_processing/repair.h>
#include <CGAL/Polygon_mesh_processing/remesh.h>
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>

View file

@ -608,6 +608,7 @@ void ModelObject::assign_new_unique_ids_recursive()
model_volume->assign_new_unique_ids_recursive();
for (ModelInstance *model_instance : this->instances)
model_instance->assign_new_unique_ids_recursive();
this->layer_height_profile.set_new_unique_id();
}
// Clone this ModelObject including its volumes and instances, keep the IDs of the copies equal to the original.
@ -1041,14 +1042,15 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial
int vol_idx = 0;
for (ModelVolume* volume : volumes)
{
volume->m_supported_facets.clear();
volume->m_seam_facets.clear();
volume->supported_facets.clear();
volume->seam_facets.clear();
if (!volume->mesh().empty()) {
TriangleMesh mesh(volume->mesh());
mesh.require_shared_vertices();
ModelVolume* vol = new_object->add_volume(mesh);
vol->name = volume->name;
vol->set_type(volume->type());
// Don't copy the config's ID.
vol->config.assign_config(volume->config);
assert(vol->config.id().valid());
@ -1059,7 +1061,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial
if (volume_idxs.empty() ||
std::find(volume_idxs.begin(), volume_idxs.end(), vol_idx) != volume_idxs.end()) {
vol->scale_geometry_after_creation(versor);
vol->set_offset(versor.cwiseProduct(vol->get_offset()));
vol->set_offset(versor.cwiseProduct(volume->get_offset()));
}
else
vol->set_offset(volume->get_offset());
@ -1147,8 +1149,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
for (ModelVolume *volume : volumes) {
const auto volume_matrix = volume->get_matrix();
volume->m_supported_facets.clear();
volume->m_seam_facets.clear();
volume->supported_facets.clear();
volume->seam_facets.clear();
if (! volume->is_model_part()) {
// Modifiers are not cut, but we still need to add the instance transformation
@ -1727,6 +1729,14 @@ void ModelObject::scale_to_fit(const Vec3d &size)
*/
}
void ModelVolume::assign_new_unique_ids_recursive()
{
ObjectBase::set_new_unique_id();
config.set_new_unique_id();
supported_facets.set_new_unique_id();
seam_facets.set_new_unique_id();
}
void ModelVolume::rotate(double angle, Axis axis)
{
switch (axis)
@ -2011,7 +2021,7 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject
assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
assert(mo.volumes.size() == mo_new.volumes.size());
for (size_t i=0; i<mo.volumes.size(); ++i) {
if (! mo_new.volumes[i]->m_supported_facets.timestamp_matches(mo.volumes[i]->m_supported_facets))
if (! mo_new.volumes[i]->supported_facets.timestamp_matches(mo.volumes[i]->supported_facets))
return true;
}
return false;
@ -2021,7 +2031,7 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo
assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
assert(mo.volumes.size() == mo_new.volumes.size());
for (size_t i=0; i<mo.volumes.size(); ++i) {
if (! mo_new.volumes[i]->m_seam_facets.timestamp_matches(mo.volumes[i]->m_seam_facets))
if (! mo_new.volumes[i]->seam_facets.timestamp_matches(mo.volumes[i]->seam_facets))
return true;
}
return false;

View file

@ -55,14 +55,14 @@ private:
// Constructors to be only called by derived classes.
// Default constructor to assign a unique ID.
explicit ModelConfigObject() {}
explicit ModelConfigObject() = default;
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
// by an existing ID copied from elsewhere.
explicit ModelConfigObject(int) : ObjectBase(-1) {}
// Copy constructor copies the ID.
explicit ModelConfigObject(const ModelConfigObject &cfg) : ObjectBase(-1), ModelConfig(cfg) { this->copy_id(cfg); }
explicit ModelConfigObject(const ModelConfigObject &cfg) = default;
// Move constructor copies the ID.
explicit ModelConfigObject(ModelConfigObject &&cfg) : ObjectBase(-1), ModelConfig(std::move(cfg)) { this->copy_id(cfg); }
explicit ModelConfigObject(ModelConfigObject &&cfg) = default;
Timestamp timestamp() const throw() override { return this->ModelConfig::timestamp(); }
bool object_id_and_timestamp_match(const ModelConfigObject &rhs) const throw() { return this->id() == rhs.id() && this->timestamp() == rhs.timestamp(); }
@ -178,6 +178,10 @@ private:
class LayerHeightProfile final : public ObjectWithTimestamp {
public:
// Assign the content if the timestamp differs, don't assign an ObjectID.
void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = rhs.m_data; this->copy_timestamp(rhs); } }
void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
std::vector<coordf_t> get() const throw() { return m_data; }
bool empty() const throw() { return m_data.empty(); }
void set(const std::vector<coordf_t> &data) { if (m_data != data) { m_data = data; this->touch(); } }
@ -190,7 +194,25 @@ public:
}
private:
// Constructors to be only called by derived classes.
// Default constructor to assign a unique ID.
explicit LayerHeightProfile() = default;
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
// by an existing ID copied from elsewhere.
explicit LayerHeightProfile(int) : ObjectWithTimestamp(-1) {}
// Copy constructor copies the ID.
explicit LayerHeightProfile(const LayerHeightProfile &rhs) = default;
// Move constructor copies the ID.
explicit LayerHeightProfile(LayerHeightProfile &&rhs) = default;
// called by ModelObject::assign_copy()
LayerHeightProfile& operator=(const LayerHeightProfile &rhs) = default;
LayerHeightProfile& operator=(LayerHeightProfile &&rhs) = default;
std::vector<coordf_t> m_data;
// to access set_new_unique_id() when copy / pasting an object
friend class ModelObject;
};
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
@ -338,41 +360,85 @@ private:
// This constructor assigns new ID to this ModelObject and its config.
explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()),
m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
{ assert(this->id().valid()); }
explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
{ assert(this->id().invalid()); assert(this->config.id().invalid()); }
{
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->layer_height_profile.id().valid());
}
explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
{
assert(this->id().invalid());
assert(this->config.id().invalid());
assert(this->layer_height_profile.id().invalid());
}
~ModelObject();
void assign_new_unique_ids_recursive() override;
// To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision"
// (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics).
ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), m_model(rhs.m_model) {
assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id());
ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(rhs.m_model) {
assert(this->id().invalid());
assert(this->config.id().invalid());
assert(this->layer_height_profile.id().invalid());
assert(rhs.id() != rhs.config.id());
assert(rhs.id() != rhs.layer_height_profile.id());
this->assign_copy(rhs);
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->layer_height_profile.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->layer_height_profile.id());
assert(this->id() == rhs.id());
assert(this->config.id() == rhs.config.id());
assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
}
explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1) {
assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id());
explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1) {
assert(this->id().invalid());
assert(this->config.id().invalid());
assert(this->layer_height_profile.id().invalid());
assert(rhs.id() != rhs.config.id());
assert(rhs.id() != rhs.layer_height_profile.id());
this->assign_copy(std::move(rhs));
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->layer_height_profile.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->layer_height_profile.id());
assert(this->id() == rhs.id());
assert(this->config.id() == rhs.config.id());
assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
}
ModelObject& operator=(const ModelObject &rhs) {
ModelObject& operator=(const ModelObject &rhs) {
this->assign_copy(rhs);
m_model = rhs.m_model;
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->layer_height_profile.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->layer_height_profile.id());
assert(this->id() == rhs.id());
assert(this->config.id() == rhs.config.id());
assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
return *this;
}
ModelObject& operator=(ModelObject &&rhs) {
ModelObject& operator=(ModelObject &&rhs) {
this->assign_copy(std::move(rhs));
m_model = rhs.m_model;
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->layer_height_profile.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->layer_height_profile.id());
assert(this->id() == rhs.id());
assert(this->config.id() == rhs.config.id());
assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
return *this;
}
void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
void set_new_unique_id() {
ObjectBase::set_new_unique_id();
this->config.set_new_unique_id();
this->layer_height_profile.set_new_unique_id();
}
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
@ -396,8 +462,12 @@ private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
// Used for deserialization -> Don't allocate any IDs for the ModelObject or its config.
ModelObject() : ObjectBase(-1), config(-1), m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {
assert(this->id().invalid()); assert(this->config.id().invalid());
ModelObject() :
ObjectBase(-1), config(-1), layer_height_profile(-1),
m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {
assert(this->id().invalid());
assert(this->config.id().invalid());
assert(this->layer_height_profile.id().invalid());
}
template<class Archive> void serialize(Archive &ar) {
ar(cereal::base_class<ObjectBase>(this));
@ -427,16 +497,33 @@ enum class EnforcerBlockerType : int8_t {
class FacetsAnnotation final : public ObjectWithTimestamp {
public:
void assign(const FacetsAnnotation &rhs) { if (! this->timestamp_matches(rhs)) this->m_data = rhs.m_data; }
void assign(FacetsAnnotation &&rhs) { if (! this->timestamp_matches(rhs)) this->m_data = rhs.m_data; }
// Assign the content if the timestamp differs, don't assign an ObjectID.
void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = rhs.m_data; this->copy_timestamp(rhs); } }
void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
const std::map<int, std::vector<bool>>& get_data() const throw() { return m_data; }
bool set(const TriangleSelector& selector);
indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
bool empty() const { return m_data.empty(); }
void clear();
std::string get_triangle_as_string(int i) const;
void set_triangle_from_string(int triangle_id, const std::string& str);
private:
// Constructors to be only called by derived classes.
// Default constructor to assign a unique ID.
explicit FacetsAnnotation() = default;
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
// by an existing ID copied from elsewhere.
explicit FacetsAnnotation(int) : ObjectWithTimestamp(-1) {}
// Copy constructor copies the ID.
explicit FacetsAnnotation(const FacetsAnnotation &rhs) = default;
// Move constructor copies the ID.
explicit FacetsAnnotation(FacetsAnnotation &&rhs) = default;
// called by ModelVolume::assign_copy()
FacetsAnnotation& operator=(const FacetsAnnotation &rhs) = default;
FacetsAnnotation& operator=(FacetsAnnotation &&rhs) = default;
friend class cereal::access;
friend class UndoRedo::StackImpl;
@ -446,6 +533,9 @@ private:
}
std::map<int, std::vector<bool>> m_data;
// To access set_new_unique_id() when copy / pasting a ModelVolume.
friend class ModelVolume;
};
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
@ -483,10 +573,10 @@ public:
ModelConfigObject config;
// List of mesh facets to be supported/unsupported.
FacetsAnnotation m_supported_facets;
FacetsAnnotation supported_facets;
// List of seam enforcers/blockers.
FacetsAnnotation m_seam_facets;
FacetsAnnotation seam_facets;
// A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; }
@ -568,7 +658,12 @@ public:
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
void set_new_unique_id() {
ObjectBase::set_new_unique_id();
this->config.set_new_unique_id();
this->supported_facets.set_new_unique_id();
this->seam_facets.set_new_unique_id();
}
protected:
friend class Print;
@ -579,7 +674,7 @@ protected:
// Copies IDs of both the ModelVolume and its config.
explicit ModelVolume(const ModelVolume &rhs) = default;
void set_model_object(ModelObject *model_object) { object = model_object; }
void assign_new_unique_ids_recursive() override { ObjectBase::set_new_unique_id(); config.set_new_unique_id(); }
void assign_new_unique_ids_recursive() override;
void transform_this_mesh(const Transform3d& t, bool fix_left_handed);
void transform_this_mesh(const Matrix3d& m, bool fix_left_handed);
@ -603,13 +698,25 @@ private:
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : m_mesh(new TriangleMesh(mesh)), m_type(ModelVolumeType::MODEL_PART), object(object)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) :
m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) {
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
}
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
@ -617,26 +724,45 @@ private:
ObjectBase(other),
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
m_supported_facets(other.m_supported_facets), m_seam_facets(other.m_seam_facets)
supported_facets(other.supported_facets), seam_facets(other.seam_facets)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == other.id() && this->config.id() == other.config.id());
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() == other.id());
assert(this->config.id() == other.config.id());
assert(this->supported_facets.id() == other.supported_facets.id());
assert(this->seam_facets.id() == other.seam_facets.id());
this->set_material_id(other.material_id());
}
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() != other.id() && this->config.id() == other.config.id());
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != other.id());
assert(this->config.id() == other.config.id());
this->set_material_id(other.material_id());
this->config.set_new_unique_id();
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id());
m_supported_facets.clear();
m_seam_facets.clear();
assert(this->config.id().valid());
assert(this->config.id() != other.config.id());
assert(this->supported_facets.id() != other.supported_facets.id());
assert(this->seam_facets.id() != other.seam_facets.id());
assert(this->id() != this->config.id());
assert(this->supported_facets.empty());
assert(this->seam_facets.empty());
}
ModelVolume& operator=(ModelVolume &rhs) = delete;
@ -644,14 +770,17 @@ private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
// Used for deserialization, therefore no IDs are allocated.
ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) {
assert(this->id().invalid()); assert(this->config.id().invalid());
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), object(nullptr) {
assert(this->id().invalid());
assert(this->config.id().invalid());
assert(this->supported_facets.id().invalid());
assert(this->seam_facets.id().invalid());
}
template<class Archive> void load(Archive &ar) {
bool has_convex_hull;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::load_by_value(ar, m_supported_facets);
cereal::load_by_value(ar, m_seam_facets);
cereal::load_by_value(ar, supported_facets);
cereal::load_by_value(ar, seam_facets);
cereal::load_by_value(ar, config);
assert(m_mesh);
if (has_convex_hull) {
@ -665,8 +794,8 @@ private:
template<class Archive> void save(Archive &ar) const {
bool has_convex_hull = m_convex_hull.get() != nullptr;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::save_by_value(ar, m_supported_facets);
cereal::save_by_value(ar, m_seam_facets);
cereal::save_by_value(ar, supported_facets);
cereal::save_by_value(ar, seam_facets);
cereal::save_by_value(ar, config);
if (has_convex_hull)
cereal::save_optional(ar, m_convex_hull);

View file

@ -108,6 +108,8 @@ protected:
// Resetting timestamp to 1 indicates the object is in its initial (cleared) state.
// To be called by the derived class's clear() method.
void reset_timestamp() { m_timestamp = 1; }
// The timestamp uniquely identifies content of the derived class' data, therefore it makes sense to copy the timestamp if the content data was copied.
void copy_timestamp(const ObjectWithTimestamp& rhs) { m_timestamp = rhs.m_timestamp; }
public:
// Return an optional timestamp of this object.

View file

@ -454,27 +454,40 @@ const std::vector<std::string>& Preset::filament_options()
return s_opts;
}
const std::vector<std::string>& Preset::machine_limits_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"machine_max_acceleration_extruding", "machine_max_acceleration_retracting",
"machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e",
"machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e",
"machine_min_extruding_rate", "machine_min_travel_rate",
"machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e",
};
}
return s_opts;
}
const std::vector<std::string>& Preset::printer_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"printer_technology",
"bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed",
"bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances",
"use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
"host_type", "print_host", "printhost_apikey", "printhost_cafile",
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
"color_change_gcode", "pause_print_gcode", "template_custom_gcode",
"between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height",
"default_print_profile", "inherits",
"remaining_times", "silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting",
"machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e",
"machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e",
"machine_min_extruding_rate", "machine_min_travel_rate",
"machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e",
"thumbnails"
"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;
@ -583,6 +596,7 @@ const std::vector<std::string>& Preset::sla_printer_options()
"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"
@ -699,38 +713,6 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string
return this->load_preset(path, name, std::move(cfg), select);
}
enum class ProfileHostParams
{
Same,
Different,
Anonymized,
};
static ProfileHostParams profile_host_params_same_or_anonymized(const DynamicPrintConfig &cfg_old, const DynamicPrintConfig &cfg_new)
{
auto opt_print_host_old = cfg_old.option<ConfigOptionString>("print_host");
auto opt_printhost_apikey_old = cfg_old.option<ConfigOptionString>("printhost_apikey");
auto opt_printhost_cafile_old = cfg_old.option<ConfigOptionString>("printhost_cafile");
auto opt_print_host_new = cfg_new.option<ConfigOptionString>("print_host");
auto opt_printhost_apikey_new = cfg_new.option<ConfigOptionString>("printhost_apikey");
auto opt_printhost_cafile_new = cfg_new.option<ConfigOptionString>("printhost_cafile");
// If the new print host data is undefined, use the old data.
bool new_print_host_undefined = (opt_print_host_new == nullptr || opt_print_host_new ->empty()) &&
(opt_printhost_apikey_new == nullptr || opt_printhost_apikey_new ->empty()) &&
(opt_printhost_cafile_new == nullptr || opt_printhost_cafile_new ->empty());
if (new_print_host_undefined)
return ProfileHostParams::Anonymized;
auto opt_same = [](const ConfigOptionString *l, const ConfigOptionString *r) {
return ((l == nullptr || l->empty()) && (r == nullptr || r->empty())) ||
(l != nullptr && r != nullptr && l->value == r->value);
};
return (opt_same(opt_print_host_old, opt_print_host_new) && opt_same(opt_printhost_apikey_old, opt_printhost_apikey_new) &&
opt_same(opt_printhost_cafile_old, opt_printhost_cafile_new)) ? ProfileHostParams::Same : ProfileHostParams::Different;
}
static bool profile_print_params_same(const DynamicPrintConfig &cfg_old, const DynamicPrintConfig &cfg_new)
{
t_config_option_keys diff = cfg_old.diff(cfg_new);
@ -740,10 +722,11 @@ static bool profile_print_params_same(const DynamicPrintConfig &cfg_old, const D
"compatible_printers", "compatible_printers_condition", "inherits",
"print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id",
"printer_model", "printer_variant", "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile",
//FIXME remove the print host keys?
"print_host", "printhost_apikey", "printhost_cafile" })
diff.erase(std::remove(diff.begin(), diff.end(), key), diff.end());
// Preset with the same name as stored inside the config exists.
return diff.empty() && profile_host_params_same_or_anonymized(cfg_old, cfg_new) != ProfileHostParams::Different;
return diff.empty();
}
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
@ -772,25 +755,11 @@ Preset& PresetCollection::load_external_preset(
it = this->find_preset_renamed(original_name);
found = it != m_presets.end();
}
if (found) {
if (profile_print_params_same(it->config, cfg)) {
// The preset exists and it matches the values stored inside config.
if (select)
this->select_preset(it - m_presets.begin());
return *it;
}
if (profile_host_params_same_or_anonymized(it->config, cfg) == ProfileHostParams::Anonymized) {
// The project being loaded is anonymized. Replace the empty host keys of the loaded profile with the data from the original profile.
// See "Octoprint Settings when Opening a .3MF file" GH issue #3244
auto opt_update = [it, &cfg](const std::string &opt_key) {
auto opt = it->config.option<ConfigOptionString>(opt_key);
if (opt != nullptr)
cfg.set_key_value(opt_key, opt->clone());
};
opt_update("print_host");
opt_update("printhost_apikey");
opt_update("printhost_cafile");
}
if (found && profile_print_params_same(it->config, cfg)) {
// The preset exists and it matches the values stored inside config.
if (select)
this->select_preset(it - m_presets.begin());
return *it;
}
// Update the "inherits" field.
std::string &inherits = Preset::inherits(cfg);
@ -1362,31 +1331,25 @@ const std::vector<std::string>& PhysicalPrinter::printer_options()
"preset_name",
"printer_technology",
// "printer_model",
"host_type",
"print_host",
"printhost_apikey",
"host_type",
"print_host",
"printhost_apikey",
"printhost_cafile",
"printhost_port",
"printhost_authorization_type",
// HTTP digest authentization (RFC 2617)
"printhost_user",
"printhost_user",
"printhost_password"
};
}
return s_opts;
}
const std::vector<std::string>& PhysicalPrinter::print_host_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"print_host",
"printhost_apikey",
"printhost_cafile"
};
}
return s_opts;
}
static constexpr auto legacy_print_host_options = {
"print_host",
"printhost_apikey",
"printhost_cafile",
};
std::vector<std::string> PhysicalPrinter::presets_with_print_host_information(const PrinterPresetCollection& printer_presets)
{
@ -1400,7 +1363,7 @@ std::vector<std::string> PhysicalPrinter::presets_with_print_host_information(co
bool PhysicalPrinter::has_print_host_information(const DynamicPrintConfig& config)
{
for (const std::string& opt : print_host_options())
for (const char *opt : legacy_print_host_options)
if (!config.opt_string(opt).empty())
return true;
@ -1417,6 +1380,7 @@ bool PhysicalPrinter::has_empty_config() const
return config.opt_string("print_host" ).empty() &&
config.opt_string("printhost_apikey" ).empty() &&
config.opt_string("printhost_cafile" ).empty() &&
config.opt_string("printhost_port" ).empty() &&
config.opt_string("printhost_user" ).empty() &&
config.opt_string("printhost_password").empty();
}
@ -1445,7 +1409,7 @@ void PhysicalPrinter::update_from_preset(const Preset& preset)
{
config.apply_only(preset.config, printer_options(), true);
// add preset names to the options list
auto ret = preset_names.emplace(preset.name);
preset_names.emplace(preset.name);
update_preset_names_in_config();
}
@ -1602,9 +1566,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti
int cnt=0;
for (Preset& preset: printer_presets) {
DynamicPrintConfig& config = preset.config;
const std::vector<std::string>& options = PhysicalPrinter::print_host_options();
for(const std::string& option : options) {
for(const char* option : legacy_print_host_options) {
if (!config.opt_string(option).empty()) {
// check if printer with those "Print Host upload" options already exist
PhysicalPrinter* existed_printer = find_printer_with_same_config(config);
@ -1623,7 +1585,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti
}
// erase "Print Host upload" information from the preset
for (const std::string& opt : options)
for (const char *opt : legacy_print_host_options)
config.opt_string(opt).clear();
// save changes for preset
preset.save();
@ -1631,7 +1593,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti
// update those changes for edited preset if it's equal to the preset
Preset& edited = printer_presets.get_edited_preset();
if (preset.name == edited.name) {
for (const std::string& opt : options)
for (const char *opt : legacy_print_host_options)
edited.config.opt_string(opt).clear();
}
@ -1664,7 +1626,7 @@ std::deque<PhysicalPrinter>::iterator PhysicalPrinterCollection::find_printer_in
std::string low_name = boost::to_lower_copy<std::string>(name);
int i = 0;
size_t i = 0;
for (const PhysicalPrinter& printer : m_printers) {
if (boost::to_lower_copy<std::string>(printer.name) == low_name)
break;
@ -1680,7 +1642,7 @@ PhysicalPrinter* PhysicalPrinterCollection::find_printer_with_same_config(const
{
for (const PhysicalPrinter& printer :*this) {
bool is_equal = true;
for (const std::string& opt : PhysicalPrinter::print_host_options())
for (const char *opt : legacy_print_host_options)
if (is_equal && printer.config.opt_string(opt) != config.opt_string(opt))
is_equal = false;
@ -1854,7 +1816,7 @@ void PhysicalPrinterCollection::unselect_printer()
bool PhysicalPrinterCollection::is_selected(PhysicalPrinterCollection::ConstIterator it, const std::string& preset_name) const
{
return m_idx_selected == it - m_printers.begin() &&
return m_idx_selected == size_t(it - m_printers.begin()) &&
m_selected_preset == preset_name;
}

View file

@ -218,6 +218,8 @@ public:
static const std::vector<std::string>& printer_options();
// Nozzle options of the printer options.
static const std::vector<std::string>& nozzle_options();
// Printer machine limits, those are contained in printer_options().
static const std::vector<std::string>& machine_limits_options();
static const std::vector<std::string>& sla_printer_options();
static const std::vector<std::string>& sla_material_options();
@ -369,19 +371,28 @@ public:
size_t i = m_default_suppressed ? m_num_default_presets : 0;
size_t n = this->m_presets.size();
size_t i_compatible = n;
int match_quality = -1;
for (; i < n; ++ i)
// Since we use the filament selection from Wizard, it's needed to control the preset visibility too
if (m_presets[i].is_compatible && m_presets[i].is_visible) {
if (prefered_condition(m_presets[i].name))
return i;
if (i_compatible == n)
// Store the first compatible profile into i_compatible.
int this_match_quality = prefered_condition(m_presets[i]);
if (this_match_quality > match_quality) {
if (match_quality == std::numeric_limits<int>::max())
// Better match will not be found.
return i;
// Store the first compatible profile with highest match quality into i_compatible.
i_compatible = i;
match_quality = this_match_quality;
}
}
return (i_compatible == n) ? 0 : i_compatible;
return (i_compatible == n) ?
// No compatible preset found, return the default preset.
0 :
// Compatible preset found.
i_compatible;
}
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
size_t first_compatible_idx() const { return this->first_compatible_idx([](const std::string&){return true;}); }
size_t first_compatible_idx() const { return this->first_compatible_idx([](const Preset&) -> int { return 0; }); }
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
// Return the first visible preset. Certainly at least the '- default -' preset shall be visible.
@ -405,7 +416,7 @@ public:
this->select_preset(this->first_compatible_idx(prefered_condition));
}
void update_compatible(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, PresetSelectCompatibleType select_other_if_incompatible)
{ this->update_compatible(active_printer, active_print, select_other_if_incompatible, [](const std::string&){return true;}); }
{ this->update_compatible(active_printer, active_print, select_other_if_incompatible, [](const Preset&) -> int { return 0; }); }
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }

View file

@ -37,11 +37,11 @@ static std::vector<std::string> s_project_options {
const char *PresetBundle::PRUSA_BUNDLE = "PrusaResearch";
PresetBundle::PresetBundle() :
prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults())),
filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults())),
prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())),
filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())),
sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast<const SLAMaterialConfig&>(SLAFullPrintConfig::defaults())),
sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast<const SLAPrintObjectConfig&>(SLAFullPrintConfig::defaults())),
printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults()), "- default FFF -"),
printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults()), "- default FFF -"),
physical_printers(PhysicalPrinter::printer_options())
{
// The following keys are handled by the UI, they do not have a counterpart in any StaticPrintConfig derived classes,
@ -77,11 +77,12 @@ PresetBundle::PresetBundle() :
for (size_t i = 0; i < 2; ++ i) {
// The following ugly switch is to avoid printers.preset(0) to return the edited instance, as the 0th default is the current one.
Preset &preset = this->printers.default_preset(i);
preset.config.optptr("printer_settings_id", true);
preset.config.optptr("printer_vendor", true);
preset.config.optptr("printer_model", true);
preset.config.optptr("printer_variant", true);
preset.config.optptr("thumbnails", true);
for (const char *key : {
"printer_settings_id", "printer_vendor", "printer_model", "printer_variant", "thumbnails",
//FIXME the following keys are only created here for compatibility to be able to parse legacy Printer profiles.
// These keys are converted to Physical Printer profile. After the conversion, they shall be removed.
"host_type", "print_host", "printhost_apikey", "printhost_cafile"})
preset.config.optptr(key, true);
if (i == 0) {
preset.config.optptr("default_print_profile", true);
preset.config.option<ConfigOptionStrings>("default_filament_profile", true)->values = { "" };
@ -458,7 +459,7 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr
this->update_multi_material_filament_presets();
// Parse the initial physical printer name.
std::string initial_physical_printer_name = remove_ini_suffix(config.get("extras", "physical_printer"));
std::string initial_physical_printer_name = remove_ini_suffix(config.get("presets", "physical_printer"));
// Activate physical printer from the config
if (!initial_physical_printer_name.empty())
@ -482,8 +483,7 @@ void PresetBundle::export_selections(AppConfig &config)
config.set("presets", "sla_print", sla_prints.get_selected_preset_name());
config.set("presets", "sla_material", sla_materials.get_selected_preset_name());
config.set("presets", "printer", printers.get_selected_preset_name());
config.set("extras", "physical_printer", physical_printers.get_selected_full_printer_name());
config.set("presets", "physical_printer", physical_printers.get_selected_full_printer_name());
}
DynamicPrintConfig PresetBundle::full_config() const
@ -496,6 +496,7 @@ DynamicPrintConfig PresetBundle::full_config() const
DynamicPrintConfig PresetBundle::full_config_secure() const
{
DynamicPrintConfig config = this->full_config();
//FIXME legacy, the keys should not be there after conversion to a Physical Printer profile.
config.erase("print_host");
config.erase("printhost_apikey");
config.erase("printhost_cafile");
@ -1439,50 +1440,132 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri
const Preset &printer_preset = this->printers.get_edited_preset();
const PresetWithVendorProfile printer_preset_with_vendor_profile = this->printers.get_preset_with_vendor_profile(printer_preset);
class PreferedProfileMatch
{
public:
PreferedProfileMatch(const std::string &prefered_alias, const std::string &prefered_name) :
m_prefered_alias(prefered_alias), m_prefered_name(prefered_name) {}
int operator()(const Preset &preset) const
{
return (! m_prefered_alias.empty() && m_prefered_alias == preset.alias) ?
// Matching an alias, always take this preset with priority.
std::numeric_limits<int>::max() :
// Otherwise take the prefered profile, or the first compatible.
preset.name == m_prefered_name;
}
private:
const std::string m_prefered_alias;
const std::string &m_prefered_name;
};
// Matching by the layer height in addition.
class PreferedPrintProfileMatch : public PreferedProfileMatch
{
public:
PreferedPrintProfileMatch(const Preset &preset, const std::string &prefered_name) :
PreferedProfileMatch(preset.alias, prefered_name), m_prefered_layer_height(preset.config.opt_float("layer_height")) {}
int operator()(const Preset &preset) const
{
int match_quality = PreferedProfileMatch::operator()(preset);
if (match_quality < std::numeric_limits<int>::max()) {
match_quality += 1;
if (m_prefered_layer_height > 0. && std::abs(preset.config.opt_float("layer_height") - m_prefered_layer_height) < 0.0005)
match_quality *= 10;
}
return match_quality;
}
private:
const double m_prefered_layer_height;
};
// Matching by the layer height in addition.
class PreferedFilamentProfileMatch : public PreferedProfileMatch
{
public:
PreferedFilamentProfileMatch(const Preset *preset, const std::string &prefered_name) :
PreferedProfileMatch(preset ? preset->alias : std::string(), prefered_name),
m_prefered_filament_type(preset ? preset->config.opt_string("filament_type", 0) : std::string()) {}
int operator()(const Preset &preset) const
{
int match_quality = PreferedProfileMatch::operator()(preset);
if (match_quality < std::numeric_limits<int>::max()) {
match_quality += 1;
if (! m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", 0))
match_quality *= 10;
}
return match_quality;
}
private:
const std::string m_prefered_filament_type;
};
// Matching by the layer height in addition.
class PreferedFilamentsProfileMatch
{
public:
PreferedFilamentsProfileMatch(const Preset &preset, const std::vector<std::string> &prefered_names) :
m_prefered_alias(preset.alias),
m_prefered_filament_type(preset.config.opt_string("filament_type", 0)),
m_prefered_names(prefered_names)
{}
int operator()(const Preset &preset) const
{
if (! m_prefered_alias.empty() && m_prefered_alias == preset.alias)
// Matching an alias, always take this preset with priority.
return std::numeric_limits<int>::max();
int match_quality = (std::find(m_prefered_names.begin(), m_prefered_names.end(), preset.name) != m_prefered_names.end()) + 1;
if (! m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", 0))
match_quality *= 10;
return match_quality;
}
private:
const std::string m_prefered_alias;
const std::string m_prefered_filament_type;
const std::vector<std::string> &m_prefered_names;
};
switch (printer_preset.printer_technology()) {
case ptFFF:
{
assert(printer_preset.config.has("default_print_profile"));
assert(printer_preset.config.has("default_filament_profile"));
const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile");
const std::vector<std::string> &prefered_filament_profiles = printer_preset.config.option<ConfigOptionStrings>("default_filament_profile")->values;
prefered_print_profile.empty() ?
this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible) :
this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible,
[&prefered_print_profile](const std::string& profile_name) { return profile_name == prefered_print_profile; });
this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible,
PreferedPrintProfileMatch(this->prints.get_edited_preset(), printer_preset.config.opt_string("default_print_profile")));
const PresetWithVendorProfile print_preset_with_vendor_profile = this->prints.get_edited_preset_with_vendor_profile();
// Remember whether the filament profiles were compatible before updating the filament compatibility.
std::vector<char> filament_preset_was_compatible(this->filament_presets.size(), false);
for (size_t idx = 0; idx < this->filament_presets.size(); ++ idx) {
std::string &filament_name = this->filament_presets[idx];
Preset *preset = this->filaments.find_preset(filament_name, false);
Preset *preset = this->filaments.find_preset(this->filament_presets[idx], false);
filament_preset_was_compatible[idx] = preset != nullptr && preset->is_compatible;
}
prefered_filament_profiles.empty() ?
this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible) :
this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible,
[&prefered_filament_profiles](const std::string& profile_name)
{ return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); });
// First select a first compatible profile for the preset editor.
this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible,
PreferedFilamentsProfileMatch(this->filaments.get_edited_preset(), prefered_filament_profiles));
if (select_other_filament_if_incompatible != PresetSelectCompatibleType::Never) {
// Verify validity of the current filament presets.
const std::string prefered_filament_profile = prefered_filament_profiles.empty() ? std::string() : prefered_filament_profiles.front();
if (this->filament_presets.size() == 1) {
// The compatible profile should have been already selected for the preset editor. Just use it.
if (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible.front())
this->filament_presets.front() = this->filaments.get_edited_preset().name;
} else {
for (size_t idx = 0; idx < this->filament_presets.size(); ++ idx) {
std::string &filament_name = this->filament_presets[idx];
Preset *preset = this->filaments.find_preset(filament_name, false);
if (preset == nullptr || (! preset->is_compatible && (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible[idx]))) {
if (preset == nullptr || (! preset->is_compatible && (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible[idx])))
// Pick a compatible profile. If there are prefered_filament_profiles, use them.
if (prefered_filament_profiles.empty())
filament_name = this->filaments.first_compatible().name;
else {
const std::string &preferred = (idx < prefered_filament_profiles.size()) ?
prefered_filament_profiles[idx] : prefered_filament_profiles.front();
filament_name = this->filaments.first_compatible(
[&preferred](const std::string& profile_name) { return profile_name == preferred; }).name;
}
}
filament_name = this->filaments.first_compatible(
PreferedFilamentProfileMatch(preset,
(idx < prefered_filament_profiles.size()) ? prefered_filament_profiles[idx] : prefered_filament_profile)).name;
}
}
}
@ -1492,17 +1575,11 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri
{
assert(printer_preset.config.has("default_sla_print_profile"));
assert(printer_preset.config.has("default_sla_material_profile"));
const PresetWithVendorProfile sla_print_preset_with_vendor_profile = this->sla_prints.get_edited_preset_with_vendor_profile();
const std::string &prefered_sla_print_profile = printer_preset.config.opt_string("default_sla_print_profile");
(prefered_sla_print_profile.empty()) ?
this->sla_prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible) :
this->sla_prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible,
[&prefered_sla_print_profile](const std::string& profile_name){ return profile_name == prefered_sla_print_profile; });
const std::string &prefered_sla_material_profile = printer_preset.config.opt_string("default_sla_material_profile");
prefered_sla_material_profile.empty() ?
this->sla_materials.update_compatible(printer_preset_with_vendor_profile, &sla_print_preset_with_vendor_profile, select_other_filament_if_incompatible) :
this->sla_materials.update_compatible(printer_preset_with_vendor_profile, &sla_print_preset_with_vendor_profile, select_other_filament_if_incompatible,
[&prefered_sla_material_profile](const std::string& profile_name){ return profile_name == prefered_sla_material_profile; });
this->sla_prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible,
PreferedPrintProfileMatch(this->sla_prints.get_edited_preset(), printer_preset.config.opt_string("default_sla_print_profile")));
const PresetWithVendorProfile sla_print_preset_with_vendor_profile = this->sla_prints.get_edited_preset_with_vendor_profile();
this->sla_materials.update_compatible(printer_preset_with_vendor_profile, &sla_print_preset_with_vendor_profile, select_other_filament_if_incompatible,
PreferedProfileMatch(this->sla_materials.get_edited_preset().alias, printer_preset.config.opt_string("default_sla_material_profile")));
break;
}
default: break;

View file

@ -10,6 +10,7 @@
#include "I18N.hpp"
#include "ShortestPath.hpp"
#include "SupportMaterial.hpp"
#include "Thread.hpp"
#include "GCode.hpp"
#include "GCode/WipeTower.hpp"
#include "Utils.hpp"
@ -404,10 +405,10 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
// Copy the ModelVolume data.
mv_dst.name = mv_src.name;
mv_dst.config.assign_config(mv_src.config);
if (! mv_dst.m_supported_facets.timestamp_matches(mv_src.m_supported_facets))
mv_dst.m_supported_facets = mv_src.m_supported_facets;
if (! mv_dst.m_seam_facets.timestamp_matches(mv_src.m_seam_facets))
mv_dst.m_seam_facets = mv_src.m_seam_facets;
assert(mv_dst.supported_facets.id() == mv_src.supported_facets.id());
mv_dst.supported_facets.assign(mv_src.supported_facets);
assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id());
mv_dst.seam_facets.assign(mv_src.seam_facets);
//FIXME what to do with the materials?
// mv_dst.m_material_id = mv_src.m_material_id;
++ i_src;
@ -577,6 +578,16 @@ void Print::config_diffs(
}
}
std::vector<ObjectID> Print::print_object_ids() const
{
std::vector<ObjectID> out;
// Reserve one more for the caller to append the ID of the Print itself.
out.reserve(m_objects.size() + 1);
for (const PrintObject *print_object : m_objects)
out.emplace_back(print_object->id());
return out;
}
Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config)
{
#ifdef _DEBUG
@ -1274,8 +1285,9 @@ std::string Print::validate() const
"and use filaments of the same diameter.");
}
if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin)
return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors.");
if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware &&
m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin)
return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors.");
if (! m_config.use_relative_e_distances)
return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).");
if (m_config.ooze_prevention)
@ -1594,6 +1606,8 @@ void Print::auto_assign_extruders(ModelObject* model_object) const
// Slicing process, running at a background thread.
void Print::process()
{
name_tbb_thread_pool_threads();
BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info();
for (PrintObject *obj : m_objects)
obj->make_perimeters();

View file

@ -37,16 +37,18 @@ namespace FillAdaptive {
};
// Print step IDs for keeping track of the print state.
// The Print steps are applied in this order.
enum PrintStep {
psSkirt,
psBrim,
// Synonym for the last step before the Wipe Tower / Tool Ordering, for the G-code preview slider to understand that
// all the extrusions are there for the layer slider to add color changes etc.
psExtrusionPaths = psBrim,
psWipeTower,
// Ordering of the tools on PrintObjects for a multi-material print.
// psToolOrdering is a synonym to psWipeTower, as the Wipe Tower calculates and modifies the ToolOrdering,
// while if printing without the Wipe Tower, the ToolOrdering is calculated as well.
psToolOrdering = psWipeTower,
psSkirt,
psBrim,
// Last step before G-code export, after this step is finished, the initial extrusion path preview
// should be refreshed.
psSlicingFinished = psBrim,
psGCodeExport,
psCount,
};
@ -370,6 +372,8 @@ public:
// a cancellation callback is executed to stop the background processing before the operation.
void clear() override;
bool empty() const override { return m_objects.empty(); }
// List of existing PrintObject IDs, to remove notifications for non-existent IDs.
std::vector<ObjectID> print_object_ids() const override;
ApplyStatus apply(const Model &model, DynamicPrintConfig config) override;

View file

@ -348,6 +348,8 @@ public:
// The Print is empty either after clear() or after apply() over an empty model,
// or after apply() over a model, where no object is printable (all outside the print volume).
virtual bool empty() const = 0;
// List of existing PrintObject IDs, to remove notifications for non-existent IDs.
virtual std::vector<ObjectID> print_object_ids() const = 0;
// Validate the print, return empty string if valid, return error if process() cannot (or should not) be started.
virtual std::string validate() const { return std::string(); }
@ -406,7 +408,7 @@ public:
// set to an ObjectID of a Print or a PrintObject based on flags
// (whether UPDATE_PRINT_STEP_WARNINGS or UPDATE_PRINT_OBJECT_STEP_WARNINGS is set).
ObjectID warning_object_id;
// For which Print or PrintObject step a new warning is beeing issued?
// For which Print or PrintObject step a new warning is being issued?
int warning_step { -1 };
};
typedef std::function<void(const SlicingStatus&)> status_callback_type;

View file

@ -39,11 +39,6 @@ void PrintConfigDef::init_common_params()
{
ConfigOptionDef* def;
def = this->add("single_instance", coBool);
def->label = L("Single Instance");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("printer_technology", coEnum);
def->label = L("Printer technology");
def->tooltip = L("Printer technology");
@ -113,7 +108,14 @@ void PrintConfigDef::init_common_params()
"the API Key or the password required for authentication.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("printhost_port", coString);
def->label = L("Printer");
def->tooltip = L("Name of the printer");
def->gui_type = "select_open";
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("printhost_cafile", coString);
def->label = L("HTTPS CA File");
def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
@ -457,20 +459,20 @@ void PrintConfigDef::init_fff_params()
def->cli = "top-fill-pattern|external-fill-pattern|solid-fill-pattern";
def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values();
def->enum_values.push_back("rectilinear");
def->enum_values.push_back("monotonous");
def->enum_values.push_back("monotonic");
def->enum_values.push_back("concentric");
def->enum_values.push_back("hilbertcurve");
def->enum_values.push_back("archimedeanchords");
def->enum_values.push_back("octagramspiral");
def->enum_labels.push_back(L("Rectilinear"));
def->enum_labels.push_back(L("Monotonous"));
def->enum_labels.push_back(L("Monotonic"));
def->enum_labels.push_back(L("Concentric"));
def->enum_labels.push_back(L("Hilbert Curve"));
def->enum_labels.push_back(L("Archimedean Chords"));
def->enum_labels.push_back(L("Octagram Spiral"));
// solid_fill_pattern is an obsolete equivalent to top_fill_pattern/bottom_fill_pattern.
def->aliases = { "solid_fill_pattern", "external_fill_pattern" };
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipMonotonous));
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipMonotonic));
def = this->add("bottom_fill_pattern", coEnum);
def->label = L("Bottom fill pattern");
@ -988,6 +990,7 @@ void PrintConfigDef::init_fff_params()
"The \"No extrusion\" flavor prevents PrusaSlicer from exporting any extrusion value at all.");
def->enum_keys_map = &ConfigOptionEnum<GCodeFlavor>::get_enum_values();
def->enum_values.push_back("reprap");
def->enum_values.push_back("reprapfirmware");
def->enum_values.push_back("repetier");
def->enum_values.push_back("teacup");
def->enum_values.push_back("makerware");
@ -998,6 +1001,7 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("smoothie");
def->enum_values.push_back("no-extrusion");
def->enum_labels.push_back("RepRap/Sprinter");
def->enum_labels.push_back("RepRapFirmware");
def->enum_labels.push_back("Repetier");
def->enum_labels.push_back("Teacup");
def->enum_labels.push_back("MakerWare (MakerBot)");
@ -1008,7 +1012,7 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back("Smoothie");
def->enum_labels.push_back(L("No extrusion"));
def->mode = comExpert;
def->set_default_value(new ConfigOptionEnum<GCodeFlavor>(gcfRepRap));
def->set_default_value(new ConfigOptionEnum<GCodeFlavor>(gcfRepRapSprinter));
def = this->add("gcode_label_objects", coBool);
def->label = L("Label objects");
@ -1201,6 +1205,21 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("machine_limits_usage", coEnum);
def->label = L("How to apply");
def->full_label = L("Purpose of Machine Limits");
def->category = L("Machine limits");
def->tooltip = L("How to apply the Machine Limits");
def->enum_keys_map = &ConfigOptionEnum<MachineLimitsUsage>::get_enum_values();
def->enum_values.push_back("emit_to_gcode");
def->enum_values.push_back("time_estimate_only");
def->enum_values.push_back("ignore");
def->enum_labels.push_back("Emit to G-code");
def->enum_labels.push_back("Use for time estimate");
def->enum_labels.push_back("Ignore");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<MachineLimitsUsage>(MachineLimitsUsage::EmitToGCode));
{
struct AxisDefault {
std::string name;
@ -1435,10 +1454,12 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("duet");
def->enum_values.push_back("flashair");
def->enum_values.push_back("astrobox");
def->enum_values.push_back("repetier");
def->enum_labels.push_back("OctoPrint");
def->enum_labels.push_back("Duet");
def->enum_labels.push_back("FlashAir");
def->enum_labels.push_back("AstroBox");
def->enum_labels.push_back("Repetier");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));
@ -1753,26 +1774,6 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(30));
#endif
def = this->add("serial_port", coString);
def->gui_type = "select_open";
def->label = "";
def->full_label = L("Serial port");
def->tooltip = L("USB/serial port for printer connection.");
def->width = 20;
def->set_default_value(new ConfigOptionString(""));
def = this->add("serial_speed", coInt);
def->gui_type = "i_enum_open";
def->label = L("Speed");
def->full_label = L("Serial port speed");
def->tooltip = L("Speed (baud) of USB/serial port for printer connection.");
def->min = 1;
def->max = 300000;
def->enum_values.push_back("115200");
def->enum_values.push_back("250000");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(250000));
def = this->add("skirt_distance", coFloat);
def->label = L("Distance from object");
def->tooltip = L("Distance between skirt and object(s). Set this to zero to attach the skirt "
@ -3164,10 +3165,15 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
"seal_position", "vibration_limit", "bed_size",
"print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe"
#ifndef 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 */
"serial_port", "serial_speed"
};
// In PrusaSlicer 2.3.0-alpha0 the "monotonous" infill was introduced, which was later renamed to "monotonic".
if (value == "monotonous" && (opt_key == "top_fill_pattern" || opt_key == "bottom_fill_pattern" || opt_key == "fill_pattern"))
value = "monotonic";
if (ignore.find(opt_key) != ignore.end()) {
opt_key = "";
return;
@ -3338,11 +3344,12 @@ std::string FullPrintConfig::validate()
if (this->use_firmware_retraction.value &&
this->gcode_flavor.value != gcfSmoothie &&
this->gcode_flavor.value != gcfRepRap &&
this->gcode_flavor.value != gcfRepRapSprinter &&
this->gcode_flavor.value != gcfRepRapFirmware &&
this->gcode_flavor.value != gcfMarlin &&
this->gcode_flavor.value != gcfMachinekit &&
this->gcode_flavor.value != gcfRepetier)
return "--use-firmware-retraction is only supported by Marlin, Smoothie, Repetier and Machinekit firmware";
return "--use-firmware-retraction is only supported by Marlin, Smoothie, RepRapFirmware, Repetier and Machinekit firmware";
if (this->use_firmware_retraction.value)
for (unsigned char wipe : this->wipe.values)
@ -3481,7 +3488,6 @@ StaticPrintConfig::StaticCache<class Slic3r::PrintRegionConfig> PrintRegionConfi
StaticPrintConfig::StaticCache<class Slic3r::MachineEnvelopeConfig> MachineEnvelopeConfig::s_cache_MachineEnvelopeConfig;
StaticPrintConfig::StaticCache<class Slic3r::GCodeConfig> GCodeConfig::s_cache_GCodeConfig;
StaticPrintConfig::StaticCache<class Slic3r::PrintConfig> PrintConfig::s_cache_PrintConfig;
StaticPrintConfig::StaticCache<class Slic3r::HostConfig> HostConfig::s_cache_HostConfig;
StaticPrintConfig::StaticCache<class Slic3r::FullPrintConfig> FullPrintConfig::s_cache_FullPrintConfig;
StaticPrintConfig::StaticCache<class Slic3r::SLAMaterialConfig> SLAMaterialConfig::s_cache_SLAMaterialConfig;
@ -3679,6 +3685,12 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file).");
def->cli = "output|o";
def = this->add("single_instance", coBool);
def->label = L("Single Instance");
def->tooltip = L("If enabled, the command line arguments are sent to an existing instance of GUI PrusaSlicer, "
"or an existing PrusaSlicer window is activated. "
"Overrides the \"single_instance\" configuration value from application preferences.");
/*
def = this->add("autosave", coString);
def->label = L("Autosave");

View file

@ -11,7 +11,6 @@
// PrintRegionConfig
// PrintConfig
// GCodeConfig
// HostConfig
//
#ifndef slic3r_PrintConfig_hpp_
@ -25,12 +24,19 @@
namespace Slic3r {
enum GCodeFlavor : unsigned char {
gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit,
gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit,
gcfSmoothie, gcfNoExtrusion,
};
enum class MachineLimitsUsage {
EmitToGCode,
TimeEstimateOnly,
Ignore,
Count,
};
enum PrintHostType {
htOctoPrint, htDuet, htFlashAir, htAstroBox
htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier
};
enum AuthorizationType {
@ -38,7 +44,7 @@ enum AuthorizationType {
};
enum InfillPattern : int {
ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipRectilinear, ipMonotonic, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipCount,
};
@ -88,7 +94,8 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<PrinterTechnology
template<> inline const t_config_enum_values& ConfigOptionEnum<GCodeFlavor>::get_enum_values() {
static t_config_enum_values keys_map;
if (keys_map.empty()) {
keys_map["reprap"] = gcfRepRap;
keys_map["reprap"] = gcfRepRapSprinter;
keys_map["reprapfirmware"] = gcfRepRapFirmware;
keys_map["repetier"] = gcfRepetier;
keys_map["teacup"] = gcfTeacup;
keys_map["makerware"] = gcfMakerWare;
@ -102,6 +109,16 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<GCodeFlavor>::get
return keys_map;
}
template<> inline const t_config_enum_values& ConfigOptionEnum<MachineLimitsUsage>::get_enum_values() {
static t_config_enum_values keys_map;
if (keys_map.empty()) {
keys_map["emit_to_gcode"] = int(MachineLimitsUsage::EmitToGCode);
keys_map["time_estimate_only"] = int(MachineLimitsUsage::TimeEstimateOnly);
keys_map["ignore"] = int(MachineLimitsUsage::Ignore);
}
return keys_map;
}
template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::get_enum_values() {
static t_config_enum_values keys_map;
if (keys_map.empty()) {
@ -109,6 +126,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::g
keys_map["duet"] = htDuet;
keys_map["flashair"] = htFlashAir;
keys_map["astrobox"] = htAstroBox;
keys_map["repetier"] = htRepetier;
}
return keys_map;
}
@ -126,7 +144,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<InfillPattern>::g
static t_config_enum_values keys_map;
if (keys_map.empty()) {
keys_map["rectilinear"] = ipRectilinear;
keys_map["monotonous"] = ipMonotonous;
keys_map["monotonic"] = ipMonotonic;
keys_map["grid"] = ipGrid;
keys_map["triangles"] = ipTriangles;
keys_map["stars"] = ipStars;
@ -597,6 +615,8 @@ class MachineEnvelopeConfig : public StaticPrintConfig
{
STATIC_PRINT_CONFIG_CACHE(MachineEnvelopeConfig)
public:
// Allowing the machine limits to be completely ignored or used just for time estimator.
ConfigOptionEnum<MachineLimitsUsage> machine_limits_usage;
// M201 X... Y... Z... E... [mm/sec^2]
ConfigOptionFloats machine_max_acceleration_x;
ConfigOptionFloats machine_max_acceleration_y;
@ -624,6 +644,7 @@ public:
protected:
void initialize(StaticCacheBase &cache, const char *base_ptr)
{
OPT_PTR(machine_limits_usage);
OPT_PTR(machine_max_acceleration_x);
OPT_PTR(machine_max_acceleration_y);
OPT_PTR(machine_max_acceleration_z);
@ -941,38 +962,14 @@ protected:
}
};
class HostConfig : public StaticPrintConfig
{
STATIC_PRINT_CONFIG_CACHE(HostConfig)
public:
ConfigOptionEnum<PrintHostType> host_type;
ConfigOptionString print_host;
ConfigOptionString printhost_apikey;
ConfigOptionString printhost_cafile;
ConfigOptionString serial_port;
ConfigOptionInt serial_speed;
protected:
void initialize(StaticCacheBase &cache, const char *base_ptr)
{
OPT_PTR(host_type);
OPT_PTR(print_host);
OPT_PTR(printhost_apikey);
OPT_PTR(printhost_cafile);
OPT_PTR(serial_port);
OPT_PTR(serial_speed);
}
};
// This object is mapped to Perl as Slic3r::Config::Full.
class FullPrintConfig :
public PrintObjectConfig,
public PrintRegionConfig,
public PrintConfig,
public HostConfig
public PrintConfig
{
STATIC_PRINT_CONFIG_CACHE_DERIVED(FullPrintConfig)
FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) { initialize_cache(); *this = s_cache_FullPrintConfig.defaults(); }
FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0) { initialize_cache(); *this = s_cache_FullPrintConfig.defaults(); }
public:
// Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned.
@ -980,13 +977,12 @@ public:
protected:
// Protected constructor to be called to initialize ConfigCache::m_default.
FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) {}
FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0) {}
void initialize(StaticCacheBase &cache, const char *base_ptr)
{
this->PrintObjectConfig::initialize(cache, base_ptr);
this->PrintRegionConfig::initialize(cache, base_ptr);
this->PrintConfig ::initialize(cache, base_ptr);
this->HostConfig ::initialize(cache, base_ptr);
}
};
@ -1385,8 +1381,6 @@ class ModelConfig
public:
void clear() { m_data.clear(); m_timestamp = 1; }
// Modification of the ModelConfig is not thread safe due to the global timestamp counter!
// Don't call modification methods from the back-end!
void assign_config(const ModelConfig &rhs) {
if (m_timestamp != rhs.m_timestamp) {
m_data = rhs.m_data;
@ -1400,6 +1394,9 @@ public:
rhs.clear();
}
}
// Modification of the ModelConfig is not thread safe due to the global timestamp counter!
// Don't call modification methods from the back-end!
// Assign methods don't assign if src==dst to not having to bump the timestamp in case they are equal.
void assign_config(const DynamicPrintConfig &rhs) { if (m_data != rhs) { m_data = rhs; this->touch(); } }
void assign_config(DynamicPrintConfig &&rhs) { if (m_data != rhs) { m_data = std::move(rhs); this->touch(); } }

View file

@ -650,14 +650,14 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
// propagate to dependent steps
if (step == posPerimeters) {
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill });
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning });
invalidated |= m_print->invalidate_steps({ psSkirt, psBrim });
} else if (step == posPrepareInfill) {
invalidated |= this->invalidate_step(posInfill);
invalidated |= this->invalidate_steps({ posInfill, posIroning });
} else if (step == posInfill) {
invalidated |= m_print->invalidate_steps({ psSkirt, psBrim });
} else if (step == posSlice) {
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posSupportMaterial });
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportMaterial });
invalidated |= m_print->invalidate_steps({ psSkirt, psBrim });
this->m_slicing_params.valid = false;
} else if (step == posSupportMaterial) {
@ -1688,12 +1688,6 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
m_typed_slices = false;
#ifdef SLIC3R_PROFILE
// Disable parallelization so the Shiny profiler works
static tbb::task_scheduler_init *tbb_init = nullptr;
tbb_init = new tbb::task_scheduler_init(1);
#endif
// 1) Initialize layers and their slice heights.
std::vector<float> slice_zs;
{
@ -2706,7 +2700,7 @@ void PrintObject::combine_infill()
// Because fill areas for rectilinear and honeycomb are grown
// later to overlap perimeters, we need to counteract that too.
((region->config().fill_pattern == ipRectilinear ||
region->config().fill_pattern == ipMonotonous ||
region->config().fill_pattern == ipMonotonic ||
region->config().fill_pattern == ipGrid ||
region->config().fill_pattern == ipLine ||
region->config().fill_pattern == ipHoneycomb) ? 1.5f : 0.5f) *
@ -2748,8 +2742,8 @@ void PrintObject::project_and_append_custom_facets(
{
for (const ModelVolume* mv : this->model_object()->volumes) {
const indexed_triangle_set custom_facets = seam
? mv->m_seam_facets.get_facets(*mv, type)
: mv->m_supported_facets.get_facets(*mv, type);
? mv->seam_facets.get_facets(*mv, type)
: mv->supported_facets.get_facets(*mv, type);
if (! mv->is_model_part() || custom_facets.indices.empty())
continue;

View file

@ -11,8 +11,12 @@
#include "Point.hpp"
#include "ClipperUtils.hpp"
#include "Tesselate.hpp"
#include "ExPolygonCollection.hpp"
#include "libslic3r.h"
#include "libnest2d/backends/clipper/geometries.hpp"
#include "libnest2d/utils/rotcalipers.hpp"
#include <iostream>
#include <random>
@ -300,32 +304,35 @@ void SupportPointGenerator::add_support_points(SupportPointGenerator::Structure
float tp = m_config.tear_pressure();
float current = s.supports_force_total();
static constexpr float SLOPE_DAMPING = .0015f;
static constexpr float DANGL_DAMPING = .09f;
static constexpr float DANGL_DAMPING = .5f;
static constexpr float SLOPE_DAMPING = .1f;
if (s.islands_below.empty()) {
// completely new island - needs support no doubt
// deficit is full, there is nothing below that would hold this island
uniformly_cover({ *s.polygon }, s, s.area * tp, grid3d, IslandCoverageFlags(icfIsNew | icfBoundaryOnly) );
uniformly_cover({ *s.polygon }, s, s.area * tp, grid3d, IslandCoverageFlags(icfIsNew | icfWithBoundary) );
return;
}
if (! s.overhangs.empty()) {
uniformly_cover(s.overhangs, s, s.overhangs_area * tp, grid3d);
}
auto areafn = [](double sum, auto &p) { return sum + p.area() * SCALING_FACTOR * SCALING_FACTOR; };
current = s.supports_force_total();
if (! s.dangling_areas.empty()) {
// Let's see if there's anything that overlaps enough to need supports:
// What we now have in polygons needs support, regardless of what the forces are, so we can add them.
double a = std::accumulate(s.dangling_areas.begin(), s.dangling_areas.end(), 0., areafn);
uniformly_cover(s.dangling_areas, s, a * tp - current * DANGL_DAMPING * std::sqrt(1. - a / s.area), grid3d);
uniformly_cover(s.dangling_areas, s, a * tp - a * current * s.area, grid3d, icfWithBoundary);
}
current = s.supports_force_total();
if (! s.overhangs_slopes.empty()) {
double a = std::accumulate(s.overhangs_slopes.begin(), s.overhangs_slopes.end(), 0., areafn);
uniformly_cover(s.overhangs_slopes, s, a * tp - current * SLOPE_DAMPING * std::sqrt(1. - a / s.area), grid3d);
}
if (! s.overhangs.empty()) {
uniformly_cover(s.overhangs, s, s.overhangs_area * tp, grid3d);
uniformly_cover(s.overhangs_slopes, s, a * tp - a * current / s.area, grid3d, icfWithBoundary);
}
}
@ -357,13 +364,26 @@ std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_m
double r = random_triangle(rng);
size_t idx_triangle = std::min<size_t>(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3;
// Select a random point on the triangle.
double u = float(std::sqrt(random_float(rng)));
double v = float(random_float(rng));
const Vec2f &a = triangles[idx_triangle ++];
const Vec2f &b = triangles[idx_triangle++];
const Vec2f &c = triangles[idx_triangle];
const Vec2f x = a * (1.f - u) + b * (u * (1.f - v)) + c * (v * u);
out.emplace_back(x);
#if 1
// https://www.cs.princeton.edu/~funk/tog02.pdf
// page 814, formula 1.
double u = float(std::sqrt(random_float(rng)));
double v = float(random_float(rng));
out.emplace_back(a * (1.f - u) + b * (u * (1.f - v)) + c * (v * u));
#else
// Greg Turk, Graphics Gems
// https://devsplorer.wordpress.com/2019/08/07/find-a-random-point-on-a-plane-using-barycentric-coordinates-in-unity/
double u = float(random_float(rng));
double v = float(random_float(rng));
if (u + v >= 1.f) {
u = 1.f - u;
v = 1.f - v;
}
out.emplace_back(a + u * (b - a) + v * (c - a));
#endif
}
}
return out;
@ -532,10 +552,14 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
float support_force_deficit = deficit;
auto bb = get_extents(islands);
// auto bb = get_extents(islands);
if (flags & icfIsNew) {
Vec2d bbdim = unscaled(Vec2crd{bb.max - bb.min});
auto chull_ex = ExPolygonCollection{islands}.convex_hull();
auto chull = Slic3rMultiPoint_to_ClipperPath(chull_ex);
auto rotbox = libnest2d::minAreaBoundingBox(chull);
Vec2d bbdim = {unscaled(rotbox.width()), unscaled(rotbox.height())};
if (bbdim.x() > bbdim.y()) std::swap(bbdim.x(), bbdim.y());
double aspectr = bbdim.y() / bbdim.x();
@ -560,7 +584,7 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
std::vector<Vec2f> raw_samples =
flags & icfBoundaryOnly ?
flags & icfWithBoundary ?
sample_expolygon_with_boundary(islands, samples_per_mm2,
5.f / poisson_radius, m_rng) :
sample_expolygon(islands, samples_per_mm2, m_rng);

View file

@ -201,7 +201,7 @@ private:
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
public:
enum IslandCoverageFlags : uint8_t { icfNone = 0x0, icfIsNew = 0x1, icfBoundaryOnly = 0x2 };
enum IslandCoverageFlags : uint8_t { icfNone = 0x0, icfIsNew = 0x1, icfWithBoundary = 0x2 };
private:

View file

@ -4,6 +4,7 @@
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "MTUtils.hpp"
#include "Thread.hpp"
#include <unordered_set>
#include <numeric>
@ -175,6 +176,16 @@ static std::vector<SLAPrintObject::Instance> sla_instances(const ModelObject &mo
return instances;
}
std::vector<ObjectID> SLAPrint::print_object_ids() const
{
std::vector<ObjectID> out;
// Reserve one more for the caller to append the ID of the Print itself.
out.reserve(m_objects.size() + 1);
for (const SLAPrintObject *print_object : m_objects)
out.emplace_back(print_object->id());
return out;
}
SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig config)
{
#ifdef _DEBUG
@ -679,7 +690,10 @@ bool SLAPrint::invalidate_step(SLAPrintStep step)
void SLAPrint::process()
{
if(m_objects.empty()) return;
if (m_objects.empty())
return;
name_tbb_thread_pool_threads();
// Assumption: at this point the print objects should be populated only with
// the model objects we have to process and the instances are also filtered

View file

@ -420,6 +420,8 @@ public:
void clear() override;
bool empty() const override { return m_objects.empty(); }
// List of existing PrintObject IDs, to remove notifications for non-existent IDs.
std::vector<ObjectID> print_object_ids() const;
ApplyStatus apply(const Model &model, DynamicPrintConfig config) override;
void set_task(const TaskParams &params) override;
void process() override;

View file

@ -93,9 +93,10 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin
coord_t clpr_offs = scaled(doffs);
faded_lyrs = std::min(po.m_slice_index.size(), faded_lyrs);
size_t faded_lyrs_efc = std::max(size_t(1), faded_lyrs - 1);
auto efc = [start_efc, faded_lyrs](size_t pos) {
return (faded_lyrs - 1 - pos) * start_efc / (faded_lyrs - 1);
auto efc = [start_efc, faded_lyrs_efc](size_t pos) {
return (faded_lyrs_efc - pos) * start_efc / faded_lyrs_efc;
};
std::vector<ExPolygons> &slices = o == soModel ?

View file

@ -39,11 +39,8 @@
//===================
#define ENABLE_2_3_0_ALPHA1 1
// Enable rendering of objects colored by facets' slope
#define ENABLE_SLOPE_RENDERING (1 && ENABLE_2_3_0_ALPHA1)
// Enable rendering of objects using environment map
#define ENABLE_ENVIRONMENT_MAP (1 && ENABLE_2_3_0_ALPHA1)
#define ENABLE_ENVIRONMENT_MAP (0 && ENABLE_2_3_0_ALPHA1)
// Enable smoothing of objects normals
#define ENABLE_SMOOTH_NORMALS (0 && ENABLE_2_3_0_ALPHA1)
@ -58,6 +55,13 @@
#define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1)
#define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER)
#define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER)
#define ENABLE_GCODE_VIEWER_TASKBAR_ICON (0 && ENABLE_GCODE_VIEWER)
//===================
// 2.3.0.alpha3 techs
//===================
#define ENABLE_2_3_0_ALPHA3 1
#define ENABLE_CTRL_M_ON_WINDOWS (0 && ENABLE_2_3_0_ALPHA3)
#endif // _prusaslicer_technologies_h_

238
src/libslic3r/Thread.cpp Normal file
View file

@ -0,0 +1,238 @@
#ifdef _WIN32
#include <windows.h>
#include <boost/nowide/convert.hpp>
#else
// any posix system
#include <pthread.h>
#endif
#include <condition_variable>
#include <mutex>
#include <tbb/parallel_for.h>
#include <tbb/tbb_thread.h>
#include <tbb/task_scheduler_init.h>
#include "Thread.hpp"
namespace Slic3r {
#ifdef _WIN32
// The new API is better than the old SEH style thread naming since the names also show up in crash dumpsand ETW traces.
// Because the new API is only available on newer Windows 10, look it up dynamically.
typedef HRESULT(__stdcall* SetThreadDescriptionType)(HANDLE, PCWSTR);
typedef HRESULT(__stdcall* GetThreadDescriptionType)(HANDLE, PWSTR*);
static bool s_SetGetThreadDescriptionInitialized = false;
static HMODULE s_hKernel32 = nullptr;
static SetThreadDescriptionType s_fnSetThreadDescription = nullptr;
static GetThreadDescriptionType s_fnGetThreadDescription = nullptr;
static bool WindowsGetSetThreadNameAPIInitialize()
{
if (! s_SetGetThreadDescriptionInitialized) {
// Not thread safe! It is therefore a good idea to name the main thread before spawning worker threads
// to initialize
s_hKernel32 = LoadLibraryW(L"Kernel32.dll");
if (s_hKernel32) {
s_fnSetThreadDescription = (SetThreadDescriptionType)::GetProcAddress(s_hKernel32, "SetThreadDescription");
s_fnGetThreadDescription = (GetThreadDescriptionType)::GetProcAddress(s_hKernel32, "GetThreadDescription");
}
s_SetGetThreadDescriptionInitialized = true;
}
return s_fnSetThreadDescription && s_fnGetThreadDescription;
}
#ifndef NDEBUG
// Use the old way by throwing an exception, so at least in Debug mode the thread names are shown by the debugger.
static constexpr DWORD MSVC_SEH_EXCEPTION_NAME_THREAD = 0x406D1388;
#pragma pack(push,8)
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)
static void WindowsSetThreadNameSEH(HANDLE hThread, const char* thread_name)
{
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = thread_name;
info.dwThreadID = ::GetThreadId(hThread);
info.dwFlags = 0;
__try {
RaiseException(MSVC_SEH_EXCEPTION_NAME_THREAD, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
} __except (EXCEPTION_EXECUTE_HANDLER) {
}
}
#endif // NDEBUG
static bool WindowsSetThreadName(HANDLE hThread, const char *thread_name)
{
if (! WindowsGetSetThreadNameAPIInitialize()) {
#ifdef NDEBUG
return false;
#else // NDEBUG
// Running on Windows 7 or old Windows 7 in debug mode,
// inform the debugger about the thread name by throwing an SEH.
WindowsSetThreadNameSEH(hThread, thread_name);
return true;
#endif // NDEBUG
}
size_t len = strlen(thread_name);
if (len < 1024) {
// Allocate the temp string on stack.
wchar_t buf[1024];
s_fnSetThreadDescription(hThread, boost::nowide::widen(buf, 1024, thread_name));
} else {
// Allocate dynamically.
s_fnSetThreadDescription(hThread, boost::nowide::widen(thread_name).c_str());
}
return true;
}
bool set_thread_name(std::thread &thread, const char *thread_name)
{
return WindowsSetThreadName(static_cast<HANDLE>(thread.native_handle()), thread_name);
}
bool set_thread_name(boost::thread &thread, const char *thread_name)
{
return WindowsSetThreadName(static_cast<HANDLE>(thread.native_handle()), thread_name);
}
bool set_current_thread_name(const char *thread_name)
{
return WindowsSetThreadName(::GetCurrentThread(), thread_name);
}
std::optional<std::string> get_current_thread_name()
{
if (! WindowsGetSetThreadNameAPIInitialize())
return std::nullopt;
wchar_t *ptr = nullptr;
s_fnGetThreadDescription(::GetCurrentThread(), &ptr);
return (ptr == nullptr) ? std::string() : boost::nowide::narrow(ptr);
}
#else // _WIN32
#ifdef __APPLE__
// Appe screwed the Posix norm.
bool set_thread_name(std::thread &thread, const char *thread_name)
{
// not supported
// pthread_setname_np(thread.native_handle(), thread_name);
return false;
}
bool set_thread_name(boost::thread &thread, const char *thread_name)
{
// not supported
// pthread_setname_np(thread.native_handle(), thread_name);
return false;
}
bool set_current_thread_name(const char *thread_name)
{
pthread_setname_np(thread_name);
return true;
}
std::optional<std::string> get_current_thread_name()
{
// not supported
// char buf[16];
// return std::string(thread_getname_np(buf, 16) == 0 ? buf : "");
return std::nullopt;
}
#else
// posix
bool set_thread_name(std::thread &thread, const char *thread_name)
{
pthread_setname_np(thread.native_handle(), thread_name);
return true;
}
bool set_thread_name(boost::thread &thread, const char *thread_name)
{
pthread_setname_np(thread.native_handle(), thread_name);
return true;
}
bool set_current_thread_name(const char *thread_name)
{
pthread_setname_np(pthread_self(), thread_name);
return true;
}
std::optional<std::string> get_current_thread_name()
{
char buf[16];
return std::string(pthread_getname_np(pthread_self(), buf, 16) == 0 ? buf : "");
}
#endif
#endif // _WIN32
// Spawn (n - 1) worker threads on Intel TBB thread pool and name them by an index and a system thread ID.
void name_tbb_thread_pool_threads()
{
static bool initialized = false;
if (initialized)
return;
initialized = true;
const size_t nthreads_hw = std::thread::hardware_concurrency();
size_t nthreads = nthreads_hw;
#ifdef SLIC3R_PROFILE
// Shiny profiler is not thread safe, thus disable parallelization.
nthreads = 1;
#endif
if (nthreads != nthreads_hw)
new tbb::task_scheduler_init(int(nthreads));
std::atomic<size_t> nthreads_running(0);
std::condition_variable cv;
std::mutex cv_m;
auto master_thread_id = tbb::this_tbb_thread::get_id();
tbb::parallel_for(
tbb::blocked_range<size_t>(0, nthreads, 1),
[&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range<size_t> &range) {
assert(range.begin() + 1 == range.end());
if (nthreads_running.fetch_add(1) + 1 == nthreads) {
// All threads are spinning.
// Wake them up.
cv.notify_all();
} else {
// Wait for the last thread to wake the others.
std::unique_lock<std::mutex> lk(cv_m);
cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;});
}
auto thread_id = tbb::this_tbb_thread::get_id();
if (thread_id == master_thread_id) {
// The calling thread runs the 0'th task.
assert(range.begin() == 0);
} else {
assert(range.begin() > 0);
std::ostringstream name;
name << "slic3r_tbb_" << range.begin();
set_current_thread_name(name.str().c_str());
}
});
}
}

57
src/libslic3r/Thread.hpp Normal file
View file

@ -0,0 +1,57 @@
#ifndef GUI_THREAD_HPP
#define GUI_THREAD_HPP
#include <utility>
#include <string>
#include <thread>
#include <boost/thread.hpp>
namespace Slic3r {
// Set / get thread name.
// Returns false if the API is not supported.
//
// It is a good idea to name the main thread before spawning children threads, because dynamic linking is used on Windows 10
// to initialize Get/SetThreadDescription functions, which is not thread safe.
//
// pthread_setname_np supports maximum 15 character thread names! (16th character is the null terminator)
//
// Methods taking the thread as an argument are not supported by OSX.
// Naming threads is only supported on newer Windows 10.
bool set_thread_name(std::thread &thread, const char *thread_name);
inline bool set_thread_name(std::thread &thread, const std::string &thread_name) { return set_thread_name(thread, thread_name.c_str()); }
bool set_thread_name(boost::thread &thread, const char *thread_name);
inline bool set_thread_name(boost::thread &thread, const std::string &thread_name) { return set_thread_name(thread, thread_name.c_str()); }
bool set_current_thread_name(const char *thread_name);
inline bool set_current_thread_name(const std::string &thread_name) { return set_current_thread_name(thread_name.c_str()); }
// Returns nullopt if not supported.
// Not supported by OSX.
// Naming threads is only supported on newer Windows 10.
std::optional<std::string> get_current_thread_name();
// To be called somewhere before the TBB threads are spinned for the first time, to
// give them names recognizible in the debugger.
void name_tbb_thread_pool_threads();
template<class Fn>
inline boost::thread create_thread(boost::thread::attributes &attrs, Fn &&fn)
{
// Duplicating the stack allocation size of Thread Building Block worker
// threads of the thread pool: allocate 4MB on a 64bit system, allocate 2MB
// on a 32bit system by default.
attrs.set_stack_size((sizeof(void*) == 4) ? (2048 * 1024) : (4096 * 1024));
return boost::thread{attrs, std::forward<Fn>(fn)};
}
template<class Fn> inline boost::thread create_thread(Fn &&fn)
{
boost::thread::attributes attrs;
return create_thread(attrs, std::forward<Fn>(fn));
}
}
#endif // GUI_THREAD_HPP

View file

@ -34,16 +34,15 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
const Vec3f& source, const Vec3f& dir,
float radius, CursorType cursor_type,
EnforcerBlockerType new_state)
const Vec3f& source, float radius,
CursorType cursor_type, EnforcerBlockerType new_state,
const Transform3d& trafo)
{
assert(facet_start < m_orig_size_indices);
assert(is_approx(dir.norm(), 1.f));
// Save current cursor center, squared radius and camera direction,
// so we don't have to pass it around.
m_cursor = {hit, source, dir, radius*radius, cursor_type};
// Save current cursor center, squared radius and camera direction, so we don't
// have to pass it around.
m_cursor = Cursor(hit, source, radius, cursor_type, trafo);
// In case user changed cursor size since last time, update triangle edge limit.
if (m_old_cursor_radius != radius) {
@ -176,10 +175,24 @@ void TriangleSelector::split_triangle(int facet_idx)
const double limit_squared = m_edge_limit_sqr;
std::array<int, 3>& facet = tr->verts_idxs;
const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v};
double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(),
(*pts[0]-*pts[2]).squaredNorm(),
(*pts[1]-*pts[0]).squaredNorm() };
std::array<const stl_vertex*, 3> pts = { &m_vertices[facet[0]].v,
&m_vertices[facet[1]].v,
&m_vertices[facet[2]].v};
std::array<stl_vertex, 3> pts_transformed; // must stay in scope of pts !!!
// In case the object is non-uniformly scaled, transform the
// points to world coords.
if (! m_cursor.uniform_scaling) {
for (size_t i=0; i<pts.size(); ++i) {
pts_transformed[i] = m_cursor.trafo * (*pts[i]);
pts[i] = &pts_transformed[i];
}
}
std::array<double, 3> sides;
sides = { (*pts[2]-*pts[1]).squaredNorm(),
(*pts[0]-*pts[2]).squaredNorm(),
(*pts[1]-*pts[0]).squaredNorm() };
std::vector<int> sides_to_split;
int side_to_keep = -1;
@ -204,38 +217,14 @@ void TriangleSelector::split_triangle(int facet_idx)
}
// Calculate distance of a point from a line.
bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const
{
Vec3f diff = m_cursor.center - point;
if (m_cursor.type == CIRCLE)
return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr;
else // SPHERE
return diff.squaredNorm() < m_cursor.radius_sqr;
}
// Is pointer in a triangle?
bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const
{
auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b,
const Vec3f& c, const Vec3f& d) -> bool {
return ((b-a).cross(c-a)).dot(d-a) > 0.;
};
const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v;
const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v;
const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v;
const Vec3f& q1 = m_cursor.center + m_cursor.dir;
const Vec3f q2 = m_cursor.center - m_cursor.dir;
if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) {
bool pos = signed_volume_sign(q1,q2,p1,p2);
if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos)
return true;
}
return false;
return m_cursor.is_pointer_in_triangle(p1, p2, p3);
}
@ -245,7 +234,13 @@ bool TriangleSelector::faces_camera(int facet) const
{
assert(facet < m_orig_size_indices);
// The normal is cached in mesh->stl, use it.
return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.);
Vec3f normal = m_mesh->stl.facet_start[facet].normal;
if (! m_cursor.uniform_scaling) {
// Transform the normal into world coords.
normal = m_cursor.trafo_normal * normal;
}
return (normal.dot(m_cursor.dir) < 0.);
}
@ -254,7 +249,7 @@ int TriangleSelector::vertices_inside(int facet_idx) const
{
int inside = 0;
for (size_t i=0; i<3; ++i) {
if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v))
if (m_cursor.is_mesh_point_inside(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v))
++inside;
}
return inside;
@ -264,9 +259,12 @@ int TriangleSelector::vertices_inside(int facet_idx) const
// Is edge inside cursor?
bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const
{
Vec3f pts[3];
for (int i=0; i<3; ++i)
std::array<Vec3f, 3> pts;
for (int i=0; i<3; ++i) {
pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v;
if (! m_cursor.uniform_scaling)
pts[i] = m_cursor.trafo * pts[i];
}
const Vec3f& p = m_cursor.center;
@ -690,6 +688,79 @@ void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data)
}
TriangleSelector::Cursor::Cursor(
const Vec3f& center_, const Vec3f& source_, float radius_world,
CursorType type_, const Transform3d& trafo_)
: center{center_},
source{source_},
type{type_},
trafo{trafo_.cast<float>()}
{
Vec3d sf = Geometry::Transformation(trafo_).get_scaling_factor();
if (is_approx(sf(0), sf(1)) && is_approx(sf(1), sf(2))) {
radius_sqr = std::pow(radius_world / sf(0), 2);
uniform_scaling = true;
}
else {
// In case that the transformation is non-uniform, all checks whether
// something is inside the cursor should be done in world coords.
// First transform center, source and dir in world coords and remember
// that we did this.
center = trafo * center;
source = trafo * source;
uniform_scaling = false;
radius_sqr = radius_world * radius_world;
trafo_normal = trafo.linear().inverse().transpose();
}
// Calculate dir, in whatever coords is appropriate.
dir = (center - source).normalized();
}
// Is a point (in mesh coords) inside a cursor?
bool TriangleSelector::Cursor::is_mesh_point_inside(Vec3f point) const
{
if (! uniform_scaling)
point = trafo * point;
Vec3f diff = center - point;
if (type == CIRCLE)
return (diff - diff.dot(dir) * dir).squaredNorm() < radius_sqr;
else // SPHERE
return diff.squaredNorm() < radius_sqr;
}
// p1, p2, p3 are in mesh coords!
bool TriangleSelector::Cursor::is_pointer_in_triangle(const Vec3f& p1_,
const Vec3f& p2_,
const Vec3f& p3_) const
{
const Vec3f& q1 = center + dir;
const Vec3f& q2 = center - dir;
auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b,
const Vec3f& c, const Vec3f& d) -> bool {
return ((b-a).cross(c-a)).dot(d-a) > 0.;
};
// In case the object is non-uniformly scaled, do the check in world coords.
const Vec3f& p1 = uniform_scaling ? p1_ : Vec3f(trafo * p1_);
const Vec3f& p2 = uniform_scaling ? p2_ : Vec3f(trafo * p2_);
const Vec3f& p3 = uniform_scaling ? p3_ : Vec3f(trafo * p3_);
if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) {
bool pos = signed_volume_sign(q1,q2,p1,p2);
if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos)
return true;
}
return false;
}
} // namespace Slic3r

View file

@ -32,10 +32,10 @@ public:
void select_patch(const Vec3f& hit, // point where to start
int facet_start, // facet that point belongs to
const Vec3f& source, // camera position (mesh coords)
const Vec3f& dir, // direction of the ray (mesh coords)
float radius, // radius of the cursor
CursorType type, // current type of cursor
EnforcerBlockerType new_state); // enforcer or blocker?
EnforcerBlockerType new_state, // enforcer or blocker?
const Transform3d& trafo); // matrix to get from mesh to world
// Get facets currently in the given state.
indexed_triangle_set get_facets(EnforcerBlockerType state) const;
@ -129,11 +129,20 @@ protected:
// Cache for cursor position, radius and direction.
struct Cursor {
Cursor() = default;
Cursor(const Vec3f& center_, const Vec3f& source_, float radius_world,
CursorType type_, const Transform3d& trafo_);
bool is_mesh_point_inside(Vec3f pt) const;
bool is_pointer_in_triangle(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) const;
Vec3f center;
Vec3f source;
Vec3f dir;
float radius_sqr;
CursorType type;
Transform3f trafo;
Transform3f trafo_normal;
bool uniform_scaling;
};
Cursor m_cursor;
@ -142,7 +151,6 @@ protected:
// Private functions:
bool select_triangle(int facet_idx, EnforcerBlockerType type,
bool recursive_call = false);
bool is_point_inside_cursor(const Vec3f& point) const;
int vertices_inside(int facet_idx) const;
bool faces_camera(int facet) const;
void undivide_triangle(int facet_idx);

View file

@ -7,6 +7,8 @@
#include <type_traits>
#include <system_error>
#include <boost/system/error_code.hpp>
#include "libslic3r.h"
namespace boost { namespace filesystem { class directory_entry; }}
@ -73,11 +75,12 @@ enum CopyFileResult {
FAIL_CHECK_TARGET_NOT_OPENED
};
// Copy a file, adjust the access attributes, so that the target is writable.
CopyFileResult copy_file_inner(const std::string &from, const std::string &to);
CopyFileResult copy_file_inner(const std::string &from, const std::string &to, std::string& error_message);
// Copy file to a temp file first, then rename it to the final file name.
// If with_check is true, then the content of the copied file is compared to the content
// of the source file before renaming.
extern CopyFileResult copy_file(const std::string &from, const std::string &to, const bool with_check = false);
// Additional error info is passed in error message.
extern CopyFileResult copy_file(const std::string &from, const std::string &to, std::string& error_message, const bool with_check = false);
// Compares two files if identical.
extern CopyFileResult check_copy(const std::string& origin, const std::string& copy);
@ -107,6 +110,12 @@ std::string string_printf(const char *format, ...);
// to be placed at the top of Slic3r generated files.
std::string header_slic3r_generated();
#if ENABLE_GCODE_VIEWER
// Standard "generated by PrusaGCodeViewer version xxx timestamp xxx" header string,
// to be placed at the top of Slic3r generated files.
std::string header_gcodeviewer_generated();
#endif // ENABLE_GCODE_VIEWER
// getpid platform wrapper
extern unsigned get_current_pid();

View file

@ -2,6 +2,9 @@
#define _libslic3r_h_
#include "libslic3r_version.h"
#define GCODEVIEWER_APP_NAME "PrusaSlicer G-code Viewer"
#define GCODEVIEWER_APP_KEY "PrusaSlicerGcodeViewer"
#define GCODEVIEWER_BUILD_ID std::string("PrusaSlicer G-code Viewer-") + std::string(SLIC3R_VERSION) + std::string("-UNKNOWN")
// this needs to be included early for MSVC (listing it in Build.PL is not enough)
#include <memory>

View file

@ -6,7 +6,4 @@
#define SLIC3R_VERSION "@SLIC3R_VERSION@"
#define SLIC3R_BUILD_ID "@SLIC3R_BUILD_ID@"
#define GCODEVIEWER_APP_NAME "@GCODEVIEWER_APP_NAME@"
#define GCODEVIEWER_BUILD_ID "@GCODEVIEWER_BUILD_ID@"
#endif /* __SLIC3R_VERSION_H */

View file

@ -417,7 +417,7 @@ std::error_code rename_file(const std::string &from, const std::string &to)
#endif
}
CopyFileResult copy_file_inner(const std::string& from, const std::string& to)
CopyFileResult copy_file_inner(const std::string& from, const std::string& to, std::string& error_message)
{
const boost::filesystem::path source(from);
const boost::filesystem::path target(to);
@ -429,20 +429,31 @@ CopyFileResult copy_file_inner(const std::string& from, const std::string& to)
// the copy_file() function will fail appropriately and we don't want the permission()
// calls to cause needless failures on permissionless filesystems (ie. FATs on SD cards etc.)
// or when the target file doesn't exist.
//This error code is ignored
boost::system::error_code ec;
boost::filesystem::permissions(target, perms, ec);
//if (ec)
// BOOST_LOG_TRIVIAL(error) << "Copy file permisions before copy error message: " << ec.message();
// This error code is passed up
ec.clear();
boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists, ec);
if (ec) {
error_message = ec.message();
return FAIL_COPY_FILE;
}
//ec.clear();
boost::filesystem::permissions(target, perms, ec);
//if (ec)
// BOOST_LOG_TRIVIAL(error) << "Copy file permisions after copy error message: " << ec.message();
return SUCCESS;
}
CopyFileResult copy_file(const std::string &from, const std::string &to, const bool with_check)
CopyFileResult copy_file(const std::string &from, const std::string &to, std::string& error_message, const bool with_check)
{
std::string to_temp = to + ".tmp";
CopyFileResult ret_val = copy_file_inner(from,to_temp);
CopyFileResult ret_val = copy_file_inner(from, to_temp, error_message);
if(ret_val == SUCCESS)
{
if (with_check)
@ -604,9 +615,16 @@ std::string string_printf(const char *format, ...)
std::string header_slic3r_generated()
{
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
}
#if ENABLE_GCODE_VIEWER
std::string header_gcodeviewer_generated()
{
return std::string("generated by " GCODEVIEWER_APP_NAME " " SLIC3R_VERSION " on ") + Utils::utc_timestamp();
}
#endif // ENABLE_GCODE_VIEWER
unsigned get_current_pid()
{
#ifdef WIN32

View file

@ -103,7 +103,7 @@
<key>CFBundleTypeName</key>
<string>GCODE</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<string>Viewer</string>
<key>LISsAppleDefaultForType</key>
<true/>
<key>LSHandlerRank</key>

View file

@ -0,0 +1,9 @@
[Desktop Entry]
Name=Prusa GCode viewer
Exec=prusa-slicer --gcodeviewer %F
Icon=PrusaSlicer # TODO: change when the new icon is ready
Terminal=false
Type=Application
MimeType=text/x.gcode;
Categories=Graphics;3DGraphics;
Keywords=3D;Printing;Slicer;

View file

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

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.8)
cmake_minimum_required(VERSION 3.13)
project(libslic3r_gui)
include(PrecompiledHeader)
@ -73,6 +73,8 @@ set(SLIC3R_GUI_SOURCES
GUI/PresetHints.hpp
GUI/GUI.cpp
GUI/GUI.hpp
GUI/GUI_Init.cpp
GUI/GUI_Init.hpp
GUI/GUI_Preview.cpp
GUI/GUI_Preview.hpp
GUI/GUI_App.cpp
@ -87,6 +89,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Plater.hpp
GUI/PresetComboBoxes.hpp
GUI/PresetComboBoxes.cpp
GUI/SavePresetDialog.hpp
GUI/SavePresetDialog.cpp
GUI/PhysicalPrinterDialog.hpp
GUI/PhysicalPrinterDialog.cpp
GUI/GUI_ObjectList.cpp
@ -107,6 +111,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Field.hpp
GUI/OptionsGroup.cpp
GUI/OptionsGroup.hpp
GUI/OG_CustomCtrl.cpp
GUI/OG_CustomCtrl.hpp
GUI/BedShapeDialog.cpp
GUI/BedShapeDialog.hpp
GUI/2DBed.cpp
@ -189,6 +195,8 @@ set(SLIC3R_GUI_SOURCES
Utils/FlashAir.hpp
Utils/AstroBox.cpp
Utils/AstroBox.hpp
Utils/Repetier.cpp
Utils/Repetier.hpp
Utils/PrintHost.cpp
Utils/PrintHost.hpp
Utils/Bonjour.cpp
@ -202,7 +210,6 @@ set(SLIC3R_GUI_SOURCES
Utils/UndoRedo.hpp
Utils/HexFile.cpp
Utils/HexFile.hpp
Utils/Thread.hpp
)
if (APPLE)

View file

@ -31,8 +31,11 @@ void Snapshot::clear()
this->comment.clear();
this->reason = SNAPSHOT_UNKNOWN;
this->print.clear();
this->sla_print.clear();
this->filaments.clear();
this->sla_material.clear();
this->printer.clear();
this->physical_printer.clear();
}
void Snapshot::load_ini(const std::string &path)
@ -94,6 +97,8 @@ void Snapshot::load_ini(const std::string &path)
for (auto &kvp : section.second) {
if (kvp.first == "print") {
this->print = kvp.second.data();
} else if (kvp.first == "sla_print") {
this->sla_print = kvp.second.data();
} else if (boost::starts_with(kvp.first, "filament")) {
int idx = 0;
if (kvp.first == "filament" || sscanf(kvp.first.c_str(), "filament_%d", &idx) == 1) {
@ -101,8 +106,12 @@ void Snapshot::load_ini(const std::string &path)
this->filaments.resize(idx + 1, std::string());
this->filaments[idx] = kvp.second.data();
}
} else if (kvp.first == "sla_material") {
this->sla_material = kvp.second.data();
} else if (kvp.first == "printer") {
this->printer = kvp.second.data();
} else if (kvp.first == "physical_printer") {
this->physical_printer = kvp.second.data();
}
}
} else if (boost::starts_with(section.first, group_name_vendor) && section.first.size() > group_name_vendor.size()) {
@ -172,10 +181,13 @@ void Snapshot::save_ini(const std::string &path)
// Export the active presets at the time of the snapshot.
c << std::endl << "[presets]" << std::endl;
c << "print = " << this->print << std::endl;
c << "sla_print = " << this->sla_print << std::endl;
c << "filament = " << this->filaments.front() << std::endl;
for (size_t i = 1; i < this->filaments.size(); ++ i)
c << "filament_" << std::to_string(i) << " = " << this->filaments[i] << std::endl;
c << "sla_material = " << this->sla_material << std::endl;
c << "printer = " << this->printer << std::endl;
c << "physical_printer = " << this->physical_printer << std::endl;
// Export the vendor configs.
for (const VendorConfig &vc : this->vendor_configs) {
@ -199,14 +211,17 @@ void Snapshot::export_selections(AppConfig &config) const
{
assert(filaments.size() >= 1);
config.clear_section("presets");
config.set("presets", "print", print);
config.set("presets", "filament", filaments.front());
config.set("presets", "print", print);
config.set("presets", "sla_print", sla_print);
config.set("presets", "filament", filaments.front());
for (unsigned i = 1; i < filaments.size(); ++i) {
char name[64];
sprintf(name, "filament_%u", i);
config.set("presets", name, filaments[i]);
}
config.set("presets", "printer", printer);
config.set("presets", "sla_material", sla_material);
config.set("presets", "printer", printer);
config.set("presets", "physical_printer", physical_printer);
}
void Snapshot::export_vendor_configs(AppConfig &config) const
@ -217,8 +232,10 @@ void Snapshot::export_vendor_configs(AppConfig &config) const
config.set_vendors(std::move(vendors));
}
// Perform a deep compare of the active print / filament / printer / vendor directories.
// Return true if the content of the current print / filament / printer / vendor directories
static constexpr auto snapshot_subdirs = { "print", "sla_print", "filament", "sla_material", "printer", "physical_printer", "vendor" };
// Perform a deep compare of the active print / sla_print / filament / sla_material / printer / physical_printer / vendor directories.
// Return true if the content of the current print / sla_print / filament / sla_material / printer / physical_printer / vendor directories
// matches the state stored in this snapshot.
bool Snapshot::equal_to_active(const AppConfig &app_config) const
{
@ -243,7 +260,7 @@ bool Snapshot::equal_to_active(const AppConfig &app_config) const
// 2) Check, whether this snapshot references the same set of ini files as the current state.
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshot_dir = boost::filesystem::path(Slic3r::data_dir()) / SLIC3R_SNAPSHOTS_DIR / this->id;
for (const char *subdir : { "print", "filament", "printer", "vendor" }) {
for (const char *subdir : snapshot_subdirs) {
boost::filesystem::path path1 = data_dir / subdir;
boost::filesystem::path path2 = snapshot_dir / subdir;
std::vector<std::string> files1, files2;
@ -369,9 +386,12 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
snapshot.comment = comment;
snapshot.reason = reason;
// Active presets at the time of the snapshot.
snapshot.print = app_config.get("presets", "print");
snapshot.print = app_config.get("presets", "print");
snapshot.sla_print = app_config.get("presets", "sla_print");
snapshot.filaments.emplace_back(app_config.get("presets", "filament"));
snapshot.printer = app_config.get("presets", "printer");
snapshot.sla_material = app_config.get("presets", "sla_material");
snapshot.printer = app_config.get("presets", "printer");
snapshot.physical_printer = app_config.get("presets", "physical_printer");
for (unsigned i = 1; i < 1000; ++ i) {
char name[64];
sprintf(name, "filament_%u", i);
@ -414,7 +434,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
boost::filesystem::create_directory(snapshot_dir);
// Backup the presets.
for (const char *subdir : { "print", "filament", "printer", "vendor" })
for (const char *subdir : snapshot_subdirs)
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
@ -438,11 +458,11 @@ void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_confi
boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
// Remove existing ini files and restore the ini files from the snapshot.
for (const char *subdir : { "print", "filament", "printer", "vendor" }) {
for (const char *subdir : snapshot_subdirs) {
delete_existing_ini_files(data_dir / subdir);
copy_config_dir_single_level(snapshot_dir / subdir, data_dir / subdir);
}
// Update AppConfig with the selections of the print / filament / printer profiles
// Update AppConfig with the selections of the print / sla_print / filament / sla_material / printer profiles
// and about the installed printer types and variants.
snapshot.export_selections(app_config);
snapshot.export_vendor_configs(app_config);

View file

@ -23,8 +23,11 @@ namespace Config {
// Slic3r.ini
// vendor/
// print/
// sla_print/
// filament/
// sla_material
// printer/
// physical_printer/
class Snapshot
{
public:
@ -42,12 +45,12 @@ public:
void load_ini(const std::string &path);
void save_ini(const std::string &path);
// Export the print / filament / printer selections to be activated into the AppConfig.
// Export the print / sla_print / filament / sla_material / printer selections to be activated into the AppConfig.
void export_selections(AppConfig &config) const;
void export_vendor_configs(AppConfig &config) const;
// Perform a deep compare of the active print / filament / printer / vendor directories.
// Return true if the content of the current print / filament / printer / vendor directories
// Perform a deep compare of the active print / sla_print / filament / sla_material / printer / physical_printer / vendor directories.
// Return true if the content of the current print / sla_print / filament / sla_material / printer / physical_printer / vendor directories
// matches the state stored in this snapshot.
bool equal_to_active(const AppConfig &app_config) const;
@ -65,8 +68,11 @@ public:
// Active presets at the time of the snapshot.
std::string print;
std::string sla_print;
std::vector<std::string> filaments;
std::string sla_material;
std::string printer;
std::string physical_printer;
// Annotation of the vendor configuration stored in the snapshot.
// This information is displayed to the user and used to decide compatibility
@ -97,7 +103,7 @@ public:
size_t load_db();
void update_slic3r_versions(std::vector<Index> &index_db);
// Create a snapshot directory, copy the vendor config bundles, user print/filament/printer profiles,
// Create a snapshot directory, copy the vendor config bundles, user print / sla_print / filament / sla_material / printer / physical_printer profiles,
// create an index.
const Snapshot& take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment = "");
const Snapshot& restore_snapshot(const std::string &id, AppConfig &app_config);

View file

@ -506,24 +506,6 @@ void GLVolume::render() const
glFrontFace(GL_CCW);
}
#if !ENABLE_SLOPE_RENDERING
void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const
{
if (color_id >= 0)
glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)render_color));
else
glsafe(::glColor4fv(render_color));
if (detection_id != -1)
glsafe(::glUniform1i(detection_id, shader_outside_printer_detection_enabled ? 1 : 0));
if (worldmatrix_id != -1)
glsafe(::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().cast<float>().data()));
render();
}
#endif // !ENABLE_SLOPE_RENDERING
bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); }
bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); }
@ -775,9 +757,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
shader->set_uniform("print_box.max", m_print_box_max, 3);
shader->set_uniform("z_range", m_z_range, 2);
shader->set_uniform("clipping_plane", m_clipping_plane, 4);
#if ENABLE_SLOPE_RENDERING
shader->set_uniform("slope.z_range", m_slope.z_range);
#endif // ENABLE_SLOPE_RENDERING
shader->set_uniform("slope.normal_z", m_slope.normal_z);
#if ENABLE_ENVIRONMENT_MAP
unsigned int environment_texture_id = GUI::wxGetApp().plater()->get_environment_texture_id();
@ -791,7 +771,6 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func);
for (GLVolumeWithIdAndZ& volume : to_render) {
volume.first->set_render_color();
#if ENABLE_SLOPE_RENDERING
shader->set_uniform("uniform_color", volume.first->render_color, 4);
shader->set_uniform("print_box.actived", volume.first->shader_outside_printer_detection_enabled);
shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix());
@ -799,9 +778,6 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
volume.first->render();
#else
volume.first->render(color_id, print_box_detection_id, print_box_worldmatrix_id);
#endif // ENABLE_SLOPE_RENDERING
}
#if ENABLE_ENVIRONMENT_MAP
@ -2020,12 +1996,8 @@ void GLModel::render() const
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
#if ENABLE_SLOPE_RENDERING
shader->set_uniform("uniform_color", m_volume.render_color, 4);
m_volume.render();
#else
m_volume.render(color_id, -1, -1);
#endif // ENABLE_SLOPE_RENDERING
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));

View file

@ -447,9 +447,6 @@ public:
void set_range(double low, double high);
void render() const;
#if !ENABLE_SLOPE_RENDERING
void render(int color_id, int detection_id, int worldmatrix_id) const;
#endif // !ENABLE_SLOPE_RENDERING
void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); }
void release_geometry() { this->indexed_vertex_array.release_geometry(); }
@ -494,26 +491,19 @@ private:
// plane coeffs for clipping in shaders
float m_clipping_plane[4];
#if ENABLE_SLOPE_RENDERING
struct Slope
{
// toggle for slope rendering
bool active{ false };
// [0] = yellow, [1] = red
std::array<float, 2> z_range;
float normal_z;
};
Slope m_slope;
#endif // ENABLE_SLOPE_RENDERING
public:
GLVolumePtrs volumes;
#if ENABLE_SLOPE_RENDERING
GLVolumeCollection() { set_default_slope_z_range(); }
#else
GLVolumeCollection() = default;
#endif // ENABLE_SLOPE_RENDERING
GLVolumeCollection() { set_default_slope_normal_z(); }
~GLVolumeCollection() { clear(); }
std::vector<int> load_object(
@ -572,14 +562,12 @@ public:
void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; }
void set_clipping_plane(const double* coeffs) { m_clipping_plane[0] = coeffs[0]; m_clipping_plane[1] = coeffs[1]; m_clipping_plane[2] = coeffs[2]; m_clipping_plane[3] = coeffs[3]; }
#if ENABLE_SLOPE_RENDERING
bool is_slope_active() const { return m_slope.active; }
void set_slope_active(bool active) { m_slope.active = active; }
const std::array<float, 2>& get_slope_z_range() const { return m_slope.z_range; }
void set_slope_z_range(const std::array<float, 2>& range) { m_slope.z_range = range; }
void set_default_slope_z_range() { m_slope.z_range = { -::cos(Geometry::deg2rad(90.0f - 45.0f)), -::cos(Geometry::deg2rad(90.0f - 70.0f)) }; }
#endif // ENABLE_SLOPE_RENDERING
float get_slope_normal_z() const { return m_slope.normal_z; }
void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; }
void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); }
// returns true if all the volumes are completely contained in the print volume
// returns the containment state in the given out_state, if non-null

View file

@ -23,6 +23,7 @@
#include "libslic3r/GCode/PreviewData.hpp"
#endif // !ENABLE_GCODE_VIEWER
#include "libslic3r/Format/SL1.hpp"
#include "libslic3r/Thread.hpp"
#include "libslic3r/libslic3r.h"
#include <cassert>
@ -36,7 +37,6 @@
#include "I18N.hpp"
#include "RemovableDriveManager.hpp"
#include "slic3r/Utils/Thread.hpp"
#include "slic3r/GUI/Plater.hpp"
namespace Slic3r {
@ -45,7 +45,7 @@ bool SlicingProcessCompletedEvent::critical_error() const
{
try {
this->rethrow_exception();
} catch (const Slic3r::SlicingError &ex) {
} catch (const Slic3r::SlicingError &) {
// Exception derived from SlicingError is non-critical.
return false;
} catch (...) {
@ -122,7 +122,9 @@ void BackgroundSlicingProcess::process_fff()
assert(m_print == m_fff_print);
m_print->process();
wxCommandEvent evt(m_event_slicing_completed_id);
evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psBrim).timestamp));
// Post the Slicing Finished message for the G-code viewer to update.
// Passing the timestamp
evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psSlicingFinished).timestamp));
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
#if ENABLE_GCODE_VIEWER
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, m_thumbnail_cb);
@ -135,11 +137,12 @@ void BackgroundSlicingProcess::process_fff()
//FIXME localize the messages
// Perform the final post-processing of the export path by applying the print statistics over the file name.
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
int copy_ret_val = copy_file(m_temp_output_path, export_path, m_export_path_on_removable_media);
std::string error_message;
int copy_ret_val = copy_file(m_temp_output_path, export_path, error_message, m_export_path_on_removable_media);
switch (copy_ret_val) {
case SUCCESS: break; // no error
case FAIL_COPY_FILE:
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?")));
throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"))) % error_message).str());
break;
case FAIL_FILES_DIFFERENT:
throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
@ -154,7 +157,8 @@ void BackgroundSlicingProcess::process_fff()
throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
break;
default:
BOOST_LOG_TRIVIAL(warning) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
throw Slic3r::RuntimeError(_utf8(L("Unknown error occured during exporting G-code.")));
BOOST_LOG_TRIVIAL(error) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
break;
}
@ -222,6 +226,9 @@ void BackgroundSlicingProcess::process_sla()
void BackgroundSlicingProcess::thread_proc()
{
set_current_thread_name("slic3r_BgSlcPcs");
name_tbb_thread_pool_threads();
assert(m_print != nullptr);
assert(m_print == m_fff_print || m_print == m_sla_print);
std::unique_lock<std::mutex> lck(m_mutex);
@ -516,7 +523,8 @@ void BackgroundSlicingProcess::prepare_upload()
if (m_print == m_fff_print) {
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
if (copy_file(m_temp_output_path, source_path.string()) != SUCCESS) {
std::string error_message;
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS) {
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
}
run_post_process_scripts(source_path.string(), m_fff_print->config());

View file

@ -32,15 +32,6 @@ double Camera::MaxFovDeg = 60.0;
Camera::Camera()
: requires_zoom_to_bed(false)
, m_type(Perspective)
, m_target(Vec3d::Zero())
, m_zenit(45.0f)
, m_zoom(1.0)
, m_distance(DefaultDistance)
, m_gui_scale(1.0)
, m_view_matrix(Transform3d::Identity())
, m_view_rotation(1., 0., 0., 0.)
, m_projection_matrix(Transform3d::Identity())
{
set_default_orientation();
}
@ -58,11 +49,12 @@ std::string Camera::get_type_as_string() const
void Camera::set_type(EType type)
{
if (m_type != type)
{
if (m_type != type) {
m_type = type;
wxGetApp().app_config->set("use_perspective_camera", (m_type == Perspective) ? "1" : "0");
wxGetApp().app_config->save();
if (m_update_config_on_type_change_enabled) {
wxGetApp().app_config->set("use_perspective_camera", (m_type == Perspective) ? "1" : "0");
wxGetApp().app_config->save();
}
}
}

View file

@ -29,19 +29,20 @@ struct Camera
bool requires_zoom_to_bed;
private:
EType m_type;
Vec3d m_target;
float m_zenit;
double m_zoom;
EType m_type{ Perspective };
bool m_update_config_on_type_change_enabled{ false };
Vec3d m_target{ Vec3d::Zero() };
float m_zenit{ 45.0f };
double m_zoom{ 1.0 };
// Distance between camera position and camera target measured along the camera Z axis
mutable double m_distance;
mutable double m_gui_scale;
mutable double m_distance{ DefaultDistance };
mutable double m_gui_scale{ 1.0 };
mutable std::array<int, 4> m_viewport;
mutable Transform3d m_view_matrix;
mutable Transform3d m_view_matrix{ Transform3d::Identity() };
// We are calculating the rotation part of the m_view_matrix from m_view_rotation.
mutable Eigen::Quaterniond m_view_rotation;
mutable Transform3d m_projection_matrix;
mutable Eigen::Quaterniond m_view_rotation{ 1.0, 0.0, 0.0, 0.0 };
mutable Transform3d m_projection_matrix{ Transform3d::Identity() };
mutable std::pair<double, double> m_frustrum_zs;
BoundingBoxf3 m_scene_box;
@ -56,6 +57,8 @@ public:
void set_type(const std::string& type);
void select_next_type();
void enable_update_config_on_type_change(bool enable) { m_update_config_on_type_change_enabled = enable; }
const Vec3d& get_target() const { return m_target; }
void set_target(const Vec3d& target);

View file

@ -48,9 +48,17 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve
text += "</b></font><br>";
// End of row header.
text += _(L("PrusaSlicer version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>";
text += _(L("print")) + ": " + snapshot.print + "<br>";
text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>";
text += _(L("printer")) + ": " + snapshot.printer + "<br>";
bool has_fff = ! snapshot.print.empty() || ! snapshot.filaments.empty();
bool has_sla = ! snapshot.sla_print.empty() || ! snapshot.sla_material.empty();
if (has_fff || ! has_sla) {
text += _(L("print")) + ": " + snapshot.print + "<br>";
text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>";
}
if (has_sla) {
text += _(L("SLA print")) + ": " + snapshot.sla_print + "<br>";
text += _(L("SLA material")) + ": " + snapshot.sla_material + "<br>";
}
text += _(L("printer")) + ": " + (snapshot.physical_printer.empty() ? snapshot.printer : snapshot.physical_printer) + "<br>";
bool compatible = true;
for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) {

View file

@ -647,7 +647,6 @@ void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt)
const wxClientDC dc(list_profile);
const wxPoint pos = evt.GetLogicalPosition(dc);
int item = list_profile->HitTest(pos);
//BOOST_LOG_TRIVIAL(debug) << "hit test: " << item;
on_material_hovered(item);
}
void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt)
@ -661,7 +660,7 @@ void PageMaterials::reload_presets()
clear();
list_printer->append(_(L("(All)")), &EMPTY);
list_printer->SetLabelMarkup("<b>bald</b>");
//list_printer->SetLabelMarkup("<b>bald</b>");
for (const Preset* printer : materials->printers) {
list_printer->append(printer->name, &printer->name);
}
@ -680,7 +679,6 @@ void PageMaterials::reload_presets()
void PageMaterials::set_compatible_printers_html_window(const std::vector<std::string>& printer_names, bool all_printers)
{
//Slic3r::GUI::wxGetApp().dark_mode()
const auto bgr_clr =
#if defined(__APPLE__)
wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
@ -690,9 +688,10 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s
const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
wxString first_line = L"Profiles marked with * are not compatible with all installed printers.";
wxString first_line = _(L("Filaments marked with <b>*</b> are <b>not</b> compatible with some installed printers."));
wxString text;
if (all_printers) {
wxString second_line = _(L("All installed printers are compatible with the selected filament."));
text = wxString::Format(
"<html>"
"<style>"
@ -701,7 +700,7 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s
"<body bgcolor= %s>"
"<font color=%s>"
"<font size=\"3\">"
"%s<br /><br /> All installed printers are compatible with selected profile."
"%s<br /><br />%s"
"</font>"
"</font>"
"</body>"
@ -709,9 +708,10 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s
, bgr_clr_str
, text_clr_str
, first_line
, second_line
);
} else {
wxString second_line = L"Compatible printers:";
wxString second_line = _(L("Only the following installed printers are compatible with the selected filament:"));
text = wxString::Format(
"<html>"
"<style>"
@ -772,7 +772,6 @@ void PageMaterials::on_material_highlighted(int sel_material)
return;
}
last_hovered_item = sel_material;
std::string compatible_printers_label = "Compatible printers:\n";
std::vector<std::string> tabs;
tabs.push_back(std::string());
tabs.push_back(std::string());
@ -910,6 +909,7 @@ void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor)
const std::string& type = list_type->get_data(sel_type);
const std::string& vendor = list_vendor->get_data(sel_vendor);
// finst printer preset
std::vector<ProfilePrintData> to_list;
for (size_t i = 0; i < sel_printers_count; i++) {
const std::string& printer_name = list_printer->get_data(sel_printers[i]);
const Preset* printer = nullptr;
@ -920,19 +920,23 @@ void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor)
}
}
materials->filter_presets(printer, type, vendor, [this](const Preset* p) {
materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) {
bool was_checked = false;
//size_t printer_counter = materials->get_printer_counter(p);
int cur_i = list_profile->find(p->alias);
if (cur_i == wxNOT_FOUND)
bool emplace_to_to_list = false;
if (cur_i == wxNOT_FOUND) {
cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
else
emplace_to_to_list = true;
} else
was_checked = list_profile->IsChecked(cur_i);
const std::string& section = materials->appconfig_section();
const bool checked = wizard_p()->appconfig_new.has(section, p->name);
list_profile->Check(cur_i, checked | was_checked);
list_profile->Check(cur_i, checked || was_checked);
if (emplace_to_to_list)
to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked || was_checked);
/* Update preset selection in config.
* If one preset from aliases bundle is selected,
@ -944,7 +948,7 @@ void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor)
wizard_p()->appconfig_new.set(section, p->name, "1");
});
}
sort_list_data(list_profile);
sort_list_data(list_profile, to_list);
}
sel_vendor_prev = sel_vendor;
@ -1010,35 +1014,39 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat
list->append(item, &const_cast<std::string&>(item.get()));
}
void PageMaterials::sort_list_data(PresetList* list)
void PageMaterials::sort_list_data(PresetList* list, const std::vector<ProfilePrintData>& data)
{
// sort data
// then prusa profiles
// then the rest
// in alphabetical order
std::vector<std::reference_wrapper<const std::string>> prusa_profiles;
std::vector<std::reference_wrapper<const std::string>> other_profiles;
for (int i = 0; i < list->size(); ++i) {
const std::string& data = list->get_data(i);
if (data == EMPTY) // do not sort <all> item
continue;
if (data.find("Prusa") != std::string::npos)
prusa_profiles.push_back(data);
std::vector<ProfilePrintData> prusa_profiles;
std::vector<ProfilePrintData> other_profiles;
//for (int i = 0; i < data.size(); ++i) {
for (const auto& item : data) {
const std::string& name = item.name;
if (name.find("Prusa") != std::string::npos)
prusa_profiles.emplace_back(item);
else
other_profiles.push_back(data);
other_profiles.emplace_back(item);
}
std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper<const std::string> a, std::reference_wrapper<const std::string> b) {
return a.get() < b.get();
std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
return a.name.get() < b.name.get();
});
std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper<const std::string> a, std::reference_wrapper<const std::string> b) {
return a.get() < b.get();
std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) {
return a.name.get() < b.name.get();
});
list->Clear();
for (const auto& item : prusa_profiles)
list->append(item, &const_cast<std::string&>(item.get()));
for (const auto& item : other_profiles)
list->append(item, &const_cast<std::string&>(item.get()));
//for (const auto& item : prusa_profiles)
for (int i = 0; i < prusa_profiles.size(); ++i) {
list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast<std::string&>(prusa_profiles[i].name.get()));
list->Check(i, prusa_profiles[i].checked);
}
//for (const auto& item : other_profiles)
for (int i = 0; i < other_profiles.size(); ++i) {
list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast<std::string&>(other_profiles[i].name.get()));
list->Check(i + prusa_profiles.size(), other_profiles[i].checked);
}
}
void PageMaterials::select_material(int i)

View file

@ -314,6 +314,14 @@ template<class T, class D> struct DataList : public T
typedef DataList<wxListBox, std::string> StringList;
typedef DataList<wxCheckListBox, std::string> PresetList;
struct ProfilePrintData
{
std::reference_wrapper<const std::string> name;
bool omnipresent;
bool checked;
ProfilePrintData(const std::string& n, bool o, bool c) : name(n), omnipresent(o), checked(c) {}
};
struct PageMaterials: ConfigWizardPage
{
Materials *materials;
@ -344,7 +352,7 @@ struct PageMaterials: ConfigWizardPage
void clear_compatible_printers_label();
void sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering);
void sort_list_data(PresetList* list);
void sort_list_data(PresetList* list, const std::vector<ProfilePrintData>& data);
void on_paint();
void on_mouse_move_on_profiles(wxMouseEvent& evt);
@ -557,6 +565,9 @@ struct ConfigWizard::priv
priv(ConfigWizard *q)
: q(q)
#if ENABLE_GCODE_VIEWER
, appconfig_new(AppConfig::EAppMode::Editor)
#endif // ENABLE_GCODE_VIEWER
, filaments(T_FFF)
, sla_materials(T_SLA)
{}

View file

@ -12,6 +12,7 @@
#include "I18N.hpp"
#include "ExtruderSequenceDialog.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/AppConfig.hpp"
#include <wx/button.h>
#include <wx/dialog.h>
@ -45,17 +46,18 @@ static std::string gcode(Type type)
{
const PrintConfig& config = GUI::wxGetApp().plater()->fff_print().config();
switch (type) {
case ColorChange:
return config.color_change_gcode;
case PausePrint:
return config.pause_print_gcode;
case Template:
return config.template_custom_gcode;
default:
return "";
case ColorChange: return config.color_change_gcode;
case PausePrint: return config.pause_print_gcode;
case Template: return config.template_custom_gcode;
default: return "";
}
}
static bool is_lower_thumb_editable()
{
return Slic3r::GUI::get_app_config()->get("seq_top_layer_only") == "0";
}
Control::Control( wxWindow *parent,
wxWindowID id,
int lowerValue,
@ -260,8 +262,7 @@ void Control::draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_p
wxCoord segm_end_x = is_horizontal() ? higher_pos : width*0.5 - 1;
wxCoord segm_end_y = is_horizontal() ? height*0.5 - 1 : higher_pos-1;
for (size_t id = 0; id < m_line_pens.size(); id++)
{
for (size_t id = 0; id < m_line_pens.size(); id++) {
dc.SetPen(*m_line_pens[id]);
dc.DrawLine(line_beg_x, line_beg_y, line_end_x, line_end_y);
dc.SetPen(*m_segm_pens[id]);
@ -341,8 +342,7 @@ Info Control::GetTicksValues() const
void Control::SetTicksValues(const Info& custom_gcode_per_print_z)
{
if (m_values.empty())
{
if (m_values.empty()) {
m_ticks.mode = m_mode;
return;
}
@ -447,32 +447,39 @@ void Control::render()
// and only in a case of no-empty m_values
draw_colored_band(dc);
// draw line
draw_scroll_line(dc, lower_pos, higher_pos);
if (!m_render_as_disabled) {
// draw line
draw_scroll_line(dc, lower_pos, higher_pos);
// draw color print ticks
draw_ticks(dc);
// draw color print ticks
draw_ticks(dc);
// draw both sliders
draw_thumbs(dc, lower_pos, higher_pos);
// draw both sliders
draw_thumbs(dc, lower_pos, higher_pos);
// draw lock/unlock
draw_one_layer_icon(dc);
// draw lock/unlock
draw_one_layer_icon(dc);
// draw revert bitmap (if it's shown)
draw_revert_icon(dc);
// draw revert bitmap (if it's shown)
draw_revert_icon(dc);
// draw cog bitmap (if it's shown)
draw_cog_icon(dc);
// draw cog bitmap (if it's shown)
draw_cog_icon(dc);
// draw mouse position
draw_tick_on_mouse_position(dc);
// draw mouse position
draw_tick_on_mouse_position(dc);
}
}
void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end)
{
const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
#if ENABLE_GCODE_VIEWER
if (!m_enable_action_icon)
return;
#endif // ENABLE_GCODE_VIEWER
// suppress add tick on first layer
if (tick == 0)
return;
@ -721,8 +728,7 @@ void Control::draw_ticks(wxDC& dc)
int height, width;
get_size(&width, &height);
const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width;
for (auto tick : m_ticks.ticks)
{
for (auto tick : m_ticks.ticks) {
const wxCoord pos = get_position_from_value(tick.tick);
is_horizontal() ? dc.DrawLine(pos, mid-14, pos, mid-9) :
@ -749,8 +755,7 @@ void Control::draw_ticks(wxDC& dc)
icon_name = focused_tick ? "edit_gcode_f" : "edit_gcode";
// Draw icon for "Pause print", "Custom Gcode" or conflict tick
if (!icon_name.empty())
{
if (!icon_name.empty()) {
wxBitmap icon = create_scaled_bitmap(icon_name);
wxCoord x_draw, y_draw;
is_horizontal() ? x_draw = pos - 0.5 * m_tick_icon_dim : y_draw = pos - 0.5 * m_tick_icon_dim;
@ -826,8 +831,7 @@ void Control::draw_colored_band(wxDC& dc)
wxRect main_band = get_colored_band_rect();
// don't color a band for MultiExtruder mode
if (m_ticks.empty() || m_mode == MultiExtruder)
{
if (m_ticks.empty() || m_mode == MultiExtruder) {
#if ENABLE_GCODE_VIEWER
#ifdef _WIN32
draw_band(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), main_band);
@ -915,13 +919,11 @@ void Control::draw_cog_icon(wxDC& dc)
wxCoord x_draw, y_draw;
#if ENABLE_GCODE_VIEWER
if (m_draw_mode == dmSequentialGCodeView)
{
if (m_draw_mode == dmSequentialGCodeView) {
is_horizontal() ? x_draw = width - 2 : x_draw = 0.5 * width - 0.5 * m_cog_icon_dim;
is_horizontal() ? y_draw = 0.5 * height - 0.5 * m_cog_icon_dim : y_draw = height - 2;
}
else
{
else {
#endif // ENABLE_GCODE_VIEWER
is_horizontal() ? x_draw = width - 2 : x_draw = width - m_cog_icon_dim - 2;
is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height - 2;
@ -961,7 +963,7 @@ int Control::get_value_from_position(const wxCoord x, const wxCoord y)
bool Control::detect_selected_slider(const wxPoint& pt)
{
if (is_point_in_rect(pt, m_rect_lower_thumb))
m_selection = ssLower;
m_selection = is_lower_thumb_editable() ? ssLower : ssUndef;
else if(is_point_in_rect(pt, m_rect_higher_thumb))
m_selection = ssHigher;
else
@ -1021,8 +1023,7 @@ void Control::OnLeftDown(wxMouseEvent& event)
m_mouse = maOneLayerIconClick;
else if (is_point_in_rect(pos, m_rect_cog_icon))
m_mouse = maCogIconClick;
else if (m_draw_mode == dmRegular)
{
else if (m_draw_mode == dmRegular) {
if (is_point_in_rect(pos, m_rect_tick_action)) {
auto it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value });
m_mouse = it == m_ticks.ticks.end() ? maAddTick : maDeleteTick;
@ -1193,8 +1194,7 @@ void Control::OnMotion(wxMouseEvent& event)
const wxPoint pos = event.GetLogicalPosition(wxClientDC(this));
int tick = -1;
if (!m_is_left_down && !m_is_right_down)
{
if (!m_is_left_down && !m_is_right_down) {
if (is_point_in_rect(pos, m_rect_one_layer_icon))
m_focus = fiOneLayerIcon;
else if (is_point_in_rect(pos, m_rect_tick_action)) {
@ -1244,8 +1244,7 @@ void Control::OnMotion(wxMouseEvent& event)
// Set tooltips with information for each icon
this->SetToolTip(get_tooltip(tick));
if (action)
{
if (action) {
wxCommandEvent e(wxEVT_SCROLL_CHANGED);
e.SetEventObject(this);
e.SetString("moving");
@ -1256,19 +1255,17 @@ void Control::OnMotion(wxMouseEvent& event)
void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current_code/* = false*/)
{
const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt();
if (extruders_cnt > 1)
{
if (extruders_cnt > 1) {
std::array<int, 2> active_extruders = get_active_extruders_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value);
std::vector<wxBitmap*> icons = get_extruder_color_icons(true);
wxMenu* change_extruder_menu = new wxMenu();
for (int i = 1; i <= extruders_cnt; i++)
{
for (int i = 1; i <= extruders_cnt; i++) {
const bool is_active_extruder = i == active_extruders[0] || i == active_extruders[1];
const wxString item_name = wxString::Format(_(L("Extruder %d")), i) +
(is_active_extruder ? " (" + _(L("active")) + ")" : "");
const wxString item_name = wxString::Format(_L("Extruder %d"), i) +
(is_active_extruder ? " (" + _L("active") + ")" : "");
if (m_mode == MultiAsSingle)
append_menu_item(change_extruder_menu, wxID_ANY, item_name, "",
@ -1280,7 +1277,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current
(switch_current_code ? _L("Switch code to Change extruder") : _L("Change extruder") ) :
_L("Change extruder (N/A)");
wxMenuItem* change_extruder_menu_item = menu->AppendSubMenu(change_extruder_menu, change_extruder_menu_name, _(L("Use another extruder")));
wxMenuItem* change_extruder_menu_item = menu->AppendSubMenu(change_extruder_menu, change_extruder_menu_name, _L("Use another extruder"));
change_extruder_menu_item->SetBitmap(create_scaled_bitmap(active_extruders[1] > 0 ? "edit_uni" : "change_extruder"));
GUI::wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this, change_extruder_menu_item](wxUpdateUIEvent& evt) {
@ -1292,19 +1289,17 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current
void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_current_code/* = false*/)
{
const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt();
if (extruders_cnt > 1)
{
if (extruders_cnt > 1) {
int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
std::set<int> used_extruders_for_tick = m_ticks.get_used_extruders_for_tick(tick, m_only_extruder, m_values[tick]);
wxMenu* add_color_change_menu = new wxMenu();
for (int i = 1; i <= extruders_cnt; i++)
{
for (int i = 1; i <= extruders_cnt; i++) {
const bool is_used_extruder = used_extruders_for_tick.empty() ? true : // #ys_FIXME till used_extruders_for_tick doesn't filled correct for mmMultiExtruder
used_extruders_for_tick.find(i) != used_extruders_for_tick.end();
const wxString item_name = wxString::Format(_(L("Extruder %d")), i) +
(is_used_extruder ? " (" + _(L("used")) + ")" : "");
const wxString item_name = wxString::Format(_L("Extruder %d"), i) +
(is_used_extruder ? " (" + _L("used") + ")" : "");
append_menu_item(add_color_change_menu, wxID_ANY, item_name, "",
[this, i](wxCommandEvent&) { add_code_as_tick(ColorChange, i); }, "", menu,
@ -1411,8 +1406,7 @@ void Control::move_current_thumb(const bool condition)
void Control::OnWheel(wxMouseEvent& event)
{
// Set nearest to the mouse thumb as a selected, if there is not selected thumb
if (m_selection == ssUndef)
{
if (m_selection == ssUndef) {
const wxPoint& pt = event.GetLogicalPosition(wxClientDC(this));
if (is_horizontal())
@ -1425,6 +1419,9 @@ void Control::OnWheel(wxMouseEvent& event)
ssLower : ssHigher;
}
if (m_selection == ssLower && !is_lower_thumb_editable())
m_selection = ssUndef;
#if ENABLE_GCODE_VIEWER
move_current_thumb((m_draw_mode == dmSequentialGCodeView) ? event.GetWheelRotation() < 0 : event.GetWheelRotation() > 0);
#else
@ -1466,29 +1463,32 @@ void Control::OnKeyDown(wxKeyEvent &event)
else if (event.GetKeyCode() == WXK_SHIFT)
UseDefaultColors(false);
#endif // ENABLE_GCODE_VIEWER
else if (is_horizontal())
{
else if (is_horizontal()) {
#if ENABLE_GCODE_VIEWER
if (m_is_focused)
{
if (m_is_focused) {
#endif // ENABLE_GCODE_VIEWER
if (key == WXK_LEFT || key == WXK_RIGHT)
move_current_thumb(key == WXK_LEFT);
else if (key == WXK_UP || key == WXK_DOWN) {
m_selection = key == WXK_UP ? ssHigher : ssLower;
if (key == WXK_UP)
m_selection = ssHigher;
else if (key == WXK_DOWN && is_lower_thumb_editable())
m_selection = ssLower;
Refresh();
}
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
}
}
else {
#if ENABLE_GCODE_VIEWER
if (m_is_focused)
{
if (m_is_focused) {
#endif // ENABLE_GCODE_VIEWER
if (key == WXK_LEFT || key == WXK_RIGHT) {
m_selection = key == WXK_LEFT ? ssHigher : ssLower;
if (key == WXK_LEFT)
m_selection = ssHigher;
else if (key == WXK_RIGHT && is_lower_thumb_editable())
m_selection = ssLower;
Refresh();
}
else if (key == WXK_UP || key == WXK_DOWN)
@ -1548,8 +1548,7 @@ void Control::OnRightDown(wxMouseEvent& event)
m_mouse = maNone;
if (m_draw_mode == dmRegular) {
if (is_point_in_rect(pos, m_rect_tick_action))
{
if (is_point_in_rect(pos, m_rect_tick_action)) {
const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
m_mouse = m_ticks.ticks.find(TickCode{ tick }) == m_ticks.ticks.end() ?
maAddMenu : maEditMenu;
@ -1608,8 +1607,7 @@ std::set<int> TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru
{
Mode e_mode = !force_mode ? mode : force_mode;
if (e_mode == MultiExtruder)
{
if (e_mode == MultiExtruder) {
// #ys_FIXME: get tool ordering from _correct_ place
const ToolOrdering& tool_ordering = GUI::wxGetApp().plater()->fff_print().get_tool_ordering();
@ -1619,8 +1617,7 @@ std::set<int> TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru
std::set<int> used_extruders;
auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(print_z));
for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools)
{
for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools) {
const std::vector<unsigned>& extruders = it_layer_tools->extruders;
for (const auto& extruder : extruders)
used_extruders.emplace(extruder+1);
@ -1704,18 +1701,18 @@ void Control::show_edit_context_menu()
append_add_color_change_menu_item(&menu, true);
}
else
append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _(L("Edit color")) :
it->type == PausePrint ? _(L("Edit pause print message")) :
_(L("Edit custom G-code")), "",
append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _L("Edit color") :
it->type == PausePrint ? _L("Edit pause print message") :
_L("Edit custom G-code"), "",
[this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu);
if (it->type == ColorChange && m_mode == MultiAsSingle)
append_change_extruder_menu_item(&menu, true);
append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _(L("Delete color change")) :
it->type == ToolChange ? _(L("Delete tool change")) :
it->type == PausePrint ? _(L("Delete pause print")) :
_(L("Delete custom G-code")), "",
append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _L("Delete color change") :
it->type == ToolChange ? _L("Delete tool change") :
it->type == PausePrint ? _L("Delete pause print") :
_L("Delete custom G-code"), "",
[this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu);
GUI::wxGetApp().plater()->PopupMenu(&menu);
@ -1725,14 +1722,14 @@ void Control::show_cog_icon_context_menu()
{
wxMenu menu;
append_menu_item(&menu, wxID_ANY, _(L("Jump to height")) + " (Shift+G)", "",
append_menu_item(&menu, wxID_ANY, _L("Jump to height") + " (Shift+G)", "",
#if ENABLE_GCODE_VIEWER
[this](wxCommandEvent&) { jump_to_value(); }, "", & menu);
#else
[this](wxCommandEvent&) { jump_to_print_z(); }, "", &menu);
#endif // ENABLE_GCODE_VIEWER
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);
GUI::wxGetApp().plater()->PopupMenu(&menu);
@ -1745,8 +1742,7 @@ void Control::OnRightUp(wxMouseEvent& event)
this->ReleaseMouse();
m_is_right_down = m_is_one_layer = false;
if (m_mouse == maForceColorEdit)
{
if (m_mouse == maForceColorEdit) {
wxPoint pos = event.GetLogicalPosition(wxClientDC(this));
int edited_tick = get_edited_tick_for_position(pos);
if (edited_tick >= 0)
@ -1805,13 +1801,12 @@ static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg, double min = -1.0,
btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl, min, max](wxUpdateUIEvent& evt)
{
bool disable = textctrl->IsEmpty();
if (!disable && min >= 0.0 && max >= 0.0)
{
if (!disable && min >= 0.0 && max >= 0.0) {
double value = -1.0;
if (!textctrl->GetValue().ToCDouble(&value)) // input value couldn't be converted to double
disable = true;
else
disable = value < min || value > max; // is input value is out of valid range ?
disable = value < min - epsilon() || value > max + epsilon(); // is input value is out of valid range ?
}
evt.Enable(!disable);
@ -1861,8 +1856,8 @@ static double get_print_z_to_jump(double active_print_z, double min_z, double ma
wxString msg_header = (mode == dmSequentialGCodeView) ? _L("Jump to move") : _L("Jump to height");
wxString msg_in = GUI::double_to_string(active_value);
#else
wxString msg_text = _(L("Enter the height you want to jump to")) + ":";
wxString msg_header = _(L("Jump to height"));
wxString msg_text = _L("Enter the height you want to jump to") + ":";
wxString msg_header = _L("Jump to height");
wxString msg_in = GUI::double_to_string(active_print_z);
#endif // ENABLE_GCODE_VIEWER
@ -1931,8 +1926,7 @@ void Control::add_current_tick(bool call_from_keyboard /*= false*/)
/* Menu position will be calculated from mouse click position, but...
* if function is called from keyboard (pressing "+"), we should to calculate it
* */
if (call_from_keyboard)
{
if (call_from_keyboard) {
int width, height;
get_size(&width, &height);
@ -1987,7 +1981,7 @@ void Control::switch_one_layer_mode()
SetHigherValue(m_max_value);
}
m_selection == ssLower ? correct_lower_value() : correct_higher_value();
if (!m_selection) m_selection = ssHigher;
if (m_selection == ssUndef) m_selection = ssHigher;
}
// discard all custom changes on DoubleSlider
@ -1997,7 +1991,7 @@ void Control::discard_all_thicks()
SetHigherValue(m_max_value);
m_selection == ssLower ? correct_lower_value() : correct_higher_value();
if (!m_selection) m_selection = ssHigher;
if (m_selection == ssUndef) m_selection = ssHigher;
m_ticks.ticks.clear();
post_ticks_changed_event();
@ -2011,19 +2005,14 @@ void Control::move_current_thumb_to_pos(wxPoint pos)
const int tick_val = get_tick_near_point(pos);
const int mouse_val = tick_val >= 0 && m_draw_mode == dmRegular ? tick_val :
get_value_from_position(pos);
if (mouse_val >= 0)
{
// if (abs(mouse_val - m_lower_value) < abs(mouse_val - m_higher_value)) {
// if (mouse_val <= m_lower_value) {
if (mouse_val >= 0) {
if (m_selection == ssLower) {
SetLowerValue(mouse_val);
correct_lower_value();
// m_selection = ssLower;
}
else {
else { // even m_selection is ssUndef, upper thumb should be selected
SetHigherValue(mouse_val);
correct_higher_value();
m_selection = ssHigher;
}
}
}
@ -2056,8 +2045,7 @@ void Control::edit_extruder_sequence()
extruder++;
if (extruder == extr_cnt)
extruder = 0;
if (m_extruders_sequence.is_mm_intervals)
{
if (m_extruders_sequence.is_mm_intervals) {
value += m_extruders_sequence.interval_by_mm;
auto val_it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon());
@ -2109,7 +2097,7 @@ void Control::jump_to_print_z()
void Control::post_ticks_changed_event(Type type /*= Custom*/)
{
m_force_mode_apply = type != ToolChange;
// m_force_mode_apply = type != ToolChange; // It looks like this condition is no needed now. Leave it for the testing
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
}
@ -2143,8 +2131,7 @@ bool Control::check_ticks_changed_event(Type type)
return false;
}
// m_ticks_mode == MultiAsSingle
if( m_ticks.has_tick_with_code(ToolChange) )
{
if( m_ticks.has_tick_with_code(ToolChange) ) {
wxString message = m_mode == SingleExtruder ? (
_L("The last color change data was saved for a multi extruder printing.") + "\n\n" +
_L("Select YES if you want to delete all saved tool changes, \n"
@ -2174,8 +2161,7 @@ bool Control::check_ticks_changed_event(Type type)
std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int extruder)
{
if (mode == SingleExtruder && type == ColorChange && m_use_default_colors)
{
if (mode == SingleExtruder && type == ColorChange && m_use_default_colors) {
#if ENABLE_GCODE_VIEWER
const std::vector<std::string>& colors = ColorPrintColors::get();
#else
@ -2190,10 +2176,8 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int
std::string color = (*m_colors)[extruder - 1];
if (type == ColorChange)
{
if (!ticks.empty())
{
if (type == ColorChange) {
if (!ticks.empty()) {
auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick );
while (before_tick_it != ticks.begin()) {
--before_tick_it;
@ -2220,15 +2204,13 @@ bool TickCodeInfo::add_tick(const int tick, Type type, const int extruder, doubl
return false;
custom_gcode = extra;
}
else if (type == PausePrint)
{
else if (type == PausePrint) {
extra = get_pause_print_msg(pause_print_msg, print_z);
if (extra.empty())
return false;
pause_print_msg = extra;
}
else
{
else {
color = get_color_for_tick(TickCode{ tick }, type, extruder);
if (color.empty())
return false;
@ -2281,8 +2263,7 @@ bool TickCodeInfo::edit_tick(std::set<TickCode>::iterator it, double print_z)
void TickCodeInfo::switch_code(Type type_from, Type type_to)
{
for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; )
if (it->type == type_from)
{
if (it->type == type_from) {
TickCode tick = *it;
tick.type = type_to;
tick.extruder = 1;
@ -2339,8 +2320,7 @@ ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, Mode out_mode,
return ctModeConflict;
// check ColorChange tick
if (tick.type == ColorChange)
{
if (tick.type == ColorChange) {
// We should mark a tick as a "MeaninglessColorChange",
// if it has a ColorChange for unused extruder from current print to end of the print
std::set<int> used_extruders_for_tick = get_used_extruders_for_tick(tick.tick, only_extruder, print_z, out_mode);
@ -2367,8 +2347,7 @@ ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, Mode out_mode,
}
// check ToolChange tick
if (mode == MultiAsSingle && tick.type == ToolChange)
{
if (mode == MultiAsSingle && tick.type == ToolChange) {
// We should mark a tick as a "MeaninglessToolChange",
// if it has a ToolChange to the same extruder
auto it = ticks.find(tick);

View file

@ -228,6 +228,9 @@ public:
void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder);
void SetExtruderColors(const std::vector<std::string>& extruder_colors);
void set_render_as_disabled(bool value) { m_render_as_disabled = value; }
bool is_rendering_as_disabled() const { return m_render_as_disabled; }
bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; }
bool is_one_layer() const { return m_is_one_layer; }
bool is_lower_at_min() const { return m_lower_value == m_min_value; }
@ -260,6 +263,7 @@ public:
void edit_extruder_sequence();
#if ENABLE_GCODE_VIEWER
void jump_to_value();
void enable_action_icon(bool enable) { m_enable_action_icon = enable; }
#else
void jump_to_print_z();
#endif // ENABLE_GCODE_VIEWER
@ -334,6 +338,9 @@ private:
int m_max_value;
int m_lower_value;
int m_higher_value;
bool m_render_as_disabled{ false };
ScalableBitmap m_bmp_thumb_higher;
ScalableBitmap m_bmp_thumb_lower;
ScalableBitmap m_bmp_add_tick_on;
@ -352,6 +359,9 @@ private:
bool m_is_one_layer = false;
bool m_is_focused = false;
bool m_force_mode_apply = true;
#if ENABLE_GCODE_VIEWER
bool m_enable_action_icon = true;
#endif // ENABLE_GCODE_VIEWER
DrawMode m_draw_mode = dmRegular;

View file

@ -141,8 +141,8 @@ public:
, int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {}
bool SetValue(const wxVariant& value);
bool GetValue(wxVariant& value) const;
bool SetValue(const wxVariant& value) override;
bool GetValue(wxVariant& value) const override;
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
virtual wxSize GetSize() const override;

View file

@ -13,6 +13,7 @@
#include <wx/tooltip.h>
#include <wx/notebook.h>
#include <boost/algorithm/string/predicate.hpp>
#include "OG_CustomCtrl.hpp"
#ifdef __WXOSX__
#define wxOSX true
@ -63,18 +64,16 @@ Field::~Field()
m_back_to_initial_value = nullptr;
if (m_back_to_sys_value)
m_back_to_sys_value = nullptr;
if (getWindow()) {
wxWindow* win = getWindow();
win->Destroy();
win = nullptr;
}
}
void Field::PostInitialize()
{
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
m_Undo_btn = new RevertButton(m_parent, "bullet_white.png");
m_Undo_to_sys_btn = new RevertButton(m_parent, "bullet_white.png");
m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_back_to_initial_value(); }));
m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_back_to_sys_value(); }));
m_blinking_bmp = new BlinkingBitmap(m_parent);
switch (m_opt.type)
{
@ -94,6 +93,7 @@ void Field::PostInitialize()
// initialize m_unit_value
m_em_unit = em_unit(m_parent);
parent_is_custom_ctrl = dynamic_cast<OG_CustomCtrl*>(m_parent) != nullptr;
BUILD();
@ -216,10 +216,7 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
break;
}
wxString label = m_Label->GetLabel();
if (label.Last() == '\n') label.RemoveLast();
while (label.Last() == ' ') label.RemoveLast();
if (label.Last() == ':') label.RemoveLast();
wxString label = m_opt.full_label.empty() ? _(m_opt.label) : _(m_opt.full_label);
show_error(m_parent, from_u8((boost::format(_utf8(L("%s doesn't support percentage"))) % label).str()));
set_value(double_to_string(m_opt.min), true);
m_value = double(m_opt.min);
@ -305,28 +302,14 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
}
}
void Field::msw_rescale(bool rescale_sidetext)
void Field::msw_rescale()
{
m_Undo_to_sys_btn->msw_rescale();
m_Undo_btn->msw_rescale();
m_blinking_bmp->msw_rescale();
// update em_unit value
m_em_unit = em_unit(m_parent);
// update sidetext if it is needed
if (m_side_text && rescale_sidetext)
{
auto size = wxSize(def_width_thinner() * m_em_unit, -1);
m_side_text->SetSize(size);
m_side_text->SetMinSize(size);
}
}
void Field::sys_color_changed()
{
m_Undo_to_sys_btn->msw_rescale();
m_Undo_btn->msw_rescale();
}
template<class T>
@ -387,7 +370,11 @@ void TextCtrl::BUILD() {
const long style = m_opt.multiline ? wxTE_MULTILINE : wxTE_PROCESS_ENTER/*0*/;
auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style);
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
if (parent_is_custom_ctrl && m_opt.height < 0)
opt_height = (double)temp->GetSize().GetHeight()/m_em_unit;
temp->SetFont(m_opt.is_code ?
Slic3r::GUI::wxGetApp().code_font():
Slic3r::GUI::wxGetApp().normal_font());
if (! m_opt.multiline && !wxOSX)
// Only disable background refresh for single line input fields, as they are completely painted over by the edit control.
@ -548,17 +535,24 @@ boost::any& TextCtrl::get_value()
return m_value;
}
void TextCtrl::msw_rescale(bool rescale_sidetext/* = false*/)
void TextCtrl::msw_rescale()
{
Field::msw_rescale(rescale_sidetext);
Field::msw_rescale();
auto size = wxSize(def_width() * m_em_unit, wxDefaultCoord);
if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
if (m_opt.height >= 0)
size.SetHeight(m_opt.height*m_em_unit);
else if (parent_is_custom_ctrl && opt_height > 0)
size.SetHeight(lround(opt_height*m_em_unit));
if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
if (size != wxDefaultSize)
{
wxTextCtrl* field = dynamic_cast<wxTextCtrl*>(window);
field->SetMinSize(size);
if (parent_is_custom_ctrl)
field->SetSize(size);
else
field->SetMinSize(size);
}
}
@ -648,7 +642,7 @@ boost::any& CheckBox::get_value()
return m_value;
}
void CheckBox::msw_rescale(bool rescale_sidetext/* = false*/)
void CheckBox::msw_rescale()
{
Field::msw_rescale();
@ -702,6 +696,9 @@ void SpinCtrl::BUILD() {
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
if (m_opt.height < 0 && parent_is_custom_ctrl)
opt_height = (double)temp->GetSize().GetHeight() / m_em_unit;
// XXX: On OS X the wxSpinCtrl widget is made up of two subwidgets, unfortunatelly
// the kill focus event is not propagated to the encompassing widget,
// so we need to bind it on the inner text widget instead. (Ugh.)
@ -783,23 +780,32 @@ void SpinCtrl::propagate_value()
suppress_propagation = false;
}
void SpinCtrl::msw_rescale(bool rescale_sidetext/* = false*/)
void SpinCtrl::msw_rescale()
{
Field::msw_rescale(rescale_sidetext);
Field::msw_rescale();
wxSpinCtrl* field = dynamic_cast<wxSpinCtrl*>(window);
field->SetMinSize(wxSize(def_width() * m_em_unit, int(1.9f*field->GetFont().GetPixelSize().y)));
if (parent_is_custom_ctrl)
field->SetSize(wxSize(def_width() * m_em_unit, lround(opt_height * m_em_unit)));
else
field->SetMinSize(wxSize(def_width() * m_em_unit, int(1.9f*field->GetFont().GetPixelSize().y)));
}
#ifdef __WXOSX__
using choice_ctrl = wxBitmapComboBox;
#else
using choice_ctrl = wxComboBox;
#endif // __WXOSX__
void Choice::BUILD() {
wxSize size(def_width_wider() * m_em_unit, wxDefaultCoord);
if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
wxBitmapComboBox* temp;
choice_ctrl* temp;
if (!m_opt.gui_type.empty() && m_opt.gui_type.compare("select_open") != 0) {
m_is_editable = true;
temp = new wxBitmapComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
}
else {
#ifdef __WXOSX__
@ -807,11 +813,11 @@ void Choice::BUILD() {
* so ToolTip doesn't shown.
* Next workaround helps to solve this problem
*/
temp = new wxBitmapComboBox();
temp = new choice_ctrl();
temp->SetTextCtrlStyle(wxTE_READONLY);
temp->Create(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr);
#else
temp = new wxBitmapComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr, wxCB_READONLY);
temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr, wxCB_READONLY);
#endif //__WXOSX__
}
@ -834,7 +840,8 @@ void Choice::BUILD() {
set_selection();
}
#ifndef __WXGTK__
#ifdef __WXOSX__
//#ifndef __WXGTK__
/* Workaround for a correct rendering of the control without Bitmap (under MSW and OSX):
*
* 1. We should create small Bitmap to fill Bitmaps RefData,
@ -861,7 +868,7 @@ void Choice::BUILD() {
}
double old_val = !m_value.empty() ? boost::any_cast<double>(m_value) : -99999;
if (is_defined_input_value<wxBitmapComboBox>(window, m_opt.type)) {
if (is_defined_input_value<choice_ctrl>(window, m_opt.type)) {
if (fabs(old_val - boost::any_cast<double>(get_value())) <= 0.0001)
return;
else
@ -884,7 +891,7 @@ void Choice::set_selection()
wxString text_value = wxString("");
wxBitmapComboBox* field = dynamic_cast<wxBitmapComboBox*>(window);
choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
switch (m_opt.type) {
case coFloat:
case coPercent: {
@ -954,7 +961,7 @@ void Choice::set_value(const std::string& value, bool change_event) //! Redunda
++idx;
}
wxBitmapComboBox* field = dynamic_cast<wxBitmapComboBox*>(window);
choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
idx == m_opt.enum_values.size() ?
field->SetValue(value) :
field->SetSelection(idx);
@ -966,7 +973,7 @@ void Choice::set_value(const boost::any& value, bool change_event)
{
m_disable_change_event = !change_event;
wxBitmapComboBox* field = dynamic_cast<wxBitmapComboBox*>(window);
choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
switch (m_opt.type) {
case coInt:
@ -1042,12 +1049,32 @@ void Choice::set_values(const std::vector<std::string>& values)
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
// # but we want to preserve it
auto ww = dynamic_cast<choice_ctrl*>(window);
auto value = ww->GetValue();
ww->Clear();
ww->Append("");
for (const auto &el : values)
ww->Append(wxString(el));
ww->SetValue(value);
m_disable_change_event = false;
}
void Choice::set_values(const wxArrayString &values)
{
if (values.empty())
return;
m_disable_change_event = true;
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
// # but we want to preserve it
auto ww = dynamic_cast<wxBitmapComboBox*>(window);
auto value = ww->GetValue();
ww->Clear();
ww->Append("");
for (auto el : values)
ww->Append(wxString(el));
for (const auto &el : values)
ww->Append(el);
ww->SetValue(value);
m_disable_change_event = false;
@ -1055,7 +1082,7 @@ void Choice::set_values(const std::vector<std::string>& values)
boost::any& Choice::get_value()
{
wxBitmapComboBox* field = dynamic_cast<wxBitmapComboBox*>(window);
choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
wxString ret_str = field->GetValue();
@ -1084,6 +1111,8 @@ boost::any& Choice::get_value()
m_value = static_cast<IroningType>(ret_enum);
else if (m_opt_id.compare("gcode_flavor") == 0)
m_value = static_cast<GCodeFlavor>(ret_enum);
else if (m_opt_id.compare("machine_limits_usage") == 0)
m_value = static_cast<MachineLimitsUsage>(ret_enum);
else if (m_opt_id.compare("support_material_pattern") == 0)
m_value = static_cast<SupportMaterialPattern>(ret_enum);
else if (m_opt_id.compare("seam_position") == 0)
@ -1113,16 +1142,20 @@ boost::any& Choice::get_value()
return m_value;
}
void Choice::msw_rescale(bool rescale_sidetext/* = false*/)
void Choice::enable() { dynamic_cast<choice_ctrl*>(window)->Enable(); };
void Choice::disable() { dynamic_cast<choice_ctrl*>(window)->Disable(); };
void Choice::msw_rescale()
{
Field::msw_rescale();
wxBitmapComboBox* field = dynamic_cast<wxBitmapComboBox*>(window);
choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
#ifdef __WXOSX__
const wxString selection = field->GetValue();// field->GetString(index);
/* To correct scaling (set new controll size) of a wxBitmapCombobox
* we need to refill control with new bitmaps. So, in our case :
* 1. clear conrol
* 1. clear control
* 2. add content
* 3. add scaled "empty" bitmap to the at least one item
*/
@ -1155,6 +1188,16 @@ void Choice::msw_rescale(bool rescale_sidetext/* = false*/)
idx == m_opt.enum_values.size() ?
field->SetValue(selection) :
field->SetSelection(idx);
#else
auto size = wxSize(def_width_wider() * m_em_unit, wxDefaultCoord);
if (m_opt.height >= 0) size.SetHeight(m_opt.height * m_em_unit);
if (m_opt.width >= 0) size.SetWidth(m_opt.width * m_em_unit);
if (parent_is_custom_ctrl)
field->SetSize(size);
else
field->SetMinSize(size);
#endif
}
void ColourPicker::BUILD()
@ -1171,6 +1214,8 @@ void ColourPicker::BUILD()
}
auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size);
if (parent_is_custom_ctrl && m_opt.height < 0)
opt_height = (double)temp->GetSize().GetHeight() / m_em_unit;
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
@ -1227,15 +1272,21 @@ boost::any& ColourPicker::get_value()
return m_value;
}
void ColourPicker::msw_rescale(bool rescale_sidetext/* = false*/)
void ColourPicker::msw_rescale()
{
Field::msw_rescale();
wxColourPickerCtrl* field = dynamic_cast<wxColourPickerCtrl*>(window);
auto size = wxSize(def_width() * m_em_unit, wxDefaultCoord);
if (m_opt.height >= 0) size.SetHeight(m_opt.height * m_em_unit);
if (m_opt.height >= 0)
size.SetHeight(m_opt.height * m_em_unit);
else if (parent_is_custom_ctrl && opt_height > 0)
size.SetHeight(lround(opt_height * m_em_unit));
if (m_opt.width >= 0) size.SetWidth(m_opt.width * m_em_unit);
field->SetMinSize(size);
if (parent_is_custom_ctrl)
field->SetSize(size);
else
field->SetMinSize(size);
if (field->GetColour() == wxTransparentColour)
set_undef_value(field);
@ -1255,6 +1306,9 @@ void PointCtrl::BUILD()
x_textctrl = new wxTextCtrl(m_parent, wxID_ANY, X, wxDefaultPosition, field_size, wxTE_PROCESS_ENTER);
y_textctrl = new wxTextCtrl(m_parent, wxID_ANY, Y, wxDefaultPosition, field_size, wxTE_PROCESS_ENTER);
if (parent_is_custom_ctrl && m_opt.height < 0)
opt_height = (double)x_textctrl->GetSize().GetHeight() / m_em_unit;
x_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font());
x_textctrl->SetBackgroundStyle(wxBG_STYLE_PAINT);
y_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font());
@ -1272,9 +1326,6 @@ void PointCtrl::BUILD()
temp->Add(static_text_y, 0, wxALIGN_CENTER_VERTICAL, 0);
temp->Add(y_textctrl);
// x_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), x_textctrl->GetId());
// y_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), y_textctrl->GetId());
x_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_value(x_textctrl); }), x_textctrl->GetId());
y_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_value(y_textctrl); }), y_textctrl->GetId());
@ -1288,14 +1339,21 @@ void PointCtrl::BUILD()
y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
}
void PointCtrl::msw_rescale(bool rescale_sidetext/* = false*/)
void PointCtrl::msw_rescale()
{
Field::msw_rescale();
const wxSize field_size(4 * m_em_unit, -1);
wxSize field_size(4 * m_em_unit, -1);
x_textctrl->SetMinSize(field_size);
y_textctrl->SetMinSize(field_size);
if (parent_is_custom_ctrl) {
field_size.SetHeight(lround(opt_height * m_em_unit));
x_textctrl->SetSize(field_size);
y_textctrl->SetSize(field_size);
}
else {
x_textctrl->SetMinSize(field_size);
y_textctrl->SetMinSize(field_size);
}
}
bool PointCtrl::value_was_changed(wxTextCtrl* win)
@ -1387,7 +1445,7 @@ void StaticText::BUILD()
temp->SetToolTip(get_tooltip_text(legend));
}
void StaticText::msw_rescale(bool rescale_sidetext/* = false*/)
void StaticText::msw_rescale()
{
Field::msw_rescale();

View file

@ -87,6 +87,8 @@ protected:
void on_set_focus(wxEvent& event);
/// Call the attached on_change method.
void on_change_field();
public:
/// Call the attached m_back_to_initial_value method.
void on_back_to_initial_value();
/// Call the attached m_back_to_sys_value method.
@ -119,6 +121,9 @@ public:
const t_config_option_key m_opt_id;//! {""};
int m_opt_idx = 0;
double opt_height{ 0.0 };
bool parent_is_custom_ctrl{ false };
/// Sets a value for this control.
/// subclasses should overload with a specific version
/// Postcondition: Method does not fire the on_change event.
@ -140,9 +145,6 @@ public:
void field_changed() { on_change_field(); }
// set icon to "UndoToSystemValue" button according to an inheritance of preset
// void set_nonsys_btn_icon(const wxBitmap& icon);
Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {};
Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {};
virtual ~Field();
@ -151,8 +153,6 @@ public:
virtual wxSizer* getSizer() { return nullptr; }
virtual wxWindow* getWindow() { return nullptr; }
wxStaticText* getLabel() { return m_Label; }
bool is_matched(const std::string& string, const std::string& pattern);
void get_value_by_opt_type(wxString& str, const bool check_value = true);
@ -168,7 +168,6 @@ public:
bool set_undo_bitmap(const ScalableBitmap *bmp) {
if (m_undo_bitmap != bmp) {
m_undo_bitmap = bmp;
m_Undo_btn->SetBitmap_(*bmp);
return true;
}
return false;
@ -177,33 +176,21 @@ public:
bool set_undo_to_sys_bitmap(const ScalableBitmap *bmp) {
if (m_undo_to_sys_bitmap != bmp) {
m_undo_to_sys_bitmap = bmp;
m_Undo_to_sys_btn->SetBitmap_(*bmp);
return true;
}
return false;
}
bool set_label_colour(const wxColour *clr) {
if (m_Label == nullptr) return false;
if (m_label_color != clr) {
m_label_color = clr;
m_Label->SetForegroundColour(*clr);
m_Label->Refresh(true);
}
return false;
}
bool set_label_colour_force(const wxColour *clr) {
if (m_Label == nullptr) return false;
m_Label->SetForegroundColour(*clr);
m_Label->Refresh(true);
return false;
}
bool set_undo_tooltip(const wxString *tip) {
if (m_undo_tooltip != tip) {
m_undo_tooltip = tip;
m_Undo_btn->SetToolTip(*tip);
return true;
}
return false;
@ -212,17 +199,16 @@ public:
bool set_undo_to_sys_tooltip(const wxString *tip) {
if (m_undo_to_sys_tooltip != tip) {
m_undo_to_sys_tooltip = tip;
m_Undo_to_sys_btn->SetToolTip(*tip);
return true;
}
return false;
}
void set_side_text_ptr(wxStaticText* side_text) {
m_side_text = side_text;
bool* get_blink_ptr() {
return &m_blink;
}
virtual void msw_rescale(bool rescale_sidetext = false);
virtual void msw_rescale();
void sys_color_changed();
bool get_enter_pressed() const { return bEnterPressed; }
@ -233,26 +219,26 @@ public:
static int def_width_wider() ;
static int def_width_thinner() ;
BlinkingBitmap* blinking_bitmap() const { return m_blinking_bmp;}
const ScalableBitmap* undo_bitmap() { return m_undo_bitmap; }
const wxString* undo_tooltip() { return m_undo_tooltip; }
const ScalableBitmap* undo_to_sys_bitmap() { return m_undo_to_sys_bitmap; }
const wxString* undo_to_sys_tooltip() { return m_undo_to_sys_tooltip; }
const wxColour* label_color() { return m_label_color; }
const bool blink() { return m_blink; }
protected:
RevertButton* m_Undo_btn = nullptr;
// Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const ScalableBitmap* m_undo_bitmap = nullptr;
const wxString* m_undo_tooltip = nullptr;
RevertButton* m_Undo_to_sys_btn = nullptr;
// Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const ScalableBitmap* m_undo_to_sys_bitmap = nullptr;
const wxString* m_undo_to_sys_tooltip = nullptr;
BlinkingBitmap* m_blinking_bmp{ nullptr };
bool m_blink{ false };
wxStaticText* m_Label = nullptr;
// Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* m_label_color = nullptr;
wxStaticText* m_side_text = nullptr;
// current value
boost::any m_value;
// last maeningful value
@ -308,7 +294,7 @@ public:
boost::any& get_value() override;
void msw_rescale(bool rescale_sidetext = false) override;
void msw_rescale() override;
void enable() override;
void disable() override;
@ -336,7 +322,7 @@ public:
void set_na_value() override;
boost::any& get_value() override;
void msw_rescale(bool rescale_sidetext = false) override;
void msw_rescale() override;
void enable() override { dynamic_cast<wxCheckBox*>(window)->Enable(); }
void disable() override { dynamic_cast<wxCheckBox*>(window)->Disable(); }
@ -379,7 +365,7 @@ public:
return m_value = value;
}
void msw_rescale(bool rescale_sidetext = false) override;
void msw_rescale() override;
void enable() override { dynamic_cast<wxSpinCtrl*>(window)->Enable(); }
void disable() override { dynamic_cast<wxSpinCtrl*>(window)->Disable(); }
@ -405,12 +391,13 @@ public:
void set_value(const std::string& value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false);
void set_values(const std::vector<std::string> &values);
void set_values(const wxArrayString &values);
boost::any& get_value() override;
void msw_rescale(bool rescale_sidetext = false) override;
void msw_rescale() override;
void enable() override { dynamic_cast<wxBitmapComboBox*>(window)->Enable(); };
void disable() override{ dynamic_cast<wxBitmapComboBox*>(window)->Disable(); };
void enable() override ;//{ dynamic_cast<wxBitmapComboBox*>(window)->Enable(); };
void disable() override;//{ dynamic_cast<wxBitmapComboBox*>(window)->Disable(); };
wxWindow* getWindow() override { return window; }
};
@ -433,7 +420,7 @@ public:
}
void set_value(const boost::any& value, bool change_event = false) override;
boost::any& get_value() override;
void msw_rescale(bool rescale_sidetext = false) override;
void msw_rescale() override;
void enable() override { dynamic_cast<wxColourPickerCtrl*>(window)->Enable(); };
void disable() override{ dynamic_cast<wxColourPickerCtrl*>(window)->Disable(); };
@ -459,7 +446,7 @@ public:
void set_value(const boost::any& value, bool change_event = false);
boost::any& get_value() override;
void msw_rescale(bool rescale_sidetext = false) override;
void msw_rescale() override;
void enable() override {
x_textctrl->Enable();
@ -494,7 +481,7 @@ public:
boost::any& get_value()override { return m_value; }
void msw_rescale(bool rescale_sidetext = false) override;
void msw_rescale() override;
void enable() override { dynamic_cast<wxStaticText*>(window)->Enable(); };
void disable() override{ dynamic_cast<wxStaticText*>(window)->Disable(); };

View file

@ -790,7 +790,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
SetFont(font);
wxFont status_font = font;//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
status_font.MakeBold();
wxFont mono_font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
wxFont mono_font = GUI::wxGetApp().code_font();
mono_font.MakeSmaller();
// Create GUI components and layout

View file

@ -13,6 +13,7 @@
#include "Camera.hpp"
#include "I18N.hpp"
#include "GUI_Utils.hpp"
#include "GUI.hpp"
#include "DoubleSlider.hpp"
#include "GLCanvas3D.hpp"
#include "GLToolbar.hpp"
@ -287,54 +288,10 @@ const std::vector<GCodeViewer::Color> GCodeViewer::Range_Colors {{
{ 0.581f, 0.149f, 0.087f } // reddish
}};
bool GCodeViewer::init()
{
for (size_t i = 0; i < m_buffers.size(); ++i) {
TBuffer& buffer = m_buffers[i];
switch (buffer_type(i))
{
default: { break; }
case EMoveType::Tool_change:
case EMoveType::Color_change:
case EMoveType::Pause_Print:
case EMoveType::Custom_GCode:
case EMoveType::Retract:
case EMoveType::Unretract:
{
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point;
buffer.vertices.format = VBuffer::EFormat::Position;
buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110";
break;
}
case EMoveType::Extrude:
{
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle;
buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
buffer.shader = "gouraud_light";
break;
}
case EMoveType::Travel:
{
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line;
buffer.vertices.format = VBuffer::EFormat::PositionNormal1;
buffer.shader = "toolpaths_lines";
break;
}
}
}
set_toolpath_move_type_visible(EMoveType::Extrude, true);
m_sequential_view.marker.init();
std::array<int, 2> point_sizes;
::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data());
m_detected_point_sizes = { static_cast<float>(point_sizes[0]), static_cast<float>(point_sizes[1]) };
return true;
}
void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized)
{
init();
// avoid processing if called with the same gcode_result
if (m_last_result_id == gcode_result.id)
return;
@ -345,6 +302,9 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print&
reset();
load_toolpaths(gcode_result);
if (m_layers_zs.empty())
return;
if (wxGetApp().is_editor())
load_shells(print, initialized);
else {
@ -477,17 +437,66 @@ void GCodeViewer::reset()
void GCodeViewer::render() const
{
auto init_gl_data = [this]() {
static bool first_run = true;
if (first_run) {
// initializes opengl data of TBuffers
for (size_t i = 0; i < m_buffers.size(); ++i) {
TBuffer& buffer = m_buffers[i];
switch (buffer_type(i))
{
default: { break; }
case EMoveType::Tool_change:
case EMoveType::Color_change:
case EMoveType::Pause_Print:
case EMoveType::Custom_GCode:
case EMoveType::Retract:
case EMoveType::Unretract:
{
buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110";
break;
}
case EMoveType::Extrude:
{
buffer.shader = "gouraud_light";
break;
}
case EMoveType::Travel:
{
buffer.shader = "toolpaths_lines";
break;
}
}
}
// initializes tool marker
m_sequential_view.marker.init();
// initializes point sizes
std::array<int, 2> point_sizes;
::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data());
m_detected_point_sizes = { static_cast<float>(point_sizes[0]), static_cast<float>(point_sizes[1]) };
first_run = false;
}
};
#if ENABLE_GCODE_VIEWER_STATISTICS
m_statistics.reset_opengl();
#endif // ENABLE_GCODE_VIEWER_STATISTICS
// OpenGL data must be initialized after the glContext has been created.
// This is ensured when this method is called by GLCanvas3D::_render_gcode().
init_gl_data();
if (m_roles.empty())
return;
glsafe(::glEnable(GL_DEPTH_TEST));
render_toolpaths();
m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
m_sequential_view.marker.render();
if (m_sequential_view.current.last != m_sequential_view.endpoints.last) {
m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
m_sequential_view.marker.render();
}
render_shells();
render_legend();
#if ENABLE_GCODE_VIEWER_STATISTICS
@ -495,6 +504,52 @@ void GCodeViewer::render() const
#endif // ENABLE_GCODE_VIEWER_STATISTICS
}
void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned int last)
{
auto is_visible = [this](unsigned int id) {
for (const TBuffer& buffer : m_buffers) {
if (buffer.visible) {
for (const Path& path : buffer.paths) {
if (path.first.s_id <= id && id <= path.last.s_id)
return true;
}
}
}
return false;
};
int first_diff = static_cast<int>(first) - static_cast<int>(m_sequential_view.last_current.first);
int last_diff = static_cast<int>(last) - static_cast<int>(m_sequential_view.last_current.last);
unsigned int new_first = first;
unsigned int new_last = last;
if (m_sequential_view.skip_invisible_moves) {
while (!is_visible(new_first)) {
if (first_diff > 0)
++new_first;
else
--new_first;
}
while (!is_visible(new_last)) {
if (last_diff > 0)
++new_last;
else
--new_last;
}
}
m_sequential_view.current.first = new_first;
m_sequential_view.current.last = new_last;
m_sequential_view.last_current = m_sequential_view.current;
refresh_render_paths(true, true);
if (new_first != first || new_last != last)
wxGetApp().plater()->update_preview_moves_slider();
}
bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const
{
size_t id = static_cast<size_t>(buffer_id(type));
@ -850,6 +905,49 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
fclose(fp);
}
void GCodeViewer::init()
{
if (m_initialized)
return;
// initializes non opengl data of TBuffers
for (size_t i = 0; i < m_buffers.size(); ++i) {
TBuffer& buffer = m_buffers[i];
switch (buffer_type(i))
{
default: { break; }
case EMoveType::Tool_change:
case EMoveType::Color_change:
case EMoveType::Pause_Print:
case EMoveType::Custom_GCode:
case EMoveType::Retract:
case EMoveType::Unretract:
{
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point;
buffer.vertices.format = VBuffer::EFormat::Position;
break;
}
case EMoveType::Extrude:
{
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle;
buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
break;
}
case EMoveType::Travel:
{
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line;
buffer.vertices.format = VBuffer::EFormat::PositionNormal1;
break;
}
}
}
set_toolpath_move_type_visible(EMoveType::Extrude, true);
// m_sequential_view.skip_invisible_moves = true;
m_initialized = true;
}
void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
{
#if ENABLE_GCODE_VIEWER_STATISTICS
@ -865,8 +963,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
unsigned int progress_count = 0;
static const unsigned int progress_threshold = 1000;
wxProgressDialog progress_dialog(_L("Generating toolpaths"), "...",
100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL);
wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ?
new wxProgressDialog(_L("Generating toolpaths"), "...",
100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr;
for (size_t i = 0; i < m_moves_count; ++i) {
const GCodeProcessor::MoveVertex& move = gcode_result.moves[i];
@ -1248,10 +1347,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
continue;
++progress_count;
if (progress_count % progress_threshold == 0) {
progress_dialog.Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))),
if (progress_dialog != nullptr && progress_count % progress_threshold == 0) {
progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))),
_L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%");
progress_dialog.Fit();
progress_dialog->Fit();
progress_count = 0;
}
@ -1322,10 +1421,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
continue;
++progress_count;
if (progress_count % progress_threshold == 0) {
progress_dialog.Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))),
if (progress_dialog != nullptr && progress_count % progress_threshold == 0) {
progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))),
_L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%");
progress_dialog.Fit();
progress_dialog->Fit();
progress_count = 0;
}
@ -1383,6 +1482,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
}
}
if (progress_dialog != nullptr) {
progress_dialog->Update(100, "");
progress_dialog->Fit();
}
log_memory_usage("Loaded G-code generated indices buffers, ", vertices, indices);
// toolpaths data -> send indices data to gpu
@ -1456,7 +1560,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
}
// set layers z range
m_layers_z_range = { m_layers_zs.front(), m_layers_zs.back() };
if (!m_layers_zs.empty())
m_layers_z_range = { m_layers_zs.front(), m_layers_zs.back() };
// roles -> remove duplicates
std::sort(m_roles.begin(), m_roles.end());
@ -1473,6 +1578,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
#if ENABLE_GCODE_VIEWER_STATISTICS
m_statistics.load_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
#endif // ENABLE_GCODE_VIEWER_STATISTICS
if (progress_dialog != nullptr)
progress_dialog->Destroy();
}
void GCodeViewer::load_shells(const Print& print, bool initialized)
@ -1562,16 +1670,46 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
Travel_Colors[0] /* Move */);
};
auto is_in_z_range = [](const Path& path, double min_z, double max_z) {
auto in_z_range = [min_z, max_z](double z) {
return z > min_z - EPSILON && z < max_z + EPSILON;
};
return in_z_range(path.first.position[2]) || in_z_range(path.last.position[2]);
};
auto is_travel_in_z_range = [this, is_in_z_range](size_t path_id, double min_z, double max_z) {
const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)];
if (path_id >= buffer.paths.size())
return false;
Path path = buffer.paths[path_id];
int first = static_cast<int>(path_id);
unsigned int last = static_cast<unsigned int>(path_id);
// check adjacent paths
while (first > 0 && path.first.position.isApprox(buffer.paths[first - 1].last.position)) {
--first;
path.first = buffer.paths[first].first;
}
while (last < static_cast<unsigned int>(buffer.paths.size() - 1) && path.last.position.isApprox(buffer.paths[last + 1].first.position)) {
++last;
path.last = buffer.paths[last].last;
}
return is_in_z_range(path, min_z, max_z);
};
#if ENABLE_GCODE_VIEWER_STATISTICS
m_statistics.render_paths_size = 0;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
m_sequential_view.endpoints.first = m_moves_count;
m_sequential_view.endpoints.last = 0;
if (!keep_sequential_current_first)
m_sequential_view.current.first = 0;
if (!keep_sequential_current_last)
m_sequential_view.current.last = m_moves_count;
bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1";
SequentialView::Endpoints global_endpoints = { m_moves_count , 0 };
SequentialView::Endpoints top_layer_endpoints = global_endpoints;
if (top_layer_only || !keep_sequential_current_first) m_sequential_view.current.first = 0;
if (!keep_sequential_current_last) m_sequential_view.current.last = m_moves_count;
// first pass: collect visible paths and update sequential view data
std::vector<std::tuple<TBuffer*, unsigned int, unsigned int>> paths;
@ -1585,10 +1723,10 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
for (size_t i = 0; i < buffer.paths.size(); ++i) {
const Path& path = buffer.paths[i];
if (path.type == EMoveType::Travel) {
if (!is_travel_in_z_range(i))
if (!is_travel_in_z_range(i, m_layers_z_range[0], m_layers_z_range[1]))
continue;
}
else if (!is_in_z_range(path))
else if (!is_in_z_range(path, m_layers_z_range[0], m_layers_z_range[1]))
continue;
if (path.type == EMoveType::Extrude && !is_visible(path))
@ -1597,14 +1735,27 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
// store valid path
paths.push_back({ &buffer, path.first.b_id, static_cast<unsigned int>(i) });
m_sequential_view.endpoints.first = std::min(m_sequential_view.endpoints.first, path.first.s_id);
m_sequential_view.endpoints.last = std::max(m_sequential_view.endpoints.last, path.last.s_id);
global_endpoints.first = std::min(global_endpoints.first, path.first.s_id);
global_endpoints.last = std::max(global_endpoints.last, path.last.s_id);
if (top_layer_only) {
if (path.type == EMoveType::Travel) {
if (is_travel_in_z_range(i, m_layers_z_range[1], m_layers_z_range[1])) {
top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.first.s_id);
top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.last.s_id);
}
}
else if (is_in_z_range(path, m_layers_z_range[1], m_layers_z_range[1])) {
top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.first.s_id);
top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.last.s_id);
}
}
}
}
// update current sequential position
m_sequential_view.current.first = keep_sequential_current_first ? std::clamp(m_sequential_view.current.first, m_sequential_view.endpoints.first, m_sequential_view.endpoints.last) : m_sequential_view.endpoints.first;
m_sequential_view.current.last = keep_sequential_current_last ? std::clamp(m_sequential_view.current.last, m_sequential_view.endpoints.first, m_sequential_view.endpoints.last) : m_sequential_view.endpoints.last;
m_sequential_view.current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(m_sequential_view.current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first;
m_sequential_view.current.last = keep_sequential_current_last ? std::clamp(m_sequential_view.current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last;
// get the world position from gpu
bool found = false;
@ -1650,8 +1801,22 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
Color color;
switch (path.type)
{
case EMoveType::Extrude: { color = extrusion_color(path); break; }
case EMoveType::Travel: { color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); break; }
case EMoveType::Extrude: {
if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_in_z_range(path, m_layers_z_range[1], m_layers_z_range[1]))
color = extrusion_color(path);
else
color = { 0.25f, 0.25f, 0.25f };
break;
}
case EMoveType::Travel: {
if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_z_range(path_id, m_layers_z_range[1], m_layers_z_range[1]))
color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path);
else
color = { 0.25f, 0.25f, 0.25f };
break;
}
default: { color = { 0.0f, 0.0f, 0.0f }; break; }
}
@ -1685,6 +1850,12 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
it->offsets.push_back(static_cast<size_t>((path.first.i_id + delta_1st) * sizeof(unsigned int)));
}
// set sequential data to their final value
m_sequential_view.endpoints = top_layer_only ? top_layer_endpoints : global_endpoints;
m_sequential_view.current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(m_sequential_view.current.first, m_sequential_view.endpoints.first, m_sequential_view.endpoints.last) : m_sequential_view.endpoints.first;
wxGetApp().plater()->enable_preview_moves_slider(!paths.empty());
#if ENABLE_GCODE_VIEWER_STATISTICS
for (const TBuffer& buffer : m_buffers) {
m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.render_paths, RenderPath);
@ -2127,6 +2298,7 @@ void GCodeViewer::render_legend() const
m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role);
// update buffers' render paths
refresh_render_paths(false, false);
wxGetApp().plater()->update_preview_moves_slider();
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
wxGetApp().plater()->update_preview_bottom_toolbar();
}
@ -2134,10 +2306,10 @@ void GCodeViewer::render_legend() const
}
break;
}
case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; }
case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; }
case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; }
case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; }
case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; }
case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; }
case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; }
case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; }
case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; }
case EViewType::Tool:
{
@ -2395,7 +2567,7 @@ void GCodeViewer::render_legend() const
// items
add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions"));
add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Unretractions"));
add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Deretractions"));
add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes"));
add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes"));
add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Pause prints"));
@ -2560,29 +2732,6 @@ void GCodeViewer::render_statistics() const
}
#endif // ENABLE_GCODE_VIEWER_STATISTICS
bool GCodeViewer::is_travel_in_z_range(size_t id) const
{
const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)];
if (id >= buffer.paths.size())
return false;
Path path = buffer.paths[id];
int first = static_cast<int>(id);
unsigned int last = static_cast<unsigned int>(id);
// check adjacent paths
while (first > 0 && path.first.position.isApprox(buffer.paths[first - 1].last.position)) {
--first;
path.first = buffer.paths[first].first;
}
while (last < static_cast<unsigned int>(buffer.paths.size() - 1) && path.last.position.isApprox(buffer.paths[last + 1].first.position)) {
++last;
path.last = buffer.paths[last].last;
}
return is_in_z_range(path);
}
void GCodeViewer::log_memory_used(const std::string& label, long long additional) const
{
if (Slic3r::get_logging_level() >= 5) {

View file

@ -342,8 +342,8 @@ public:
Vec3f m_world_position;
Transform3f m_world_transform;
float m_z_offset{ 0.5f };
std::array<float, 4> m_color{ 1.0f, 1.0f, 1.0f, 1.0f };
bool m_visible{ false };
std::array<float, 4> m_color{ 1.0f, 1.0f, 1.0f, 0.5f };
bool m_visible{ true };
public:
void init();
@ -365,8 +365,10 @@ public:
size_t last{ 0 };
};
bool skip_invisible_moves{ false };
Endpoints endpoints;
Endpoints current;
Endpoints last_current;
Vec3f current_position{ Vec3f::Zero() };
Marker marker;
};
@ -385,6 +387,7 @@ public:
};
private:
bool m_initialized{ false };
unsigned int m_last_result_id{ 0 };
size_t m_moves_count{ 0 };
mutable std::vector<TBuffer> m_buffers{ static_cast<size_t>(EMoveType::Extrude) };
@ -407,14 +410,12 @@ private:
#if ENABLE_GCODE_VIEWER_STATISTICS
mutable Statistics m_statistics;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
std::array<float, 2> m_detected_point_sizes = { 0.0f, 0.0f };
mutable std::array<float, 2> m_detected_point_sizes = { 0.0f, 0.0f };
public:
GCodeViewer() = default;
~GCodeViewer() { reset(); }
bool init();
// extract rendering data from the given parameters
void load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized);
// recalculate ranges in dependence of what is visible and sets tool/print colors
@ -430,12 +431,7 @@ public:
const std::vector<double>& get_layers_zs() const { return m_layers_zs; };
const SequentialView& get_sequential_view() const { return m_sequential_view; }
void update_sequential_view_current(unsigned int first, unsigned int last)
{
m_sequential_view.current.first = first;
m_sequential_view.current.last = last;
refresh_render_paths(true, true);
}
void update_sequential_view_current(unsigned int first, unsigned int last);
EViewType get_view_type() const { return m_view_type; }
void set_view_type(EViewType type) {
@ -459,6 +455,7 @@ public:
void export_toolpaths_to_obj(const char* filename) const;
private:
void init();
void load_toolpaths(const GCodeProcessor::Result& gcode_result);
void load_shells(const Print& print, bool initialized);
void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const;
@ -472,14 +469,6 @@ private:
return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0;
}
bool is_visible(const Path& path) const { return is_visible(path.role); }
bool is_in_z_range(const Path& path) const {
auto in_z_range = [this](double z) {
return z > m_layers_z_range[0] - EPSILON && z < m_layers_z_range[1] + EPSILON;
};
return in_z_range(path.first.position[2]) || in_z_range(path.last.position[2]);
}
bool is_travel_in_z_range(size_t id) const;
void log_memory_used(const std::string& label, long long additional = 0) const;
};

View file

@ -215,6 +215,8 @@ void GLCanvas3D::LayersEditing::set_enabled(bool enabled)
m_enabled = is_allowed() && enabled;
}
float GLCanvas3D::LayersEditing::s_overelay_window_width;
void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
{
if (!m_enabled)
@ -297,6 +299,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
if (imgui.button(_L("Reset")))
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE));
GLCanvas3D::LayersEditing::s_overelay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/;
imgui.end();
const Rect& bar_rect = get_bar_rect_viewport(canvas);
@ -637,16 +640,17 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
error = true;
break;
}
if(state) {
auto &notification_manager = *wxGetApp().plater()->get_notification_manager();
if (state) {
if(error)
wxGetApp().plater()->get_notification_manager()->push_plater_error_notification(text,*(wxGetApp().plater()->get_current_canvas3D()));
notification_manager.push_plater_error_notification(text,*(wxGetApp().plater()->get_current_canvas3D()));
else
wxGetApp().plater()->get_notification_manager()->push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D()));
notification_manager.push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D()));
} else {
if (error)
wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(text);
notification_manager.close_plater_error_notification(text);
else
wxGetApp().plater()->get_notification_manager()->close_plater_warning_notification(text);
notification_manager.close_plater_warning_notification(text);
}
/*
@ -1411,7 +1415,7 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
imgui.set_next_window_pos(position(0), position(1), ImGuiCond_Always, 0.0f, 0.0f);
imgui.begin(_(L("canvas_tooltip")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing);
imgui.begin(_L("canvas_tooltip"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing);
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
ImGui::TextUnformatted(m_text.c_str());
@ -1425,70 +1429,7 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas
ImGui::PopStyleVar(2);
}
#if ENABLE_SLOPE_RENDERING
void GLCanvas3D::Slope::render() const
{
if (m_dialog_shown) {
const std::array<float, 2>& z_range = m_volumes.get_slope_z_range();
std::array<float, 2> angle_range = { Geometry::rad2deg(::acos(z_range[0])) - 90.0f, Geometry::rad2deg(::acos(z_range[1])) - 90.0f };
bool modified = false;
ImGuiWrapper& imgui = *wxGetApp().imgui();
const Size& cnv_size = m_canvas.get_canvas_size();
imgui.set_next_window_pos((float)cnv_size.get_width(), (float)cnv_size.get_height(), ImGuiCond_Always, 1.0f, 1.0f);
imgui.begin(_L("Slope visualization"), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
imgui.text(_L("Facets' slope range (degrees)") + ":");
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.0f, 0.0f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 0.0f, 0.0f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.0f, 0.0f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.0f, 0.0f, 1.0f));
// angle_range is range of normal angle, GUI should
// show facet slope angle
float slope_bound = 90.f - angle_range[1];
bool mod = ImGui::SliderFloat("##red", &slope_bound, 0.0f, 90.0f, "%.1f");
angle_range[1] = 90.f - slope_bound;
if (mod) {
modified = true;
if (angle_range[0] > angle_range[1])
angle_range[0] = angle_range[1];
}
ImGui::PopStyleColor(4);
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.75f, 0.0f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f));
slope_bound = 90.f - angle_range[0];
mod = ImGui::SliderFloat("##yellow", &slope_bound, 0.0f, 90.0f, "%.1f");
angle_range[0] = 90.f - slope_bound;
if (mod) {
modified = true;
if (angle_range[1] < angle_range[0])
angle_range[1] = angle_range[0];
}
ImGui::PopStyleColor(4);
ImGui::Separator();
if (imgui.button(_(L("Default"))))
m_volumes.set_default_slope_z_range();
// to let the dialog immediately showup without waiting for a mouse move
if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowExpectedSize(ImGui::GetCurrentWindow()).x)
m_canvas.request_extra_frame();
imgui.end();
if (modified)
set_range(angle_range);
}
}
#endif // ENABLE_SLOPE_RENDERING
float GLCanvas3D::Slope::s_window_width;
wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
@ -1562,9 +1503,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
#endif // ENABLE_RENDER_PICKING_PASS
, m_render_sla_auxiliaries(true)
, m_labels(*this)
#if ENABLE_SLOPE_RENDERING
, m_slope(*this, m_volumes)
#endif // ENABLE_SLOPE_RENDERING
{
if (m_canvas != nullptr) {
m_timer.SetOwner(m_canvas);
@ -1592,7 +1531,7 @@ bool GLCanvas3D::init()
if (m_initialized)
return true;
if ((m_canvas == nullptr) || (m_context == nullptr))
if (m_canvas == nullptr || m_context == nullptr)
return false;
glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
@ -1641,14 +1580,6 @@ bool GLCanvas3D::init()
if (m_main_toolbar.is_enabled())
m_layers_editing.init();
#if ENABLE_GCODE_VIEWER
if (!m_main_toolbar.is_enabled())
{
if (!m_gcode_viewer.init())
return false;
}
#endif // ENABLE_GCODE_VIEWER
// on linux the gl context is not valid until the canvas is not shown on screen
// we defer the geometry finalization of volumes until the first call to render()
m_volumes.finalize_geometry(true);
@ -1732,8 +1663,9 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
} else {
const GLGizmosManager& gm = get_gizmos_manager();
auto gizmo_type = gm.get_current_type();
if (gizmo_type == GLGizmosManager::FdmSupports
|| gizmo_type == GLGizmosManager::Seam)
if ( (gizmo_type == GLGizmosManager::FdmSupports
|| gizmo_type == GLGizmosManager::Seam)
&& ! vol->is_modifier)
vol->force_neutral_color = true;
else
vol->force_native_color = true;
@ -1872,11 +1804,6 @@ bool GLCanvas3D::is_reload_delayed() const
void GLCanvas3D::enable_layers_editing(bool enable)
{
#if ENABLE_SLOPE_RENDERING
if (enable && m_slope.is_dialog_shown())
m_slope.show_dialog(false);
#endif // ENABLE_SLOPE_RENDERING
m_layers_editing.set_enabled(enable);
const Selection::IndicesList& idxs = m_selection.get_volume_idxs();
for (unsigned int idx : idxs)
@ -2012,7 +1939,10 @@ void GLCanvas3D::render()
}
#if ENABLE_ENVIRONMENT_MAP
wxGetApp().plater()->init_environment_texture();
#if ENABLE_GCODE_VIEWER
if (wxGetApp().is_editor())
#endif // ENABLE_GCODE_VIEWER
wxGetApp().plater()->init_environment_texture();
#endif // ENABLE_ENVIRONMENT_MAP
const Size& cnv_size = get_canvas_size();
@ -2151,7 +2081,7 @@ void GLCanvas3D::render()
wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this);
wxGetApp().plater()->get_notification_manager()->render_notifications(*this);
wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width());
wxGetApp().imgui()->render();
@ -2662,6 +2592,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
_set_warning_texture(WarningTexture::ObjectClashed, state == ModelInstancePVS_Partly_Outside);
_set_warning_texture(WarningTexture::ObjectOutside, state == ModelInstancePVS_Fully_Outside);
if(printer_technology != ptSLA || state == ModelInstancePVS_Inside)
_set_warning_texture(WarningTexture::SlaSupportsOutside, false);
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS,
contained_min_one && !m_model->objects.empty() && state != ModelInstancePVS_Partly_Outside));
@ -2670,6 +2602,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
{
_set_warning_texture(WarningTexture::ObjectOutside, false);
_set_warning_texture(WarningTexture::ObjectClashed, false);
_set_warning_texture(WarningTexture::SlaSupportsOutside, false);
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false));
}
@ -2871,8 +2804,7 @@ void GLCanvas3D::load_preview(const std::vector<std::string>& str_tool_colors, c
void GLCanvas3D::bind_event_handlers()
{
if (m_canvas != nullptr)
{
if (m_canvas != nullptr) {
m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this);
m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this);
@ -2894,13 +2826,14 @@ void GLCanvas3D::bind_event_handlers()
m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
m_event_handlers_bound = true;
}
}
void GLCanvas3D::unbind_event_handlers()
{
if (m_canvas != nullptr)
{
if (m_canvas != nullptr && m_event_handlers_bound) {
m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this);
m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this);
@ -2922,6 +2855,8 @@ void GLCanvas3D::unbind_event_handlers()
m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
m_canvas->Unbind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
m_event_handlers_bound = false;
}
}
@ -2947,8 +2882,7 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
_refresh_if_shown_on_screen();
if (m_extra_frame_requested || mouse3d_controller_applied)
{
if (m_extra_frame_requested || mouse3d_controller_applied) {
m_dirty = true;
m_extra_frame_requested = false;
evt.RequestMore();
@ -3000,7 +2934,16 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
post_event(SimpleEvent(EVT_GLTOOLBAR_COPY));
break;
#ifdef __linux__
#if ENABLE_CTRL_M_ON_WINDOWS
case WXK_CONTROL_M:
{
Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
controller.show_settings_dialog(!controller.is_settings_dialog_shown());
m_dirty = true;
break;
}
#else
#if defined(__linux__) || defined(__APPLE__)
case WXK_CONTROL_M:
{
Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
@ -3009,6 +2952,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
break;
}
#endif /* __linux__ */
#endif // ENABLE_CTRL_M_ON_WINDOWS
#ifdef __APPLE__
case 'v':
@ -3087,17 +3031,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; }
case 'B':
case 'b': { zoom_to_bed(); break; }
#if ENABLE_SLOPE_RENDERING
case 'D':
case 'd': {
if (!is_layers_editing_enabled())
{
m_slope.show_dialog(!m_slope.is_dialog_shown());
m_dirty = true;
}
break;
}
#endif // ENABLE_SLOPE_RENDERING
case 'E':
case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; }
case 'I':
@ -3286,6 +3219,10 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
// m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux
post_event(SimpleEvent(EVT_GLCANVAS_TAB));
}
else if (keyCode == WXK_TAB && evt.ShiftDown() && ! wxGetApp().is_gcode_viewer()) {
// Collapse side-panel with Shift+Tab
post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR));
}
else if (keyCode == WXK_SHIFT)
{
translationProcessor.process(evt);
@ -3421,17 +3358,17 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
if (evt.MiddleIsDown())
return;
if (wxGetApp().imgui()->update_mouse_data(evt)) {
m_dirty = true;
return;
}
#if ENABLE_RETINA_GL
const float scale = m_retina_helper->get_scale_factor();
evt.SetX(evt.GetX() * scale);
evt.SetY(evt.GetY() * scale);
#endif
if (wxGetApp().imgui()->update_mouse_data(evt)) {
m_dirty = true;
return;
}
#ifdef __WXMSW__
// For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad,
// if the event is not allowed to be passed further.
@ -3472,7 +3409,8 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
return;
// Calculate the zoom delta and apply it to the current zoom factor
_update_camera_zoom((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta());
double direction_factor = (wxGetApp().app_config->get("reverse_mouse_wheel_zoom") == "1") ? -1.0 : 1.0;
_update_camera_zoom(direction_factor * (double)evt.GetWheelRotation() / (double)evt.GetWheelDelta());
}
void GLCanvas3D::on_timer(wxTimerEvent& evt)
@ -3578,40 +3516,35 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
}
if (m_main_toolbar.on_mouse(evt, *this))
{
if (m_main_toolbar.on_mouse(evt, *this)) {
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
m_mouse.set_start_position_3D_as_invalid();
return;
}
if (m_undoredo_toolbar.on_mouse(evt, *this))
{
if (m_undoredo_toolbar.on_mouse(evt, *this)) {
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
m_mouse.set_start_position_3D_as_invalid();
return;
}
if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this))
{
if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) {
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
m_mouse.set_start_position_3D_as_invalid();
return;
}
if (wxGetApp().plater()->get_view_toolbar().on_mouse(evt, *this))
{
if (wxGetApp().plater()->get_view_toolbar().on_mouse(evt, *this)) {
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
m_mouse.set_start_position_3D_as_invalid();
return;
}
if (m_gizmos.on_mouse(evt))
{
if (m_gizmos.on_mouse(evt)) {
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
@ -3620,12 +3553,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
return;
}
bool any_gizmo_active = m_gizmos.get_current() != nullptr;
int selected_object_idx = m_selection.get_object_idx();
int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1;
m_layers_editing.select_object(*m_model, layer_editing_object_idx);
if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos))
{
if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) {
m_mouse.drag.move_requires_threshold = false;
m_mouse.set_move_start_threshold_position_2D_as_invalid();
}
@ -3634,8 +3568,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// Grab keyboard focus on any mouse click event.
m_canvas->SetFocus();
if (evt.Entering())
{
if (evt.Entering()) {
//#if defined(__WXMSW__) || defined(__linux__)
// // On Windows and Linux needs focus in order to catch key events
// Set focus in order to remove it from sidebar fields
@ -3660,49 +3593,41 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_mouse.set_start_position_2D_as_invalid();
//#endif
}
else if (evt.Leaving())
{
else if (evt.Leaving()) {
_deactivate_undo_redo_toolbar_items();
// to remove hover on objects when the mouse goes out of this canvas
m_mouse.position = Vec2d(-1.0, -1.0);
m_dirty = true;
}
else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown())
{
else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) {
if (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item())
return;
// If user pressed left or right button we first check whether this happened
// on a volume or not.
m_layers_editing.state = LayersEditing::Unknown;
if ((layer_editing_object_idx != -1) && m_layers_editing.bar_rect_contains(*this, pos(0), pos(1)))
{
if (layer_editing_object_idx != -1 && m_layers_editing.bar_rect_contains(*this, pos(0), pos(1))) {
// A volume is selected and the mouse is inside the layer thickness bar.
// Start editing the layer height.
m_layers_editing.state = LayersEditing::Editing;
_perform_layer_editing_action(&evt);
}
else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled)
{
else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) {
if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports
&& m_gizmos.get_current_type() != GLGizmosManager::FdmSupports
&& m_gizmos.get_current_type() != GLGizmosManager::Seam)
{
&& m_gizmos.get_current_type() != GLGizmosManager::Seam) {
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
m_dirty = true;
}
}
else
{
else {
// Select volume in this 3D canvas.
// Don't deselect a volume if layer editing is enabled. We want the object to stay selected
// Don't deselect a volume if layer editing is enabled or any gizmo is active. We want the object to stay selected
// during the scene manipulation.
if (m_picking_enabled && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()))
{
if (evt.LeftDown() && !m_hover_volume_idxs.empty())
{
if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled())) {
if (evt.LeftDown() && !m_hover_volume_idxs.empty()) {
int volume_idx = get_first_hover_volume_idx();
bool already_selected = m_selection.contains_volume(volume_idx);
bool ctrl_down = evt.CmdDown();
@ -3711,8 +3636,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (already_selected && ctrl_down)
m_selection.remove(volume_idx);
else
{
else {
m_selection.add(volume_idx, !ctrl_down, true);
m_mouse.drag.move_requires_threshold = !already_selected;
if (already_selected)
@ -3722,8 +3646,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
// propagate event through callback
if (curr_idxs != m_selection.get_volume_idxs())
{
if (curr_idxs != m_selection.get_volume_idxs()) {
if (m_selection.is_empty())
m_gizmos.reset_all_states();
else
@ -3736,16 +3659,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
}
if (!m_hover_volume_idxs.empty())
{
if (evt.LeftDown() && m_moving_enabled && (m_mouse.drag.move_volume_idx == -1))
{
if (!m_hover_volume_idxs.empty()) {
if (evt.LeftDown() && m_moving_enabled && m_mouse.drag.move_volume_idx == -1) {
// Only accept the initial position, if it is inside the volume bounding box.
int volume_idx = get_first_hover_volume_idx();
BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
volume_bbox.offset(1.0);
if (volume_bbox.contains(m_mouse.scene_position))
{
if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position)) {
m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None;
// The dragging operation is initiated.
m_mouse.drag.move_volume_idx = volume_idx;
@ -3757,18 +3677,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
}
}
else if (evt.Dragging() && evt.LeftIsDown() && (m_layers_editing.state == LayersEditing::Unknown) && (m_mouse.drag.move_volume_idx != -1))
{
if (!m_mouse.drag.move_requires_threshold)
{
else if (evt.Dragging() && evt.LeftIsDown() && m_layers_editing.state == LayersEditing::Unknown && m_mouse.drag.move_volume_idx != -1) {
if (!m_mouse.drag.move_requires_threshold) {
m_mouse.dragging = true;
Vec3d cur_pos = m_mouse.drag.start_position_3D;
// we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag
if (m_selection.contains_volume(get_first_hover_volume_idx()))
{
if (m_selection.contains_volume(get_first_hover_volume_idx())) {
const Camera& camera = wxGetApp().plater()->get_camera();
if (std::abs(camera.get_dir_forward()(2)) < EPSILON)
{
if (std::abs(camera.get_dir_forward()(2)) < EPSILON) {
// side view -> move selected volumes orthogonally to camera view direction
Linef3 ray = mouse_ray(pos);
Vec3d dir = ray.unit_vector();
@ -3790,8 +3706,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// apply offset
cur_pos = m_mouse.drag.start_position_3D + projection_x * camera_right + projection_z * camera_up;
}
else
{
else {
// Generic view
// Get new position at the same Z of the initial click point.
float z0 = 0.0f;
@ -3805,35 +3720,28 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true;
}
}
else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging())
{
else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) {
m_rectangle_selection.dragging(pos.cast<double>());
m_dirty = true;
}
else if (evt.Dragging())
{
else if (evt.Dragging()) {
m_mouse.dragging = true;
if ((m_layers_editing.state != LayersEditing::Unknown) && (layer_editing_object_idx != -1))
{
if (m_layers_editing.state == LayersEditing::Editing)
{
if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) {
if (m_layers_editing.state == LayersEditing::Editing) {
_perform_layer_editing_action(&evt);
m_mouse.position = pos.cast<double>();
}
}
// do not process the dragging if the left mouse was set down in another canvas
else if (evt.LeftIsDown())
{
else if (evt.LeftIsDown()) {
// if dragging over blank area with left button, rotate
if (m_hover_volume_idxs.empty() && m_mouse.is_start_position_3D_defined())
{
if ((any_gizmo_active || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) {
const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.);
if (wxGetApp().app_config->get("use_free_camera") == "1")
// Virtual track ball (similar to the 3DConnexion mouse).
wxGetApp().plater()->get_camera().rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.));
else
{
else {
// Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
// It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
// which checks an atomics (flushes CPU caches).
@ -3847,11 +3755,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
}
else if (evt.MiddleIsDown() || evt.RightIsDown())
{
else if (evt.MiddleIsDown() || evt.RightIsDown()) {
// If dragging over blank area with right button, pan.
if (m_mouse.is_start_position_2D_defined())
{
if (m_mouse.is_start_position_2D_defined()) {
// get point in model space at Z = 0
float z = 0.0f;
const Vec3d& cur_pos = _mouse_to_3d(pos, &z);
@ -3871,43 +3777,36 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_mouse.drag.start_position_2D = pos;
}
}
else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
{
if (m_layers_editing.state != LayersEditing::Unknown)
{
else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) {
if (m_layers_editing.state != LayersEditing::Unknown) {
m_layers_editing.state = LayersEditing::Unknown;
_stop_timer();
m_layers_editing.accept_changes(*this);
}
else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging)
{
else if (m_mouse.drag.move_volume_idx != -1 && m_mouse.dragging) {
do_move(L("Move Object"));
wxGetApp().obj_manipul()->set_dirty();
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
}
else if (evt.LeftUp() && m_picking_enabled && m_rectangle_selection.is_dragging())
{
else if (evt.LeftUp() && m_picking_enabled && m_rectangle_selection.is_dragging()) {
if (evt.ShiftDown() || evt.AltDown())
_update_selection_from_hover();
m_rectangle_selection.stop_dragging();
}
else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_hover_volume_idxs.empty() && !is_layers_editing_enabled())
{
else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_hover_volume_idxs.empty() && !is_layers_editing_enabled()) {
// deselect and propagate event through callback
if (!evt.ShiftDown() && m_picking_enabled)
if (!evt.ShiftDown() && (!any_gizmo_active || !evt.CmdDown()) && m_picking_enabled)
deselect_all();
}
else if (evt.RightUp())
{
else if (evt.RightUp()) {
m_mouse.position = pos.cast<double>();
// forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while
// the context menu is already shown
render();
if (!m_hover_volume_idxs.empty())
{
if (!m_hover_volume_idxs.empty()) {
// if right clicking on volume, propagate event through callback (shows context menu)
int volume_idx = get_first_hover_volume_idx();
if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower
@ -3939,8 +3838,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
mouse_up_cleanup();
}
else if (evt.Moving())
{
else if (evt.Moving()) {
m_mouse.position = pos.cast<double>();
// updates gizmos overlay
@ -4315,7 +4213,7 @@ void GLCanvas3D::update_ui_from_settings()
#if ENABLE_GCODE_VIEWER
if (wxGetApp().is_editor())
wxGetApp().plater()->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1");
wxGetApp().plater()->enable_collapse_toolbar(wxGetApp().app_config->get("show_collapse_button") == "1");
#else
bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1";
wxGetApp().plater()->get_collapse_toolbar().set_enabled(enable_collapse);
@ -4503,10 +4401,14 @@ bool GLCanvas3D::_render_search_list(float pos_x) const
if (selected >= 0) {
// selected == 9999 means that Esc kye was pressed
/*// revert commit https://github.com/prusa3d/PrusaSlicer/commit/91897589928789b261ca0dc735ffd46f2b0b99f2
if (selected == 9999)
action_taken = true;
else
sidebar.jump_to_option(selected);*/
if (selected != 9999)
sidebar.jump_to_option(selected);
action_taken = true;
}
imgui->end();
@ -4539,8 +4441,7 @@ static void debug_output_thumbnail(const ThumbnailData& thumbnail_data)
void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const
{
auto is_visible = [](const GLVolume& v) -> bool
{
auto is_visible = [](const GLVolume& v) {
bool ret = v.printable;
ret &= (!v.shader_outside_printer_detection_enabled || !v.is_outside);
return ret;
@ -4551,10 +4452,8 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
GLVolumePtrs visible_volumes;
for (GLVolume* vol : m_volumes.volumes)
{
if (!vol->is_modifier && !vol->is_wipe_tower && (!parts_only || (vol->composite_id.volume_id >= 0)))
{
for (GLVolume* vol : m_volumes.volumes) {
if (!vol->is_modifier && !vol->is_wipe_tower && (!parts_only || (vol->composite_id.volume_id >= 0))) {
if (!printable_only || is_visible(*vol))
visible_volumes.emplace_back(vol);
}
@ -4564,8 +4463,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
return;
BoundingBoxf3 box;
for (const GLVolume* vol : visible_volumes)
{
for (const GLVolume* vol : visible_volumes) {
box.merge(vol->transformed_bounding_box());
}
@ -4579,8 +4477,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
double near_z = -1.0;
double far_z = -1.0;
if (show_bed)
{
if (show_bed) {
// extends the near and far z of the frustrum to avoid the bed being clipped
// box in eye space
@ -4591,7 +4488,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
camera.apply_projection(box, near_z, far_z);
GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
@ -4604,8 +4501,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
shader->start_using();
shader->set_uniform("print_box.volume_detection", 0);
for (const GLVolume* vol : visible_volumes)
{
for (const GLVolume* vol : visible_volumes) {
shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray);
vol->render();
}
@ -5199,7 +5095,7 @@ bool GLCanvas3D::_set_current()
void GLCanvas3D::_resize(unsigned int w, unsigned int h)
{
if ((m_canvas == nullptr) && (m_context == nullptr))
if (m_canvas == nullptr && m_context == nullptr)
return;
auto *imgui = wxGetApp().imgui();
@ -5266,8 +5162,7 @@ void GLCanvas3D::_refresh_if_shown_on_screen()
void GLCanvas3D::_picking_pass() const
{
if (m_picking_enabled && !m_mouse.dragging && (m_mouse.position != Vec2d(DBL_MAX, DBL_MAX)))
{
if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX)) {
m_hover_volume_idxs.clear();
// Render the object for picking.
@ -5301,17 +5196,17 @@ void GLCanvas3D::_picking_pass() const
GLubyte color[4] = { 0, 0, 0, 0 };
const Size& cnv_size = get_canvas_size();
bool inside = (0 <= m_mouse.position(0)) && (m_mouse.position(0) < cnv_size.get_width()) && (0 <= m_mouse.position(1)) && (m_mouse.position(1) < cnv_size.get_height());
if (inside)
{
bool inside = 0 <= m_mouse.position(0) && m_mouse.position(0) < cnv_size.get_width() && 0 <= m_mouse.position(1) && m_mouse.position(1) < cnv_size.get_height();
if (inside) {
glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color));
if (picking_checksum_alpha_channel(color[0], color[1], color[2]) == color[3])
// Only non-interpolated colors are valid, those have their lowest three bits zeroed.
volume_id = color[0] + (color[1] << 8) + (color[2] << 16);
}
if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size()))
{
m_hover_volume_idxs.emplace_back(volume_id);
if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) {
// do not add the volume id if any gizmo is active and CTRL is pressed
if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL))
m_hover_volume_idxs.emplace_back(volume_id);
m_gizmos.set_hover_id(-1);
}
else
@ -5484,6 +5379,7 @@ void GLCanvas3D::_render_bed(bool bottom, bool show_axes) const
bool show_texture = ! bottom ||
(m_gizmos.get_current_type() != GLGizmosManager::FdmSupports
&& m_gizmos.get_current_type() != GLGizmosManager::SlaSupports
&& m_gizmos.get_current_type() != GLGizmosManager::Hollow
&& m_gizmos.get_current_type() != GLGizmosManager::Seam);
wxGetApp().plater()->get_bed().render(const_cast<GLCanvas3D&>(*this), bottom, scale_factor, show_axes, show_texture);
@ -5535,6 +5431,20 @@ void GLCanvas3D::_render_objects() const
});
}
// In case a painting gizmo is open, it should render the painted triangles
// before transparent objects are rendered. Otherwise they would not be
// visible when inside modifier meshes etc.
{
const GLGizmosManager& gm = get_gizmos_manager();
GLGizmosManager::EType type = gm.get_current_type();
if (type == GLGizmosManager::FdmSupports
|| type == GLGizmosManager::Seam) {
shader->stop_using();
gm.render_painter_gizmo();
shader->start_using();
}
}
m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix());
shader->stop_using();
}
@ -5666,10 +5576,6 @@ void GLCanvas3D::_render_overlays() const
}
m_labels.render(sorted_instances);
#if ENABLE_SLOPE_RENDERING
m_slope.render();
#endif // ENABLE_SLOPE_RENDERING
glsafe(::glPopMatrix());
}

View file

@ -186,6 +186,8 @@ private:
mutable float m_adaptive_quality;
mutable HeightProfileSmoothingParams m_smooth_params;
static float s_overelay_window_width;
class LayersTexture
{
@ -241,6 +243,7 @@ private:
static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y);
static Rect get_bar_rect_screen(const GLCanvas3D& canvas);
static Rect get_bar_rect_viewport(const GLCanvas3D& canvas);
static float get_overlay_window_width() { return LayersEditing::s_overelay_window_width; }
float object_max_z() const { return m_object_max_z; }
@ -254,6 +257,7 @@ private:
void update_slicing_parameters();
static float thickness_bar_width(const GLCanvas3D &canvas);
};
struct Mouse
@ -418,14 +422,13 @@ private:
bool is_in_imgui() const { return m_in_imgui; }
};
#if ENABLE_SLOPE_RENDERING
class Slope
{
bool m_enabled{ false };
bool m_dialog_shown{ false };
GLCanvas3D& m_canvas;
GLVolumeCollection& m_volumes;
static float s_window_width;
public:
Slope(GLCanvas3D& canvas, GLVolumeCollection& volumes) : m_canvas(canvas), m_volumes(volumes) {}
@ -433,14 +436,11 @@ private:
bool is_enabled() const { return m_enabled; }
void use(bool use) { m_volumes.set_slope_active(m_enabled ? use : false); }
bool is_used() const { return m_volumes.is_slope_active(); }
void show_dialog(bool show) { if (show && is_used()) return; use(show); m_dialog_shown = show; }
bool is_dialog_shown() const { return m_dialog_shown; }
void render() const;
void set_range(const std::array<float, 2>& range) const {
m_volumes.set_slope_z_range({ -::cos(Geometry::deg2rad(90.0f - range[0])), -::cos(Geometry::deg2rad(90.0f - range[1])) });
void set_normal_angle(float angle_in_deg) const {
m_volumes.set_slope_normal_z(-::cos(Geometry::deg2rad(90.0f - angle_in_deg)));
}
static float get_window_width() { return s_window_width; };
};
#endif // ENABLE_SLOPE_RENDERING
public:
enum ECursorType : unsigned char
@ -474,6 +474,7 @@ private:
// when true renders an extra frame by not resetting m_dirty to false
// see request_extra_frame()
bool m_extra_frame_requested;
bool m_event_handlers_bound{ false };
mutable GLVolumeCollection m_volumes;
#if ENABLE_GCODE_VIEWER
@ -529,9 +530,7 @@ private:
Labels m_labels;
mutable Tooltip m_tooltip;
mutable bool m_tooltip_enabled{ true };
#if ENABLE_SLOPE_RENDERING
Slope m_slope;
#endif // ENABLE_SLOPE_RENDERING
public:
explicit GLCanvas3D(wxGLCanvas* canvas);
@ -616,9 +615,7 @@ public:
void enable_undoredo_toolbar(bool enable);
void enable_dynamic_background(bool enable);
void enable_labels(bool enable) { m_labels.enable(enable); }
#if ENABLE_SLOPE_RENDERING
void enable_slope(bool enable) { m_slope.enable(enable); }
#endif // ENABLE_SLOPE_RENDERING
void allow_multisample(bool allow);
void zoom_to_bed();
@ -765,12 +762,9 @@ public:
bool are_labels_shown() const { return m_labels.is_shown(); }
void show_labels(bool show) { m_labels.show(show); }
#if ENABLE_SLOPE_RENDERING
bool is_slope_shown() const { return m_slope.is_dialog_shown(); }
bool is_using_slope() const { return m_slope.is_used(); }
void use_slope(bool use) { m_slope.use(use); }
void show_slope(bool show) { m_slope.show_dialog(show); }
void set_slope_range(const std::array<float, 2>& range) { m_slope.set_range(range); }
#endif // ENABLE_SLOPE_RENDERING
void set_slope_normal_angle(float angle_in_deg) { m_slope.set_normal_angle(angle_in_deg); }
private:
bool _is_shown_on_screen() const;
@ -892,6 +886,8 @@ private:
bool _activate_search_toolbar_item();
bool _deactivate_collapse_toolbar_items();
float get_overlay_window_width() { return LayersEditing::get_overlay_window_width(); }
static std::vector<float> _parse_colors(const std::vector<std::string>& colors);
public:

View file

@ -184,6 +184,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
config.set_key_value(opt_key, new ConfigOptionEnum<IroningType>(boost::any_cast<IroningType>(value)));
else if (opt_key.compare("gcode_flavor") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<GCodeFlavor>(boost::any_cast<GCodeFlavor>(value)));
else if (opt_key.compare("machine_limits_usage") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<MachineLimitsUsage>(boost::any_cast<MachineLimitsUsage>(value)));
else if (opt_key.compare("support_material_pattern") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<SupportMaterialPattern>(boost::any_cast<SupportMaterialPattern>(value)));
else if (opt_key.compare("seam_position") == 0)

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,7 @@ class wxNotebook;
struct wxLanguageInfo;
namespace Slic3r {
class AppConfig;
class PresetBundle;
class PresetUpdater;
@ -32,6 +33,7 @@ class PrintHostJobQueue;
class Model;
namespace GUI{
class RemovableDriveManager;
class OtherInstanceMessageHandler;
class MainFrame;
@ -41,6 +43,7 @@ class ObjectSettings;
class ObjectList;
class ObjectLayers;
class Plater;
struct GUI_InitParams;
@ -106,9 +109,10 @@ private:
#endif // ENABLE_GCODE_VIEWER
bool m_initialized { false };
bool app_conf_exists{ false };
bool m_app_conf_exists{ false };
#if ENABLE_GCODE_VIEWER
EAppMode m_app_mode{ EAppMode::Editor };
bool m_is_recreating_gui{ false };
#endif // ENABLE_GCODE_VIEWER
wxColour m_color_label_modified;
@ -118,8 +122,9 @@ private:
wxFont m_small_font;
wxFont m_bold_font;
wxFont m_normal_font;
wxFont m_code_font;
int m_em_unit; // width of a "m"-symbol in pixels for current system font
int m_em_unit; // width of a "m"-symbol in pixels for current system font
// Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls
std::unique_ptr<wxLocale> m_wxLocale;
@ -155,11 +160,16 @@ public:
EAppMode get_app_mode() const { return m_app_mode; }
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }
bool is_recreating_gui() const { return m_is_recreating_gui; }
#endif // ENABLE_GCODE_VIEWER
// To be called after the GUI is fully built up.
// Process command line parameters cached in this->init_params,
// load configs, STLs etc.
void post_init();
static std::string get_gl_info(bool format_as_html, bool extensions);
wxGLContext* init_glcontext(wxGLCanvas& canvas);
bool init_opengl();
wxGLContext* init_glcontext(wxGLCanvas& canvas);
bool init_opengl();
static unsigned get_colour_approx_luma(const wxColour &colour);
static bool dark_mode();
@ -177,6 +187,7 @@ public:
const wxFont& small_font() { return m_small_font; }
const wxFont& bold_font() { return m_bold_font; }
const wxFont& normal_font() { return m_normal_font; }
const wxFont& code_font() { return m_code_font; }
int em_unit() const { return m_em_unit; }
wxSize get_min_size() const;
float toolbar_icon_scale(const bool is_limited = false) const;
@ -195,7 +206,7 @@ public:
static bool catch_error(std::function<void()> cb, const std::string& err);
void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false);
void update_ui_from_settings();
void update_ui_from_settings(bool apply_free_camera_correction = true);
bool switch_language();
bool load_language(wxString language, bool initial);
@ -218,6 +229,7 @@ public:
virtual bool OnExceptionInMainLoop() override;
#ifdef __APPLE__
void OSXStoreOpenFiles(const wxArrayString &files) override;
// wxWidgets override to get an event on open files.
void MacOpenFiles(const wxArrayString &fileNames) override;
#endif /* __APPLE */
@ -231,6 +243,9 @@ public:
Model& model();
// Parameters extracted from the command line to be passed to GUI after initialization.
const GUI_InitParams* init_params { nullptr };
AppConfig* app_config{ nullptr };
PresetBundle* preset_bundle{ nullptr };
PresetUpdater* preset_updater{ nullptr };
@ -285,6 +300,7 @@ private:
#ifdef __WXMSW__
void associate_3mf_files();
void associate_gcode_files();
#endif // __WXMSW__
};
DECLARE_APP(GUI_App)

View file

@ -0,0 +1,96 @@
#include "GUI_Init.hpp"
#include "libslic3r/AppConfig.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/InstanceCheck.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/Plater.hpp"
// To show a message box if GUI initialization ends up with an exception thrown.
#include <wx/msgdlg.h>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/convert.hpp>
namespace Slic3r {
namespace GUI {
int GUI_Run(GUI_InitParams &params)
{
try {
#if ENABLE_GCODE_VIEWER
GUI::GUI_App* gui = new GUI::GUI_App(params.start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor);
if (gui->get_app_mode() != GUI::GUI_App::EAppMode::GCodeViewer) {
// G-code viewer is currently not performing instance check, a new G-code viewer is started every time.
bool gui_single_instance_setting = gui->app_config->get("single_instance") == "1";
if (Slic3r::instance_check(params.argc, params.argv, gui_single_instance_setting)) {
//TODO: do we have delete gui and other stuff?
return -1;
}
}
#else
GUI::GUI_App *gui = new GUI::GUI_App();
#endif // ENABLE_GCODE_VIEWER
// gui->autosave = m_config.opt_string("autosave");
GUI::GUI_App::SetInstance(gui);
gui->init_params = &params;
/*
#if ENABLE_GCODE_VIEWER
gui->CallAfter([gui, this, &load_configs, params.start_as_gcodeviewer] {
#else
gui->CallAfter([gui, this, &load_configs] {
#endif // ENABLE_GCODE_VIEWER
if (!gui->initialized()) {
return;
}
#if ENABLE_GCODE_VIEWER
if (params.start_as_gcodeviewer) {
if (!m_input_files.empty())
gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str()));
} else {
#endif // ENABLE_GCODE_VIEWER_AS
#if 0
// Load the cummulative config over the currently active profiles.
//FIXME if multiple configs are loaded, only the last one will have an effect.
// We need to decide what to do about loading of separate presets (just print preset, just filament preset etc).
// As of now only the full configs are supported here.
if (!m_print_config.empty())
gui->mainframe->load_config(m_print_config);
#endif
if (!load_configs.empty())
// Load the last config to give it a name at the UI. The name of the preset may be later
// changed by loading an AMF or 3MF.
//FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
gui->mainframe->load_config_file(load_configs.back());
// If loading a 3MF file, the config is loaded from the last one.
if (!m_input_files.empty())
gui->plater()->load_files(m_input_files, true, true);
if (!m_extra_config.empty())
gui->mainframe->load_config(m_extra_config);
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
});
*/
int result = wxEntry(params.argc, params.argv);
return result;
} catch (const Slic3r::Exception &ex) {
boost::nowide::cerr << ex.what() << std::endl;
wxMessageBox(boost::nowide::widen(ex.what()), _L("PrusaSlicer GUI initialization failed"), wxICON_STOP);
} catch (const std::exception &ex) {
boost::nowide::cerr << "PrusaSlicer GUI initialization failed: " << ex.what() << std::endl;
wxMessageBox(format_wxstr(_L("Fatal error, exception catched: %1%"), ex.what()), _L("PrusaSlicer GUI initialization failed"), wxICON_STOP);
}
// error
return 1;
}
}
}

View file

@ -0,0 +1,27 @@
#ifndef slic3r_GUI_Init_hpp_
#define slic3r_GUI_Init_hpp_
#include <libslic3r/PrintConfig.hpp>
namespace Slic3r {
namespace GUI {
struct GUI_InitParams
{
int argc;
char **argv;
std::vector<std::string> load_configs;
DynamicPrintConfig extra_config;
std::vector<std::string> input_files;
bool start_as_gcodeviewer;
};
int GUI_Run(GUI_InitParams &params);
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GUI_Init_hpp_

View file

@ -1855,10 +1855,10 @@ void ObjectList::append_menu_item_scale_selection_to_fit_print_volume(wxMenu* me
void ObjectList::append_menu_items_convert_unit(wxMenu* menu)
{
append_menu_item(menu, wxID_ANY, _L("Convert from imperial unit"), _L("Convert from imperial unit"),
append_menu_item(menu, wxID_ANY, _L("Convert from imperial units"), _L("Convert from imperial units"),
[](wxCommandEvent&) { wxGetApp().plater()->convert_unit(true); }, "", menu);
append_menu_item(menu, wxID_ANY, _L("Convert to imperial unit"), _L("Convert to imperial unit"),
append_menu_item(menu, wxID_ANY, _L("Revert conversion from imperial units"), _L("Revert conversion from imperial units"),
[](wxCommandEvent&) { wxGetApp().plater()->convert_unit(false); }, "", menu);
}
@ -4442,7 +4442,8 @@ void ObjectList::show_multi_selection_menu()
}, wxGetApp().plater());
append_menu_items_convert_unit(menu);
append_menu_item_merge_to_multipart_object(menu);
if (can_merge_to_multipart_object())
append_menu_item_merge_to_multipart_object(menu);
wxGetApp().plater()->PopupMenu(menu);
}

View file

@ -59,7 +59,6 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba
m_canvas = new GLCanvas3D(m_canvas_widget);
m_canvas->set_context(wxGetApp().init_glcontext(*m_canvas_widget));
m_canvas->bind_event_handlers();
m_canvas->allow_multisample(OpenGLManager::can_multisample());
// XXX: If have OpenGL
@ -74,9 +73,7 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba
m_canvas->enable_main_toolbar(true);
m_canvas->enable_undoredo_toolbar(true);
m_canvas->enable_labels(true);
#if ENABLE_SLOPE_RENDERING
m_canvas->enable_slope(true);
#endif // ENABLE_SLOPE_RENDERING
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0);
@ -224,8 +221,7 @@ Preview::Preview(
, m_volumes_cleanup_required(false)
#endif // __linux__
{
if (init(parent, model))
{
if (init(parent, model)) {
#if !ENABLE_GCODE_VIEWER
show_hide_ui_elements("none");
#endif // !ENABLE_GCODE_VIEWER
@ -253,7 +249,6 @@ bool Preview::init(wxWindow* parent, Model* model)
m_canvas = new GLCanvas3D(m_canvas_widget);
m_canvas->set_context(wxGetApp().init_glcontext(*m_canvas_widget));
m_canvas->bind_event_handlers();
m_canvas->allow_multisample(OpenGLManager::can_multisample());
m_canvas->set_config(m_config);
m_canvas->set_model(model);
@ -330,16 +325,16 @@ bool Preview::init(wxWindow* parent, Model* model)
get_option_type_string(OptionType::PausePrints) + "|0|" +
get_option_type_string(OptionType::CustomGCodes) + "|0|" +
get_option_type_string(OptionType::Shells) + "|0|" +
get_option_type_string(OptionType::ToolMarker) + "|0|" +
get_option_type_string(OptionType::ToolMarker) + "|1|" +
get_option_type_string(OptionType::Legend) + "|1"
);
Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_L("Options")), options_items);
#else
m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel")));
m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _(L("Retractions")));
m_checkbox_unretractions = new wxCheckBox(this, wxID_ANY, _(L("Unretractions")));
m_checkbox_shells = new wxCheckBox(this, wxID_ANY, _(L("Shells")));
m_checkbox_legend = new wxCheckBox(this, wxID_ANY, _(L("Legend")));
m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _L("Travel"));
m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _L("Retractions"));
m_checkbox_unretractions = new wxCheckBox(this, wxID_ANY, _L("Deretractions"));
m_checkbox_shells = new wxCheckBox(this, wxID_ANY, _L("Shells"));
m_checkbox_legend = new wxCheckBox(this, wxID_ANY, _L("Legend"));
m_checkbox_legend->SetValue(true);
#endif // ENABLE_GCODE_VIEWER
@ -849,6 +844,7 @@ wxBoxSizer* Preview::create_layers_slider_sizer()
m_layers_slider->SetDrawMode(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA,
wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects"));
m_layers_slider->enable_action_icon(wxGetApp().is_editor());
sizer->Add(m_layers_slider, 0, wxEXPAND, 0);
@ -1164,8 +1160,7 @@ void Preview::update_moves_slider()
std::vector<double> values(view.endpoints.last - view.endpoints.first + 1);
unsigned int count = 0;
for (unsigned int i = view.endpoints.first; i <= view.endpoints.last; ++i)
{
for (unsigned int i = view.endpoints.first; i <= view.endpoints.last; ++i) {
values[count++] = static_cast<double>(i + 1);
}
@ -1173,6 +1168,15 @@ void Preview::update_moves_slider()
m_moves_slider->SetMaxValue(view.endpoints.last - view.endpoints.first);
m_moves_slider->SetSelectionSpan(view.current.first - view.endpoints.first, view.current.last - view.endpoints.first);
}
void Preview::enable_moves_slider(bool enable)
{
bool render_as_disabled = !enable;
if (m_moves_slider != nullptr && m_moves_slider->is_rendering_as_disabled() != render_as_disabled) {
m_moves_slider->set_render_as_disabled(render_as_disabled);
m_moves_slider->Refresh();
}
}
#else
void Preview::update_double_slider_from_canvas(wxKeyEvent & event)
{
@ -1205,8 +1209,8 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent & event)
void Preview::load_print_as_fff(bool keep_z_range)
{
#if ENABLE_GCODE_VIEWER
if (wxGetApp().mainframe == nullptr)
// avoid proessing while mainframe is being constructed
if (wxGetApp().mainframe == nullptr || wxGetApp().is_recreating_gui())
// avoid processing while mainframe is being constructed
return;
#endif // ENABLE_GCODE_VIEWER
@ -1234,17 +1238,13 @@ void Preview::load_print_as_fff(bool keep_z_range)
}
#if ENABLE_GCODE_VIEWER
if (wxGetApp().is_editor() && !has_layers)
#else
if (! has_layers)
#endif // ENABLE_GCODE_VIEWER
{
#if ENABLE_GCODE_VIEWER
if (wxGetApp().is_editor() && !has_layers) {
hide_layers_slider();
m_left_sizer->Hide(m_bottom_toolbar_panel);
m_left_sizer->Layout();
Refresh();
#else
if (! has_layers) {
reset_sliders(true);
m_canvas->reset_legend_texture();
#endif // ENABLE_GCODE_VIEWER
@ -1252,12 +1252,11 @@ void Preview::load_print_as_fff(bool keep_z_range)
return;
}
if (m_preferred_color_mode == "tool_or_feature")
{
if (m_preferred_color_mode == "tool_or_feature") {
// It is left to Slic3r to decide whether the print shall be colored by the tool or by the feature.
// Color by feature if it is a single extruder print.
unsigned int number_extruders = (unsigned int)print->extruders().size();
int tool_idx = m_choice_view_type->FindString(_(L("Tool")));
int tool_idx = m_choice_view_type->FindString(_L("Tool"));
int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type
m_choice_view_type->SetSelection(type);
#if ENABLE_GCODE_VIEWER
@ -1282,31 +1281,32 @@ void Preview::load_print_as_fff(bool keep_z_range)
std::vector<CustomGCode::Item> color_print_values = {};
// set color print values, if it si selected "ColorPrint" view type
#if ENABLE_GCODE_VIEWER
if (gcode_view_type == GCodeViewer::EViewType::ColorPrint)
if (gcode_view_type == GCodeViewer::EViewType::ColorPrint) {
#else
if (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint)
if (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint) {
#endif // ENABLE_GCODE_VIEWER
{
colors = wxGetApp().plater()->get_colors_for_color_print();
#if !ENABLE_GCODE_VIEWER
colors.push_back("#808080"); // gray color for pause print or custom G-code
#endif // !ENABLE_GCODE_VIEWER
if (!gcode_preview_data_valid)
if (!gcode_preview_data_valid) {
color_print_values = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes;
#if ENABLE_GCODE_VIEWER
colors.push_back("#808080"); // gray color for pause print or custom G-code
#endif // ENABLE_GCODE_VIEWER
}
}
#if ENABLE_GCODE_VIEWER
else if (gcode_preview_data_valid || gcode_view_type == GCodeViewer::EViewType::Tool)
else if (gcode_preview_data_valid || gcode_view_type == GCodeViewer::EViewType::Tool) {
#else
else if (gcode_preview_data_valid || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool) )
else if (gcode_preview_data_valid || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool) ) {
#endif // ENABLE_GCODE_VIEWER
{
colors = wxGetApp().plater()->get_extruder_colors_from_plater_config();
color_print_values.clear();
}
if (IsShown())
{
if (IsShown()) {
#if ENABLE_GCODE_VIEWER
std::vector<double> zs;
#endif // ENABLE_GCODE_VIEWER
@ -1460,7 +1460,7 @@ wxString Preview::get_option_type_string(OptionType type) const
{
case OptionType::Travel: { return _L("Travel"); }
case OptionType::Retractions: { return _L("Retractions"); }
case OptionType::Unretractions: { return _L("Unretractions"); }
case OptionType::Unretractions: { return _L("Deretractions"); }
case OptionType::ToolChanges: { return _L("Tool changes"); }
case OptionType::ColorChanges: { return _L("Color changes"); }
case OptionType::PausePrints: { return _L("Pause prints"); }

View file

@ -194,6 +194,7 @@ Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config,
#if ENABLE_GCODE_VIEWER
void update_bottom_toolbar();
void update_moves_slider();
void enable_moves_slider(bool enable);
void hide_layers_slider();
#endif // ENABLE_GCODE_VIEWER

View file

@ -267,5 +267,26 @@ std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics)
}
TaskTimer::TaskTimer(std::string task_name):
task_name(task_name.empty() ? "task" : task_name)
{
start_timer = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
}
TaskTimer::~TaskTimer()
{
std::chrono::milliseconds stop_timer = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
auto process_duration = std::chrono::milliseconds(stop_timer - start_timer).count();
std::string out = (boost::format("\n!!! %1% duration = %2% ms \n\n") % task_name % process_duration).str();
printf("%s", out.c_str());
#ifdef __WXMSW__
std::wstring stemp = std::wstring(out.begin(), out.end());
OutputDebugString(stemp.c_str());
#endif
}
}
}

View file

@ -399,25 +399,9 @@ class TaskTimer
std::chrono::milliseconds start_timer;
std::string task_name;
public:
TaskTimer(std::string task_name):
task_name(task_name.empty() ? "task" : task_name)
{
start_timer = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
}
TaskTimer(std::string task_name);
~TaskTimer()
{
std::chrono::milliseconds stop_timer = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
auto process_duration = std::chrono::milliseconds(stop_timer - start_timer).count();
std::string out = (boost::format("\n!!! %1% duration = %2% ms \n\n") % task_name % process_duration).str();
printf(out.c_str());
#ifdef __WXMSW__
std::wstring stemp = std::wstring(out.begin(), out.end());
OutputDebugString(stemp.c_str());
#endif
}
~TaskTimer();
};
}}

View file

@ -30,7 +30,7 @@ void GLGizmoFdmSupports::on_shutdown()
std::string GLGizmoFdmSupports::on_get_name() const
{
return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data();
return (_L("Paint-on supports") + " [L]").ToUTF8().data();
}
@ -50,13 +50,15 @@ bool GLGizmoFdmSupports::on_init()
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _L("Remove selection");
m_desc["remove_all"] = _L("Remove all selection");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
return true;
}
void GLGizmoFdmSupports::on_render() const
void GLGizmoFdmSupports::render_painter_gizmo() const
{
const Selection& selection = m_parent.get_selection();
@ -86,18 +88,15 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
if (! m_setting_angle) {
m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
std::vector<std::string> cursor_types;
cursor_types.push_back(_L("Circle").ToUTF8().data());
cursor_types.push_back(_L("Sphere").ToUTF8().data());
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
m_imgui->calc_text_size(m_desc.at("reset_direction")).x)
+ m_imgui->scaled(1.5f);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float cursor_type_combo_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f);
const float cursor_type_combo_width = std::max(m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[0].c_str())).x,
m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[1].c_str())).x)
const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_width1 = m_imgui->calc_text_size(m_desc["circle"]).x
+ m_imgui->scaled(2.5f);
const float cursor_type_radio_width2 = m_imgui->calc_text_size(m_desc["sphere"]).x
+ m_imgui->scaled(2.5f);
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float minimal_slider_width = m_imgui->scaled(4.f);
@ -114,7 +113,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left);
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, cursor_type_combo_left + cursor_type_combo_width);
window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_width1 + cursor_type_radio_width2);
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption);
@ -150,6 +149,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(cursor_slider_left);
ImGui::PushItemWidth(window_width - cursor_slider_left);
@ -163,25 +163,49 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
}
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_type"));
ImGui::SameLine(window_width - cursor_type_combo_width - m_imgui->scaled(0.5f));
ImGui::PushItemWidth(cursor_type_combo_width);
int selection = int(m_cursor_type);
m_imgui->combo("", cursor_types, selection);
m_cursor_type = TriangleSelector::CursorType(selection);
ImGui::SameLine(cursor_type_radio_left + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_width1);
bool sphere_sel = m_cursor_type == TriangleSelector::CursorType::SPHERE;
if (m_imgui->radio_button(m_desc["sphere"], sphere_sel))
sphere_sel = true;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Sphere paints all facets inside, regardless of their orientation.\n\n"
"Circle ignores facets facing away from the camera.").ToUTF8().data());
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_radio_left + cursor_type_radio_width2 + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_width2);
if (m_imgui->radio_button(m_desc["circle"], ! sphere_sel))
sphere_sel = false;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_cursor_type = sphere_sel
? TriangleSelector::CursorType::SPHERE
: TriangleSelector::CursorType::CIRCLE;
ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f)
if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
}
else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){
@ -206,23 +230,24 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
m_imgui->end();
}
else {
std::string name = "Autoset custom supports";
m_imgui->begin(wxString(name), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
m_imgui->text("Threshold:");
m_imgui->begin(_L("Autoset custom supports"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Threshold:") + " " + _L("deg"));
ImGui::SameLine();
if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, "%.f"))
m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg});
if (m_imgui->button("Enforce"))
m_parent.set_slope_normal_angle(90.f - m_angle_threshold_deg);
if (m_imgui->button(_L("Enforce")))
select_facets_by_angle(m_angle_threshold_deg, false);
ImGui::SameLine();
if (m_imgui->button("Block"))
if (m_imgui->button(_L("Block")))
select_facets_by_angle(m_angle_threshold_deg, true);
ImGui::SameLine();
if (m_imgui->button("Cancel"))
if (m_imgui->button(_L("Cancel")))
m_setting_angle = false;
m_imgui->end();
if (! m_setting_angle) {
m_parent.use_slope(false);
bool needs_update = !(m_setting_angle && m_parent.is_using_slope());
if (needs_update) {
m_parent.use_slope(m_setting_angle);
m_parent.set_as_dirty();
}
}
@ -282,7 +307,7 @@ void GLGizmoFdmSupports::update_model_object() const
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get());
updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get());
}
if (updated)
@ -309,7 +334,7 @@ void GLGizmoFdmSupports::update_from_model_object()
const TriangleMesh* mesh = &mv->mesh();
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data());
m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data());
}
}

View file

@ -13,14 +13,14 @@ public:
GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoPainterBase(parent, icon_filename, sprite_id) {}
void render_painter_gizmo() const override;
protected:
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
private:
bool on_init() override;
void on_render() const override;
void on_render_for_picking() const override {}
void update_model_object() const override;
void update_from_model_object() override;

View file

@ -28,7 +28,6 @@ bool GLGizmoFlatten::on_init()
void GLGizmoFlatten::on_set_state()
{
}
CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const
@ -38,7 +37,7 @@ CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const
std::string GLGizmoFlatten::on_get_name() const
{
return (_(L("Place on face")) + " [F]").ToUTF8().data();
return (_L("Place on face") + " [F]").ToUTF8().data();
}
bool GLGizmoFlatten::on_is_activable() const
@ -48,8 +47,7 @@ bool GLGizmoFlatten::on_is_activable() const
void GLGizmoFlatten::on_start_dragging()
{
if (m_hover_id != -1)
{
if (m_hover_id != -1) {
assert(m_planes_valid);
m_normal = m_planes[m_hover_id].normal;
m_starting_center = m_parent.get_selection().get_bounding_box().center();
@ -65,16 +63,14 @@ void GLGizmoFlatten::on_render() const
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glEnable(GL_BLEND));
if (selection.is_single_full_instance())
{
if (selection.is_single_full_instance()) {
const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix();
glsafe(::glPushMatrix());
glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()));
glsafe(::glMultMatrixd(m.data()));
if (this->is_plane_update_necessary())
const_cast<GLGizmoFlatten*>(this)->update_planes();
for (int i = 0; i < (int)m_planes.size(); ++i)
{
for (int i = 0; i < (int)m_planes.size(); ++i) {
if (i == m_hover_id)
glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.75f));
else
@ -97,16 +93,14 @@ void GLGizmoFlatten::on_render_for_picking() const
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glDisable(GL_BLEND));
if (selection.is_single_full_instance())
{
if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) {
const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix();
glsafe(::glPushMatrix());
glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()));
glsafe(::glMultMatrixd(m.data()));
if (this->is_plane_update_necessary())
const_cast<GLGizmoFlatten*>(this)->update_planes();
for (int i = 0; i < (int)m_planes.size(); ++i)
{
for (int i = 0; i < (int)m_planes.size(); ++i) {
glsafe(::glColor4fv(picking_color_component(i).data()));
m_planes[i].vbo.render();
}
@ -129,8 +123,7 @@ void GLGizmoFlatten::update_planes()
{
const ModelObject* mo = m_c->selection_info()->model_object();
TriangleMesh ch;
for (const ModelVolume* vol : mo->volumes)
{
for (const ModelVolume* vol : mo->volumes) {
if (vol->type() != ModelVolumeType::MODEL_PART)
continue;
TriangleMesh vol_ch = vol->get_convex_hull();

View file

@ -20,7 +20,6 @@ namespace GUI {
GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
{
m_clipping_plane.reset(new ClippingPlane());
// Make sphere and save it into a vertex buffer.
const TriangleMesh sphere_mesh = make_sphere(1., (2*M_PI)/24.);
for (size_t i=0; i<sphere_mesh.its.vertices.size(); ++i)
@ -80,7 +79,7 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); } );
glsafe(::glPolygonOffset(-1.0, 1.0));
glsafe(::glPolygonOffset(-5.0, -5.0));
// Take care of the clipping plane. The normal of the clipping plane is
// saved with opposite sign than we need to pass to OpenGL (FIXME)
@ -127,8 +126,26 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const
void GLGizmoPainterBase::render_cursor() const
{
// First check that the mouse pointer is on an object.
const ModelObject* mo = m_c->selection_info()->model_object();
const Selection& selection = m_parent.get_selection();
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
const Camera& camera = wxGetApp().plater()->get_camera();
// Precalculate transformations of individual meshes.
std::vector<Transform3d> trafo_matrices;
for (const ModelVolume* mv : mo->volumes) {
if (mv->is_model_part())
trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix());
}
// Raycast and return if there's no hit.
update_raycast_cache(m_parent.get_local_mouse_position(), camera, trafo_matrices);
if (m_rr.mesh_id == -1)
return;
if (m_cursor_type == TriangleSelector::SPHERE)
render_cursor_sphere();
render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
else
render_cursor_circle();
}
@ -177,34 +194,17 @@ void GLGizmoPainterBase::render_cursor_circle() const
glsafe(::glPopAttrib());
glsafe(::glPopMatrix());
glsafe(::glEnable(GL_DEPTH_TEST));
}
void GLGizmoPainterBase::render_cursor_sphere() const
void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const
{
Vec2d mouse_position(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1));
const ModelObject* mo = m_c->selection_info()->model_object();
const Selection& selection = m_parent.get_selection();
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
const Camera& camera = wxGetApp().plater()->get_camera();
// Precalculate transformations of individual meshes.
std::vector<Transform3d> trafo_matrices;
for (const ModelVolume* mv : mo->volumes) {
if (mv->is_model_part())
trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix());
}
update_raycast_cache(mouse_position, camera, trafo_matrices);
if (m_rr.mesh_id == -1)
return;
const Transform3d& complete_matrix = trafo_matrices[m_rr.mesh_id];
const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(complete_matrix).get_matrix(true, true, false, true).inverse();
const bool is_left_handed = Geometry::Transformation(complete_matrix).is_left_handed();
const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse();
const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed();
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(complete_matrix.data()));
glsafe(::glMultMatrixd(trafo.data()));
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
glsafe(::glTranslatef(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)));
glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data()));
@ -331,38 +331,28 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
// The mouse button click detection is enabled when there is a valid hit
// or when the user clicks the clipping plane. Missing the object entirely
// The mouse button click detection is enabled when there is a valid hit.
// Missing the object entirely
// shall not capture the mouse.
if (m_rr.mesh_id != -1 || m_rr.clipped_mesh_was_hit) {
if (m_rr.mesh_id != -1) {
if (m_button_down == Button::None)
m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
}
if (m_rr.mesh_id == -1) {
// In case we have no valid hit, we can return. The event will
// be stopped in following two cases:
// 1. clicking the clipping plane
// 2. dragging while painting (to prevent scene rotations and moving the object)
return m_rr.clipped_mesh_was_hit
|| dragging_while_painting;
// In case we have no valid hit, we can return. The event will be stopped when
// dragging while painting (to prevent scene rotations and moving the object)
return dragging_while_painting;
}
const Transform3d& trafo_matrix = trafo_matrices[m_rr.mesh_id];
// Calculate how far can a point be from the line (in mesh coords).
// FIXME: The scaling of the mesh can be non-uniform.
const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor();
const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.;
const float limit = m_cursor_radius/avg_scaling;
// Calculate direction from camera to the hit (in mesh coords):
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
Vec3f dir = (m_rr.hit - camera_pos).normalized();
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, m_rr.facet, camera_pos,
dir, limit, m_cursor_type, new_state);
m_cursor_radius, m_cursor_type, new_state, trafo_matrix);
m_last_mouse_click = mouse_position;
}
@ -417,7 +407,6 @@ void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position,
return;
}
bool clipped_mesh_was_hit{false};
Vec3f normal = Vec3f::Zero();
Vec3f hit = Vec3f::Zero();
size_t facet = 0;
@ -435,14 +424,12 @@ void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position,
camera,
hit,
normal,
m_clipping_plane.get(),
m_c->object_clipper()->get_clipping_plane(),
&facet))
{
// In case this hit is clipped, skip it.
if (is_mesh_point_clipped(hit.cast<double>(), trafo_matrices[mesh_id])) {
clipped_mesh_was_hit = true;
if (is_mesh_point_clipped(hit.cast<double>(), trafo_matrices[mesh_id]))
continue;
}
// Is this hit the closest to the camera so far?
double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm();
@ -455,7 +442,7 @@ void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position,
}
}
m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet, clipped_mesh_was_hit};
m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet};
}
bool GLGizmoPainterBase::on_is_activable() const

View file

@ -59,6 +59,8 @@ class GLGizmoPainterBase : public GLGizmoBase
private:
ObjectID m_old_mo_id;
size_t m_old_volumes_size = 0;
virtual void on_render() const {}
virtual void on_render_for_picking() const {}
public:
GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
@ -66,11 +68,17 @@ public:
void set_painter_gizmo_data(const Selection& selection);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
// Following function renders the triangles and cursor. Having this separated
// from usual on_render method allows to render them before transparent objects,
// so they can be seen inside them. The usual on_render is called after all
// volumes (including transparent ones) are rendered.
virtual void render_painter_gizmo() const = 0;
protected:
void render_triangles(const Selection& selection) const;
void render_cursor() const;
void render_cursor_circle() const;
void render_cursor_sphere() const;
void render_cursor_sphere(const Transform3d& trafo) const;
virtual void update_model_object() const = 0;
virtual void update_from_model_object() = 0;
void activate_internal_undo_redo_stack(bool activate);
@ -93,8 +101,6 @@ private:
const Camera& camera,
const std::vector<Transform3d>& trafo_matrices) const;
float m_clipping_plane_distance = 0.f;
std::unique_ptr<ClippingPlane> m_clipping_plane;
GLIndexedVertexArray m_vbo_sphere;
bool m_internal_stack_active = false;
@ -118,7 +124,6 @@ private:
int mesh_id;
Vec3f hit;
size_t facet;
bool clipped_mesh_was_hit;
};
mutable RaycastResult m_rr;

View file

@ -33,6 +33,8 @@ bool GLGizmoSeam::on_init()
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _L("Remove selection");
m_desc["remove_all"] = _L("Remove all selection");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
return true;
}
@ -41,12 +43,12 @@ bool GLGizmoSeam::on_init()
std::string GLGizmoSeam::on_get_name() const
{
return (_(L("Seam Editing")) + " [P]").ToUTF8().data();
return (_L("Seam painting") + " [P]").ToUTF8().data();
}
void GLGizmoSeam::on_render() const
void GLGizmoSeam::render_painter_gizmo() const
{
const Selection& selection = m_parent.get_selection();
@ -71,12 +73,6 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
const float approx_height = m_imgui->scaled(14.0f);
y = std::min(y, bottom_limit - approx_height);
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
std::vector<std::string> cursor_types;
cursor_types.push_back(_L("Circle").ToUTF8().data());
cursor_types.push_back(_L("Sphere").ToUTF8().data());
m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
@ -84,9 +80,10 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
m_imgui->calc_text_size(m_desc.at("reset_direction")).x)
+ m_imgui->scaled(1.5f);
const float cursor_size_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float cursor_type_combo_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f);
const float cursor_type_combo_width = std::max(m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[0].c_str())).x,
m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[1].c_str())).x)
const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_width1 = m_imgui->calc_text_size(m_desc["circle"]).x
+ m_imgui->scaled(2.5f);
const float cursor_type_radio_width2 = m_imgui->calc_text_size(m_desc["sphere"]).x
+ m_imgui->scaled(2.5f);
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float minimal_slider_width = m_imgui->scaled(4.f);
@ -103,7 +100,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
float window_width = minimal_slider_width + std::max(cursor_size_slider_left, clipping_slider_left);
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, cursor_type_combo_left + cursor_type_combo_width);
window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_width1 + cursor_type_radio_width2);
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
@ -147,21 +144,41 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
}
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_type"));
ImGui::SameLine(window_width - cursor_type_combo_width - m_imgui->scaled(0.5f));
ImGui::PushItemWidth(cursor_type_combo_width);
int selection = int(m_cursor_type);
m_imgui->combo("", cursor_types, selection);
m_cursor_type = TriangleSelector::CursorType(selection);
ImGui::SameLine(cursor_type_radio_left + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_width1);
bool sphere_sel = m_cursor_type == TriangleSelector::CursorType::SPHERE;
if (m_imgui->radio_button(m_desc["sphere"], sphere_sel))
sphere_sel = true;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Sphere paints all facets inside, regardless of their orientation.\n\n"
"Circle ignores facets facing away from the camera.").ToUTF8().data());
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_radio_left + cursor_type_radio_width2 + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_width2);
if (m_imgui->radio_button(m_desc["circle"], ! sphere_sel))
sphere_sel = false;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_cursor_type = sphere_sel
? TriangleSelector::CursorType::SPHERE
: TriangleSelector::CursorType::CIRCLE;
ImGui::Separator();
@ -202,7 +219,7 @@ void GLGizmoSeam::update_model_object() const
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->m_seam_facets.set(*m_triangle_selectors[idx].get());
updated |= mv->seam_facets.set(*m_triangle_selectors[idx].get());
}
if (updated)
@ -229,7 +246,7 @@ void GLGizmoSeam::update_from_model_object()
const TriangleMesh* mesh = &mv->mesh();
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
m_triangle_selectors.back()->deserialize(mv->m_seam_facets.get_data());
m_triangle_selectors.back()->deserialize(mv->seam_facets.get_data());
}
}

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