mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-27 02:31:10 -06:00
Merge branch 'master' into lm_tm_hollowing
This commit is contained in:
commit
537260494d
185 changed files with 83280 additions and 4591 deletions
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.8)
|
||||
project(libslic3r_gui)
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
|
||||
include(PrecompiledHeader)
|
||||
|
||||
|
|
@ -107,6 +107,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/Camera.hpp
|
||||
GUI/wxExtensions.cpp
|
||||
GUI/wxExtensions.hpp
|
||||
GUI/ExtruderSequenceDialog.cpp
|
||||
GUI/ExtruderSequenceDialog.hpp
|
||||
GUI/WipeTowerDialog.cpp
|
||||
GUI/WipeTowerDialog.hpp
|
||||
GUI/RammingChart.cpp
|
||||
|
|
@ -138,6 +140,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/ProgressStatusBar.cpp
|
||||
GUI/PrintHostDialogs.cpp
|
||||
GUI/PrintHostDialogs.hpp
|
||||
GUI/Mouse3DController.cpp
|
||||
GUI/Mouse3DController.hpp
|
||||
Utils/Http.cpp
|
||||
Utils/Http.hpp
|
||||
Utils/FixModelByWin10.cpp
|
||||
|
|
@ -172,7 +176,7 @@ add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES})
|
|||
|
||||
encoding_check(libslic3r_gui)
|
||||
|
||||
target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui ${GLEW_LIBRARIES})
|
||||
target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL OpenGL::GLU hidapi)
|
||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||
add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE)
|
||||
endif ()
|
||||
|
|
|
|||
|
|
@ -286,16 +286,21 @@ Index::const_iterator Index::find(const Semver &ver) const
|
|||
return (it == m_configs.end() || it->config_version == ver) ? it : m_configs.end();
|
||||
}
|
||||
|
||||
Index::const_iterator Index::recommended() const
|
||||
Index::const_iterator Index::recommended(const Semver &slic3r_version) const
|
||||
{
|
||||
const_iterator highest = this->end();
|
||||
for (const_iterator it = this->begin(); it != this->end(); ++ it)
|
||||
if (it->is_current_slic3r_supported() &&
|
||||
if (it->is_slic3r_supported(slic3r_version) &&
|
||||
(highest == this->end() || highest->config_version < it->config_version))
|
||||
highest = it;
|
||||
return highest;
|
||||
}
|
||||
|
||||
Index::const_iterator Index::recommended() const
|
||||
{
|
||||
return this->recommended(Slic3r::SEMVER);
|
||||
}
|
||||
|
||||
std::vector<Index> Index::load_db()
|
||||
{
|
||||
boost::filesystem::path cache_dir = boost::filesystem::path(Slic3r::data_dir()) / "cache";
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ public:
|
|||
// Returns configs().end() if such version does not exist in the index. This shall never happen
|
||||
// if the index is valid.
|
||||
const_iterator recommended() const;
|
||||
// Recommended config for a provided slic3r version. Used when checking for slic3r update (slic3r_version is the old one read out from PrusaSlicer.ini)
|
||||
const_iterator recommended(const Semver &slic3r_version) const;
|
||||
|
||||
// Returns the filesystem path from which this index has originally been loaded
|
||||
const boost::filesystem::path& path() const { return m_path; }
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@ void Bed3D::Axes::render() const
|
|||
glsafe(::glPopMatrix());
|
||||
|
||||
glsafe(::glDisable(GL_LIGHTING));
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
}
|
||||
|
||||
void Bed3D::Axes::render_axis(double length) const
|
||||
|
|
@ -264,11 +265,14 @@ Point Bed3D::point_projection(const Point& point) const
|
|||
return m_polygon.point_projection(point);
|
||||
}
|
||||
|
||||
void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const
|
||||
void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor, bool show_axes) const
|
||||
{
|
||||
m_scale_factor = scale_factor;
|
||||
|
||||
render_axes();
|
||||
if (show_axes)
|
||||
render_axes();
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
switch (m_type)
|
||||
{
|
||||
|
|
@ -280,6 +284,8 @@ void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const
|
|||
default:
|
||||
case Custom: { render_custom(canvas, theta > 90.0f); break; }
|
||||
}
|
||||
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
}
|
||||
|
||||
void Bed3D::calc_bounding_boxes() const
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public:
|
|||
bool contains(const Point& point) const;
|
||||
Point point_projection(const Point& point) const;
|
||||
|
||||
void render(GLCanvas3D& canvas, float theta, float scale_factor) const;
|
||||
void render(GLCanvas3D& canvas, float theta, float scale_factor, bool show_axes) const;
|
||||
|
||||
private:
|
||||
void calc_bounding_boxes() const;
|
||||
|
|
|
|||
|
|
@ -303,6 +303,8 @@ public:
|
|||
int instance_id;
|
||||
bool operator==(const CompositeID &rhs) const { return object_id == rhs.object_id && volume_id == rhs.volume_id && instance_id == rhs.instance_id; }
|
||||
bool operator!=(const CompositeID &rhs) const { return ! (*this == rhs); }
|
||||
bool operator< (const CompositeID &rhs) const
|
||||
{ return object_id < rhs.object_id || (object_id == rhs.object_id && (volume_id < rhs.volume_id || (volume_id == rhs.volume_id && instance_id < rhs.instance_id))); }
|
||||
};
|
||||
CompositeID composite_id;
|
||||
// Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID,
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ void AppConfig::load()
|
|||
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
|
||||
throw std::runtime_error(
|
||||
_utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
|
||||
"Try to manualy delete the file to recover from the error. Your user profiles will not be affected.")) +
|
||||
"Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) +
|
||||
"\n\n" + AppConfig::config_path() + "\n\n" + ex.what());
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +271,80 @@ void AppConfig::set_recent_projects(const std::vector<std::string>& recent_proje
|
|||
}
|
||||
}
|
||||
|
||||
void AppConfig::set_mouse_device(const std::string& name, double translation_speed, double translation_deadzone, float rotation_speed, float rotation_deadzone)
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
it = m_storage.insert(std::map<std::string, std::map<std::string, std::string>>::value_type(key, std::map<std::string, std::string>())).first;
|
||||
|
||||
it->second.clear();
|
||||
it->second["translation_speed"] = std::to_string(translation_speed);
|
||||
it->second["translation_deadzone"] = std::to_string(translation_deadzone);
|
||||
it->second["rotation_speed"] = std::to_string(rotation_speed);
|
||||
it->second["rotation_deadzone"] = std::to_string(rotation_deadzone);
|
||||
}
|
||||
|
||||
bool AppConfig::get_mouse_device_translation_speed(const std::string& name, double& speed)
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
|
||||
auto it_val = it->second.find("translation_speed");
|
||||
if (it_val == it->second.end())
|
||||
return false;
|
||||
|
||||
speed = ::atof(it_val->second.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppConfig::get_mouse_device_translation_deadzone(const std::string& name, double& deadzone)
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
|
||||
auto it_val = it->second.find("translation_deadzone");
|
||||
if (it_val == it->second.end())
|
||||
return false;
|
||||
|
||||
deadzone = ::atof(it_val->second.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppConfig::get_mouse_device_rotation_speed(const std::string& name, float& speed)
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
|
||||
auto it_val = it->second.find("rotation_speed");
|
||||
if (it_val == it->second.end())
|
||||
return false;
|
||||
|
||||
speed = (float)::atof(it_val->second.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppConfig::get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone)
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
|
||||
auto it_val = it->second.find("rotation_deadzone");
|
||||
if (it_val == it->second.end())
|
||||
return false;
|
||||
|
||||
deadzone = (float)::atof(it_val->second.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppConfig::update_config_dir(const std::string &dir)
|
||||
{
|
||||
this->set("recent", "config_directory", dir);
|
||||
|
|
|
|||
|
|
@ -131,8 +131,15 @@ public:
|
|||
std::vector<std::string> get_recent_projects() const;
|
||||
void set_recent_projects(const std::vector<std::string>& recent_projects);
|
||||
|
||||
void set_mouse_device(const std::string& name, double translation_speed, double translation_deadzone, float rotation_speed, float rotation_deadzone);
|
||||
bool get_mouse_device_translation_speed(const std::string& name, double& speed);
|
||||
bool get_mouse_device_translation_deadzone(const std::string& name, double& deadzone);
|
||||
bool get_mouse_device_rotation_speed(const std::string& name, float& speed);
|
||||
bool get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone);
|
||||
|
||||
static const std::string SECTION_FILAMENTS;
|
||||
static const std::string SECTION_MATERIALS;
|
||||
|
||||
private:
|
||||
// Map of section, name -> value
|
||||
std::map<std::string, std::map<std::string, std::string>> m_storage;
|
||||
|
|
|
|||
|
|
@ -20,9 +20,6 @@
|
|||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/GCode/PostProcessor.hpp"
|
||||
#include "libslic3r/GCode/PreviewData.hpp"
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include <cassert>
|
||||
|
|
@ -36,6 +33,7 @@
|
|||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include "I18N.hpp"
|
||||
#include "GUI.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
|
@ -91,11 +89,19 @@ void BackgroundSlicingProcess::process_fff()
|
|||
m_print->process();
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id));
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_data);
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb);
|
||||
#else
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
|
||||
if (m_fff_print->model().custom_gcode_per_height != GUI::wxGetApp().model().custom_gcode_per_height) {
|
||||
GUI::wxGetApp().model().custom_gcode_per_height = m_fff_print->model().custom_gcode_per_height;
|
||||
// #ys_FIXME : controll text
|
||||
GUI::show_info(nullptr, _(L("To except of redundant tool manipulation, \n"
|
||||
"Color change(s) for unused extruder(s) was(were) deleted")), _(L("Info")));
|
||||
}
|
||||
|
||||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
if (! m_export_path.empty()) {
|
||||
//FIXME localize the messages
|
||||
// Perform the final post-processing of the export path by applying the print statistics over the file name.
|
||||
|
|
@ -111,7 +117,7 @@ void BackgroundSlicingProcess::process_fff()
|
|||
m_print->set_status(100, _utf8(L("Slicing complete")));
|
||||
}
|
||||
this->set_step_done(bspsGCodeFinalize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
|
|
@ -139,9 +145,12 @@ void BackgroundSlicingProcess::process_sla()
|
|||
m_sla_print->export_raster(zipper);
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
if (m_thumbnail_data != nullptr)
|
||||
if (m_thumbnail_cb != nullptr)
|
||||
{
|
||||
for (const ThumbnailData& data : *m_thumbnail_data)
|
||||
ThumbnailsList thumbnails;
|
||||
m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true);
|
||||
// m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, true, true); // renders also supports and pad
|
||||
for (const ThumbnailData& data : thumbnails)
|
||||
{
|
||||
if (data.is_valid())
|
||||
write_thumbnail(zipper, data);
|
||||
|
|
@ -461,9 +470,12 @@ void BackgroundSlicingProcess::prepare_upload()
|
|||
Zipper zipper{source_path.string()};
|
||||
m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string());
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
if (m_thumbnail_data != nullptr)
|
||||
if (m_thumbnail_cb != nullptr)
|
||||
{
|
||||
for (const ThumbnailData& data : *m_thumbnail_data)
|
||||
ThumbnailsList thumbnails;
|
||||
m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true);
|
||||
// m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, true, true); // renders also supports and pad
|
||||
for (const ThumbnailData& data : thumbnails)
|
||||
{
|
||||
if (data.is_valid())
|
||||
write_thumbnail(zipper, data);
|
||||
|
|
|
|||
|
|
@ -17,9 +17,6 @@ namespace Slic3r {
|
|||
|
||||
class DynamicPrintConfig;
|
||||
class GCodePreviewData;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
struct ThumbnailData;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
class Model;
|
||||
class SLAPrint;
|
||||
|
||||
|
|
@ -53,7 +50,7 @@ public:
|
|||
void set_sla_print(SLAPrint *print) { m_sla_print = print; }
|
||||
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void set_thumbnail_data(const std::vector<ThumbnailData>* data) { m_thumbnail_data = data; }
|
||||
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished
|
||||
|
|
@ -135,6 +132,11 @@ public:
|
|||
// This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs),
|
||||
// and it does not account for the OctoPrint scheduling.
|
||||
bool finished() const { return m_print->finished(); }
|
||||
|
||||
void set_force_update_print_regions(bool force_update_print_regions) {
|
||||
if (m_fff_print)
|
||||
m_fff_print->set_force_update_print_regions(force_update_print_regions);
|
||||
}
|
||||
|
||||
private:
|
||||
void thread_proc();
|
||||
|
|
@ -159,8 +161,8 @@ private:
|
|||
// Data structure, to which the G-code export writes its annotations.
|
||||
GCodePreviewData *m_gcode_preview_data = nullptr;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// Data structures, used to write thumbnails into gcode.
|
||||
const std::vector<ThumbnailData>* m_thumbnail_data = nullptr;
|
||||
// Callback function, used to write thumbnails into gcode.
|
||||
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
|
||||
std::string m_temp_output_path;
|
||||
|
|
|
|||
|
|
@ -91,10 +91,16 @@ void Camera::select_next_type()
|
|||
|
||||
void Camera::set_target(const Vec3d& target)
|
||||
{
|
||||
m_target = target;
|
||||
m_target(0) = clamp(m_scene_box.min(0), m_scene_box.max(0), m_target(0));
|
||||
m_target(1) = clamp(m_scene_box.min(1), m_scene_box.max(1), m_target(1));
|
||||
m_target(2) = clamp(m_scene_box.min(2), m_scene_box.max(2), m_target(2));
|
||||
BoundingBoxf3 test_box = m_scene_box;
|
||||
test_box.translate(-m_scene_box.center());
|
||||
// We may let this factor be customizable
|
||||
static const double ScaleFactor = 1.5;
|
||||
test_box.scale(ScaleFactor);
|
||||
test_box.translate(m_scene_box.center());
|
||||
|
||||
m_target(0) = clamp(test_box.min(0), test_box.max(0), target(0));
|
||||
m_target(1) = clamp(test_box.min(1), test_box.max(1), target(1));
|
||||
m_target(2) = clamp(test_box.min(2), test_box.max(2), target(2));
|
||||
}
|
||||
|
||||
void Camera::set_theta(float theta, bool apply_limit)
|
||||
|
|
@ -109,20 +115,20 @@ void Camera::set_theta(float theta, bool apply_limit)
|
|||
}
|
||||
}
|
||||
|
||||
void Camera::set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h)
|
||||
void Camera::update_zoom(double delta_zoom)
|
||||
{
|
||||
zoom = std::max(std::min(zoom, 4.0), -4.0) / 10.0;
|
||||
zoom = m_zoom / (1.0 - zoom);
|
||||
set_zoom(m_zoom / (1.0 - std::max(std::min(delta_zoom, 4.0), -4.0) * 0.1));
|
||||
}
|
||||
|
||||
void Camera::set_zoom(double zoom)
|
||||
{
|
||||
// Don't allow to zoom too far outside the scene.
|
||||
double zoom_min = calc_zoom_to_bounding_box_factor(max_box, canvas_w, canvas_h);
|
||||
double zoom_min = calc_zoom_to_bounding_box_factor(m_scene_box, (int)m_viewport[2], (int)m_viewport[3]);
|
||||
if (zoom_min > 0.0)
|
||||
zoom = std::max(zoom, zoom_min * 0.7);
|
||||
|
||||
// Don't allow to zoom too close to the scene.
|
||||
zoom = std::min(zoom, 100.0);
|
||||
|
||||
m_zoom = zoom;
|
||||
m_zoom = std::min(zoom, 100.0);
|
||||
}
|
||||
|
||||
bool Camera::select_view(const std::string& direction)
|
||||
|
|
@ -190,7 +196,7 @@ void Camera::apply_view_matrix() const
|
|||
glsafe(::glGetDoublev(GL_MODELVIEW_MATRIX, m_view_matrix.data()));
|
||||
}
|
||||
|
||||
void Camera::apply_projection(const BoundingBoxf3& box) const
|
||||
void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double far_z) const
|
||||
{
|
||||
set_distance(DefaultDistance);
|
||||
|
||||
|
|
@ -201,6 +207,12 @@ void Camera::apply_projection(const BoundingBoxf3& box) const
|
|||
{
|
||||
m_frustrum_zs = calc_tight_frustrum_zs_around(box);
|
||||
|
||||
if (near_z > 0.0)
|
||||
m_frustrum_zs.first = std::min(m_frustrum_zs.first, near_z);
|
||||
|
||||
if (far_z > 0.0)
|
||||
m_frustrum_zs.second = std::max(m_frustrum_zs.second, far_z);
|
||||
|
||||
w = 0.5 * (double)m_viewport[2];
|
||||
h = 0.5 * (double)m_viewport[3];
|
||||
|
||||
|
|
@ -310,7 +322,6 @@ void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canv
|
|||
void Camera::debug_render() const
|
||||
{
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
imgui.set_next_window_bg_alpha(0.5f);
|
||||
imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
std::string type = get_type_as_string();
|
||||
|
|
@ -355,27 +366,10 @@ std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBo
|
|||
|
||||
while (true)
|
||||
{
|
||||
ret = std::make_pair(DBL_MAX, -DBL_MAX);
|
||||
|
||||
// box vertices in world space
|
||||
std::vector<Vec3d> vertices;
|
||||
vertices.reserve(8);
|
||||
vertices.push_back(box.min);
|
||||
vertices.emplace_back(box.max(0), box.min(1), box.min(2));
|
||||
vertices.emplace_back(box.max(0), box.max(1), box.min(2));
|
||||
vertices.emplace_back(box.min(0), box.max(1), box.min(2));
|
||||
vertices.emplace_back(box.min(0), box.min(1), box.max(2));
|
||||
vertices.emplace_back(box.max(0), box.min(1), box.max(2));
|
||||
vertices.push_back(box.max);
|
||||
vertices.emplace_back(box.min(0), box.max(1), box.max(2));
|
||||
|
||||
// set the Z range in eye coordinates (negative Zs are in front of the camera)
|
||||
for (const Vec3d& v : vertices)
|
||||
{
|
||||
double z = -(m_view_matrix * v)(2);
|
||||
ret.first = std::min(ret.first, z);
|
||||
ret.second = std::max(ret.second, z);
|
||||
}
|
||||
// box in eye space
|
||||
BoundingBoxf3 eye_box = box.transformed(m_view_matrix);
|
||||
ret.first = -eye_box.max(2);
|
||||
ret.second = -eye_box.min(2);
|
||||
|
||||
// apply margin
|
||||
ret.first -= FrustrumZMargin;
|
||||
|
|
@ -434,8 +428,10 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca
|
|||
vertices.push_back(box.max);
|
||||
vertices.emplace_back(box.min(0), box.max(1), box.max(2));
|
||||
|
||||
double max_x = 0.0;
|
||||
double max_y = 0.0;
|
||||
double min_x = DBL_MAX;
|
||||
double min_y = DBL_MAX;
|
||||
double max_x = -DBL_MAX;
|
||||
double max_y = -DBL_MAX;
|
||||
|
||||
#if !ENABLE_THUMBNAIL_GENERATOR
|
||||
// margin factor to give some empty space around the box
|
||||
|
|
@ -452,17 +448,24 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca
|
|||
double x_on_plane = proj_on_plane.dot(right);
|
||||
double y_on_plane = proj_on_plane.dot(up);
|
||||
|
||||
max_x = std::max(max_x, std::abs(x_on_plane));
|
||||
max_y = std::max(max_y, std::abs(y_on_plane));
|
||||
min_x = std::min(min_x, x_on_plane);
|
||||
min_y = std::min(min_y, y_on_plane);
|
||||
max_x = std::max(max_x, x_on_plane);
|
||||
max_y = std::max(max_y, y_on_plane);
|
||||
}
|
||||
|
||||
if ((max_x == 0.0) || (max_y == 0.0))
|
||||
double dx = max_x - min_x;
|
||||
double dy = max_y - min_y;
|
||||
if ((dx <= 0.0) || (dy <= 0.0))
|
||||
return -1.0f;
|
||||
|
||||
max_x *= margin_factor;
|
||||
max_y *= margin_factor;
|
||||
double med_x = 0.5 * (max_x + min_x);
|
||||
double med_y = 0.5 * (max_y + min_y);
|
||||
|
||||
return std::min((double)canvas_w / (2.0 * max_x), (double)canvas_h / (2.0 * max_y));
|
||||
dx *= margin_factor;
|
||||
dy *= margin_factor;
|
||||
|
||||
return std::min((double)canvas_w / dx, (double)canvas_h / dy);
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
|
|
@ -524,7 +527,7 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canv
|
|||
double dx = margin_factor * (max_x - min_x);
|
||||
double dy = margin_factor * (max_y - min_y);
|
||||
|
||||
if ((dx == 0.0) || (dy == 0.0))
|
||||
if ((dx <= 0.0) || (dy <= 0.0))
|
||||
return -1.0f;
|
||||
|
||||
return std::min((double)canvas_w / dx, (double)canvas_h / dy);
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ public:
|
|||
void set_theta(float theta, bool apply_limit);
|
||||
|
||||
double get_zoom() const { return m_zoom; }
|
||||
void set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h);
|
||||
void set_zoom(double zoom) { m_zoom = zoom; }
|
||||
void update_zoom(double delta_zoom);
|
||||
void set_zoom(double zoom);
|
||||
|
||||
const BoundingBoxf3& get_scene_box() const { return m_scene_box; }
|
||||
void set_scene_box(const BoundingBoxf3& box) { m_scene_box = box; }
|
||||
|
|
@ -95,7 +95,9 @@ public:
|
|||
|
||||
void apply_viewport(int x, int y, unsigned int w, unsigned int h) const;
|
||||
void apply_view_matrix() const;
|
||||
void apply_projection(const BoundingBoxf3& box) const;
|
||||
// Calculates and applies the projection matrix tighting the frustrum z range around the given box.
|
||||
// If larger z span is needed, pass the desired values of near and far z (negative values are ignored)
|
||||
void apply_projection(const BoundingBoxf3& box, double near_z = -1.0, double far_z = -1.0) const;
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor);
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
|||
"- no top solid layers\n"
|
||||
"- 0% fill density\n"
|
||||
"- no support material\n"
|
||||
"- no ensure_vertical_shell_thickness"));
|
||||
"- inactive Ensure vertical shell thickness"));
|
||||
if (is_global_config)
|
||||
msg_text += "\n\n" + _(L("Shall I adjust those settings in order to enable Spiral Vase?"));
|
||||
wxMessageDialog dialog(nullptr, msg_text, _(L("Spiral Vase")),
|
||||
|
|
|
|||
|
|
@ -374,7 +374,7 @@ ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxStrin
|
|||
sizer->AddSpacer(10);
|
||||
|
||||
content = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(content, 1);
|
||||
sizer->Add(content, 1, wxEXPAND);
|
||||
|
||||
SetSizer(sizer);
|
||||
|
||||
|
|
@ -504,7 +504,8 @@ void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
|
|||
{
|
||||
if (technology == T_FFF
|
||||
&& (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY)
|
||||
&& printer_pickers.size() > 0) {
|
||||
&& printer_pickers.size() > 0
|
||||
&& printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) {
|
||||
printer_pickers[0]->select_one(0, true);
|
||||
}
|
||||
}
|
||||
|
|
@ -528,15 +529,17 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin
|
|||
list_l2->SetMinSize(wxSize(13*em, list_h));
|
||||
list_l3->SetMinSize(wxSize(25*em, list_h));
|
||||
|
||||
auto *grid = new wxFlexGridSizer(3, 0, em);
|
||||
auto *grid = new wxFlexGridSizer(3, em/2, em);
|
||||
grid->AddGrowableCol(2, 1);
|
||||
grid->AddGrowableRow(1, 1);
|
||||
|
||||
grid->Add(new wxStaticText(this, wxID_ANY, list1name));
|
||||
grid->Add(new wxStaticText(this, wxID_ANY, _(L("Vendor:"))));
|
||||
grid->Add(new wxStaticText(this, wxID_ANY, _(L("Profile:"))));
|
||||
|
||||
grid->Add(list_l1);
|
||||
grid->Add(list_l2);
|
||||
grid->Add(list_l3);
|
||||
grid->Add(list_l1, 0, wxEXPAND);
|
||||
grid->Add(list_l2, 0, wxEXPAND);
|
||||
grid->Add(list_l3, 1, wxEXPAND);
|
||||
|
||||
auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto *sel_all = new wxButton(this, wxID_ANY, _(L("All")));
|
||||
|
|
@ -548,7 +551,7 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin
|
|||
grid->Add(new wxBoxSizer(wxHORIZONTAL));
|
||||
grid->Add(btn_sizer, 0, wxALIGN_RIGHT);
|
||||
|
||||
append(grid);
|
||||
append(grid, 1, wxEXPAND);
|
||||
|
||||
list_l1->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
|
||||
update_lists(list_l1->GetSelection(), list_l2->GetSelection());
|
||||
|
|
@ -626,10 +629,28 @@ void PageMaterials::update_lists(int sel1, int sel2)
|
|||
const std::string &vendor = list_l2->get_data(sel2);
|
||||
|
||||
materials->filter_presets(type, vendor, [this](const Preset *p) {
|
||||
const int i = list_l3->append(p->name, p);
|
||||
const bool checked = wizard_p()->appconfig_new.has(materials->appconfig_section(), p->name);
|
||||
list_l3->Check(i, checked);
|
||||
});
|
||||
bool was_checked = false;
|
||||
|
||||
int cur_i = list_l3->find(p->alias);
|
||||
if (cur_i == wxNOT_FOUND)
|
||||
cur_i = list_l3->append(p->alias, &p->alias);
|
||||
else
|
||||
was_checked = list_l3->IsChecked(cur_i);
|
||||
|
||||
const std::string& section = materials->appconfig_section();
|
||||
|
||||
const bool checked = wizard_p()->appconfig_new.has(section, p->name);
|
||||
list_l3->Check(cur_i, checked | was_checked);
|
||||
|
||||
/* Update preset selection in config.
|
||||
* If one preset from aliases bundle is selected,
|
||||
* than mark all presets with this aliases as selected
|
||||
* */
|
||||
if (checked && !was_checked)
|
||||
wizard_p()->update_presets_in_config(section, p->alias, true);
|
||||
else if (!checked && was_checked)
|
||||
wizard_p()->appconfig_new.set(section, p->name, "1");
|
||||
} );
|
||||
}
|
||||
|
||||
sel2_prev = sel2;
|
||||
|
|
@ -639,13 +660,9 @@ void PageMaterials::update_lists(int sel1, int sel2)
|
|||
void PageMaterials::select_material(int i)
|
||||
{
|
||||
const bool checked = list_l3->IsChecked(i);
|
||||
const Preset &preset = list_l3->get_data(i);
|
||||
|
||||
if (checked) {
|
||||
wizard_p()->appconfig_new.set(materials->appconfig_section(), preset.name, "1");
|
||||
} else {
|
||||
wizard_p()->appconfig_new.erase(materials->appconfig_section(), preset.name);
|
||||
}
|
||||
const std::string& alias_key = list_l3->get_data(i);
|
||||
wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked);
|
||||
}
|
||||
|
||||
void PageMaterials::select_all(bool select)
|
||||
|
|
@ -753,8 +770,8 @@ PageMode::PageMode(ConfigWizard *parent)
|
|||
{
|
||||
append_text(_(L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n"
|
||||
"The Simple mode shows only the most frequently used settings relevant for regular 3D printing. "
|
||||
"The other two offer progressivly more sophisticated fine-tuning, "
|
||||
"they are suitable for advanced and expert usiser, respectively.")));
|
||||
"The other two offer progressively more sophisticated fine-tuning, "
|
||||
"they are suitable for advanced and expert users, respectively.")));
|
||||
|
||||
radio_simple = new wxRadioButton(this, wxID_ANY, _(L("Simple mode")));
|
||||
radio_advanced = new wxRadioButton(this, wxID_ANY, _(L("Advanced mode")));
|
||||
|
|
@ -777,11 +794,17 @@ void PageMode::on_activate()
|
|||
|
||||
void PageMode::serialize_mode(AppConfig *app_config) const
|
||||
{
|
||||
const char *mode = "simple";
|
||||
std::string mode = "";
|
||||
|
||||
if (radio_simple->GetValue()) { mode = "simple"; }
|
||||
if (radio_advanced->GetValue()) { mode = "advanced"; }
|
||||
if (radio_expert->GetValue()) { mode = "expert"; }
|
||||
|
||||
// If "Mode" page wasn't selected (no one radiobutton is checked),
|
||||
// we shouldn't to update a view_mode value in app_config
|
||||
if (mode.empty())
|
||||
return;
|
||||
|
||||
app_config->set("view_mode", mode);
|
||||
}
|
||||
|
||||
|
|
@ -790,7 +813,7 @@ PageVendors::PageVendors(ConfigWizard *parent)
|
|||
{
|
||||
const AppConfig &appconfig = this->wizard_p()->appconfig_new;
|
||||
|
||||
append_text(wxString::Format(_(L("Pick another vendor supported by %s: (FIXME: this text)")), SLIC3R_APP_NAME));
|
||||
append_text(wxString::Format(_(L("Pick another vendor supported by %s")), SLIC3R_APP_NAME) + ":");
|
||||
|
||||
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
|
|
@ -809,9 +832,11 @@ PageVendors::PageVendors(ConfigWizard *parent)
|
|||
if (enabled) {
|
||||
cbox->SetValue(true);
|
||||
|
||||
auto pair = wizard_p()->pages_3rdparty.find(vendor->id);
|
||||
wxCHECK_RET(pair != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created");
|
||||
pair->second->install = true;
|
||||
auto pages = wizard_p()->pages_3rdparty.find(vendor->id);
|
||||
wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created");
|
||||
|
||||
for (PagePrinters* page : { pages->second.first, pages->second.second })
|
||||
if (page) page->install = true;
|
||||
}
|
||||
|
||||
append(cbox);
|
||||
|
|
@ -1221,7 +1246,7 @@ const std::string Materials::UNKNOWN = "(Unknown)";
|
|||
|
||||
void Materials::push(const Preset *preset)
|
||||
{
|
||||
presets.insert(preset);
|
||||
presets.push_back(preset);
|
||||
types.insert(technology & T_FFF
|
||||
? Materials::get_filament_type(preset)
|
||||
: Materials::get_material_type(preset));
|
||||
|
|
@ -1309,9 +1334,10 @@ void ConfigWizard::priv::load_pages()
|
|||
index->add_page(page_fff);
|
||||
index->add_page(page_msla);
|
||||
index->add_page(page_vendors);
|
||||
for (const auto &pair : pages_3rdparty) {
|
||||
PagePrinters *page = pair.second;
|
||||
if (page->install) { index->add_page(page); }
|
||||
for (const auto &pages : pages_3rdparty) {
|
||||
for ( PagePrinters* page : { pages.second.first, pages.second.second })
|
||||
if (page && page->install)
|
||||
index->add_page(page);
|
||||
}
|
||||
|
||||
index->add_page(page_custom);
|
||||
|
|
@ -1326,6 +1352,9 @@ void ConfigWizard::priv::load_pages()
|
|||
if (any_fff_selected) { index->add_page(page_filaments); }
|
||||
if (any_sla_selected) { index->add_page(page_sla_materials); }
|
||||
|
||||
// there should to be selected at least one printer
|
||||
btn_finish->Enable(any_fff_selected || any_sla_selected);
|
||||
|
||||
index->add_page(page_update);
|
||||
index->add_page(page_mode);
|
||||
|
||||
|
|
@ -1384,8 +1413,6 @@ void ConfigWizard::priv::load_vendors()
|
|||
pair.second.preset_bundle->load_installed_printers(appconfig_new);
|
||||
}
|
||||
|
||||
update_materials(T_ANY);
|
||||
|
||||
if (app_config->has_section(AppConfig::SECTION_FILAMENTS)) {
|
||||
appconfig_new.set_section(AppConfig::SECTION_FILAMENTS, app_config->get_section(AppConfig::SECTION_FILAMENTS));
|
||||
}
|
||||
|
|
@ -1396,7 +1423,8 @@ void ConfigWizard::priv::load_vendors()
|
|||
|
||||
void ConfigWizard::priv::add_page(ConfigWizardPage *page)
|
||||
{
|
||||
hscroll_sizer->Add(page, 0, wxEXPAND);
|
||||
const int proportion = (page->shortname == _(L("Filaments"))) || (page->shortname == _(L("SLA Materials"))) ? 1 : 0;
|
||||
hscroll_sizer->Add(page, proportion, wxEXPAND);
|
||||
all_pages.push_back(page);
|
||||
}
|
||||
|
||||
|
|
@ -1409,10 +1437,22 @@ void ConfigWizard::priv::enable_next(bool enable)
|
|||
void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page)
|
||||
{
|
||||
switch (start_page) {
|
||||
case ConfigWizard::SP_PRINTERS: index->go_to(page_fff); break;
|
||||
case ConfigWizard::SP_FILAMENTS: index->go_to(page_filaments); break;
|
||||
case ConfigWizard::SP_MATERIALS: index->go_to(page_sla_materials); break;
|
||||
default: index->go_to(page_welcome); break;
|
||||
case ConfigWizard::SP_PRINTERS:
|
||||
index->go_to(page_fff);
|
||||
btn_next->SetFocus();
|
||||
break;
|
||||
case ConfigWizard::SP_FILAMENTS:
|
||||
index->go_to(page_filaments);
|
||||
btn_finish->SetFocus();
|
||||
break;
|
||||
case ConfigWizard::SP_MATERIALS:
|
||||
index->go_to(page_sla_materials);
|
||||
btn_finish->SetFocus();
|
||||
break;
|
||||
default:
|
||||
index->go_to(page_welcome);
|
||||
btn_next->SetFocus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1422,10 +1462,31 @@ void ConfigWizard::priv::create_3rdparty_pages()
|
|||
const VendorProfile *vendor = pair.second.vendor_profile;
|
||||
if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
|
||||
|
||||
auto *page = new PagePrinters(q, vendor->name, vendor->name, *vendor, 1, T_ANY);
|
||||
add_page(page);
|
||||
bool is_fff_technology = false;
|
||||
bool is_sla_technology = false;
|
||||
|
||||
pages_3rdparty.insert({vendor->id, page});
|
||||
for (auto& model: vendor->models)
|
||||
{
|
||||
if (!is_fff_technology && model.technology == ptFFF)
|
||||
is_fff_technology = true;
|
||||
if (!is_sla_technology && model.technology == ptSLA)
|
||||
is_sla_technology = true;
|
||||
}
|
||||
|
||||
PagePrinters* pageFFF = nullptr;
|
||||
PagePrinters* pageSLA = nullptr;
|
||||
|
||||
if (is_fff_technology) {
|
||||
pageFFF = new PagePrinters(q, vendor->name + " " +_(L("FFF Technology Printers")), vendor->name+" FFF", *vendor, 1, T_FFF);
|
||||
add_page(pageFFF);
|
||||
}
|
||||
|
||||
if (is_sla_technology) {
|
||||
pageSLA = new PagePrinters(q, vendor->name + " " + _(L("SLA Technology Printers")), vendor->name+" MSLA", *vendor, 1, T_SLA);
|
||||
add_page(pageSLA);
|
||||
}
|
||||
|
||||
pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1441,52 +1502,54 @@ void ConfigWizard::priv::update_materials(Technology technology)
|
|||
{
|
||||
if (any_fff_selected && (technology & T_FFF)) {
|
||||
filaments.clear();
|
||||
aliases_fff.clear();
|
||||
|
||||
// Iterate filaments in all bundles
|
||||
for (const auto &pair : bundles) {
|
||||
for (const auto &filament : pair.second.preset_bundle->filaments) {
|
||||
// Check if filament is already added
|
||||
if (filaments.containts(&filament)) { continue; }
|
||||
|
||||
if (filaments.containts(&filament))
|
||||
continue;
|
||||
// Iterate printers in all bundles
|
||||
for (const auto &pair : bundles) {
|
||||
for (const auto &printer : pair.second.preset_bundle->printers) {
|
||||
// For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
|
||||
// for (const auto &pair : bundles)
|
||||
for (const auto &printer : pair.second.preset_bundle->printers)
|
||||
// Filter out inapplicable printers
|
||||
if (!printer.is_visible || printer.printer_technology() != ptFFF) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (filament.is_compatible_with_printer(printer)) {
|
||||
if (printer.is_visible && printer.printer_technology() == ptFFF &&
|
||||
is_compatible_with_printer(PresetWithVendorProfile(filament, nullptr), PresetWithVendorProfile(printer, nullptr)) &&
|
||||
// Check if filament is already added
|
||||
! filaments.containts(&filament)) {
|
||||
filaments.push(&filament);
|
||||
if (!filament.alias.empty())
|
||||
aliases_fff[filament.alias].insert(filament.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (any_sla_selected && (technology & T_SLA)) {
|
||||
sla_materials.clear();
|
||||
aliases_sla.clear();
|
||||
|
||||
// Iterate SLA materials in all bundles
|
||||
for (const auto &pair : bundles) {
|
||||
for (const auto &material : pair.second.preset_bundle->sla_materials) {
|
||||
// Check if material is already added
|
||||
if (sla_materials.containts(&material)) { continue; }
|
||||
|
||||
if (sla_materials.containts(&material))
|
||||
continue;
|
||||
// Iterate printers in all bundles
|
||||
for (const auto &pair : bundles) {
|
||||
for (const auto &printer : pair.second.preset_bundle->printers) {
|
||||
// For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
|
||||
// for (const auto &pair : bundles)
|
||||
for (const auto &printer : pair.second.preset_bundle->printers)
|
||||
// Filter out inapplicable printers
|
||||
if (!printer.is_visible || printer.printer_technology() != ptSLA) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (material.is_compatible_with_printer(printer)) {
|
||||
if (printer.is_visible && printer.printer_technology() == ptSLA &&
|
||||
is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr)) &&
|
||||
// Check if material is already added
|
||||
! sla_materials.containts(&material)) {
|
||||
sla_materials.push(&material);
|
||||
if (!material.alias.empty())
|
||||
aliases_sla[material.alias].insert(material.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1499,10 +1562,10 @@ void ConfigWizard::priv::on_custom_setup()
|
|||
|
||||
void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt)
|
||||
{
|
||||
if (page_msla->any_selected() != any_sla_selected ||
|
||||
page_fff->any_selected() != any_fff_selected) {
|
||||
any_fff_selected = page_fff->any_selected();
|
||||
any_sla_selected = page_msla->any_selected();
|
||||
if (check_sla_selected() != any_sla_selected ||
|
||||
check_fff_selected() != any_fff_selected) {
|
||||
any_fff_selected = check_fff_selected();
|
||||
any_sla_selected = check_sla_selected();
|
||||
|
||||
load_pages();
|
||||
}
|
||||
|
|
@ -1519,9 +1582,9 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker
|
|||
}
|
||||
}
|
||||
|
||||
if (page == page_fff) {
|
||||
if (page->technology & T_FFF) {
|
||||
page_filaments->clear();
|
||||
} else if (page == page_msla) {
|
||||
} else if (page->technology & T_SLA) {
|
||||
page_sla_materials->clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -1530,17 +1593,48 @@ void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool i
|
|||
{
|
||||
auto it = pages_3rdparty.find(vendor->id);
|
||||
wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile");
|
||||
PagePrinters *page = it->second;
|
||||
|
||||
if (page->install && !install) {
|
||||
page->select_all(false);
|
||||
}
|
||||
page->install = install;
|
||||
page->Layout();
|
||||
for (PagePrinters* page : { it->second.first, it->second.second })
|
||||
if (page) {
|
||||
if (page->install && !install)
|
||||
page->select_all(false);
|
||||
page->install = install;
|
||||
page->Layout();
|
||||
}
|
||||
|
||||
load_pages();
|
||||
}
|
||||
|
||||
bool ConfigWizard::priv::check_material_config(Technology technology)
|
||||
{
|
||||
const auto exist_preset = [this](const std::string& section, const Materials& materials)
|
||||
{
|
||||
if (appconfig_new.has_section(section) &&
|
||||
!appconfig_new.get_section(section).empty())
|
||||
{
|
||||
const std::map<std::string, std::string>& appconfig_presets = appconfig_new.get_section(section);
|
||||
for (const auto& preset : appconfig_presets)
|
||||
if (materials.exist_preset(preset.first))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (any_fff_selected && technology & T_FFF && !exist_preset(AppConfig::SECTION_FILAMENTS, filaments))
|
||||
{
|
||||
show_info(q, _(L("You have to select at least one filament for selected printers")), "");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (any_sla_selected && technology & T_SLA && !exist_preset(AppConfig::SECTION_MATERIALS, sla_materials))
|
||||
{
|
||||
show_info(q, _(L("You have to select at least one material for selected printers")), "");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
|
||||
{
|
||||
const auto enabled_vendors = appconfig_new.vendors();
|
||||
|
|
@ -1666,11 +1760,48 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
|||
preset_bundle->export_selections(*app_config);
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add)
|
||||
{
|
||||
const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla;
|
||||
|
||||
auto update = [this, add](const std::string& s, const std::string& key) {
|
||||
if (add)
|
||||
appconfig_new.set(s, key, "1");
|
||||
else
|
||||
appconfig_new.erase(s, key);
|
||||
};
|
||||
|
||||
// add or delete presets had a same alias
|
||||
auto it = aliases.find(alias_key);
|
||||
if (it != aliases.end())
|
||||
for (const std::string& name : it->second)
|
||||
update(section, name);
|
||||
}
|
||||
|
||||
bool ConfigWizard::priv::check_fff_selected()
|
||||
{
|
||||
bool ret = page_fff->any_selected();
|
||||
for (const auto& printer: pages_3rdparty)
|
||||
if (printer.second.first) // FFF page
|
||||
ret |= printer.second.first->any_selected();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ConfigWizard::priv::check_sla_selected()
|
||||
{
|
||||
bool ret = page_msla->any_selected();
|
||||
for (const auto& printer: pages_3rdparty)
|
||||
if (printer.second.second) // SLA page
|
||||
ret |= printer.second.second->any_selected();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// Public
|
||||
|
||||
ConfigWizard::ConfigWizard(wxWindow *parent)
|
||||
: DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + name(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
: DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name().ToStdString()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
, p(new priv(this))
|
||||
{
|
||||
this->SetFont(wxGetApp().normal_font());
|
||||
|
|
@ -1722,10 +1853,15 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
|||
p->page_msla = new PagePrinters(this, _(L("Prusa MSLA Technology Printers")), "Prusa MSLA", *vendor_prusa, 0, T_SLA);
|
||||
p->add_page(p->page_msla);
|
||||
|
||||
p->any_sla_selected = p->check_sla_selected();
|
||||
p->any_fff_selected = p->check_fff_selected();
|
||||
|
||||
p->update_materials(T_ANY);
|
||||
|
||||
p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
|
||||
_(L("Filament Profiles Selection")), _(L("Filaments")), _(L("Type:")) ));
|
||||
p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
|
||||
_(L("SLA Material Profiles Selection")), _(L("SLA Materials")), _(L("Layer height:")) ));
|
||||
_(L("SLA Material Profiles Selection")) + " ", _(L("SLA Materials")), _(L("Layer height:")) ));
|
||||
|
||||
p->add_page(p->page_custom = new PageCustom(this));
|
||||
p->add_page(p->page_update = new PageUpdate(this));
|
||||
|
|
@ -1739,9 +1875,6 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
|||
p->create_3rdparty_pages(); // Needs to ne done _before_ creating PageVendors
|
||||
p->add_page(p->page_vendors = new PageVendors(this));
|
||||
|
||||
p->any_sla_selected = p->page_msla->any_selected();
|
||||
p->any_fff_selected = p->page_fff->any_selected();
|
||||
|
||||
p->load_pages();
|
||||
p->index->go_to(size_t{0});
|
||||
|
||||
|
|
@ -1760,9 +1893,23 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
|||
});
|
||||
|
||||
p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); });
|
||||
p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_next(); });
|
||||
p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->EndModal(wxID_OK); });
|
||||
p->btn_finish->Hide();
|
||||
|
||||
p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
|
||||
{
|
||||
// check, that there is selected at least one filament/material
|
||||
ConfigWizardPage* active_page = this->p->index->active_page();
|
||||
if ( (active_page == p->page_filaments || active_page == p->page_sla_materials)
|
||||
&& !p->check_material_config(dynamic_cast<PageMaterials*>(active_page)->materials->technology))
|
||||
return;
|
||||
this->p->index->go_next();
|
||||
});
|
||||
|
||||
p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
|
||||
{
|
||||
if (!p->check_material_config(T_ANY))
|
||||
return;
|
||||
this->EndModal(wxID_OK);
|
||||
});
|
||||
|
||||
p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) {
|
||||
p->any_sla_selected = true;
|
||||
|
|
@ -1775,7 +1922,8 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
|||
p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) {
|
||||
const bool is_last = p->index->active_is_last();
|
||||
p->btn_next->Show(! is_last);
|
||||
p->btn_finish->Show(is_last);
|
||||
if (is_last)
|
||||
p->btn_finish->SetFocus();
|
||||
|
||||
Layout();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -58,15 +58,16 @@ enum Technology {
|
|||
struct Materials
|
||||
{
|
||||
Technology technology;
|
||||
std::set<const Preset*> presets;
|
||||
// use vector for the presets to purpose of save of presets sorting in the bundle
|
||||
std::vector<const Preset*> presets;
|
||||
std::set<std::string> types;
|
||||
|
||||
Materials(Technology technology) : technology(technology) {}
|
||||
|
||||
void push(const Preset *preset);
|
||||
void clear();
|
||||
bool containts(const Preset *preset) {
|
||||
return presets.find(preset) != presets.end();
|
||||
bool containts(const Preset *preset) const {
|
||||
return std::find(presets.begin(), presets.end(), preset) != presets.end();
|
||||
}
|
||||
|
||||
const std::string& appconfig_section() const;
|
||||
|
|
@ -81,6 +82,14 @@ struct Materials
|
|||
}
|
||||
}
|
||||
|
||||
bool exist_preset(const std::string& preset_name) const
|
||||
{
|
||||
for (const Preset* preset : presets)
|
||||
if (preset->name == preset_name)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static const std::string UNKNOWN;
|
||||
static const std::string& get_filament_type(const Preset *preset);
|
||||
static const std::string& get_filament_vendor(const Preset *preset);
|
||||
|
|
@ -240,10 +249,12 @@ template<class T, class D> struct DataList : public T
|
|||
|
||||
return wxNOT_FOUND;
|
||||
}
|
||||
|
||||
int size() { return this->GetCount(); }
|
||||
};
|
||||
|
||||
typedef DataList<wxListBox, std::string> StringList;
|
||||
typedef DataList<wxCheckListBox, Preset> PresetList;
|
||||
typedef DataList<wxCheckListBox, std::string> PresetList;
|
||||
|
||||
struct PageMaterials: ConfigWizardPage
|
||||
{
|
||||
|
|
@ -343,7 +354,10 @@ struct PageTemperatures: ConfigWizardPage
|
|||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
typedef std::map<std::string /* = vendor ID */, PagePrinters*> Pages3rdparty;
|
||||
// hypothetically, each vendor can has printers both of technologies (FFF and SLA)
|
||||
typedef std::map<std::string /* = vendor ID */,
|
||||
std::pair<PagePrinters* /* = FFF page */,
|
||||
PagePrinters* /* = SLA page */>> Pages3rdparty;
|
||||
|
||||
|
||||
class ConfigWizardIndex: public wxPanel
|
||||
|
|
@ -404,6 +418,8 @@ wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
|
|||
|
||||
// ConfigWizard private data
|
||||
|
||||
typedef std::map<std::string, std::set<std::string>> PresetAliases;
|
||||
|
||||
struct ConfigWizard::priv
|
||||
{
|
||||
ConfigWizard *q;
|
||||
|
|
@ -415,6 +431,8 @@ struct ConfigWizard::priv
|
|||
// PrinterPickers state.
|
||||
Materials filaments; // Holds available filament presets and their types & vendors
|
||||
Materials sla_materials; // Ditto for SLA materials
|
||||
PresetAliases aliases_fff; // Map of aliase to preset names
|
||||
PresetAliases aliases_sla; // Map of aliase to preset names
|
||||
std::unique_ptr<DynamicPrintConfig> custom_config; // Backing for custom printer definition
|
||||
bool any_fff_selected; // Used to decide whether to display Filaments page
|
||||
bool any_sla_selected; // Used to decide whether to display SLA Materials page
|
||||
|
|
@ -454,7 +472,6 @@ struct ConfigWizard::priv
|
|||
: q(q)
|
||||
, filaments(T_FFF)
|
||||
, sla_materials(T_SLA)
|
||||
, any_sla_selected(false)
|
||||
{}
|
||||
|
||||
void load_pages();
|
||||
|
|
@ -472,13 +489,17 @@ struct ConfigWizard::priv
|
|||
void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt);
|
||||
void on_3rdparty_install(const VendorProfile *vendor, bool install);
|
||||
|
||||
bool check_material_config(Technology technology);
|
||||
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
|
||||
// #ys_FIXME_alise
|
||||
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
|
||||
|
||||
bool check_fff_selected(); // Used to decide whether to display Filaments page
|
||||
bool check_sla_selected(); // Used to decide whether to display SLA Materials page
|
||||
|
||||
int em() const { return index->em(); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
235
src/slic3r/GUI/ExtruderSequenceDialog.cpp
Normal file
235
src/slic3r/GUI/ExtruderSequenceDialog.cpp
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#include "ExtruderSequenceDialog.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/bmpcbox.h>
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <functional>
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "OptionsGroup.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequence& sequence)
|
||||
: DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Set extruder sequence")),
|
||||
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
|
||||
m_sequence(sequence)
|
||||
{
|
||||
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
SetDoubleBuffered(true);
|
||||
SetFont(wxGetApp().normal_font());
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
const int em = wxGetApp().em_unit();
|
||||
|
||||
m_bmp_del = ScalableBitmap(this, "remove_copies");
|
||||
m_bmp_add = ScalableBitmap(this, "add_copies");
|
||||
|
||||
auto option_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto intervals_box = new wxStaticBox(this, wxID_ANY, _(L("Set extruder change for every"))+ " : ");
|
||||
auto intervals_box_sizer = new wxStaticBoxSizer(intervals_box, wxVERTICAL);
|
||||
|
||||
m_intervals_grid_sizer = new wxFlexGridSizer(3, 5, em);
|
||||
|
||||
auto editor_sz = wxSize(4*em, wxDefaultCoord);
|
||||
|
||||
auto ID_RADIO_BUTTON = wxWindow::NewControlId(1);
|
||||
|
||||
wxRadioButton* rb_by_layers = new wxRadioButton(this, ID_RADIO_BUTTON, "", wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
|
||||
rb_by_layers->Bind(wxEVT_RADIOBUTTON, [this](wxCommandEvent& event) { m_sequence.is_mm_intervals = false; });
|
||||
rb_by_layers->SetValue(!m_sequence.is_mm_intervals);
|
||||
|
||||
wxStaticText* st_by_layers = new wxStaticText(this, wxID_ANY, _(L("layers")));
|
||||
m_interval_by_layers = new wxTextCtrl(this, wxID_ANY,
|
||||
wxString::Format("%d", m_sequence.interval_by_layers),
|
||||
wxDefaultPosition, editor_sz);
|
||||
m_interval_by_layers->Bind(wxEVT_TEXT, [this, rb_by_layers](wxEvent&)
|
||||
{
|
||||
wxString str = m_interval_by_layers->GetValue();
|
||||
if (str.IsEmpty()) {
|
||||
m_interval_by_layers->SetValue(wxString::Format("%d", m_sequence.interval_by_layers));
|
||||
return;
|
||||
}
|
||||
|
||||
int val = wxAtoi(str);
|
||||
if (val < 1) {
|
||||
m_interval_by_layers->SetValue("1");
|
||||
val = 1;
|
||||
}
|
||||
|
||||
if (m_sequence.interval_by_layers == val)
|
||||
return;
|
||||
|
||||
m_sequence.interval_by_layers = val;
|
||||
|
||||
m_sequence.is_mm_intervals = false;
|
||||
rb_by_layers->SetValue(true);
|
||||
});
|
||||
|
||||
m_intervals_grid_sizer->Add(rb_by_layers, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_intervals_grid_sizer->Add(m_interval_by_layers,0, wxALIGN_CENTER_VERTICAL);
|
||||
m_intervals_grid_sizer->Add(st_by_layers,0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
wxRadioButton* rb_by_mm = new wxRadioButton(this, ID_RADIO_BUTTON, "");
|
||||
rb_by_mm->Bind(wxEVT_RADIOBUTTON, [this](wxEvent&) { m_sequence.is_mm_intervals = true; });
|
||||
rb_by_mm->SetValue(m_sequence.is_mm_intervals);
|
||||
|
||||
wxStaticText* st_by_mm = new wxStaticText(this, wxID_ANY, _(L("mm")));
|
||||
m_interval_by_mm = new wxTextCtrl(this, wxID_ANY,
|
||||
double_to_string(sequence.interval_by_mm),
|
||||
wxDefaultPosition, editor_sz, wxTE_PROCESS_ENTER);
|
||||
|
||||
auto change_value = [this]()
|
||||
{
|
||||
wxString str = m_interval_by_mm->GetValue();
|
||||
if (str.IsEmpty()) {
|
||||
m_interval_by_mm->SetValue(wxString::Format("%d", m_sequence.interval_by_mm));
|
||||
return;
|
||||
}
|
||||
|
||||
str.Replace(",", ".", false);
|
||||
double val;
|
||||
if (str == "." || !str.ToCDouble(&val) || val <= 0.0)
|
||||
val = 3.0; // default value
|
||||
|
||||
if (fabs(m_sequence.interval_by_layers - val) < 0.001)
|
||||
return;
|
||||
|
||||
m_sequence.interval_by_mm = val;
|
||||
};
|
||||
|
||||
m_interval_by_mm->Bind(wxEVT_TEXT, [this, rb_by_mm](wxEvent&)
|
||||
{
|
||||
m_sequence.is_mm_intervals = true;
|
||||
rb_by_mm->SetValue(true);
|
||||
});
|
||||
|
||||
m_interval_by_mm->Bind(wxEVT_KILL_FOCUS, [this, change_value](wxFocusEvent& event)
|
||||
{
|
||||
change_value();
|
||||
event.Skip();
|
||||
});
|
||||
|
||||
m_interval_by_mm->Bind(wxEVT_TEXT_ENTER, [this, change_value](wxEvent&)
|
||||
{
|
||||
change_value();
|
||||
});
|
||||
|
||||
m_intervals_grid_sizer->Add(rb_by_mm, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_intervals_grid_sizer->Add(m_interval_by_mm,0, wxALIGN_CENTER_VERTICAL);
|
||||
m_intervals_grid_sizer->Add(st_by_mm,0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
intervals_box_sizer->Add(m_intervals_grid_sizer, 0, wxLEFT, em);
|
||||
option_sizer->Add(intervals_box_sizer, 0, wxEXPAND);
|
||||
|
||||
|
||||
auto extruders_box = new wxStaticBox(this, wxID_ANY, _(L("Set extruder(tool) sequence"))+ " : ");
|
||||
auto extruders_box_sizer = new wxStaticBoxSizer(extruders_box, wxVERTICAL);
|
||||
|
||||
m_extruders_grid_sizer = new wxFlexGridSizer(3, 5, em);
|
||||
|
||||
apply_extruder_sequence();
|
||||
|
||||
extruders_box_sizer->Add(m_extruders_grid_sizer, 0, wxALL, em);
|
||||
option_sizer->Add(extruders_box_sizer, 0, wxEXPAND | wxTOP, em);
|
||||
|
||||
main_sizer->Add(option_sizer, 0, wxEXPAND | wxALL, em);
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||
main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, em);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
|
||||
/* For this moment min sizes for dialog and its sizer are calculated.
|
||||
* If we left them, it can cause a problem with layouts during deleting of extruders
|
||||
*/
|
||||
if (m_sequence.extruders.size()>1)
|
||||
{
|
||||
wxSize sz = wxSize(-1, 10 * em);
|
||||
SetMinSize(sz);
|
||||
GetSizer()->SetMinSize(sz);
|
||||
}
|
||||
}
|
||||
|
||||
void ExtruderSequenceDialog::apply_extruder_sequence()
|
||||
{
|
||||
m_extruders_grid_sizer->Clear(true);
|
||||
|
||||
for (size_t extruder=0; extruder < m_sequence.extruders.size(); ++extruder)
|
||||
{
|
||||
wxBitmapComboBox* extruder_selector = nullptr;
|
||||
apply_extruder_selector(&extruder_selector, this, "", wxDefaultPosition, wxSize(15*wxGetApp().em_unit(), -1));
|
||||
extruder_selector->SetSelection(m_sequence.extruders[extruder]);
|
||||
|
||||
extruder_selector->Bind(wxEVT_COMBOBOX, [this, extruder_selector, extruder](wxCommandEvent& evt)
|
||||
{
|
||||
m_sequence.extruders[extruder] = extruder_selector->GetSelection();
|
||||
evt.StopPropagation();
|
||||
});
|
||||
|
||||
auto del_btn = new ScalableButton(this, wxID_ANY, m_bmp_del);
|
||||
del_btn->SetToolTip(_(L("Remove extruder from sequence")));
|
||||
if (m_sequence.extruders.size()==1)
|
||||
del_btn->Disable();
|
||||
|
||||
del_btn->Bind(wxEVT_BUTTON, [this, extruder](wxEvent&) {
|
||||
m_sequence.delete_extruder(extruder);
|
||||
apply_extruder_sequence();
|
||||
});
|
||||
|
||||
auto add_btn = new ScalableButton(this, wxID_ANY, m_bmp_add);
|
||||
add_btn->SetToolTip(_(L("Add extruder to sequence")));
|
||||
|
||||
add_btn->Bind(wxEVT_BUTTON, [this, extruder](wxEvent&) {
|
||||
m_sequence.add_extruder(extruder);
|
||||
apply_extruder_sequence();
|
||||
});
|
||||
|
||||
m_extruders_grid_sizer->Add(extruder_selector, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_extruders_grid_sizer->Add(del_btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_extruders_grid_sizer->Add(add_btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
m_extruders_grid_sizer->ShowItems(true); // show items hidden in apply_extruder_selector()
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void ExtruderSequenceDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
|
||||
m_bmp_add.msw_rescale();
|
||||
m_bmp_del.msw_rescale();
|
||||
|
||||
const int em = em_unit();
|
||||
|
||||
m_intervals_grid_sizer->SetHGap(em);
|
||||
m_intervals_grid_sizer->SetVGap(em);
|
||||
m_extruders_grid_sizer->SetHGap(em);
|
||||
m_extruders_grid_sizer->SetVGap(em);
|
||||
|
||||
msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL });
|
||||
|
||||
// wxSize size = get_size();
|
||||
// SetMinSize(size);
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
45
src/slic3r/GUI/ExtruderSequenceDialog.hpp
Normal file
45
src/slic3r/GUI/ExtruderSequenceDialog.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#ifndef slic3r_GUI_ExtruderSequenceDialog_hpp_
|
||||
#define slic3r_GUI_ExtruderSequenceDialog_hpp_
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
class wxTextCtrl;
|
||||
class wxFlexGridSizer;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ExtruderSequenceDialog: a node inside ObjectDataViewModel
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class ExtruderSequenceDialog: public DPIDialog
|
||||
{
|
||||
ScalableBitmap m_bmp_del;
|
||||
ScalableBitmap m_bmp_add;
|
||||
DoubleSlider::ExtrudersSequence m_sequence;
|
||||
|
||||
wxTextCtrl* m_interval_by_layers {nullptr};
|
||||
wxTextCtrl* m_interval_by_mm {nullptr};
|
||||
|
||||
wxFlexGridSizer* m_intervals_grid_sizer {nullptr};
|
||||
wxFlexGridSizer* m_extruders_grid_sizer {nullptr};
|
||||
public:
|
||||
ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequence& sequence);
|
||||
|
||||
~ExtruderSequenceDialog() {}
|
||||
|
||||
DoubleSlider::ExtrudersSequence GetValue() { return m_sequence; }
|
||||
|
||||
protected:
|
||||
void apply_extruder_sequence();
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif // slic3r_GUI_ExtruderSequenceDialog_hpp_
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -81,7 +81,11 @@ template <size_t N> using Vec2dsEvent = ArrayEvent<Vec2d, N>;
|
|||
using Vec3dEvent = Event<Vec3d>;
|
||||
template <size_t N> using Vec3dsEvent = ArrayEvent<Vec3d, N>;
|
||||
|
||||
using HeightProfileSmoothEvent = Event<HeightProfileSmoothingParams>;
|
||||
|
||||
#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent);
|
||||
#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent);
|
||||
|
|
@ -104,6 +108,11 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
|
|||
wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
class GLCanvas3D
|
||||
{
|
||||
|
|
@ -153,13 +162,17 @@ private:
|
|||
|
||||
private:
|
||||
static const float THICKNESS_BAR_WIDTH;
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static const float THICKNESS_RESET_BUTTON_HEIGHT;
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
bool m_enabled;
|
||||
Shader m_shader;
|
||||
unsigned int m_z_texture_id;
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
mutable GLTexture m_tooltip_texture;
|
||||
mutable GLTexture m_reset_texture;
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
// Not owned by LayersEditing.
|
||||
const DynamicPrintConfig *m_config;
|
||||
// ModelObject for the currently selected object (Model::objects[last_object_id]).
|
||||
|
|
@ -168,9 +181,14 @@ private:
|
|||
float m_object_max_z;
|
||||
// Owned by LayersEditing.
|
||||
SlicingParameters *m_slicing_parameters;
|
||||
std::vector<coordf_t> m_layer_height_profile;
|
||||
std::vector<double> m_layer_height_profile;
|
||||
bool m_layer_height_profile_modified;
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
mutable float m_adaptive_cusp;
|
||||
mutable HeightProfileSmoothingParams m_smooth_params;
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
class LayersTexture
|
||||
{
|
||||
public:
|
||||
|
|
@ -217,28 +235,44 @@ private:
|
|||
void adjust_layer_height_profile();
|
||||
void accept_changes(GLCanvas3D& canvas);
|
||||
void reset_layer_height_profile(GLCanvas3D& canvas);
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void adaptive_layer_height_profile(GLCanvas3D& canvas, float cusp);
|
||||
void smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_paramsn);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
static float get_cursor_z_relative(const GLCanvas3D& canvas);
|
||||
static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y);
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static bool reset_rect_contains(const GLCanvas3D& canvas, float x, float y);
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static Rect get_bar_rect_screen(const GLCanvas3D& canvas);
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static Rect get_reset_rect_screen(const GLCanvas3D& canvas);
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static Rect get_bar_rect_viewport(const GLCanvas3D& canvas);
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static Rect get_reset_rect_viewport(const GLCanvas3D& canvas);
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
float object_max_z() const { return m_object_max_z; }
|
||||
|
||||
std::string get_tooltip(const GLCanvas3D& canvas) const;
|
||||
|
||||
private:
|
||||
bool _is_initialized() const;
|
||||
bool is_initialized() const;
|
||||
void generate_layer_height_texture();
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void _render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const;
|
||||
void _render_reset_texture(const Rect& reset_rect) const;
|
||||
void _render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const;
|
||||
void _render_profile(const Rect& bar_rect) const;
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const;
|
||||
void render_profile(const Rect& bar_rect) const;
|
||||
void update_slicing_parameters();
|
||||
|
||||
static float thickness_bar_width(const GLCanvas3D &canvas);
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static float reset_button_height(const GLCanvas3D &canvas);
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
};
|
||||
|
||||
struct Mouse
|
||||
|
|
@ -352,8 +386,10 @@ private:
|
|||
|
||||
public:
|
||||
LegendTexture();
|
||||
void fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas,
|
||||
std::vector<std::pair<double, double>>& cp_legend_values);
|
||||
void fill_color_print_legend_items(const GLCanvas3D& canvas,
|
||||
const std::vector<float>& colors_in,
|
||||
std::vector<float>& colors,
|
||||
std::vector<std::string>& cp_legend_items);
|
||||
|
||||
bool generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas, bool compress);
|
||||
|
||||
|
|
@ -383,7 +419,6 @@ private:
|
|||
std::unique_ptr<RetinaHelper> m_retina_helper;
|
||||
#endif
|
||||
bool m_in_render;
|
||||
bool m_render_enabled;
|
||||
LegendTexture m_legend_texture;
|
||||
WarningTexture m_warning_texture;
|
||||
wxTimer m_timer;
|
||||
|
|
@ -442,7 +477,8 @@ private:
|
|||
RenderStats m_render_stats;
|
||||
#endif // ENABLE_RENDER_STATISTICS
|
||||
|
||||
int m_imgui_undo_redo_hovered_pos{ -1 };
|
||||
mutable int m_imgui_undo_redo_hovered_pos{ -1 };
|
||||
int m_selected_extruder;
|
||||
|
||||
public:
|
||||
GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar);
|
||||
|
|
@ -494,6 +530,7 @@ public:
|
|||
|
||||
const Camera& get_camera() const { return m_camera; }
|
||||
const Shader& get_shader() const { return m_shader; }
|
||||
Camera& get_camera() { return m_camera; }
|
||||
|
||||
BoundingBoxf3 volumes_bounding_box() const;
|
||||
BoundingBoxf3 scene_bounding_box() const;
|
||||
|
|
@ -501,6 +538,12 @@ public:
|
|||
bool is_layers_editing_enabled() const;
|
||||
bool is_layers_editing_allowed() const;
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void reset_layer_height_profile();
|
||||
void adaptive_layer_height_profile(float cusp);
|
||||
void smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
bool is_reload_delayed() const;
|
||||
|
||||
void enable_layers_editing(bool enable);
|
||||
|
|
@ -514,9 +557,6 @@ public:
|
|||
void enable_dynamic_background(bool enable);
|
||||
void allow_multisample(bool allow);
|
||||
|
||||
void enable_render(bool enable) { m_render_enabled = enable; }
|
||||
bool is_render_enabled() const { return m_render_enabled; }
|
||||
|
||||
void zoom_to_bed();
|
||||
void zoom_to_volumes();
|
||||
void zoom_to_selection();
|
||||
|
|
@ -530,7 +570,7 @@ public:
|
|||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// printable_only == false -> render also non printable volumes as grayed
|
||||
// parts_only == false -> render also sla support and pad
|
||||
void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
|
||||
void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void select_all();
|
||||
|
|
@ -550,7 +590,7 @@ public:
|
|||
|
||||
void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors);
|
||||
void load_sla_preview();
|
||||
void load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<double>& color_print_values);
|
||||
void load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<Model::CustomGCode>& color_print_values);
|
||||
void bind_event_handlers();
|
||||
void unbind_event_handlers();
|
||||
|
||||
|
|
@ -577,8 +617,6 @@ public:
|
|||
void do_flatten(const Vec3d& normal, const std::string& snapshot_type);
|
||||
void do_mirror(const std::string& snapshot_type);
|
||||
|
||||
void set_camera_zoom(double zoom);
|
||||
|
||||
void update_gizmos_on_off_state();
|
||||
void reset_all_gizmos() { m_gizmos.reset_all_states(); }
|
||||
|
||||
|
|
@ -591,6 +629,7 @@ public:
|
|||
|
||||
int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; }
|
||||
int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); }
|
||||
void set_selected_extruder(int extruder) { m_selected_extruder = extruder;}
|
||||
|
||||
class WipeTowerInfo {
|
||||
protected:
|
||||
|
|
@ -645,6 +684,9 @@ private:
|
|||
bool _init_toolbars();
|
||||
bool _init_main_toolbar();
|
||||
bool _init_undoredo_toolbar();
|
||||
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
bool _init_view_toolbar();
|
||||
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
|
||||
bool _set_current();
|
||||
void _resize(unsigned int w, unsigned int h);
|
||||
|
|
@ -656,13 +698,14 @@ private:
|
|||
#else
|
||||
void _zoom_to_box(const BoundingBoxf3& box);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
void _update_camera_zoom(double zoom);
|
||||
|
||||
void _refresh_if_shown_on_screen();
|
||||
|
||||
void _picking_pass() const;
|
||||
void _rectangular_selection_picking_pass() const;
|
||||
void _render_background() const;
|
||||
void _render_bed(float theta) const;
|
||||
void _render_bed(float theta, bool show_axes) const;
|
||||
void _render_objects() const;
|
||||
void _render_selection() const;
|
||||
#if ENABLE_RENDER_SELECTION_CENTER
|
||||
|
|
@ -682,14 +725,15 @@ private:
|
|||
#endif // ENABLE_SHOW_CAMERA_TARGET
|
||||
void _render_sla_slices() const;
|
||||
void _render_selection_sidebar_hints() const;
|
||||
void _render_undo_redo_stack(const bool is_undo, float pos_x);
|
||||
void _render_undo_redo_stack(const bool is_undo, float pos_x) const;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void _render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
|
||||
// render thumbnail using an off-screen framebuffer
|
||||
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
|
||||
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
|
||||
// render thumbnail using an off-screen framebuffer when GLEW_EXT_framebuffer_object is supported
|
||||
void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
|
||||
void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
|
||||
// render thumbnail using the default framebuffer
|
||||
void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
|
||||
void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void _update_volumes_hover_state() const;
|
||||
|
|
@ -713,7 +757,7 @@ private:
|
|||
// Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
|
||||
// one for perimeters, one for infill and one for supports.
|
||||
void _load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors,
|
||||
const std::vector<double>& color_print_values);
|
||||
const std::vector<Model::CustomGCode>& color_print_values);
|
||||
// Create 3D thick extrusion lines for wipe tower extrusions
|
||||
void _load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors);
|
||||
|
||||
|
|
|
|||
|
|
@ -118,9 +118,9 @@ bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_sh
|
|||
glsafe(::glGetProgramiv(this->shader_program_id, GL_LINK_STATUS, ¶ms));
|
||||
if (params == GL_FALSE) {
|
||||
// Linking failed. Get the log.
|
||||
glsafe(::glGetProgramiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, ¶ms));
|
||||
glsafe(::glGetProgramiv(this->shader_program_id, GL_INFO_LOG_LENGTH, ¶ms));
|
||||
std::vector<char> msg(params);
|
||||
glsafe(::glGetProgramInfoLog(this->vertex_program_id, params, ¶ms, msg.data()));
|
||||
glsafe(::glGetProgramInfoLog(this->shader_program_id, params, ¶ms, msg.data()));
|
||||
this->last_error = std::string("Shader linking failed:\n") + msg.data();
|
||||
this->release();
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ void GLToolbar::get_additional_tooltip(int item_id, std::string& text)
|
|||
}
|
||||
}
|
||||
|
||||
text = L("");
|
||||
text.clear();
|
||||
}
|
||||
|
||||
void GLToolbar::set_additional_tooltip(int item_id, const std::string& text)
|
||||
|
|
@ -443,7 +443,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
|
|||
if (item_id == -1)
|
||||
{
|
||||
// mouse is outside the toolbar
|
||||
m_tooltip = L("");
|
||||
m_tooltip.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -610,7 +610,7 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas
|
|||
std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return L("");
|
||||
return "";
|
||||
|
||||
switch (m_layout.type)
|
||||
{
|
||||
|
|
@ -665,7 +665,7 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC
|
|||
{
|
||||
const std::string& additional_tooltip = item->get_additional_tooltip();
|
||||
if (!additional_tooltip.empty())
|
||||
tooltip += L("\n") + additional_tooltip;
|
||||
tooltip += "\n" + additional_tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -769,7 +769,7 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan
|
|||
{
|
||||
const std::string& additional_tooltip = item->get_additional_tooltip();
|
||||
if (!additional_tooltip.empty())
|
||||
tooltip += L("\n") + additional_tooltip;
|
||||
tooltip += "\n" + additional_tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1194,7 +1194,12 @@ bool GLToolbar::generate_icons_texture() const
|
|||
states.push_back(std::make_pair(1, true));
|
||||
}
|
||||
|
||||
bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_layout.icons_size * m_layout.scale), true);
|
||||
unsigned int sprite_size_px = (unsigned int)(m_layout.icons_size * m_layout.scale);
|
||||
// // force even size
|
||||
// if (sprite_size_px % 2 != 0)
|
||||
// sprite_size_px += 1;
|
||||
|
||||
bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, sprite_size_px, false);
|
||||
if (res)
|
||||
m_icons_texture_dirty = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -293,6 +293,9 @@ public:
|
|||
|
||||
bool is_any_item_pressed() const;
|
||||
|
||||
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
unsigned int get_items_count() const { return (unsigned int)m_items.size(); }
|
||||
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
int get_item_id(const std::string& name) const;
|
||||
|
||||
void force_left_action(int item_id, GLCanvas3D& parent) { do_action(GLToolbarItem::Left, item_id, parent, false); }
|
||||
|
|
|
|||
|
|
@ -51,10 +51,10 @@
|
|||
#include <Shlobj.h>
|
||||
#endif // __WXMSW__
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
|
||||
#include <boost/beast/core/detail/base64.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
|
@ -185,7 +185,9 @@ bool GUI_App::on_init_inner()
|
|||
wxCHECK_MSG(wxDirExists(resources_dir), false,
|
||||
wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir));
|
||||
|
||||
SetAppName(SLIC3R_APP_KEY);
|
||||
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
|
||||
// SetAppName(SLIC3R_APP_KEY);
|
||||
SetAppName(SLIC3R_APP_KEY "-alpha");
|
||||
SetAppDisplayName(SLIC3R_APP_NAME);
|
||||
|
||||
// Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
|
||||
|
|
@ -281,7 +283,7 @@ bool GUI_App::on_init_inner()
|
|||
|
||||
PresetUpdater::UpdateResult updater_result;
|
||||
try {
|
||||
updater_result = preset_updater->config_update();
|
||||
updater_result = preset_updater->config_update(app_config->orig_version());
|
||||
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
|
||||
mainframe->Close();
|
||||
} else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) {
|
||||
|
|
@ -464,6 +466,9 @@ void GUI_App::recreate_GUI()
|
|||
|
||||
dlg.Update(30, _(L("Recreating")) + dots);
|
||||
topwindow->Destroy();
|
||||
|
||||
// For this moment ConfigWizard is deleted, invalidate it
|
||||
m_wizard = nullptr;
|
||||
}
|
||||
|
||||
dlg.Update(80, _(L("Loading of current presets")) + dots);
|
||||
|
|
@ -1126,7 +1131,6 @@ void GUI_App::gcode_thumbnails_debug()
|
|||
}
|
||||
else if (reading_image && boost::starts_with(gcode_line, END_MASK))
|
||||
{
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png";
|
||||
boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary);
|
||||
if (out_file.good())
|
||||
|
|
@ -1138,46 +1142,6 @@ void GUI_App::gcode_thumbnails_debug()
|
|||
out_file.write(decoded.c_str(), decoded.size());
|
||||
out_file.close();
|
||||
}
|
||||
#else
|
||||
if (!row.empty())
|
||||
{
|
||||
rows.push_back(row);
|
||||
row.clear();
|
||||
}
|
||||
|
||||
if ((unsigned int)rows.size() == height)
|
||||
{
|
||||
std::vector<unsigned char> thumbnail(4 * width * height, 0);
|
||||
for (unsigned int r = 0; r < (unsigned int)rows.size(); ++r)
|
||||
{
|
||||
std::string decoded_row;
|
||||
decoded_row.resize(boost::beast::detail::base64::decoded_size(rows[r].size()));
|
||||
decoded_row.resize(boost::beast::detail::base64::decode((void*)&decoded_row[0], rows[r].data(), rows[r].size()).first);
|
||||
|
||||
if ((unsigned int)decoded_row.size() == width * 4)
|
||||
{
|
||||
void* image_ptr = (void*)(thumbnail.data() + r * width * 4);
|
||||
::memcpy(image_ptr, (const void*)decoded_row.c_str(), width * 4);
|
||||
}
|
||||
}
|
||||
|
||||
wxImage image(width, height);
|
||||
image.InitAlpha();
|
||||
|
||||
for (unsigned int r = 0; r < height; ++r)
|
||||
{
|
||||
unsigned int rr = r * width;
|
||||
for (unsigned int c = 0; c < width; ++c)
|
||||
{
|
||||
unsigned char* px = thumbnail.data() + 4 * (rr + c);
|
||||
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
|
||||
image.SetAlpha((int)c, (int)r, px[3]);
|
||||
}
|
||||
}
|
||||
|
||||
image.SaveFile(out_path + std::to_string(width) + "x" + std::to_string(height) + ".png", wxBITMAP_TYPE_PNG);
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
|
||||
reading_image = false;
|
||||
width = 0;
|
||||
|
|
@ -1185,17 +1149,7 @@ void GUI_App::gcode_thumbnails_debug()
|
|||
rows.clear();
|
||||
}
|
||||
else if (reading_image)
|
||||
{
|
||||
#if !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
if (!row.empty() && (gcode_line[1] == ' '))
|
||||
{
|
||||
rows.push_back(row);
|
||||
row.clear();
|
||||
}
|
||||
#endif // !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
|
||||
row += gcode_line.substr(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -188,10 +188,10 @@ public:
|
|||
void open_web_page_localized(const std::string &http_address);
|
||||
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
|
||||
// temporary and debug only -> extract thumbnails from selected gcode and save them as png files
|
||||
void gcode_thumbnails_debug();
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
|
||||
|
||||
private:
|
||||
bool on_init_inner();
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@ static PrinterTechnology printer_technology()
|
|||
return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology();
|
||||
}
|
||||
|
||||
static const Selection& scene_selection()
|
||||
{
|
||||
return wxGetApp().plater()->canvas3D()->get_selection();
|
||||
}
|
||||
|
||||
// Config from current edited printer preset
|
||||
static DynamicPrintConfig& printer_config()
|
||||
{
|
||||
|
|
@ -329,7 +334,7 @@ wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx /
|
|||
return ""; // hide tooltip
|
||||
|
||||
// Create tooltip string, if there are errors
|
||||
wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors);
|
||||
wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):")), errors) + "\n";
|
||||
|
||||
const stl_stats& stats = vol_idx == -1 ?
|
||||
(*m_objects)[obj_idx]->get_object_stl_stats() :
|
||||
|
|
@ -526,6 +531,8 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item)
|
|||
if (!m_config)
|
||||
return;
|
||||
|
||||
take_snapshot(_(L("Change Extruder")));
|
||||
|
||||
const int extruder = m_objects_model->GetExtruderNumber(item);
|
||||
m_config->set_key_value("extruder", new ConfigOptionInt(extruder));
|
||||
|
||||
|
|
@ -806,7 +813,7 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
|
|||
|
||||
if (!item) {
|
||||
if (col == nullptr) {
|
||||
if (wxOSX)
|
||||
if (wxOSX && !multiple_selection())
|
||||
UnselectAll();
|
||||
else if (!evt_context_menu)
|
||||
// Case, when last item was deleted and under GTK was called wxEVT_DATAVIEW_SELECTION_CHANGED,
|
||||
|
|
@ -821,8 +828,13 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
|
|||
}
|
||||
|
||||
if (wxOSX && item && col) {
|
||||
wxDataViewItemArray sels;
|
||||
GetSelections(sels);
|
||||
UnselectAll();
|
||||
Select(item);
|
||||
if (sels.Count() > 1)
|
||||
SetSelections(sels);
|
||||
else
|
||||
Select(item);
|
||||
}
|
||||
|
||||
const wxString title = col->GetTitle();
|
||||
|
|
@ -896,10 +908,6 @@ void ObjectList::extruder_editing()
|
|||
if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject)))
|
||||
return;
|
||||
|
||||
std::vector<wxBitmap*> icons = get_extruder_color_icons();
|
||||
if (icons.empty())
|
||||
return;
|
||||
|
||||
const int column_width = GetColumn(colExtruder)->GetWidth() + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) + 5;
|
||||
|
||||
wxPoint pos = get_mouse_position_in_control();
|
||||
|
|
@ -907,29 +915,10 @@ void ObjectList::extruder_editing()
|
|||
pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + 5;
|
||||
pos.y -= GetTextExtent("m").y;
|
||||
|
||||
if (!m_extruder_editor)
|
||||
m_extruder_editor = new wxBitmapComboBox(this, wxID_ANY, wxEmptyString, pos, size,
|
||||
0, nullptr, wxCB_READONLY);
|
||||
else
|
||||
{
|
||||
m_extruder_editor->SetPosition(pos);
|
||||
m_extruder_editor->SetMinSize(size);
|
||||
m_extruder_editor->SetSize(size);
|
||||
m_extruder_editor->Clear();
|
||||
m_extruder_editor->Show();
|
||||
}
|
||||
apply_extruder_selector(&m_extruder_editor, this, L("default"), pos, size);
|
||||
|
||||
int i = 0;
|
||||
for (wxBitmap* bmp : icons) {
|
||||
if (i == 0) {
|
||||
m_extruder_editor->Append(_(L("default")), *bmp);
|
||||
++i;
|
||||
}
|
||||
|
||||
m_extruder_editor->Append(wxString::Format("%d", i), *bmp);
|
||||
++i;
|
||||
}
|
||||
m_extruder_editor->SetSelection(m_objects_model->GetExtruderNumber(item));
|
||||
m_extruder_editor->Show();
|
||||
|
||||
auto set_extruder = [this]()
|
||||
{
|
||||
|
|
@ -941,6 +930,7 @@ void ObjectList::extruder_editing()
|
|||
m_objects_model->SetExtruder(m_extruder_editor->GetString(selection), item);
|
||||
|
||||
m_extruder_editor->Hide();
|
||||
update_extruder_in_config(item);
|
||||
};
|
||||
|
||||
// to avoid event propagation to other sidebar items
|
||||
|
|
@ -949,13 +939,6 @@ void ObjectList::extruder_editing()
|
|||
set_extruder();
|
||||
evt.StopPropagation();
|
||||
});
|
||||
/*
|
||||
m_extruder_editor->Bind(wxEVT_KILL_FOCUS, [set_extruder](wxFocusEvent& evt)
|
||||
{
|
||||
set_extruder();
|
||||
evt.Skip();
|
||||
});*/
|
||||
|
||||
}
|
||||
|
||||
void ObjectList::copy()
|
||||
|
|
@ -1335,7 +1318,10 @@ void ObjectList::get_settings_choice(const wxString& category_name)
|
|||
void ObjectList::get_freq_settings_choice(const wxString& bundle_name)
|
||||
{
|
||||
std::vector<std::string> options = get_options_for_bundle(bundle_name);
|
||||
wxDataViewItem item = GetSelection();
|
||||
const Selection& selection = scene_selection();
|
||||
wxDataViewItem item = GetSelectedItemsCount() > 1 && selection.is_single_full_object() ?
|
||||
m_objects_model->GetItemById(selection.get_object_idx()) :
|
||||
GetSelection();
|
||||
|
||||
ItemType item_type = m_objects_model->GetItemType(item);
|
||||
|
||||
|
|
@ -1423,17 +1409,23 @@ void ObjectList::append_menu_items_add_volume(wxMenu* menu)
|
|||
|
||||
const ConfigOptionMode mode = wxGetApp().get_mode();
|
||||
|
||||
wxWindow* parent = wxGetApp().plater();
|
||||
|
||||
if (mode == comAdvanced) {
|
||||
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "",
|
||||
[this](wxCommandEvent&) { load_subobject(ModelVolumeType::MODEL_PART); }, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second);
|
||||
[this](wxCommandEvent&) { load_subobject(ModelVolumeType::MODEL_PART); },
|
||||
ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr,
|
||||
[this]() { return is_instance_or_object_selected(); }, parent);
|
||||
}
|
||||
if (mode == comSimple) {
|
||||
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "",
|
||||
[this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); },
|
||||
ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].second);
|
||||
ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].second, nullptr,
|
||||
[this]() { return is_instance_or_object_selected(); }, parent);
|
||||
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].first), "",
|
||||
[this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_BLOCKER); },
|
||||
ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].second);
|
||||
ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].second, nullptr,
|
||||
[this]() { return is_instance_or_object_selected(); }, parent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -1443,7 +1435,8 @@ void ObjectList::append_menu_items_add_volume(wxMenu* menu)
|
|||
auto& item = ADD_VOLUME_MENU_ITEMS[type];
|
||||
|
||||
wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type));
|
||||
append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second);
|
||||
append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second,
|
||||
[this]() { return is_instance_or_object_selected(); }, parent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1454,10 +1447,17 @@ wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu)
|
|||
[this]() { return is_splittable(); }, wxGetApp().plater());
|
||||
}
|
||||
|
||||
wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu)
|
||||
bool ObjectList::is_instance_or_object_selected()
|
||||
{
|
||||
const Selection& selection = scene_selection();
|
||||
return selection.is_single_full_instance() || selection.is_single_full_object();
|
||||
}
|
||||
|
||||
wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu, wxWindow* parent)
|
||||
{
|
||||
return append_menu_item(menu, wxID_ANY, _(L("Height range Modifier")), "",
|
||||
[this](wxCommandEvent&) { layers_editing(); }, "edit_layers_all", menu);
|
||||
[this](wxCommandEvent&) { layers_editing(); }, "edit_layers_all", menu,
|
||||
[this]() { return is_instance_or_object_selected(); }, parent);
|
||||
}
|
||||
|
||||
wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_)
|
||||
|
|
@ -1498,6 +1498,12 @@ wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_)
|
|||
#endif
|
||||
menu->DestroySeparators(); // delete old separators
|
||||
|
||||
// If there are selected more then one instance but not all of them
|
||||
// don't add settings menu items
|
||||
const Selection& selection = scene_selection();
|
||||
if (selection.is_multiple_full_instance() && !selection.is_single_full_object())
|
||||
return nullptr;
|
||||
|
||||
const auto sel_vol = get_selected_model_volume();
|
||||
if (sel_vol && sel_vol->type() >= ModelVolumeType::SUPPORT_ENFORCER)
|
||||
return nullptr;
|
||||
|
|
@ -1513,7 +1519,8 @@ wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_)
|
|||
menu->SetFirstSeparator();
|
||||
|
||||
// Add frequently settings
|
||||
const bool is_object_settings = m_objects_model->GetItemType(GetSelection()) == itObject;
|
||||
const ItemType item_type = m_objects_model->GetItemType(GetSelection());
|
||||
const bool is_object_settings = item_type & itObject || item_type & itInstance || selection.is_single_full_object();
|
||||
create_freq_settings_popupmenu(menu, is_object_settings);
|
||||
|
||||
if (mode == comAdvanced)
|
||||
|
|
@ -1539,15 +1546,35 @@ wxMenuItem* ObjectList::append_menu_item_change_type(wxMenu* menu)
|
|||
|
||||
wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent)
|
||||
{
|
||||
return append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")), "",
|
||||
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")), "",
|
||||
[this](wxCommandEvent&) { split_instances(); }, "", menu, [](){return wxGetApp().plater()->can_set_instance_to_object(); }, parent);
|
||||
|
||||
/* New behavior logic:
|
||||
* 1. Split Object to several separated object, if ALL instances are selected
|
||||
* 2. Separate selected instances from the initial object to the separated object,
|
||||
* if some (not all) instances are selected
|
||||
*/
|
||||
parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) {
|
||||
evt.SetText(wxGetApp().plater()->canvas3D()->get_selection().is_single_full_object() ?
|
||||
_(L("Set as a Separated Objects")) : _(L("Set as a Separated Object")));
|
||||
}, menu_item->GetId());
|
||||
|
||||
return menu_item;
|
||||
}
|
||||
|
||||
wxMenuItem* ObjectList::append_menu_item_printable(wxMenu* menu, wxWindow* /*parent*/)
|
||||
{
|
||||
return append_menu_check_item(menu, wxID_ANY, _(L("Printable")), "", [](wxCommandEvent&) {
|
||||
wxGetApp().plater()->canvas3D()->get_selection().toggle_instance_printable_state();
|
||||
}, menu);
|
||||
return append_menu_check_item(menu, wxID_ANY, _(L("Printable")), "", [this](wxCommandEvent&) {
|
||||
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||
wxDataViewItem item;
|
||||
if (GetSelectedItemsCount() > 1 && selection.is_single_full_object())
|
||||
item = m_objects_model->GetItemById(selection.get_object_idx());
|
||||
else
|
||||
item = GetSelection();
|
||||
|
||||
if (item)
|
||||
toggle_printable_state(item);
|
||||
}, menu);
|
||||
}
|
||||
|
||||
void ObjectList::append_menu_items_osx(wxMenu* menu)
|
||||
|
|
@ -1643,7 +1670,7 @@ void ObjectList::create_object_popupmenu(wxMenu *menu)
|
|||
menu->AppendSeparator();
|
||||
|
||||
// Layers Editing for object
|
||||
append_menu_item_layers_editing(menu);
|
||||
append_menu_item_layers_editing(menu, wxGetApp().plater());
|
||||
menu->AppendSeparator();
|
||||
|
||||
// rest of a object_menu will be added later in:
|
||||
|
|
@ -1687,17 +1714,6 @@ void ObjectList::create_part_popupmenu(wxMenu *menu)
|
|||
void ObjectList::create_instance_popupmenu(wxMenu*menu)
|
||||
{
|
||||
m_menu_item_split_instances = append_menu_item_instance_to_object(menu, wxGetApp().plater());
|
||||
|
||||
/* New behavior logic:
|
||||
* 1. Split Object to several separated object, if ALL instances are selected
|
||||
* 2. Separate selected instances from the initial object to the separated object,
|
||||
* if some (not all) instances are selected
|
||||
*/
|
||||
wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) {
|
||||
// evt.Enable(can_split_instances()); }, m_menu_item_split_instances->GetId());
|
||||
evt.SetText(wxGetApp().plater()->canvas3D()->get_selection().is_single_full_object() ?
|
||||
_(L("Set as a Separated Objects")) : _(L("Set as a Separated Object")));
|
||||
}, m_menu_item_split_instances->GetId());
|
||||
}
|
||||
|
||||
void ObjectList::create_default_popupmenu(wxMenu*menu)
|
||||
|
|
@ -1711,7 +1727,7 @@ wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
|
|||
wxMenu *menu = new wxMenu;
|
||||
|
||||
settings_menu_hierarchy settings_menu;
|
||||
const bool is_part = m_objects_model->GetParent(GetSelection()) != wxDataViewItem(nullptr);
|
||||
const bool is_part = !(m_objects_model->GetItemType(GetSelection()) == itObject || scene_selection().is_single_full_object());
|
||||
get_options_menu(settings_menu, is_part);
|
||||
|
||||
for (auto cat : settings_menu) {
|
||||
|
|
@ -1880,7 +1896,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
|||
if (obj_idx < 0)
|
||||
return;
|
||||
|
||||
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||
const Selection& selection = scene_selection();
|
||||
assert(obj_idx == selection.get_object_idx());
|
||||
|
||||
/** Any changes of the Object's composition is duplicated for all Object's Instances
|
||||
|
|
@ -2205,9 +2221,13 @@ void ObjectList::split()
|
|||
|
||||
void ObjectList::layers_editing()
|
||||
{
|
||||
const auto item = GetSelection();
|
||||
const int obj_idx = get_selected_obj_idx();
|
||||
if (!item || obj_idx < 0)
|
||||
const Selection& selection = scene_selection();
|
||||
const int obj_idx = selection.get_object_idx();
|
||||
wxDataViewItem item = obj_idx >= 0 && GetSelectedItemsCount() > 1 && selection.is_single_full_object() ?
|
||||
m_objects_model->GetItemById(obj_idx) :
|
||||
GetSelection();
|
||||
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
const wxDataViewItem obj_item = m_objects_model->GetTopParent(item);
|
||||
|
|
@ -2320,7 +2340,7 @@ bool ObjectList::selected_instances_of_same_object()
|
|||
|
||||
bool ObjectList::can_split_instances()
|
||||
{
|
||||
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||
const Selection& selection = scene_selection();
|
||||
return selection.is_multiple_full_instance() || selection.is_single_full_instance();
|
||||
}
|
||||
|
||||
|
|
@ -2348,7 +2368,7 @@ void ObjectList::part_selection_changed()
|
|||
{
|
||||
og_name = _(L("Group manipulation"));
|
||||
|
||||
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||
const Selection& selection = scene_selection();
|
||||
// don't show manipulation panel for case of all Object's parts selection
|
||||
update_and_show_manipulations = !selection.is_single_full_instance();
|
||||
}
|
||||
|
|
@ -2956,7 +2976,7 @@ int ObjectList::get_selected_layers_range_idx() const
|
|||
|
||||
void ObjectList::update_selections()
|
||||
{
|
||||
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||
const Selection& selection = scene_selection();
|
||||
wxDataViewItemArray sels;
|
||||
|
||||
if ( ( m_selection_mode & (smSettings|smLayer|smLayerRoot) ) == 0)
|
||||
|
|
@ -3663,7 +3683,7 @@ void ObjectList::instances_to_separated_objects(const int obj_idx)
|
|||
|
||||
void ObjectList::split_instances()
|
||||
{
|
||||
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||
const Selection& selection = scene_selection();
|
||||
const int obj_idx = selection.get_object_idx();
|
||||
if (obj_idx == -1)
|
||||
return;
|
||||
|
|
@ -3876,6 +3896,9 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const
|
|||
wxDataViewItemArray sels;
|
||||
GetSelections(sels);
|
||||
|
||||
if (!sels.empty())
|
||||
take_snapshot(_(L("Change Extruders")));
|
||||
|
||||
for (const wxDataViewItem& item : sels)
|
||||
{
|
||||
DynamicPrintConfig& config = get_item_config(item);
|
||||
|
|
|
|||
|
|
@ -226,11 +226,12 @@ public:
|
|||
void get_settings_choice(const wxString& category_name);
|
||||
void get_freq_settings_choice(const wxString& bundle_name);
|
||||
void show_settings(const wxDataViewItem settings_item);
|
||||
bool is_instance_or_object_selected();
|
||||
|
||||
wxMenu* append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type);
|
||||
void append_menu_items_add_volume(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_split(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_layers_editing(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_layers_editing(wxMenu* menu, wxWindow* parent);
|
||||
wxMenuItem* append_menu_item_settings(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_change_type(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent);
|
||||
|
|
|
|||
|
|
@ -243,11 +243,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
|||
|
||||
// Add Axes labels with icons
|
||||
static const char axes[] = { 'X', 'Y', 'Z' };
|
||||
// std::vector<wxString> axes_color = {"#990000", "#009900","#000099"};
|
||||
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) {
|
||||
const char label = axes[axis_idx];
|
||||
|
||||
wxStaticText* axis_name = new wxStaticText(m_parent, wxID_ANY, wxString(label));
|
||||
set_font_and_background_style(axis_name, wxGetApp().bold_font());
|
||||
// axis_name->SetForegroundColour(wxColour(axes_color[axis_idx]));
|
||||
|
||||
sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
// Under OSX we use font, smaller than default font, so
|
||||
|
|
|
|||
|
|
@ -492,18 +492,23 @@ void Preview::show_hide_ui_elements(const std::string& what)
|
|||
m_choice_view_type->Show(visible);
|
||||
}
|
||||
|
||||
void Preview::reset_sliders()
|
||||
void Preview::reset_sliders(bool reset_all)
|
||||
{
|
||||
m_enabled = false;
|
||||
// reset_double_slider();
|
||||
m_double_slider_sizer->Hide((size_t)0);
|
||||
if (reset_all)
|
||||
m_double_slider_sizer->Hide((size_t)0);
|
||||
else
|
||||
m_double_slider_sizer->GetItem(size_t(0))->GetSizer()->Hide(1);
|
||||
}
|
||||
|
||||
void Preview::update_sliders(const std::vector<double>& layers_z, bool keep_z_range)
|
||||
{
|
||||
m_enabled = true;
|
||||
|
||||
update_double_slider(layers_z, keep_z_range);
|
||||
m_double_slider_sizer->Show((size_t)0);
|
||||
|
||||
Layout();
|
||||
}
|
||||
|
||||
|
|
@ -560,12 +565,12 @@ void Preview::on_checkbox_legend(wxCommandEvent& evt)
|
|||
m_canvas_widget->Refresh();
|
||||
}
|
||||
|
||||
void Preview::update_view_type()
|
||||
void Preview::update_view_type(bool slice_completed)
|
||||
{
|
||||
const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config;
|
||||
|
||||
const wxString& choice = !config.option<ConfigOptionFloats>("colorprint_heights")->values.empty() &&
|
||||
wxGetApp().extruders_edited_cnt()==1 ?
|
||||
const wxString& choice = !wxGetApp().plater()->model().custom_gcode_per_height.empty() /*&&
|
||||
(wxGetApp().extruders_edited_cnt()==1 || !slice_completed) */?
|
||||
_(L("Color Print")) :
|
||||
config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1 ?
|
||||
_(L("Tool")) :
|
||||
|
|
@ -583,6 +588,8 @@ void Preview::update_view_type()
|
|||
void Preview::create_double_slider()
|
||||
{
|
||||
m_slider = new DoubleSlider(this, wxID_ANY, 0, 0, 0, 100);
|
||||
m_slider->EnableTickManipulation(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF);
|
||||
|
||||
m_double_slider_sizer->Add(m_slider, 0, wxEXPAND, 0);
|
||||
|
||||
// sizer, m_canvas_widget
|
||||
|
|
@ -592,10 +599,11 @@ void Preview::create_double_slider()
|
|||
|
||||
|
||||
Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) {
|
||||
wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights")->values = m_slider->GetTicksValues();
|
||||
Model& model = wxGetApp().plater()->model();
|
||||
model.custom_gcode_per_height = m_slider->GetTicksValues();
|
||||
m_schedule_background_process();
|
||||
|
||||
update_view_type();
|
||||
update_view_type(false);
|
||||
|
||||
reload_print();
|
||||
});
|
||||
|
|
@ -628,6 +636,24 @@ static int find_close_layer_idx(const std::vector<double>& zs, double &z, double
|
|||
return -1;
|
||||
}
|
||||
|
||||
void Preview::check_slider_values(std::vector<Model::CustomGCode>& ticks_from_model,
|
||||
const std::vector<double>& layers_z)
|
||||
{
|
||||
// All ticks that would end up outside the slider range should be erased.
|
||||
// TODO: this should be placed into more appropriate part of code,
|
||||
// this function is e.g. not called when the last object is deleted
|
||||
unsigned int old_size = ticks_from_model.size();
|
||||
ticks_from_model.erase(std::remove_if(ticks_from_model.begin(), ticks_from_model.end(),
|
||||
[layers_z](Model::CustomGCode val)
|
||||
{
|
||||
auto it = std::lower_bound(layers_z.begin(), layers_z.end(), val.height - DoubleSlider::epsilon());
|
||||
return it == layers_z.end();
|
||||
}),
|
||||
ticks_from_model.end());
|
||||
if (ticks_from_model.size() != old_size)
|
||||
m_schedule_background_process();
|
||||
}
|
||||
|
||||
void Preview::update_double_slider(const std::vector<double>& layers_z, bool keep_z_range)
|
||||
{
|
||||
// Save the initial slider span.
|
||||
|
|
@ -643,8 +669,8 @@ void Preview::update_double_slider(const std::vector<double>& layers_z, bool kee
|
|||
bool snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min();
|
||||
bool snap_to_max = force_sliders_full_range || m_slider->is_higher_at_max();
|
||||
|
||||
std::vector<double> &ticks_from_config = (wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights"))->values;
|
||||
check_slider_values(ticks_from_config, layers_z);
|
||||
std::vector<Model::CustomGCode> &ticks_from_model = wxGetApp().plater()->model().custom_gcode_per_height;
|
||||
check_slider_values(ticks_from_model, layers_z);
|
||||
|
||||
m_slider->SetSliderValues(layers_z);
|
||||
assert(m_slider->GetMinValue() == 0);
|
||||
|
|
@ -666,33 +692,12 @@ void Preview::update_double_slider(const std::vector<double>& layers_z, bool kee
|
|||
}
|
||||
m_slider->SetSelectionSpan(idx_low, idx_high);
|
||||
|
||||
m_slider->SetTicksValues(ticks_from_config);
|
||||
m_slider->SetTicksValues(ticks_from_model);
|
||||
|
||||
bool color_print_enable = (wxGetApp().plater()->printer_technology() == ptFFF);
|
||||
if (color_print_enable) {
|
||||
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
if (cfg.opt<ConfigOptionFloats>("nozzle_diameter")->values.size() > 1)
|
||||
color_print_enable = false;
|
||||
}
|
||||
m_slider->EnableTickManipulation(color_print_enable);
|
||||
}
|
||||
|
||||
void Preview::check_slider_values(std::vector<double>& ticks_from_config,
|
||||
const std::vector<double> &layers_z)
|
||||
{
|
||||
// All ticks that would end up outside the slider range should be erased.
|
||||
// TODO: this should be placed into more appropriate part of code,
|
||||
// this function is e.g. not called when the last object is deleted
|
||||
unsigned int old_size = ticks_from_config.size();
|
||||
ticks_from_config.erase(std::remove_if(ticks_from_config.begin(), ticks_from_config.end(),
|
||||
[layers_z](double val)
|
||||
{
|
||||
auto it = std::lower_bound(layers_z.begin(), layers_z.end(), val - DoubleSlider::epsilon());
|
||||
return it == layers_z.end();
|
||||
}),
|
||||
ticks_from_config.end());
|
||||
if (ticks_from_config.size() != old_size)
|
||||
m_schedule_background_process();
|
||||
m_slider->EnableTickManipulation(color_print_enable);
|
||||
m_slider->SetManipulationState(wxGetApp().extruders_edited_cnt());
|
||||
}
|
||||
|
||||
void Preview::reset_double_slider()
|
||||
|
|
@ -753,7 +758,7 @@ void Preview::load_print_as_fff(bool keep_z_range)
|
|||
|
||||
if (! has_layers)
|
||||
{
|
||||
reset_sliders();
|
||||
reset_sliders(true);
|
||||
m_canvas->reset_legend_texture();
|
||||
m_canvas_widget->Refresh();
|
||||
return;
|
||||
|
|
@ -776,51 +781,30 @@ void Preview::load_print_as_fff(bool keep_z_range)
|
|||
bool gcode_preview_data_valid = print->is_step_done(psGCodeExport) && ! m_gcode_preview_data->empty();
|
||||
// Collect colors per extruder.
|
||||
std::vector<std::string> colors;
|
||||
std::vector<double> color_print_values = {};
|
||||
std::vector<Model::CustomGCode> color_print_values = {};
|
||||
// set color print values, if it si selected "ColorPrint" view type
|
||||
if (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint)
|
||||
{
|
||||
colors = GCodePreviewData::ColorPrintColors();
|
||||
if (! gcode_preview_data_valid) {
|
||||
//FIXME accessing full_config() is pretty expensive.
|
||||
// Only initialize color_print_values for the initial preview, not for the full preview where the color_print_values is extracted from the G-code.
|
||||
const auto& config = wxGetApp().preset_bundle->project_config;
|
||||
color_print_values = config.option<ConfigOptionFloats>("colorprint_heights")->values;
|
||||
}
|
||||
colors = wxGetApp().plater()->get_colors_for_color_print();
|
||||
colors.push_back("#808080"); // gray color for pause print or custom G-code
|
||||
|
||||
if (!gcode_preview_data_valid)
|
||||
color_print_values = wxGetApp().plater()->model().custom_gcode_per_height;
|
||||
}
|
||||
else if (gcode_preview_data_valid || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool) )
|
||||
{
|
||||
const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(m_config->option("extruder_colour"));
|
||||
const ConfigOptionStrings* filamemts_opt = dynamic_cast<const ConfigOptionStrings*>(m_config->option("filament_colour"));
|
||||
unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size());
|
||||
|
||||
unsigned char rgb[3];
|
||||
for (unsigned int i = 0; i < colors_count; ++i)
|
||||
{
|
||||
std::string color = m_config->opt_string("extruder_colour", i);
|
||||
if (!PresetBundle::parse_color(color, rgb))
|
||||
{
|
||||
color = m_config->opt_string("filament_colour", i);
|
||||
if (!PresetBundle::parse_color(color, rgb))
|
||||
color = "#FFFFFF";
|
||||
}
|
||||
|
||||
colors.emplace_back(color);
|
||||
}
|
||||
colors = wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
color_print_values.clear();
|
||||
}
|
||||
|
||||
if (IsShown())
|
||||
{
|
||||
m_canvas->set_selected_extruder(0);
|
||||
if (gcode_preview_data_valid) {
|
||||
// Load the real G-code preview.
|
||||
m_canvas->load_gcode_preview(*m_gcode_preview_data, colors);
|
||||
m_loaded = true;
|
||||
} else {
|
||||
// disable color change information for multi-material presets
|
||||
if (wxGetApp().extruders_edited_cnt() > 1)
|
||||
color_print_values.clear();
|
||||
|
||||
// Load the initial preview based on slices, not the final G-code.
|
||||
m_canvas->load_preview(colors, color_print_values);
|
||||
}
|
||||
|
|
@ -829,7 +813,7 @@ void Preview::load_print_as_fff(bool keep_z_range)
|
|||
std::vector<double> zs = m_canvas->get_current_print_zs(true);
|
||||
if (zs.empty()) {
|
||||
// all layers filtered out
|
||||
reset_sliders();
|
||||
reset_sliders(true);
|
||||
m_canvas_widget->Refresh();
|
||||
} else
|
||||
update_sliders(zs, keep_z_range);
|
||||
|
|
@ -860,7 +844,7 @@ void Preview::load_print_as_sla()
|
|||
n_layers = (unsigned int)zs.size();
|
||||
if (n_layers == 0)
|
||||
{
|
||||
reset_sliders();
|
||||
reset_sliders(true);
|
||||
m_canvas_widget->Refresh();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "libslic3r/Point.hpp"
|
||||
|
||||
#include <string>
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
class wxNotebook;
|
||||
class wxGLCanvas;
|
||||
|
|
@ -12,6 +13,7 @@ class wxBoxSizer;
|
|||
class wxStaticText;
|
||||
class wxChoice;
|
||||
class wxComboCtrl;
|
||||
class wxBitmapComboBox;
|
||||
class wxCheckBox;
|
||||
class DoubleSlider;
|
||||
|
||||
|
|
@ -101,7 +103,7 @@ class Preview : public wxPanel
|
|||
bool m_loaded;
|
||||
bool m_enabled;
|
||||
|
||||
DoubleSlider* m_slider {nullptr};
|
||||
DoubleSlider* m_slider {nullptr};
|
||||
|
||||
public:
|
||||
Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config,
|
||||
|
|
@ -128,7 +130,7 @@ public:
|
|||
void move_double_slider(wxKeyEvent& evt);
|
||||
void edit_double_slider(wxKeyEvent& evt);
|
||||
|
||||
void update_view_type();
|
||||
void update_view_type(bool slice_completed);
|
||||
|
||||
bool is_loaded() const { return m_loaded; }
|
||||
|
||||
|
|
@ -140,7 +142,7 @@ private:
|
|||
|
||||
void show_hide_ui_elements(const std::string& what);
|
||||
|
||||
void reset_sliders();
|
||||
void reset_sliders(bool reset_all);
|
||||
void update_sliders(const std::vector<double>& layers_z, bool keep_z_range = false);
|
||||
|
||||
void on_size(wxSizeEvent& evt);
|
||||
|
|
@ -154,9 +156,9 @@ private:
|
|||
|
||||
// Create/Update/Reset double slider on 3dPreview
|
||||
void create_double_slider();
|
||||
void check_slider_values(std::vector<Model::CustomGCode> &ticks_from_model,
|
||||
const std::vector<double> &layers_z);
|
||||
void update_double_slider(const std::vector<double>& layers_z, bool keep_z_range = false);
|
||||
void check_slider_values(std::vector<double> &ticks_from_config,
|
||||
const std::vector<double> &layers_z);
|
||||
void reset_double_slider();
|
||||
// update DoubleSlider after keyDown in canvas
|
||||
void update_double_slider_from_canvas(wxKeyEvent& event);
|
||||
|
|
|
|||
|
|
@ -140,17 +140,22 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
|
|||
y = std::min(y, bottom_limit - approx_height);
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
|
||||
m_imgui->set_next_window_bg_alpha(0.5f);
|
||||
m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text("Z");
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f);
|
||||
ImGui::InputDouble("", &m_cut_z, 0.0f, 0.0f, "%.2f");
|
||||
|
||||
ImGui::PushItemWidth(m_imgui->scaled(5.0f));
|
||||
ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f");
|
||||
ImGui::Separator();
|
||||
|
||||
m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper);
|
||||
m_imgui->checkbox(_(L("Keep lower part")), m_keep_lower);
|
||||
m_imgui->checkbox(_(L("Rotate lower part upwards")), m_rotate_lower);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
m_imgui->disabled_begin(!m_keep_upper && !m_keep_lower);
|
||||
const bool cut_clicked = m_imgui->button(_(L("Perform cut")));
|
||||
m_imgui->disabled_end();
|
||||
|
|
|
|||
|
|
@ -48,6 +48,13 @@ std::string GLGizmoMove3D::on_get_name() const
|
|||
return (_(L("Move")) + " [M]").ToUTF8().data();
|
||||
}
|
||||
|
||||
#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
bool GLGizmoMove3D::on_is_activable() const
|
||||
{
|
||||
return !m_parent.get_selection().is_empty();
|
||||
}
|
||||
#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
|
||||
void GLGizmoMove3D::on_start_dragging()
|
||||
{
|
||||
if (m_hover_id != -1)
|
||||
|
|
@ -166,25 +173,6 @@ void GLGizmoMove3D::on_render_for_picking() const
|
|||
render_grabber_extension(Z, box, true);
|
||||
}
|
||||
|
||||
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
bool show_position = selection.is_single_full_instance();
|
||||
const Vec3d& position = selection.get_bounding_box().center();
|
||||
|
||||
Vec3d displacement = show_position ? position : m_displacement;
|
||||
wxString label = show_position ? _(L("Position (mm)")) : _(L("Displacement (mm)"));
|
||||
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
m_imgui->set_next_window_bg_alpha(0.5f);
|
||||
m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
m_imgui->input_vec3("", displacement, 100.0f, "%.2f");
|
||||
|
||||
m_imgui->end();
|
||||
}
|
||||
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
|
||||
double GLGizmoMove3D::calc_projection(const UpdateData& data) const
|
||||
{
|
||||
double projection = 0.0;
|
||||
|
|
|
|||
|
|
@ -33,14 +33,14 @@ public:
|
|||
protected:
|
||||
virtual bool on_init();
|
||||
virtual std::string on_get_name() const;
|
||||
#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
virtual bool on_is_activable() const;
|
||||
#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
virtual void on_start_dragging();
|
||||
virtual void on_stop_dragging();
|
||||
virtual void on_update(const UpdateData& data);
|
||||
virtual void on_render() const;
|
||||
virtual void on_render_for_picking() const;
|
||||
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit);
|
||||
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
|
||||
private:
|
||||
double calc_projection(const UpdateData& data) const;
|
||||
|
|
|
|||
|
|
@ -449,6 +449,13 @@ std::string GLGizmoRotate3D::on_get_name() const
|
|||
return (_(L("Rotate")) + " [R]").ToUTF8().data();
|
||||
}
|
||||
|
||||
#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
bool GLGizmoRotate3D::on_is_activable() const
|
||||
{
|
||||
return !m_parent.get_selection().is_empty();
|
||||
}
|
||||
#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
|
||||
void GLGizmoRotate3D::on_start_dragging()
|
||||
{
|
||||
if ((0 <= m_hover_id) && (m_hover_id < 3))
|
||||
|
|
@ -475,21 +482,5 @@ void GLGizmoRotate3D::on_render() const
|
|||
m_gizmos[Z].render();
|
||||
}
|
||||
|
||||
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
Vec3d rotation(Geometry::rad2deg(m_gizmos[0].get_angle()), Geometry::rad2deg(m_gizmos[1].get_angle()), Geometry::rad2deg(m_gizmos[2].get_angle()));
|
||||
wxString label = _(L("Rotation (deg)"));
|
||||
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
m_imgui->set_next_window_bg_alpha(0.5f);
|
||||
m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
m_imgui->input_vec3("", rotation, 100.0f, "%.2f");
|
||||
m_imgui->end();
|
||||
}
|
||||
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -104,6 +104,9 @@ protected:
|
|||
if (id < 3)
|
||||
m_gizmos[id].disable_grabber(0);
|
||||
}
|
||||
#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
virtual bool on_is_activable() const;
|
||||
#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
virtual void on_start_dragging();
|
||||
virtual void on_stop_dragging();
|
||||
virtual void on_update(const UpdateData& data)
|
||||
|
|
@ -121,13 +124,8 @@ protected:
|
|||
g.render_for_picking();
|
||||
}
|
||||
}
|
||||
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit);
|
||||
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,12 @@ std::string GLGizmoScale3D::on_get_name() const
|
|||
|
||||
bool GLGizmoScale3D::on_is_activable() const
|
||||
{
|
||||
#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
return !selection.is_empty() && !selection.is_wipe_tower();
|
||||
#else
|
||||
return !m_parent.get_selection().is_wipe_tower();
|
||||
#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::on_start_dragging()
|
||||
|
|
@ -284,21 +289,6 @@ void GLGizmoScale3D::on_render_for_picking() const
|
|||
render_grabbers_for_picking(m_parent.get_selection().get_bounding_box());
|
||||
}
|
||||
|
||||
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
bool single_instance = selection.is_single_full_instance();
|
||||
wxString label = _(L("Scale (%)"));
|
||||
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
m_imgui->set_next_window_bg_alpha(0.5f);
|
||||
m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f");
|
||||
m_imgui->end();
|
||||
}
|
||||
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
|
||||
void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const
|
||||
{
|
||||
unsigned int grabbers_count = (unsigned int)m_grabbers.size();
|
||||
|
|
|
|||
|
|
@ -50,9 +50,6 @@ protected:
|
|||
virtual void on_update(const UpdateData& data);
|
||||
virtual void on_render() const;
|
||||
virtual void on_render_for_picking() const;
|
||||
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit);
|
||||
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
|
||||
|
||||
private:
|
||||
void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const;
|
||||
|
|
|
|||
|
|
@ -741,7 +741,6 @@ RENDER_AGAIN:
|
|||
const float approx_height = m_imgui->scaled(18.0f);
|
||||
y = std::min(y, bottom_limit - approx_height);
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
m_imgui->set_next_window_bg_alpha(0.5f);
|
||||
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:
|
||||
|
|
@ -765,6 +764,7 @@ RENDER_AGAIN:
|
|||
float diameter_upper_cap = static_cast<ConfigOptionFloat*>(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value;
|
||||
if (m_new_point_head_diameter > diameter_upper_cap)
|
||||
m_new_point_head_diameter = diameter_upper_cap;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("head_diameter"));
|
||||
ImGui::SameLine(diameter_slider_left);
|
||||
ImGui::PushItemWidth(window_width - diameter_slider_left);
|
||||
|
|
@ -825,6 +825,7 @@ RENDER_AGAIN:
|
|||
}
|
||||
}
|
||||
else { // not in editing mode:
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("minimal_distance"));
|
||||
ImGui::SameLine(settings_sliders_left);
|
||||
ImGui::PushItemWidth(window_width - settings_sliders_left);
|
||||
|
|
@ -838,6 +839,7 @@ RENDER_AGAIN:
|
|||
bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider
|
||||
bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("points_density"));
|
||||
ImGui::SameLine(settings_sliders_left);
|
||||
|
||||
|
|
@ -868,7 +870,7 @@ RENDER_AGAIN:
|
|||
if (generate)
|
||||
auto_generate();
|
||||
|
||||
m_imgui->text("");
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("manual_editing")))
|
||||
switch_to_editing_mode();
|
||||
|
||||
|
|
@ -885,9 +887,12 @@ RENDER_AGAIN:
|
|||
|
||||
|
||||
// Following is rendered in both editing and non-editing mode:
|
||||
m_imgui->text("");
|
||||
ImGui::Separator();
|
||||
if (m_clipping_plane_distance == 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](){
|
||||
|
|
@ -1226,10 +1231,10 @@ void GLGizmoSlaSupports::get_data_from_backend()
|
|||
|
||||
void GLGizmoSlaSupports::auto_generate()
|
||||
{
|
||||
wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L(
|
||||
"Autogeneration will erase all manually edited points.\n\n"
|
||||
"Are you sure you want to do it?\n"
|
||||
)), _(L("Warning")), wxICON_WARNING | wxYES | wxNO);
|
||||
wxMessageDialog dlg(GUI::wxGetApp().plater(),
|
||||
_(L("Autogeneration will erase all manually edited points.")) + "\n\n" +
|
||||
_(L("Are you sure you want to do it?")) + "\n",
|
||||
_(L("Warning")), wxICON_WARNING | wxYES | wxNO);
|
||||
|
||||
if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points")));
|
||||
|
|
|
|||
|
|
@ -905,7 +905,11 @@ void GLGizmosManager::do_render_overlay() const
|
|||
GLGizmoBase* gizmo = m_gizmos[idx].get();
|
||||
|
||||
unsigned int sprite_id = gizmo->get_sprite_id();
|
||||
#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
int icon_idx = (m_current == idx) ? 2 : ((m_hover == idx) ? 1 : (gizmo->is_activable()? 0 : 3));
|
||||
#else
|
||||
int icon_idx = m_current == idx ? 2 : (m_hover == idx ? 1 : 0);
|
||||
#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
|
||||
float u_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_width;
|
||||
float v_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_height;
|
||||
|
|
@ -971,11 +975,19 @@ bool GLGizmosManager::generate_icons_texture() const
|
|||
}
|
||||
|
||||
std::vector<std::pair<int, bool>> states;
|
||||
states.push_back(std::make_pair(1, false));
|
||||
states.push_back(std::make_pair(0, false));
|
||||
states.push_back(std::make_pair(0, true));
|
||||
states.push_back(std::make_pair(1, false)); // Activable
|
||||
states.push_back(std::make_pair(0, false)); // Hovered
|
||||
states.push_back(std::make_pair(0, true)); // Selected
|
||||
#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
states.push_back(std::make_pair(2, false)); // Disabled
|
||||
#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
|
||||
|
||||
bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_overlay_icons_size * m_overlay_scale), true);
|
||||
unsigned int sprite_size_px = (unsigned int)(m_overlay_icons_size * m_overlay_scale);
|
||||
// // force even size
|
||||
// if (sprite_size_px % 2 != 0)
|
||||
// sprite_size_px += 1;
|
||||
|
||||
bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, sprite_size_px, false);
|
||||
if (res)
|
||||
m_icons_texture_dirty = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ void ImGuiWrapper::set_language(const std::string &language)
|
|||
ranges = ranges_turkish;
|
||||
} else if (lang == "vi") {
|
||||
ranges = ranges_vietnamese;
|
||||
} else if (lang == "jp") {
|
||||
} else if (lang == "ja") {
|
||||
ranges = ImGui::GetIO().Fonts->GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs
|
||||
m_font_cjk = true;
|
||||
} else if (lang == "ko") {
|
||||
|
|
@ -317,6 +317,22 @@ void ImGuiWrapper::text(const wxString &label)
|
|||
this->text(label_utf8.c_str());
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/)
|
||||
{
|
||||
return ImGui::SliderFloat(label, v, v_min, v_max, format, power);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/)
|
||||
{
|
||||
return this->slider_float(label.c_str(), v, v_min, v_max, format, power);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/)
|
||||
{
|
||||
auto label_utf8 = into_u8(label);
|
||||
return this->slider_float(label_utf8.c_str(), v, v_min, v_max, format, power);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>& options, int& selection)
|
||||
{
|
||||
// this is to force the label to the left of the widget:
|
||||
|
|
@ -498,13 +514,18 @@ void ImGuiWrapper::init_style()
|
|||
(hex_color & 0xff) / 255.0f);
|
||||
};
|
||||
|
||||
static const unsigned COL_GREY_DARK = 0x444444ff;
|
||||
static const unsigned COL_WINDOW_BACKGROND = 0x222222cc;
|
||||
static const unsigned COL_GREY_DARK = 0x555555ff;
|
||||
static const unsigned COL_GREY_LIGHT = 0x666666ff;
|
||||
static const unsigned COL_ORANGE_DARK = 0xc16737ff;
|
||||
static const unsigned COL_ORANGE_LIGHT = 0xff7d38ff;
|
||||
|
||||
// Generics
|
||||
// Window
|
||||
style.WindowRounding = 4.0f;
|
||||
set_color(ImGuiCol_WindowBg, COL_WINDOW_BACKGROND);
|
||||
set_color(ImGuiCol_TitleBgActive, COL_ORANGE_DARK);
|
||||
|
||||
// Generics
|
||||
set_color(ImGuiCol_FrameBg, COL_GREY_DARK);
|
||||
set_color(ImGuiCol_FrameBgHovered, COL_GREY_LIGHT);
|
||||
set_color(ImGuiCol_FrameBgActive, COL_GREY_LIGHT);
|
||||
|
|
@ -528,6 +549,9 @@ void ImGuiWrapper::init_style()
|
|||
// Slider
|
||||
set_color(ImGuiCol_SliderGrab, COL_ORANGE_DARK);
|
||||
set_color(ImGuiCol_SliderGrabActive, COL_ORANGE_LIGHT);
|
||||
|
||||
// Separator
|
||||
set_color(ImGuiCol_Separator, COL_ORANGE_LIGHT);
|
||||
}
|
||||
|
||||
void ImGuiWrapper::render_draw_data(ImDrawData *draw_data)
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ public:
|
|||
void text(const char *label);
|
||||
void text(const std::string &label);
|
||||
void text(const wxString &label);
|
||||
bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
|
||||
bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
|
||||
bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
|
||||
bool combo(const wxString& label, const std::vector<std::string>& options, int& selection); // Use -1 to not mark any option as selected
|
||||
bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected);
|
||||
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ void KBShortcutsDialog::fill_shortcuts()
|
|||
plater_shortcuts.push_back(Shortcut("Z", L("Zoom to selected object")));
|
||||
plater_shortcuts.push_back(Shortcut("I", L("Zoom in")));
|
||||
plater_shortcuts.push_back(Shortcut("O", L("Zoom out")));
|
||||
plater_shortcuts.push_back(Shortcut(ctrl+"M", L("Show/Hide 3Dconnexion devices settings dialog")));
|
||||
plater_shortcuts.push_back(Shortcut("ESC", L("Unselect gizmo / Clear selection")));
|
||||
#if ENABLE_RENDER_PICKING_PASS
|
||||
// Don't localize debugging texts.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include "PrintHostDialogs.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
|
@ -684,7 +685,7 @@ void MainFrame::init_menubar()
|
|||
[this](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
|
||||
helpMenu->AppendSeparator();
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("DEBUG gcode thumbnails")), _(L("DEBUG ONLY - read the selected gcode file and generates png for the contained thumbnails")),
|
||||
append_menu_item(helpMenu, wxID_ANY, "DEBUG gcode thumbnails", "DEBUG ONLY - read the selected gcode file and generates png for the contained thumbnails",
|
||||
[this](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); });
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ void MeshClipper::recalculate_triangles()
|
|||
}
|
||||
|
||||
|
||||
|
||||
bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
|
||||
Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace GUI {
|
|||
struct Camera;
|
||||
|
||||
|
||||
|
||||
// lm_FIXME: Following class might possibly be replaced by Eigen::Hyperplane
|
||||
class ClippingPlane
|
||||
{
|
||||
double m_data[4];
|
||||
|
|
@ -68,13 +68,23 @@ public:
|
|||
};
|
||||
|
||||
|
||||
|
||||
// MeshClipper class cuts a mesh and is able to return a triangulated cut.
|
||||
class MeshClipper {
|
||||
public:
|
||||
// Inform MeshClipper about which plane we want to use to cut the mesh
|
||||
// This is supposed to be in world coordinates.
|
||||
void set_plane(const ClippingPlane& plane);
|
||||
|
||||
// Which mesh to cut. MeshClipper remembers const * to it, caller
|
||||
// must make sure that it stays valid.
|
||||
void set_mesh(const TriangleMesh& mesh);
|
||||
|
||||
// Inform the MeshClipper about the transformation that transforms the mesh
|
||||
// into world coordinates.
|
||||
void set_transformation(const Geometry::Transformation& trafo);
|
||||
|
||||
// Return the triangulated cut. The points are returned directly
|
||||
// in world coordinates.
|
||||
const std::vector<Vec3f>& get_triangles();
|
||||
|
||||
private:
|
||||
|
|
@ -91,21 +101,39 @@ private:
|
|||
|
||||
|
||||
|
||||
|
||||
// MeshRaycaster class answers queries such as where on the mesh someone clicked,
|
||||
// whether certain points are visible or obscured by the mesh etc.
|
||||
class MeshRaycaster {
|
||||
public:
|
||||
// The class saves a const* to the mesh, calledz is responsible
|
||||
// for making sure it does not get invalid.
|
||||
MeshRaycaster(const TriangleMesh& mesh)
|
||||
: m_mesh(&mesh), m_emesh(mesh)
|
||||
: m_emesh(mesh), m_mesh(&mesh)
|
||||
{}
|
||||
void set_transformation(const Geometry::Transformation& trafo);
|
||||
void set_camera(const Camera& camera);
|
||||
|
||||
bool unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
|
||||
Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane = nullptr) const;
|
||||
// Given a mouse position, this returns true in case it is on the mesh.
|
||||
bool unproject_on_mesh(
|
||||
const Vec2d& mouse_pos,
|
||||
const Transform3d& trafo, // how to get the mesh into world coords
|
||||
const Camera& camera, // current camera position
|
||||
Vec3f& position, // where to save the positibon of the hit (mesh coords)
|
||||
Vec3f& normal, // normal of the triangle that was hit
|
||||
const ClippingPlane* clipping_plane = nullptr // clipping plane (if active)
|
||||
) const;
|
||||
|
||||
std::vector<unsigned> get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera,
|
||||
const std::vector<Vec3f>& points, const ClippingPlane* clipping_plane = nullptr) const;
|
||||
// Given a vector of points in woorld coordinates, this returns vector
|
||||
// of indices of points that are visible (i.e. not cut by clipping plane
|
||||
// or obscured by part of the mesh.
|
||||
std::vector<unsigned> get_unobscured_idxs(
|
||||
const Geometry::Transformation& trafo, // how to get the mesh into world coords
|
||||
const Camera& camera, // current camera position
|
||||
const std::vector<Vec3f>& points, // points in world coords
|
||||
const ClippingPlane* clipping_plane = nullptr // clipping plane (if active)
|
||||
) const;
|
||||
|
||||
// Given a point in world coords, the method returns closest point on the mesh.
|
||||
// The output is in mesh coords.
|
||||
// normal* can be used to also get normal of the respective triangle.
|
||||
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
|
||||
|
||||
private:
|
||||
|
|
|
|||
816
src/slic3r/GUI/Mouse3DController.cpp
Normal file
816
src/slic3r/GUI/Mouse3DController.cpp
Normal file
|
|
@ -0,0 +1,816 @@
|
|||
#include "libslic3r/libslic3r.h"
|
||||
#include "Mouse3DController.hpp"
|
||||
|
||||
#include "Camera.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
|
||||
#include <wx/glcanvas.h>
|
||||
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
// WARN: If updating these lists, please also update resources/udev/90-3dconnexion.rules
|
||||
|
||||
static const std::vector<int> _3DCONNEXION_VENDORS =
|
||||
{
|
||||
0x046d, // LOGITECH = 1133 // Logitech (3Dconnexion is made by Logitech)
|
||||
0x256F // 3DCONNECTION = 9583 // 3Dconnexion
|
||||
};
|
||||
|
||||
// See: https://github.com/FreeSpacenav/spacenavd/blob/a9eccf34e7cac969ee399f625aef827f4f4aaec6/src/dev.c#L202
|
||||
static const std::vector<int> _3DCONNEXION_DEVICES =
|
||||
{
|
||||
0xc603, /* 50691 spacemouse plus XT */
|
||||
0xc605, /* 50693 cadman */
|
||||
0xc606, /* 50694 spacemouse classic */
|
||||
0xc621, /* 50721 spaceball 5000 */
|
||||
0xc623, /* 50723 space traveller */
|
||||
0xc625, /* 50725 space pilot */
|
||||
0xc626, /* 50726 space navigator *TESTED* */
|
||||
0xc627, /* 50727 space explorer */
|
||||
0xc628, /* 50728 space navigator for notebooks*/
|
||||
0xc629, /* 50729 space pilot pro*/
|
||||
0xc62b, /* 50731 space mouse pro*/
|
||||
0xc62e, /* 50734 spacemouse wireless (USB cable) *TESTED* */
|
||||
0xc62f, /* 50735 spacemouse wireless receiver */
|
||||
0xc631, /* 50737 spacemouse pro wireless *TESTED* */
|
||||
0xc632, /* 50738 spacemouse pro wireless receiver */
|
||||
0xc633, /* 50739 spacemouse enterprise */
|
||||
0xc635, /* 50741 spacemouse compact *TESTED* */
|
||||
0xc636, /* 50742 spacemouse module */
|
||||
0xc640, /* 50752 nulooq */
|
||||
0xc652, /* 50770 3Dconnexion universal receiver *TESTED* */
|
||||
};
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
const double Mouse3DController::State::DefaultTranslationScale = 2.5;
|
||||
const double Mouse3DController::State::MaxTranslationDeadzone = 0.2;
|
||||
const double Mouse3DController::State::DefaultTranslationDeadzone = 0.5 * Mouse3DController::State::MaxTranslationDeadzone;
|
||||
const float Mouse3DController::State::DefaultRotationScale = 1.0f;
|
||||
const float Mouse3DController::State::MaxRotationDeadzone = (float)Mouse3DController::State::MaxTranslationDeadzone;
|
||||
const float Mouse3DController::State::DefaultRotationDeadzone = 0.5f * Mouse3DController::State::MaxRotationDeadzone;
|
||||
|
||||
Mouse3DController::State::State()
|
||||
: m_buttons_enabled(false)
|
||||
, m_translation_params(DefaultTranslationScale, DefaultTranslationDeadzone)
|
||||
, m_rotation_params(DefaultRotationScale, DefaultRotationDeadzone)
|
||||
, m_mouse_wheel_counter(0)
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
, m_translation_queue_max_size(0)
|
||||
, m_rotation_queue_max_size(0)
|
||||
, m_buttons_queue_max_size(0)
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
{
|
||||
}
|
||||
|
||||
void Mouse3DController::State::append_translation(const Vec3d& translation)
|
||||
{
|
||||
while (m_translation.queue.size() >= m_translation.max_size)
|
||||
{
|
||||
m_translation.queue.pop();
|
||||
}
|
||||
m_translation.queue.push(translation);
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_translation_queue_max_size = std::max(m_translation_queue_max_size, m_translation.queue.size());
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
}
|
||||
|
||||
void Mouse3DController::State::append_rotation(const Vec3f& rotation)
|
||||
{
|
||||
while (m_rotation.queue.size() >= m_rotation.max_size)
|
||||
{
|
||||
m_rotation.queue.pop();
|
||||
}
|
||||
m_rotation.queue.push(rotation);
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_rotation_queue_max_size = std::max(m_rotation_queue_max_size, m_rotation.queue.size());
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
if (rotation(0) != 0.0f)
|
||||
++m_mouse_wheel_counter;
|
||||
}
|
||||
|
||||
void Mouse3DController::State::append_button(unsigned int id)
|
||||
{
|
||||
if (!m_buttons_enabled)
|
||||
return;
|
||||
|
||||
m_buttons.push(id);
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_buttons_queue_max_size = std::max(m_buttons_queue_max_size, m_buttons.size());
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
}
|
||||
|
||||
bool Mouse3DController::State::process_mouse_wheel()
|
||||
{
|
||||
if (m_mouse_wheel_counter == 0)
|
||||
return false;
|
||||
else if (!m_rotation.queue.empty())
|
||||
{
|
||||
--m_mouse_wheel_counter;
|
||||
return true;
|
||||
}
|
||||
|
||||
m_mouse_wheel_counter = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Mouse3DController::State::set_queues_max_size(size_t size)
|
||||
{
|
||||
if (size > 0)
|
||||
{
|
||||
m_translation.max_size = size;
|
||||
m_rotation.max_size = size;
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_translation_queue_max_size = 0;
|
||||
m_rotation_queue_max_size = 0;
|
||||
m_buttons_queue_max_size = 0;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
}
|
||||
}
|
||||
|
||||
bool Mouse3DController::State::apply(Camera& camera)
|
||||
{
|
||||
if (!wxGetApp().IsActive())
|
||||
return false;
|
||||
|
||||
bool ret = false;
|
||||
|
||||
if (has_translation())
|
||||
{
|
||||
const Vec3d& translation = m_translation.queue.front();
|
||||
camera.set_target(camera.get_target() + m_translation_params.scale * (translation(0) * camera.get_dir_right() + translation(1) * camera.get_dir_forward() + translation(2) * camera.get_dir_up()));
|
||||
m_translation.queue.pop();
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (has_rotation())
|
||||
{
|
||||
const Vec3f& rotation = m_rotation.queue.front();
|
||||
float theta = m_rotation_params.scale * rotation(0);
|
||||
float phi = m_rotation_params.scale * rotation(2);
|
||||
float sign = camera.inverted_phi ? -1.0f : 1.0f;
|
||||
camera.phi += sign * phi;
|
||||
camera.set_theta(camera.get_theta() + theta, wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA);
|
||||
m_rotation.queue.pop();
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (m_buttons_enabled && has_button())
|
||||
{
|
||||
unsigned int button = m_buttons.front();
|
||||
switch (button)
|
||||
{
|
||||
case 0: { camera.update_zoom(1.0); break; }
|
||||
case 1: { camera.update_zoom(-1.0); break; }
|
||||
default: { break; }
|
||||
}
|
||||
m_buttons.pop();
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Mouse3DController::Mouse3DController()
|
||||
: m_initialized(false)
|
||||
, m_device(nullptr)
|
||||
, m_device_str("")
|
||||
, m_running(false)
|
||||
, m_settings_dialog(false)
|
||||
{
|
||||
m_last_time = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void Mouse3DController::init()
|
||||
{
|
||||
if (m_initialized)
|
||||
return;
|
||||
|
||||
// Initialize the hidapi library
|
||||
int res = hid_init();
|
||||
if (res != 0)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library";
|
||||
return;
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
void Mouse3DController::shutdown()
|
||||
{
|
||||
if (!m_initialized)
|
||||
return;
|
||||
|
||||
stop();
|
||||
disconnect_device();
|
||||
|
||||
// Finalize the hidapi library
|
||||
hid_exit();
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
bool Mouse3DController::apply(Camera& camera)
|
||||
{
|
||||
if (!m_initialized)
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
// check if the user unplugged the device
|
||||
if (!m_running && is_device_connected())
|
||||
{
|
||||
disconnect_device();
|
||||
// hides the settings dialog if the user re-plug the device
|
||||
m_settings_dialog = false;
|
||||
}
|
||||
|
||||
// check if the user plugged the device
|
||||
if (connect_device())
|
||||
start();
|
||||
|
||||
return is_device_connected() ? m_state.apply(camera) : false;
|
||||
}
|
||||
|
||||
void Mouse3DController::render_settings_dialog(unsigned int canvas_width, unsigned int canvas_height) const
|
||||
{
|
||||
if (!m_running || !m_settings_dialog)
|
||||
return;
|
||||
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
|
||||
imgui.set_next_window_pos(0.5f * (float)canvas_width, 0.5f * (float)canvas_height, ImGuiCond_Always, 0.5f, 0.5f);
|
||||
|
||||
imgui.begin(_(L("3Dconnexion settings")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text(_(L("Device:")));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
imgui.text(m_device_str);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text(_(L("Speed:")));
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
float translation_scale = (float)m_state.get_translation_scale() / State::DefaultTranslationScale;
|
||||
if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.5f, 2.0f, "%.1f"))
|
||||
m_state.set_translation_scale(State::DefaultTranslationScale * (double)translation_scale);
|
||||
|
||||
float rotation_scale = m_state.get_rotation_scale() / State::DefaultRotationScale;
|
||||
if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.5f, 2.0f, "%.1f"))
|
||||
m_state.set_rotation_scale(State::DefaultRotationScale * rotation_scale);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text(_(L("Deadzone:")));
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
float translation_deadzone = (float)m_state.get_translation_deadzone();
|
||||
if (imgui.slider_float(_(L("Translation")) + "##2", &translation_deadzone, 0.0f, (float)State::MaxTranslationDeadzone, "%.2f"))
|
||||
m_state.set_translation_deadzone((double)translation_deadzone);
|
||||
|
||||
float rotation_deadzone = m_state.get_rotation_deadzone();
|
||||
if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, State::MaxRotationDeadzone, "%.2f"))
|
||||
m_state.set_rotation_deadzone(rotation_deadzone);
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
ImGui::Separator();
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text("DEBUG:");
|
||||
imgui.text("Vectors:");
|
||||
ImGui::PopStyleColor();
|
||||
Vec3f translation = m_state.get_translation().cast<float>();
|
||||
Vec3f rotation = m_state.get_rotation();
|
||||
ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text("Queue size:");
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
int translation_size[2] = { (int)m_state.get_translation_queue_size(), (int)m_state.get_translation_queue_max_size() };
|
||||
int rotation_size[2] = { (int)m_state.get_rotation_queue_size(), (int)m_state.get_rotation_queue_max_size() };
|
||||
int buttons_size[2] = { (int)m_state.get_buttons_queue_size(), (int)m_state.get_buttons_queue_max_size() };
|
||||
|
||||
ImGui::InputInt2("Translation##4", translation_size, ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputInt2("Rotation##4", rotation_size, ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputInt2("Buttons", buttons_size, ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
int queue_size = (int)m_state.get_queues_max_size();
|
||||
if (ImGui::InputInt("Max size", &queue_size, 1, 1, ImGuiInputTextFlags_ReadOnly))
|
||||
{
|
||||
if (queue_size > 0)
|
||||
m_state.set_queues_max_size(queue_size);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text("Camera:");
|
||||
ImGui::PopStyleColor();
|
||||
Vec3f target = wxGetApp().plater()->get_camera().get_target().cast<float>();
|
||||
ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
imgui.end();
|
||||
}
|
||||
|
||||
bool Mouse3DController::connect_device()
|
||||
{
|
||||
static const long long DETECTION_TIME_MS = 2000; // seconds
|
||||
|
||||
if (is_device_connected())
|
||||
return false;
|
||||
|
||||
// check time since last detection took place
|
||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_last_time).count() < DETECTION_TIME_MS)
|
||||
return false;
|
||||
|
||||
m_last_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// Enumerates devices
|
||||
hid_device_info* devices = hid_enumerate(0, 0);
|
||||
if (devices == nullptr)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Unable to enumerate HID devices";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Searches for 1st connected 3Dconnexion device
|
||||
struct DeviceData
|
||||
{
|
||||
std::string path;
|
||||
unsigned short usage_page;
|
||||
unsigned short usage;
|
||||
|
||||
DeviceData()
|
||||
: path(""), usage_page(0), usage(0)
|
||||
{}
|
||||
DeviceData(const std::string& path, unsigned short usage_page, unsigned short usage)
|
||||
: path(path), usage_page(usage_page), usage(usage)
|
||||
{}
|
||||
|
||||
bool has_valid_usage() const { return (usage_page == 1) && (usage == 8); }
|
||||
};
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
hid_device_info* cur = devices;
|
||||
std::cout << std::endl << "======================================================================================================================================" << std::endl;
|
||||
std::cout << "Detected devices:" << std::endl;
|
||||
while (cur != nullptr)
|
||||
{
|
||||
std::cout << "\"";
|
||||
std::wcout << ((cur->manufacturer_string != nullptr) ? cur->manufacturer_string : L"Unknown");
|
||||
std::cout << "/";
|
||||
std::wcout << ((cur->product_string != nullptr) ? cur->product_string : L"Unknown");
|
||||
std::cout << "\" code: " << cur->vendor_id << "/" << cur->product_id << " (" << std::hex << cur->vendor_id << "/" << cur->product_id << std::dec << ")";
|
||||
std::cout << " serial number: '";
|
||||
std::wcout << ((cur->serial_number != nullptr) ? cur->serial_number : L"Unknown");
|
||||
std::cout << "' usage page: " << cur->usage_page << " usage: " << cur->usage << " interface number: " << cur->interface_number << std::endl;
|
||||
|
||||
cur = cur->next;
|
||||
}
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
// When using 3Dconnexion universal receiver, multiple devices are detected sharing the same vendor_id and product_id.
|
||||
// To choose from them the right one we use:
|
||||
// On Windows and Mac: usage_page == 1 and usage == 8
|
||||
// On Linux: as usage_page and usage are not defined (see hidapi.h) we try all detected devices until one is succesfully open
|
||||
// When only a single device is detected, as for wired connections, vendor_id and product_id are enough
|
||||
|
||||
// First we count all the valid devices from the enumerated list,
|
||||
|
||||
hid_device_info* current = devices;
|
||||
typedef std::pair<unsigned short, unsigned short> DeviceIds;
|
||||
typedef std::vector<DeviceData> DeviceDataList;
|
||||
typedef std::map<DeviceIds, DeviceDataList> DetectedDevices;
|
||||
DetectedDevices detected_devices;
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << std::endl << "Detected 3D connexion devices:" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
while (current != nullptr)
|
||||
{
|
||||
unsigned short vendor_id = 0;
|
||||
unsigned short product_id = 0;
|
||||
|
||||
for (size_t i = 0; i < _3DCONNEXION_VENDORS.size(); ++i)
|
||||
{
|
||||
if (_3DCONNEXION_VENDORS[i] == current->vendor_id)
|
||||
{
|
||||
vendor_id = current->vendor_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (vendor_id != 0)
|
||||
{
|
||||
for (size_t i = 0; i < _3DCONNEXION_DEVICES.size(); ++i)
|
||||
{
|
||||
if (_3DCONNEXION_DEVICES[i] == current->product_id)
|
||||
{
|
||||
product_id = current->product_id;
|
||||
DeviceIds detected_device(vendor_id, product_id);
|
||||
DetectedDevices::iterator it = detected_devices.find(detected_device);
|
||||
if (it == detected_devices.end())
|
||||
it = detected_devices.insert(DetectedDevices::value_type(detected_device, DeviceDataList())).first;
|
||||
|
||||
it->second.emplace_back(current->path, current->usage_page, current->usage);
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::wcout << "\"" << ((current->manufacturer_string != nullptr) ? current->manufacturer_string : L"Unknown");
|
||||
std::cout << "/";
|
||||
std::wcout << ((current->product_string != nullptr) ? current->product_string : L"Unknown");
|
||||
std::cout << "\" code: " << current->vendor_id << "/" << current->product_id << " (" << std::hex << current->vendor_id << "/" << current->product_id << std::dec << ")";
|
||||
std::cout << " serial number: '";
|
||||
std::wcout << ((current->serial_number != nullptr) ? current->serial_number : L"Unknown");
|
||||
std::cout << "' usage page: " << current->usage_page << " usage: " << current->usage << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
// Free enumerated devices
|
||||
hid_free_enumeration(devices);
|
||||
|
||||
if (detected_devices.empty())
|
||||
return false;
|
||||
|
||||
std::string path = "";
|
||||
unsigned short vendor_id = 0;
|
||||
unsigned short product_id = 0;
|
||||
|
||||
// Then we'll decide the choosing logic to apply in dependence of the device count and operating system
|
||||
|
||||
for (const DetectedDevices::value_type& device : detected_devices)
|
||||
{
|
||||
if (device.second.size() == 1)
|
||||
{
|
||||
#ifdef __linux__
|
||||
hid_device* test_device = hid_open(device.first.first, device.first.second, nullptr);
|
||||
if (test_device != nullptr)
|
||||
{
|
||||
hid_close(test_device);
|
||||
#else
|
||||
if (device.second.front().has_valid_usage())
|
||||
{
|
||||
#endif // __linux__
|
||||
vendor_id = device.first.first;
|
||||
product_id = device.first.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool found = false;
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
for (const DeviceData& data : device.second)
|
||||
{
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << "Test device: " << std::hex << device.first.first << std::dec << "/" << std::hex << device.first.second << std::dec << " \"" << data.path << "\"";
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
#ifdef __linux__
|
||||
hid_device* test_device = hid_open_path(data.path.c_str());
|
||||
if (test_device != nullptr)
|
||||
{
|
||||
path = data.path;
|
||||
vendor_id = device.first.first;
|
||||
product_id = device.first.second;
|
||||
found = true;
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << "-> PASSED" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
hid_close(test_device);
|
||||
break;
|
||||
}
|
||||
#else
|
||||
if (data.has_valid_usage())
|
||||
{
|
||||
path = data.path;
|
||||
vendor_id = device.first.first;
|
||||
product_id = device.first.second;
|
||||
found = true;
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << "-> PASSED" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
break;
|
||||
}
|
||||
#endif // __linux__
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
else
|
||||
std::cout << "-> NOT PASSED" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
}
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.empty())
|
||||
{
|
||||
if ((vendor_id != 0) && (product_id != 0))
|
||||
{
|
||||
// Open the 3Dconnexion device using vendor_id and product_id
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << std::endl << "Opening device: " << std::hex << vendor_id << std::dec << "/" << std::hex << product_id << std::dec << " using hid_open()" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_device = hid_open(vendor_id, product_id, nullptr);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Open the 3Dconnexion device using the device path
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << std::endl << "Opening device: " << std::hex << vendor_id << std::dec << "/" << std::hex << product_id << std::dec << "\"" << path << "\" using hid_open_path()" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_device = hid_open_path(path.c_str());
|
||||
}
|
||||
|
||||
if (m_device != nullptr)
|
||||
{
|
||||
std::vector<wchar_t> manufacturer(1024, 0);
|
||||
hid_get_manufacturer_string(m_device, manufacturer.data(), 1024);
|
||||
m_device_str = boost::nowide::narrow(manufacturer.data());
|
||||
|
||||
std::vector<wchar_t> product(1024, 0);
|
||||
hid_get_product_string(m_device, product.data(), 1024);
|
||||
m_device_str += "/" + boost::nowide::narrow(product.data());
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Connected device: " << m_device_str;
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << std::endl << "Connected device:" << std::endl;
|
||||
std::cout << "Manufacturer/product: " << m_device_str << std::endl;
|
||||
std::cout << "Manufacturer id.....: " << vendor_id << " (" << std::hex << vendor_id << std::dec << ")" << std::endl;
|
||||
std::cout << "Product id..........: " << product_id << " (" << std::hex << product_id << std::dec << ")" << std::endl;
|
||||
std::cout << "Path................: '" << path << "'" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
// get device parameters from the config, if present
|
||||
double translation_speed = 1.0;
|
||||
float rotation_speed = 1.0;
|
||||
double translation_deadzone = State::DefaultTranslationDeadzone;
|
||||
float rotation_deadzone = State::DefaultRotationDeadzone;
|
||||
wxGetApp().app_config->get_mouse_device_translation_speed(m_device_str, translation_speed);
|
||||
wxGetApp().app_config->get_mouse_device_translation_deadzone(m_device_str, translation_deadzone);
|
||||
wxGetApp().app_config->get_mouse_device_rotation_speed(m_device_str, rotation_speed);
|
||||
wxGetApp().app_config->get_mouse_device_rotation_deadzone(m_device_str, rotation_deadzone);
|
||||
// clamp to valid values
|
||||
m_state.set_translation_scale(State::DefaultTranslationScale * std::max(0.5, std::min(2.0, translation_speed)));
|
||||
m_state.set_translation_deadzone(std::max(0.0, std::min(State::MaxTranslationDeadzone, translation_deadzone)));
|
||||
m_state.set_rotation_scale(State::DefaultRotationScale * std::max(0.5f, std::min(2.0f, rotation_speed)));
|
||||
m_state.set_rotation_deadzone(std::max(0.0f, std::min(State::MaxRotationDeadzone, rotation_deadzone)));
|
||||
}
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
else
|
||||
{
|
||||
std::cout << std::endl << "Unable to connect to device:" << std::endl;
|
||||
std::cout << "Manufacturer/product: " << m_device_str << std::endl;
|
||||
std::cout << "Manufacturer id.....: " << vendor_id << " (" << std::hex << vendor_id << std::dec << ")" << std::endl;
|
||||
std::cout << "Product id..........: " << product_id << " (" << std::hex << product_id << std::dec << ")" << std::endl;
|
||||
std::cout << "Path................: '" << path << "'" << std::endl;
|
||||
}
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
return (m_device != nullptr);
|
||||
}
|
||||
|
||||
void Mouse3DController::disconnect_device()
|
||||
{
|
||||
if (!is_device_connected())
|
||||
return;
|
||||
|
||||
// Stop the secondary thread, if running
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
|
||||
// Store current device parameters into the config
|
||||
wxGetApp().app_config->set_mouse_device(m_device_str, m_state.get_translation_scale() / State::DefaultTranslationScale, m_state.get_translation_deadzone(),
|
||||
m_state.get_rotation_scale() / State::DefaultRotationScale, m_state.get_rotation_deadzone());
|
||||
wxGetApp().app_config->save();
|
||||
|
||||
// Close the 3Dconnexion device
|
||||
hid_close(m_device);
|
||||
m_device = nullptr;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Disconnected device: " << m_device_str;
|
||||
|
||||
m_device_str = "";
|
||||
}
|
||||
|
||||
void Mouse3DController::start()
|
||||
{
|
||||
if (!is_device_connected() || m_running)
|
||||
return;
|
||||
|
||||
m_thread = std::thread(&Mouse3DController::run, this);
|
||||
}
|
||||
|
||||
void Mouse3DController::run()
|
||||
{
|
||||
m_running = true;
|
||||
while (m_running)
|
||||
{
|
||||
collect_input();
|
||||
}
|
||||
}
|
||||
|
||||
void Mouse3DController::collect_input()
|
||||
{
|
||||
DataPacket packet = { 0 };
|
||||
int res = hid_read_timeout(m_device, packet.data(), packet.size(), 100);
|
||||
if (res < 0)
|
||||
{
|
||||
// An error occourred (device detached from pc ?)
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wxGetApp().IsActive())
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
bool updated = false;
|
||||
|
||||
if (res == 7)
|
||||
updated = handle_packet(packet);
|
||||
else if (res == 13)
|
||||
updated = handle_wireless_packet(packet);
|
||||
else if ((res == 3) && (packet[0] == 3))
|
||||
// On Mac button packets can be 3 bytes long
|
||||
updated = handle_packet(packet);
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
else if (res > 0)
|
||||
std::cout << "Got unknown data packet of length: " << res << ", code:" << (int)packet[0] << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
if (updated)
|
||||
// ask for an idle event to update 3D scene
|
||||
wxWakeUpIdle();
|
||||
}
|
||||
|
||||
bool Mouse3DController::handle_packet(const DataPacket& packet)
|
||||
{
|
||||
switch (packet[0])
|
||||
{
|
||||
case 1: // Translation
|
||||
{
|
||||
if (handle_packet_translation(packet))
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: // Rotation
|
||||
{
|
||||
if (handle_packet_rotation(packet, 1))
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // Button
|
||||
{
|
||||
if (handle_packet_button(packet, packet.size() - 1))
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
case 23: // Battery charge
|
||||
{
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Mouse3DController::handle_wireless_packet(const DataPacket& packet)
|
||||
{
|
||||
switch (packet[0])
|
||||
{
|
||||
case 1: // Translation + Rotation
|
||||
{
|
||||
bool updated = handle_packet_translation(packet);
|
||||
updated |= handle_packet_rotation(packet, 7);
|
||||
|
||||
if (updated)
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // Button
|
||||
{
|
||||
if (handle_packet_button(packet, 12))
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
case 23: // Battery charge
|
||||
{
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
double convert_input(unsigned char first, unsigned char second, double deadzone)
|
||||
{
|
||||
short value = first | second << 8;
|
||||
double ret = (double)value / 350.0;
|
||||
return (std::abs(ret) > deadzone) ? ret : 0.0;
|
||||
}
|
||||
|
||||
bool Mouse3DController::handle_packet_translation(const DataPacket& packet)
|
||||
{
|
||||
double deadzone = m_state.get_translation_deadzone();
|
||||
Vec3d translation(-convert_input(packet[1], packet[2], deadzone),
|
||||
convert_input(packet[3], packet[4], deadzone),
|
||||
convert_input(packet[5], packet[6], deadzone));
|
||||
|
||||
if (!translation.isApprox(Vec3d::Zero()))
|
||||
{
|
||||
m_state.append_translation(translation);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Mouse3DController::handle_packet_rotation(const DataPacket& packet, unsigned int first_byte)
|
||||
{
|
||||
double deadzone = (double)m_state.get_rotation_deadzone();
|
||||
Vec3f rotation(-(float)convert_input(packet[first_byte + 0], packet[first_byte + 1], deadzone),
|
||||
(float)convert_input(packet[first_byte + 2], packet[first_byte + 3], deadzone),
|
||||
-(float)convert_input(packet[first_byte + 4], packet[first_byte + 5], deadzone));
|
||||
|
||||
if (!rotation.isApprox(Vec3f::Zero()))
|
||||
{
|
||||
m_state.append_rotation(rotation);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Mouse3DController::handle_packet_button(const DataPacket& packet, unsigned int packet_size)
|
||||
{
|
||||
unsigned int data = 0;
|
||||
for (unsigned int i = 1; i < packet_size; ++i)
|
||||
{
|
||||
data |= packet[i] << 8 * (i - 1);
|
||||
}
|
||||
|
||||
const std::bitset<32> data_bits{ data };
|
||||
for (size_t i = 0; i < data_bits.size(); ++i)
|
||||
{
|
||||
if (data_bits.test(i))
|
||||
{
|
||||
m_state.append_button((unsigned int)i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
175
src/slic3r/GUI/Mouse3DController.hpp
Normal file
175
src/slic3r/GUI/Mouse3DController.hpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#ifndef slic3r_Mouse3DController_hpp_
|
||||
#define slic3r_Mouse3DController_hpp_
|
||||
|
||||
// Enabled debug output to console and extended imgui dialog
|
||||
#define ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT 0
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
#include "hidapi.h"
|
||||
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
struct Camera;
|
||||
|
||||
class Mouse3DController
|
||||
{
|
||||
class State
|
||||
{
|
||||
public:
|
||||
static const double DefaultTranslationScale;
|
||||
static const double MaxTranslationDeadzone;
|
||||
static const double DefaultTranslationDeadzone;
|
||||
static const float DefaultRotationScale;
|
||||
static const float MaxRotationDeadzone;
|
||||
static const float DefaultRotationDeadzone;
|
||||
|
||||
private:
|
||||
template <typename Number>
|
||||
struct CustomParameters
|
||||
{
|
||||
Number scale;
|
||||
Number deadzone;
|
||||
|
||||
CustomParameters(Number scale, Number deadzone) : scale(scale), deadzone(deadzone) {}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct InputQueue
|
||||
{
|
||||
size_t max_size;
|
||||
std::queue<T> queue;
|
||||
|
||||
// The default value of 5 for max_size seems to work fine on all platforms
|
||||
// The effects of changing this value can be tested by setting ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT to 1
|
||||
// and playing with the imgui dialog which shows by pressing CTRL+M
|
||||
InputQueue() : max_size(5) {}
|
||||
};
|
||||
|
||||
InputQueue<Vec3d> m_translation;
|
||||
InputQueue<Vec3f> m_rotation;
|
||||
std::queue<unsigned int> m_buttons;
|
||||
|
||||
bool m_buttons_enabled;
|
||||
|
||||
CustomParameters<double> m_translation_params;
|
||||
CustomParameters<float> m_rotation_params;
|
||||
|
||||
// When the 3Dconnexion driver is running the system gets, by default, mouse wheel events when rotations around the X axis are detected.
|
||||
// We want to filter these out because we are getting the data directly from the device, bypassing the driver, and those mouse wheel events interfere
|
||||
// by triggering unwanted zoom in/out of the scene
|
||||
// The following variable is used to count the potential mouse wheel events triggered and is updated by:
|
||||
// Mouse3DController::collect_input() through the call to the append_rotation() method
|
||||
// GLCanvas3D::on_mouse_wheel() through the call to the process_mouse_wheel() method
|
||||
// GLCanvas3D::on_idle() through the call to the apply() method
|
||||
unsigned int m_mouse_wheel_counter;
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
size_t m_translation_queue_max_size;
|
||||
size_t m_rotation_queue_max_size;
|
||||
size_t m_buttons_queue_max_size;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
public:
|
||||
State();
|
||||
|
||||
void append_translation(const Vec3d& translation);
|
||||
void append_rotation(const Vec3f& rotation);
|
||||
void append_button(unsigned int id);
|
||||
|
||||
bool has_translation() const { return !m_translation.queue.empty(); }
|
||||
bool has_rotation() const { return !m_rotation.queue.empty(); }
|
||||
bool has_button() const { return !m_buttons.empty(); }
|
||||
|
||||
bool process_mouse_wheel();
|
||||
|
||||
double get_translation_scale() const { return m_translation_params.scale; }
|
||||
void set_translation_scale(double scale) { m_translation_params.scale = scale; }
|
||||
|
||||
float get_rotation_scale() const { return m_rotation_params.scale; }
|
||||
void set_rotation_scale(float scale) { m_rotation_params.scale = scale; }
|
||||
|
||||
double get_translation_deadzone() const { return m_translation_params.deadzone; }
|
||||
void set_translation_deadzone(double deadzone) { m_translation_params.deadzone = deadzone; }
|
||||
|
||||
float get_rotation_deadzone() const { return m_rotation_params.deadzone; }
|
||||
void set_rotation_deadzone(float deadzone) { m_rotation_params.deadzone = deadzone; }
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
Vec3d get_translation() const { return has_translation() ? m_translation.queue.front() : Vec3d::Zero(); }
|
||||
Vec3f get_rotation() const { return has_rotation() ? m_rotation.queue.front() : Vec3f::Zero(); }
|
||||
unsigned int get_button() const { return has_button() ? m_buttons.front() : 0; }
|
||||
|
||||
unsigned int get_translation_queue_size() const { return (unsigned int)m_translation.queue.size(); }
|
||||
unsigned int get_rotation_queue_size() const { return (unsigned int)m_rotation.queue.size(); }
|
||||
unsigned int get_buttons_queue_size() const { return (unsigned int)m_buttons.size(); }
|
||||
|
||||
size_t get_translation_queue_max_size() const { return m_translation_queue_max_size; }
|
||||
size_t get_rotation_queue_max_size() const { return m_rotation_queue_max_size; }
|
||||
size_t get_buttons_queue_max_size() const { return m_buttons_queue_max_size; }
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
size_t get_queues_max_size() const { return m_translation.max_size; }
|
||||
void set_queues_max_size(size_t size);
|
||||
|
||||
// return true if any change to the camera took place
|
||||
bool apply(Camera& camera);
|
||||
};
|
||||
|
||||
bool m_initialized;
|
||||
mutable State m_state;
|
||||
std::mutex m_mutex;
|
||||
std::thread m_thread;
|
||||
hid_device* m_device;
|
||||
std::string m_device_str;
|
||||
bool m_running;
|
||||
bool m_settings_dialog;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> m_last_time;
|
||||
|
||||
public:
|
||||
Mouse3DController();
|
||||
|
||||
void init();
|
||||
void shutdown();
|
||||
|
||||
bool is_device_connected() const { return m_device != nullptr; }
|
||||
bool is_running() const { return m_running; }
|
||||
|
||||
bool process_mouse_wheel() { std::lock_guard<std::mutex> lock(m_mutex); return m_state.process_mouse_wheel(); }
|
||||
|
||||
bool apply(Camera& camera);
|
||||
|
||||
bool is_settings_dialog_shown() const { return m_settings_dialog; }
|
||||
void show_settings_dialog(bool show) { m_settings_dialog = show && is_running(); }
|
||||
void render_settings_dialog(unsigned int canvas_width, unsigned int canvas_height) const;
|
||||
|
||||
private:
|
||||
bool connect_device();
|
||||
void disconnect_device();
|
||||
void start();
|
||||
void stop() { m_running = false; }
|
||||
|
||||
// secondary thread methods
|
||||
void run();
|
||||
void collect_input();
|
||||
|
||||
typedef std::array<unsigned char, 13> DataPacket;
|
||||
bool handle_packet(const DataPacket& packet);
|
||||
bool handle_wireless_packet(const DataPacket& packet);
|
||||
bool handle_packet_translation(const DataPacket& packet);
|
||||
bool handle_packet_rotation(const DataPacket& packet, unsigned int first_byte);
|
||||
bool handle_packet_button(const DataPacket& packet, unsigned int packet_size);
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_Mouse3DController_hpp_
|
||||
|
||||
|
|
@ -67,6 +67,7 @@
|
|||
#include "GUI_Preview.hpp"
|
||||
#include "3DBed.hpp"
|
||||
#include "Camera.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
|
|
@ -353,7 +354,10 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 *
|
|||
|
||||
// Call select_preset() only if there is new preset and not just modified
|
||||
if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) )
|
||||
tab->select_preset(selected_preset);
|
||||
{
|
||||
const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset);
|
||||
tab->select_preset(/*selected_preset*/preset_name);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
|
@ -1389,9 +1393,6 @@ struct Plater::priv
|
|||
Slic3r::Model model;
|
||||
PrinterTechnology printer_technology = ptFFF;
|
||||
Slic3r::GCodePreviewData gcode_preview_data;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
std::vector<Slic3r::ThumbnailData> thumbnail_data;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// GUI elements
|
||||
wxSizer* panel_sizer{ nullptr };
|
||||
|
|
@ -1400,6 +1401,7 @@ struct Plater::priv
|
|||
Sidebar *sidebar;
|
||||
Bed3D bed;
|
||||
Camera camera;
|
||||
Mouse3DController mouse3d_controller;
|
||||
View3D* view3D;
|
||||
GLToolbar view_toolbar;
|
||||
Preview *preview;
|
||||
|
|
@ -1839,6 +1841,10 @@ struct Plater::priv
|
|||
bool is_preview_loaded() const { return preview->is_loaded(); }
|
||||
bool is_view3D_shown() const { return current_panel == view3D; }
|
||||
|
||||
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
bool init_view_toolbar();
|
||||
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
|
||||
void reset_all_gizmos();
|
||||
void update_ui_from_settings();
|
||||
ProgressStatusBar* statusbar();
|
||||
|
|
@ -1968,7 +1974,8 @@ struct Plater::priv
|
|||
bool can_reload_from_disk() const;
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
|
||||
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
|
||||
void generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void msw_rescale_object_menu();
|
||||
|
|
@ -1984,7 +1991,9 @@ private:
|
|||
bool complit_init_object_menu();
|
||||
bool complit_init_sla_object_menu();
|
||||
bool complit_init_part_menu();
|
||||
#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
void init_view_toolbar();
|
||||
#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
|
||||
bool can_split() const;
|
||||
bool layers_height_allowed() const;
|
||||
|
|
@ -2039,7 +2048,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
background_process.set_sla_print(&sla_print);
|
||||
background_process.set_gcode_preview_data(&gcode_preview_data);
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
background_process.set_thumbnail_data(&thumbnail_data);
|
||||
background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
|
||||
{
|
||||
std::packaged_task<void(ThumbnailsList&, const Vec2ds&, bool, bool, bool, bool)> task([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) {
|
||||
generate_thumbnails(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background);
|
||||
});
|
||||
std::future<void> result = task.get_future();
|
||||
wxTheApp->CallAfter([&]() { task(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background); });
|
||||
result.wait();
|
||||
});
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
|
||||
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
|
||||
|
|
@ -2110,6 +2127,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); });
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); });
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); });
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); });
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event<float>& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); });
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); });
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
// 3DScene/Toolbar:
|
||||
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
|
||||
|
|
@ -2123,7 +2145,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this);
|
||||
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this);
|
||||
view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this);
|
||||
#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); });
|
||||
#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&)
|
||||
{
|
||||
set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values,
|
||||
|
|
@ -2159,12 +2183,16 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
// updates camera type from .ini file
|
||||
camera.set_type(get_config("use_perspective_camera"));
|
||||
|
||||
mouse3d_controller.init();
|
||||
|
||||
// Initialize the Undo / Redo stack with a first snapshot.
|
||||
this->take_snapshot(_(L("New Project")));
|
||||
}
|
||||
|
||||
Plater::priv::~priv()
|
||||
{
|
||||
mouse3d_controller.shutdown();
|
||||
|
||||
if (config != nullptr)
|
||||
delete config;
|
||||
}
|
||||
|
|
@ -2287,7 +2315,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
for (size_t i = 0; i < input_files.size(); i++) {
|
||||
const auto &path = input_files[i];
|
||||
const auto filename = path.filename();
|
||||
const auto dlg_info = wxString::Format(_(L("Processing input file %s\n")), from_path(filename));
|
||||
const auto dlg_info = wxString::Format(_(L("Processing input file %s")), from_path(filename)) + "\n";
|
||||
dlg.Update(100 * i / input_files.size(), dlg_info);
|
||||
|
||||
const bool type_3mf = std::regex_match(path.string(), pattern_3mf);
|
||||
|
|
@ -2328,6 +2356,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
// and place the loaded config over the base.
|
||||
config += std::move(config_loaded);
|
||||
}
|
||||
|
||||
this->model.custom_gcode_per_height = model.custom_gcode_per_height;
|
||||
}
|
||||
|
||||
if (load_config)
|
||||
|
|
@ -2359,17 +2389,17 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
if (! is_project_file) {
|
||||
if (model.looks_like_multipart_object()) {
|
||||
wxMessageDialog msg_dlg(q, _(L(
|
||||
"This file contains several objects positioned at multiple heights. "
|
||||
"This file contains several objects positioned at multiple heights.\n"
|
||||
"Instead of considering them as multiple objects, should I consider\n"
|
||||
"this file as a single object having multiple parts?\n"
|
||||
)), _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO);
|
||||
"this file as a single object having multiple parts?")) + "\n",
|
||||
_(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO);
|
||||
if (msg_dlg.ShowModal() == wxID_YES) {
|
||||
model.convert_multipart_object(nozzle_dmrs->values.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) {
|
||||
wxMessageDialog msg_dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")),
|
||||
wxMessageDialog msg_dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?"))+"\n",
|
||||
_(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO);
|
||||
if (msg_dlg.ShowModal() == wxID_YES)
|
||||
{
|
||||
|
|
@ -2414,8 +2444,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
wxMessageDialog msg_dlg(q, _(L(
|
||||
"Multiple objects were loaded for a multi-material printer.\n"
|
||||
"Instead of considering them as multiple objects, should I consider\n"
|
||||
"these files to represent a single object having multiple parts?\n"
|
||||
)), _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO);
|
||||
"these files to represent a single object having multiple parts?")) + "\n",
|
||||
_(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO);
|
||||
if (msg_dlg.ShowModal() == wxID_YES) {
|
||||
new_model->convert_multipart_object(nozzle_dmrs->values.size());
|
||||
}
|
||||
|
|
@ -2746,8 +2776,7 @@ void Plater::priv::reset()
|
|||
// The hiding of the slicing results, if shown, is not taken care by the background process, so we do it here
|
||||
this->sidebar->show_sliced_info_sizer(false);
|
||||
|
||||
auto& config = wxGetApp().preset_bundle->project_config;
|
||||
config.option<ConfigOptionFloats>("colorprint_heights")->values.clear();
|
||||
model.custom_gcode_per_height.clear();
|
||||
}
|
||||
|
||||
void Plater::priv::mirror(Axis axis)
|
||||
|
|
@ -3046,6 +3075,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
|
|||
this->update_print_volume_state();
|
||||
// Apply new config to the possibly running background task.
|
||||
bool was_running = this->background_process.running();
|
||||
this->background_process.set_force_update_print_regions(force_validation);
|
||||
Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config());
|
||||
|
||||
// Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
|
||||
|
|
@ -3153,37 +3183,6 @@ bool Plater::priv::restart_background_process(unsigned int state)
|
|||
( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) ||
|
||||
(state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 ||
|
||||
(state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) {
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
if (((state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) == 0) &&
|
||||
(this->background_process.state() != BackgroundSlicingProcess::STATE_RUNNING))
|
||||
{
|
||||
// update thumbnail data
|
||||
const std::vector<Vec2d> &thumbnail_sizes = this->background_process.current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values;
|
||||
if (this->printer_technology == ptFFF)
|
||||
{
|
||||
// for ptFFF we need to generate the thumbnails before the export of gcode starts
|
||||
this->thumbnail_data.clear();
|
||||
for (const Vec2d &sized : thumbnail_sizes)
|
||||
{
|
||||
this->thumbnail_data.push_back(ThumbnailData());
|
||||
Point size(sized); // round to ints
|
||||
generate_thumbnail(this->thumbnail_data.back(), size.x(), size.y(), true, true, false);
|
||||
}
|
||||
}
|
||||
else if (this->printer_technology == ptSLA)
|
||||
{
|
||||
// for ptSLA generate thumbnails without supports and pad (not yet calculated)
|
||||
// to render also supports and pad see on_slicing_update()
|
||||
this->thumbnail_data.clear();
|
||||
for (const Vec2d &sized : thumbnail_sizes)
|
||||
{
|
||||
this->thumbnail_data.push_back(ThumbnailData());
|
||||
Point size(sized); // round to ints
|
||||
generate_thumbnail(this->thumbnail_data.back(), size.x(), size.y(), true, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
// The print is valid and it can be started.
|
||||
if (this->background_process.start()) {
|
||||
this->statusbar()->set_cancel_callback([this]() {
|
||||
|
|
@ -3282,21 +3281,80 @@ void Plater::priv::reload_from_disk()
|
|||
for (unsigned int idx : selected_volumes_idxs)
|
||||
{
|
||||
const GLVolume* v = selection.get_volume(idx);
|
||||
int o_idx = v->object_idx();
|
||||
int v_idx = v->volume_idx();
|
||||
selected_volumes.push_back({ o_idx, v_idx });
|
||||
if (v_idx >= 0)
|
||||
{
|
||||
int o_idx = v->object_idx();
|
||||
if ((0 <= o_idx) && (o_idx < (int)model.objects.size()))
|
||||
selected_volumes.push_back({ o_idx, v_idx });
|
||||
}
|
||||
}
|
||||
std::sort(selected_volumes.begin(), selected_volumes.end());
|
||||
selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end());
|
||||
|
||||
// collects paths of files to load
|
||||
std::vector<fs::path> input_paths;
|
||||
#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
|
||||
std::vector<fs::path> missing_input_paths;
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
|
||||
for (const SelectedVolume& v : selected_volumes)
|
||||
{
|
||||
#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
|
||||
const ModelObject* object = model.objects[v.object_idx];
|
||||
const ModelVolume* volume = object->volumes[v.volume_idx];
|
||||
|
||||
if (!volume->source.input_file.empty())
|
||||
{
|
||||
if (fs::exists(volume->source.input_file))
|
||||
input_paths.push_back(volume->source.input_file);
|
||||
else
|
||||
missing_input_paths.push_back(volume->source.input_file);
|
||||
}
|
||||
#else
|
||||
const ModelVolume* volume = model.objects[v.object_idx]->volumes[v.volume_idx];
|
||||
if (!volume->source.input_file.empty() && boost::filesystem::exists(volume->source.input_file))
|
||||
input_paths.push_back(volume->source.input_file);
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
|
||||
}
|
||||
|
||||
#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
|
||||
std::sort(missing_input_paths.begin(), missing_input_paths.end());
|
||||
missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end());
|
||||
|
||||
while (!missing_input_paths.empty())
|
||||
{
|
||||
// ask user to select the missing file
|
||||
std::string search = missing_input_paths.back().string();
|
||||
wxFileDialog dialog(q, _(L("Please select the file to reload:")), "", search, file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dialog.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
std::string sel_filename_path = dialog.GetPath().ToUTF8().data();
|
||||
std::string sel_filename = fs::path(sel_filename_path).filename().string();
|
||||
if (boost::algorithm::iends_with(search, sel_filename))
|
||||
{
|
||||
input_paths.push_back(sel_filename_path);
|
||||
missing_input_paths.pop_back();
|
||||
|
||||
std::string sel_path = fs::path(sel_filename_path).remove_filename().string();
|
||||
|
||||
std::vector<fs::path>::iterator it = missing_input_paths.begin();
|
||||
while (it != missing_input_paths.end())
|
||||
{
|
||||
// try to use the path of the selected file with all remaining missing files
|
||||
std::string repathed_filename = sel_path + "/" + it->filename().string();
|
||||
if (fs::exists(repathed_filename))
|
||||
{
|
||||
input_paths.push_back(repathed_filename);
|
||||
it = missing_input_paths.erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
|
||||
|
||||
std::sort(input_paths.begin(), input_paths.end());
|
||||
input_paths.erase(std::unique(input_paths.begin(), input_paths.end()), input_paths.end());
|
||||
|
||||
|
|
@ -3317,7 +3375,6 @@ void Plater::priv::reload_from_disk()
|
|||
catch (std::exception&)
|
||||
{
|
||||
// error while loading
|
||||
view3D->get_canvas3d()->enable_render(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -3331,22 +3388,20 @@ void Plater::priv::reload_from_disk()
|
|||
|
||||
if (old_volume->source.input_file == path)
|
||||
{
|
||||
if (new_object_idx < (int)new_model.objects.size())
|
||||
assert(new_object_idx < (int)new_model.objects.size());
|
||||
ModelObject* new_model_object = new_model.objects[new_object_idx];
|
||||
if (new_volume_idx < (int)new_model_object->volumes.size())
|
||||
{
|
||||
ModelObject* new_model_object = new_model.objects[new_object_idx];
|
||||
if (new_volume_idx < (int)new_model_object->volumes.size())
|
||||
{
|
||||
old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]);
|
||||
ModelVolume* new_volume = old_model_object->volumes.back();
|
||||
new_volume->set_new_unique_id();
|
||||
new_volume->config.apply(old_volume->config);
|
||||
new_volume->set_type(old_volume->type());
|
||||
new_volume->set_material_id(old_volume->material_id());
|
||||
new_volume->set_transformation(old_volume->get_transformation());
|
||||
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
|
||||
std::swap(old_model_object->volumes[old_v.volume_idx], old_model_object->volumes.back());
|
||||
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
|
||||
}
|
||||
old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]);
|
||||
ModelVolume* new_volume = old_model_object->volumes.back();
|
||||
new_volume->set_new_unique_id();
|
||||
new_volume->config.apply(old_volume->config);
|
||||
new_volume->set_type(old_volume->type());
|
||||
new_volume->set_material_id(old_volume->material_id());
|
||||
new_volume->set_transformation(old_volume->get_transformation());
|
||||
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
|
||||
std::swap(old_model_object->volumes[old_v.volume_idx], old_model_object->volumes.back());
|
||||
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3430,6 +3485,7 @@ void Plater::priv::set_current_panel(wxPanel* panel)
|
|||
} else
|
||||
view3D->reload_scene(true);
|
||||
}
|
||||
|
||||
// sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably)
|
||||
view3D->set_as_dirty();
|
||||
view_toolbar.select_item("3D");
|
||||
|
|
@ -3444,6 +3500,7 @@ void Plater::priv::set_current_panel(wxPanel* panel)
|
|||
this->q->reslice();
|
||||
// keeps current gcode preview, if any
|
||||
preview->reload_print(true);
|
||||
|
||||
preview->set_canvas_as_dirty();
|
||||
view_toolbar.select_item("Preview");
|
||||
}
|
||||
|
|
@ -3466,9 +3523,10 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
|||
//! combo->GetStringSelection().ToUTF8().data());
|
||||
|
||||
const std::string& selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data();
|
||||
const std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, selected_string);
|
||||
|
||||
if (preset_type == Preset::TYPE_FILAMENT) {
|
||||
wxGetApp().preset_bundle->set_filament_preset(idx, selected_string);
|
||||
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
|
||||
}
|
||||
|
||||
// TODO: ?
|
||||
|
|
@ -3478,7 +3536,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
|||
}
|
||||
else {
|
||||
wxWindowUpdateLocker noUpdates(sidebar->presets_panel());
|
||||
wxGetApp().get_tab(preset_type)->select_preset(selected_string);
|
||||
wxGetApp().get_tab(preset_type)->select_preset(preset_name);
|
||||
}
|
||||
|
||||
// update plater with new config
|
||||
|
|
@ -3489,7 +3547,6 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
|||
* and for SLA presets they should be deleted
|
||||
*/
|
||||
if (preset_type == Preset::TYPE_PRINTER)
|
||||
// wxGetApp().obj_list()->update_settings_items();
|
||||
wxGetApp().obj_list()->update_object_list_by_printer_technology();
|
||||
}
|
||||
|
||||
|
|
@ -3521,25 +3578,6 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
|||
} else if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) {
|
||||
// Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways.
|
||||
this->preview->reload_print();
|
||||
|
||||
// uncomment the following lines if you want to render into the thumbnail also supports and pad for SLA printer
|
||||
/*
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// update thumbnail data
|
||||
// for ptSLA generate the thumbnail after supports and pad have been calculated to have them rendered
|
||||
if ((this->printer_technology == ptSLA) && (evt.status.percent == -3))
|
||||
{
|
||||
const std::vector<Vec2d>& thumbnail_sizes = this->background_process.current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values;
|
||||
this->thumbnail_data.clear();
|
||||
for (const Vec2d &sized : thumbnail_sizes)
|
||||
{
|
||||
this->thumbnail_data.push_back(ThumbnailData());
|
||||
Point size(sized); // round to ints
|
||||
generate_thumbnail(this->thumbnail_data.back(), size.x(), size.y(), true, false, false);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3655,17 +3693,29 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
|
|||
|
||||
wxMenu* menu = nullptr;
|
||||
|
||||
if (obj_idx == -1)
|
||||
menu = &default_menu;
|
||||
if (obj_idx == -1) // no one or several object are selected
|
||||
{
|
||||
if (evt.data.second) // right button was clicked on empty space
|
||||
menu = &default_menu;
|
||||
else
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If in 3DScene is(are) selected volume(s), but right button was clicked on empty space
|
||||
if (evt.data.second)
|
||||
return;
|
||||
|
||||
menu = printer_technology == ptSLA ? &sla_object_menu :
|
||||
get_selection().is_single_full_instance() ? // show "Object menu" for each FullInstance instead of FullObject
|
||||
&object_menu : &part_menu;
|
||||
if (printer_technology == ptSLA)
|
||||
menu = &sla_object_menu;
|
||||
else
|
||||
{
|
||||
// show "Object menu" for each one or several FullInstance instead of FullObject
|
||||
const bool is_some_full_instances = get_selection().is_single_full_instance() ||
|
||||
get_selection().is_single_full_object() ||
|
||||
get_selection().is_multiple_full_instance();
|
||||
menu = is_some_full_instances ? &object_menu : &part_menu;
|
||||
}
|
||||
|
||||
sidebar->obj_list()->append_menu_item_settings(menu);
|
||||
|
||||
|
|
@ -3766,9 +3816,22 @@ bool Plater::priv::init_object_menu()
|
|||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
|
||||
void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
|
||||
{
|
||||
view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, transparent_background);
|
||||
view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background);
|
||||
}
|
||||
|
||||
void Plater::priv::generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
|
||||
{
|
||||
thumbnails.clear();
|
||||
for (const Vec2d& size : sizes)
|
||||
{
|
||||
thumbnails.push_back(ThumbnailData());
|
||||
Point isize(size); // round to ints
|
||||
generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), printable_only, parts_only, show_bed, transparent_background);
|
||||
if (!thumbnails.back().is_valid())
|
||||
thumbnails.pop_back();
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
|
|
@ -3845,14 +3908,18 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
|
|||
[this](wxCommandEvent&) { reload_from_disk(); }, "", nullptr, [this]() { return can_reload_from_disk(); }, q);
|
||||
|
||||
append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, _(L("Export the selected object as STL file")),
|
||||
[this](wxCommandEvent&) { q->export_stl(false, true); });
|
||||
[this](wxCommandEvent&) { q->export_stl(false, true); }, "", nullptr,
|
||||
[this]() {
|
||||
const Selection& selection = get_selection();
|
||||
return selection.is_single_full_instance() || selection.is_single_full_object();
|
||||
}, q);
|
||||
|
||||
menu->AppendSeparator();
|
||||
|
||||
q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) {
|
||||
const Selection& selection = get_selection();
|
||||
int instance_idx = selection.get_instance_idx();
|
||||
evt.Enable(instance_idx != -1);
|
||||
evt.Enable(selection.is_single_full_instance() || selection.is_single_full_object());
|
||||
if (instance_idx != -1)
|
||||
{
|
||||
evt.Check(model.objects[selection.get_object_idx()]->instances[instance_idx]->printable);
|
||||
|
|
@ -3898,7 +3965,7 @@ bool Plater::priv::complit_init_object_menu()
|
|||
object_menu.AppendSeparator();
|
||||
|
||||
// Layers Editing for object
|
||||
sidebar->obj_list()->append_menu_item_layers_editing(&object_menu);
|
||||
sidebar->obj_list()->append_menu_item_layers_editing(&object_menu, q);
|
||||
object_menu.AppendSeparator();
|
||||
|
||||
// "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume()
|
||||
|
|
@ -3933,8 +4000,18 @@ bool Plater::priv::complit_init_part_menu()
|
|||
return true;
|
||||
}
|
||||
|
||||
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
bool Plater::priv::init_view_toolbar()
|
||||
#else
|
||||
void Plater::priv::init_view_toolbar()
|
||||
#endif //!ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
{
|
||||
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
if (view_toolbar.get_items_count() > 0)
|
||||
// already initialized
|
||||
return true;
|
||||
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
|
||||
BackgroundTexture::Metadata background_data;
|
||||
background_data.filename = "toolbar_background.png";
|
||||
background_data.left = 16;
|
||||
|
|
@ -3943,7 +4020,11 @@ void Plater::priv::init_view_toolbar()
|
|||
background_data.bottom = 16;
|
||||
|
||||
if (!view_toolbar.init(background_data))
|
||||
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
return false;
|
||||
#else
|
||||
return;
|
||||
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
|
||||
view_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left);
|
||||
view_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Bottom);
|
||||
|
|
@ -3958,7 +4039,11 @@ void Plater::priv::init_view_toolbar()
|
|||
item.sprite_id = 0;
|
||||
item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); };
|
||||
if (!view_toolbar.add_item(item))
|
||||
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
return false;
|
||||
#else
|
||||
return;
|
||||
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
|
||||
item.name = "Preview";
|
||||
item.icon_filename = "preview.svg";
|
||||
|
|
@ -3966,10 +4051,18 @@ void Plater::priv::init_view_toolbar()
|
|||
item.sprite_id = 1;
|
||||
item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); };
|
||||
if (!view_toolbar.add_item(item))
|
||||
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
return false;
|
||||
#else
|
||||
return;
|
||||
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
|
||||
view_toolbar.select_item("3D");
|
||||
view_toolbar.set_enabled(true);
|
||||
|
||||
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
return true;
|
||||
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
}
|
||||
|
||||
bool Plater::priv::can_set_instance_to_object() const
|
||||
|
|
@ -4030,7 +4123,11 @@ bool Plater::priv::can_reload_from_disk() const
|
|||
for (const SelectedVolume& v : selected_volumes)
|
||||
{
|
||||
const ModelVolume* volume = model.objects[v.object_idx]->volumes[v.volume_idx];
|
||||
#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
|
||||
if (!volume->source.input_file.empty())
|
||||
#else
|
||||
if (!volume->source.input_file.empty() && boost::filesystem::exists(volume->source.input_file))
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
|
||||
paths.push_back(volume->source.input_file);
|
||||
}
|
||||
std::sort(paths.begin(), paths.end());
|
||||
|
|
@ -4282,6 +4379,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
|
|||
// Disable layer editing before the Undo / Redo jump.
|
||||
if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled())
|
||||
view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
|
||||
|
||||
// Make a copy of the snapshot, undo/redo could invalidate the iterator
|
||||
const UndoRedo::Snapshot snapshot_copy = *it_snapshot;
|
||||
// Do the jump in time.
|
||||
|
|
@ -4812,7 +4910,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
|
|||
wxBusyCursor wait;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
ThumbnailData thumbnail_data;
|
||||
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true);
|
||||
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true);
|
||||
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, &thumbnail_data)) {
|
||||
#else
|
||||
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
|
||||
|
|
@ -4893,7 +4991,7 @@ void Plater::reslice()
|
|||
p->show_action_buttons(true);
|
||||
|
||||
// update type of preview
|
||||
p->preview->update_view_type();
|
||||
p->preview->update_view_type(true);
|
||||
}
|
||||
|
||||
void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages)
|
||||
|
|
@ -5172,6 +5270,9 @@ std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const
|
|||
return extruder_colors;
|
||||
|
||||
extruder_colors = (config->option<ConfigOptionStrings>("extruder_colour"))->values;
|
||||
if (!wxGetApp().plater())
|
||||
return extruder_colors;
|
||||
|
||||
const std::vector<std::string>& filament_colours = (p->config->option<ConfigOptionStrings>("filament_colour"))->values;
|
||||
for (size_t i = 0; i < extruder_colors.size(); ++i)
|
||||
if (extruder_colors[i] == "" && i < filament_colours.size())
|
||||
|
|
@ -5180,6 +5281,17 @@ std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const
|
|||
return extruder_colors;
|
||||
}
|
||||
|
||||
std::vector<std::string> Plater::get_colors_for_color_print() const
|
||||
{
|
||||
std::vector<std::string> colors = get_extruder_colors_from_plater_config();
|
||||
|
||||
for (const Model::CustomGCode& code : p->model.custom_gcode_per_height)
|
||||
if (code.gcode == ColorChangeCode)
|
||||
colors.push_back(code.color);
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
wxString Plater::get_project_filename(const wxString& extension) const
|
||||
{
|
||||
return p->get_project_filename(extension);
|
||||
|
|
@ -5332,11 +5444,28 @@ void Plater::msw_rescale()
|
|||
GetParent()->Layout();
|
||||
}
|
||||
|
||||
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
bool Plater::init_view_toolbar()
|
||||
{
|
||||
return p->init_view_toolbar();
|
||||
}
|
||||
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
|
||||
const Camera& Plater::get_camera() const
|
||||
{
|
||||
return p->camera;
|
||||
}
|
||||
|
||||
const Mouse3DController& Plater::get_mouse3d_controller() const
|
||||
{
|
||||
return p->mouse3d_controller;
|
||||
}
|
||||
|
||||
Mouse3DController& Plater::get_mouse3d_controller()
|
||||
{
|
||||
return p->mouse3d_controller;
|
||||
}
|
||||
|
||||
bool Plater::can_delete() const { return p->can_delete(); }
|
||||
bool Plater::can_delete_all() const { return p->can_delete_all(); }
|
||||
bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class ObjectSettings;
|
|||
class ObjectLayers;
|
||||
class ObjectList;
|
||||
class GLCanvas3D;
|
||||
class Mouse3DController;
|
||||
|
||||
using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
|
||||
|
||||
|
|
@ -223,6 +224,7 @@ public:
|
|||
void on_activate();
|
||||
const DynamicPrintConfig* get_plater_config() const;
|
||||
std::vector<std::string> get_extruder_colors_from_plater_config() const;
|
||||
std::vector<std::string> get_colors_for_color_print() const;
|
||||
|
||||
void update_object_menu();
|
||||
|
||||
|
|
@ -260,7 +262,13 @@ public:
|
|||
|
||||
void msw_rescale();
|
||||
|
||||
#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
bool init_view_toolbar();
|
||||
#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
|
||||
|
||||
const Camera& get_camera() const;
|
||||
const Mouse3DController& get_mouse3d_controller() const;
|
||||
Mouse3DController& get_mouse3d_controller();
|
||||
|
||||
// ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
|
||||
class SuppressSnapshots
|
||||
|
|
|
|||
|
|
@ -303,60 +303,58 @@ std::string Preset::label() const
|
|||
return this->name + (this->is_dirty ? g_suffix_modified : "");
|
||||
}
|
||||
|
||||
bool Preset::is_compatible_with_print(const Preset &active_print) const
|
||||
bool is_compatible_with_print(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print)
|
||||
{
|
||||
auto &condition = this->compatible_prints_condition();
|
||||
auto *compatible_prints = dynamic_cast<const ConfigOptionStrings*>(this->config.option("compatible_prints"));
|
||||
if (preset.vendor != nullptr && preset.vendor != active_print.vendor)
|
||||
// The current profile has a vendor assigned and it is different from the active print's vendor.
|
||||
return false;
|
||||
auto &condition = preset.preset.compatible_prints_condition();
|
||||
auto *compatible_prints = dynamic_cast<const ConfigOptionStrings*>(preset.preset.config.option("compatible_prints"));
|
||||
bool has_compatible_prints = compatible_prints != nullptr && ! compatible_prints->values.empty();
|
||||
if (! has_compatible_prints && ! condition.empty()) {
|
||||
try {
|
||||
return PlaceholderParser::evaluate_boolean_expression(condition, active_print.config);
|
||||
return PlaceholderParser::evaluate_boolean_expression(condition, active_print.preset.config);
|
||||
} catch (const std::runtime_error &err) {
|
||||
//FIXME in case of an error, return "compatible with everything".
|
||||
printf("Preset::is_compatible_with_print - parsing error of compatible_prints_condition %s:\n%s\n", active_print.name.c_str(), err.what());
|
||||
printf("Preset::is_compatible_with_print - parsing error of compatible_prints_condition %s:\n%s\n", active_print.preset.name.c_str(), err.what());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return this->is_default || active_print.name.empty() || ! has_compatible_prints ||
|
||||
std::find(compatible_prints->values.begin(), compatible_prints->values.end(), active_print.name) !=
|
||||
return preset.preset.is_default || active_print.preset.name.empty() || ! has_compatible_prints ||
|
||||
std::find(compatible_prints->values.begin(), compatible_prints->values.end(), active_print.preset.name) !=
|
||||
compatible_prints->values.end();
|
||||
}
|
||||
|
||||
bool Preset::is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const
|
||||
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config)
|
||||
{
|
||||
auto &condition = this->compatible_printers_condition();
|
||||
auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(this->config.option("compatible_printers"));
|
||||
if (preset.vendor != nullptr && preset.vendor != active_printer.vendor)
|
||||
// The current profile has a vendor assigned and it is different from the active print's vendor.
|
||||
return false;
|
||||
auto &condition = preset.preset.compatible_printers_condition();
|
||||
auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(preset.preset.config.option("compatible_printers"));
|
||||
bool has_compatible_printers = compatible_printers != nullptr && ! compatible_printers->values.empty();
|
||||
if (! has_compatible_printers && ! condition.empty()) {
|
||||
try {
|
||||
return PlaceholderParser::evaluate_boolean_expression(condition, active_printer.config, extra_config);
|
||||
return PlaceholderParser::evaluate_boolean_expression(condition, active_printer.preset.config, extra_config);
|
||||
} catch (const std::runtime_error &err) {
|
||||
//FIXME in case of an error, return "compatible with everything".
|
||||
printf("Preset::is_compatible_with_printer - parsing error of compatible_printers_condition %s:\n%s\n", active_printer.name.c_str(), err.what());
|
||||
printf("Preset::is_compatible_with_printer - parsing error of compatible_printers_condition %s:\n%s\n", active_printer.preset.name.c_str(), err.what());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return this->is_default || active_printer.name.empty() || ! has_compatible_printers ||
|
||||
std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.name) !=
|
||||
return preset.preset.is_default || active_printer.preset.name.empty() || ! has_compatible_printers ||
|
||||
std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.preset.name) !=
|
||||
compatible_printers->values.end();
|
||||
}
|
||||
|
||||
bool Preset::is_compatible_with_printer(const Preset &active_printer) const
|
||||
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer)
|
||||
{
|
||||
DynamicPrintConfig config;
|
||||
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name));
|
||||
const ConfigOption *opt = active_printer.config.option("nozzle_diameter");
|
||||
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name));
|
||||
const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter");
|
||||
if (opt)
|
||||
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size()));
|
||||
return this->is_compatible_with_printer(active_printer, &config);
|
||||
}
|
||||
|
||||
bool Preset::update_compatible(const Preset &active_printer, const DynamicPrintConfig *extra_config, const Preset *active_print)
|
||||
{
|
||||
this->is_compatible = is_compatible_with_printer(active_printer, extra_config);
|
||||
if (active_print != nullptr)
|
||||
this->is_compatible &= is_compatible_with_print(*active_print);
|
||||
return this->is_compatible;
|
||||
return is_compatible_with_printer(preset, active_printer, &config);
|
||||
}
|
||||
|
||||
void Preset::set_visible_from_appconfig(const AppConfig &app_config)
|
||||
|
|
@ -814,6 +812,8 @@ void PresetCollection::save_current_preset(const std::string &new_name)
|
|||
preset.is_external = false;
|
||||
// The newly saved preset will be activated -> make it visible.
|
||||
preset.is_visible = true;
|
||||
// Just system presets have aliases
|
||||
preset.alias.clear();
|
||||
}
|
||||
// 2) Activate the saved preset.
|
||||
this->select_preset_by_name(new_name, true);
|
||||
|
|
@ -907,6 +907,35 @@ const Preset* PresetCollection::get_preset_parent(const Preset& child) const
|
|||
return (preset == nullptr/* || preset->is_default */|| preset->is_external) ? nullptr : preset;
|
||||
}
|
||||
|
||||
// Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist.
|
||||
PresetWithVendorProfile PresetCollection::get_preset_with_vendor_profile(const Preset &preset) const
|
||||
{
|
||||
const Preset *p = &preset;
|
||||
const VendorProfile *v = nullptr;
|
||||
do {
|
||||
if (p->vendor != nullptr) {
|
||||
v = p->vendor;
|
||||
break;
|
||||
}
|
||||
p = this->get_preset_parent(*p);
|
||||
} while (p != nullptr);
|
||||
return PresetWithVendorProfile(preset, v);
|
||||
}
|
||||
|
||||
const std::string& PresetCollection::get_preset_name_by_alias(const std::string& alias) const
|
||||
{
|
||||
for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) {
|
||||
const Preset& preset = this->m_presets[i];
|
||||
if (!preset.is_visible || (!preset.is_compatible && i != m_idx_selected))
|
||||
continue;
|
||||
|
||||
if (preset.alias == alias)
|
||||
return preset.name;
|
||||
}
|
||||
|
||||
return alias;
|
||||
}
|
||||
|
||||
const std::string& PresetCollection::get_suffix_modified() {
|
||||
return g_suffix_modified;
|
||||
}
|
||||
|
|
@ -944,19 +973,22 @@ void PresetCollection::set_default_suppressed(bool default_suppressed)
|
|||
}
|
||||
}
|
||||
|
||||
size_t PresetCollection::update_compatible_internal(const Preset &active_printer, const Preset *active_print, bool unselect_if_incompatible)
|
||||
size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, bool unselect_if_incompatible)
|
||||
{
|
||||
DynamicPrintConfig config;
|
||||
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name));
|
||||
const ConfigOption *opt = active_printer.config.option("nozzle_diameter");
|
||||
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name));
|
||||
const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter");
|
||||
if (opt)
|
||||
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size()));
|
||||
for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) {
|
||||
bool selected = idx_preset == m_idx_selected;
|
||||
Preset &preset_selected = m_presets[idx_preset];
|
||||
Preset &preset_edited = selected ? m_edited_preset : preset_selected;
|
||||
if (! preset_edited.update_compatible(active_printer, &config, active_print) &&
|
||||
selected && unselect_if_incompatible)
|
||||
const PresetWithVendorProfile this_preset_with_vendor_profile = this->get_preset_with_vendor_profile(preset_edited);
|
||||
preset_edited.is_compatible = is_compatible_with_printer(this_preset_with_vendor_profile, active_printer, &config);
|
||||
if (active_print != nullptr)
|
||||
preset_edited.is_compatible &= is_compatible_with_print(this_preset_with_vendor_profile, *active_print);
|
||||
if (! preset_edited.is_compatible && selected && unselect_if_incompatible)
|
||||
m_idx_selected = -1;
|
||||
if (selected)
|
||||
preset_selected.is_compatible = preset_edited.is_compatible;
|
||||
|
|
@ -982,7 +1014,7 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
|
|||
// Otherwise fill in the list from scratch.
|
||||
ui->Freeze();
|
||||
ui->Clear();
|
||||
size_t selected_preset_item = 0;
|
||||
size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected
|
||||
|
||||
const Preset &selected_preset = this->get_selected_preset();
|
||||
// Show wide icons if the currently selected preset is not compatible with the current printer,
|
||||
|
|
@ -1001,6 +1033,7 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
|
|||
|
||||
std::map<wxString, wxBitmap*> nonsys_presets;
|
||||
wxString selected = "";
|
||||
wxString tooltip = "";
|
||||
if (!this->m_presets.front().is_visible)
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap));
|
||||
for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++ i) {
|
||||
|
|
@ -1029,17 +1062,24 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
|
|||
bmp = m_bitmap_cache->insert(bitmap_key, bmps);
|
||||
}
|
||||
|
||||
const std::string name = preset.alias.empty() ? preset.name : preset.alias;
|
||||
if (preset.is_default || preset.is_system) {
|
||||
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
|
||||
ui->Append(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
|
||||
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
|
||||
if (i == m_idx_selected)
|
||||
if (i == m_idx_selected ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX) {
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
tooltip = wxString::FromUTF8(preset.name.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/);
|
||||
if (i == m_idx_selected)
|
||||
selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str());
|
||||
nonsys_presets.emplace(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/);
|
||||
if (i == m_idx_selected) {
|
||||
selected = wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str());
|
||||
tooltip = wxString::FromUTF8(preset.name.c_str());
|
||||
}
|
||||
}
|
||||
if (i + 1 == m_num_default_presets)
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap));
|
||||
|
|
@ -1049,7 +1089,9 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
|
|||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap));
|
||||
for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
|
||||
ui->Append(it->first, *it->second);
|
||||
if (it->first == selected)
|
||||
if (it->first == selected ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1080,8 +1122,15 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
|
|||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove materials")), wxNullBitmap), GUI::PresetComboBox::LABEL_ITEM_WIZARD_MATERIALS);
|
||||
}
|
||||
|
||||
/* But, if selected_preset_item is still equal to INT_MAX, it means that
|
||||
* there is no presets added to the list.
|
||||
* So, select last combobox item ("Add/Remove preset")
|
||||
*/
|
||||
if (selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
|
||||
ui->SetSelection(selected_preset_item);
|
||||
ui->SetToolTip(ui->GetString(selected_preset_item));
|
||||
ui->SetToolTip(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip);
|
||||
ui->check_selection();
|
||||
ui->Thaw();
|
||||
|
||||
|
|
@ -1096,7 +1145,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati
|
|||
return 0;
|
||||
ui->Freeze();
|
||||
ui->Clear();
|
||||
size_t selected_preset_item = 0;
|
||||
size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected
|
||||
|
||||
/* It's supposed that standard size of an icon is 16px*16px for 100% scaled display.
|
||||
* So set sizes for solid_colored(empty) icons used for preset
|
||||
|
|
@ -1131,7 +1180,9 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati
|
|||
if (preset.is_default || preset.is_system) {
|
||||
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
|
||||
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
|
||||
if (i == m_idx_selected)
|
||||
if (i == m_idx_selected ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
}
|
||||
else
|
||||
|
|
@ -1148,7 +1199,9 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati
|
|||
ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap);
|
||||
for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
|
||||
ui->Append(it->first, *it->second);
|
||||
if (it->first == selected)
|
||||
if (it->first == selected ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1163,6 +1216,14 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati
|
|||
}
|
||||
ui->Append(PresetCollection::separator("Add a new printer"), *bmp);
|
||||
}
|
||||
|
||||
/* But, if selected_preset_item is still equal to INT_MAX, it means that
|
||||
* there is no presets added to the list.
|
||||
* So, select last combobox item ("Add/Remove preset")
|
||||
*/
|
||||
if (selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
|
||||
ui->SetSelection(selected_preset_item);
|
||||
ui->SetToolTip(ui->GetString(selected_preset_item));
|
||||
ui->Thaw();
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ public:
|
|||
VendorProfile() {}
|
||||
VendorProfile(std::string id) : id(std::move(id)) {}
|
||||
|
||||
bool valid() const { return ! name.empty() && ! id.empty() && config_version.valid(); }
|
||||
|
||||
// Load VendorProfile from an ini file.
|
||||
// If `load_all` is false, only the header with basic info (name, version, URLs) is loaded.
|
||||
static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true);
|
||||
|
|
@ -91,6 +93,17 @@ public:
|
|||
bool operator==(const VendorProfile &rhs) const { return this->id == rhs.id; }
|
||||
};
|
||||
|
||||
class Preset;
|
||||
|
||||
// Helper to hold a profile with its vendor definition, where the vendor definition may have been extracted from a parent system preset.
|
||||
// The parent preset is only accessible through PresetCollection, therefore to allow definition of the various is_compatible_with methods
|
||||
// outside of the PresetCollection, this composite is returned by PresetCollection::get_preset_with_vendor_profile() when needed.
|
||||
struct PresetWithVendorProfile {
|
||||
PresetWithVendorProfile(const Preset &preset, const VendorProfile *vendor) : preset(preset), vendor(vendor) {}
|
||||
const Preset &preset;
|
||||
const VendorProfile *vendor;
|
||||
};
|
||||
|
||||
// Note: it is imporant that map is used here rather than unordered_map,
|
||||
// because we need iterators to not be invalidated,
|
||||
// because Preset and the ConfigWizard hold pointers to VendorProfiles.
|
||||
|
|
@ -148,6 +161,9 @@ public:
|
|||
// Configuration data, loaded from a file, or set from the defaults.
|
||||
DynamicPrintConfig config;
|
||||
|
||||
// Alias of the preset
|
||||
std::string alias = "";
|
||||
|
||||
void save();
|
||||
|
||||
// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty.
|
||||
|
|
@ -158,10 +174,6 @@ public:
|
|||
void set_dirty(bool dirty = true) { this->is_dirty = dirty; }
|
||||
void reset_dirty() { this->is_dirty = false; }
|
||||
|
||||
bool is_compatible_with_print(const Preset &active_print) const;
|
||||
bool is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const;
|
||||
bool is_compatible_with_printer(const Preset &active_printer) const;
|
||||
|
||||
// Returns the name of the preset, from which this preset inherits.
|
||||
static std::string& inherits(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("inherits", true)->value; }
|
||||
std::string& inherits() { return Preset::inherits(this->config); }
|
||||
|
|
@ -195,9 +207,6 @@ public:
|
|||
// This call returns a reference, it may add a new entry into the DynamicPrintConfig.
|
||||
PrinterTechnology& printer_technology_ref() { return this->config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value; }
|
||||
|
||||
// Mark this preset as compatible if it is compatible with active_printer.
|
||||
bool update_compatible(const Preset &active_printer, const DynamicPrintConfig *extra_config, const Preset *active_print = nullptr);
|
||||
|
||||
// Set is_visible according to application config
|
||||
void set_visible_from_appconfig(const AppConfig &app_config);
|
||||
|
||||
|
|
@ -230,6 +239,10 @@ protected:
|
|||
static std::string remove_suffix_modified(const std::string &name);
|
||||
};
|
||||
|
||||
bool is_compatible_with_print (const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print);
|
||||
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config);
|
||||
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer);
|
||||
|
||||
// Collections of presets of the same type (one of the Print, Filament or Printer type).
|
||||
class PresetCollection
|
||||
{
|
||||
|
|
@ -326,6 +339,12 @@ public:
|
|||
Preset& get_edited_preset() { return m_edited_preset; }
|
||||
const Preset& get_edited_preset() const { return m_edited_preset; }
|
||||
|
||||
// Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist.
|
||||
PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const;
|
||||
PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); }
|
||||
|
||||
const std::string& get_preset_name_by_alias(const std::string& alias) const;
|
||||
|
||||
// used to update preset_choice from Tab
|
||||
const std::deque<Preset>& get_presets() const { return m_presets; }
|
||||
int get_idx_selected() { return m_idx_selected; }
|
||||
|
|
@ -383,13 +402,13 @@ public:
|
|||
|
||||
// For Print / Filament presets, disable those, which are not compatible with the printer.
|
||||
template<typename PreferedCondition>
|
||||
void update_compatible(const Preset &active_printer, const Preset *active_print, bool select_other_if_incompatible, PreferedCondition prefered_condition)
|
||||
void update_compatible(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, bool select_other_if_incompatible, PreferedCondition prefered_condition)
|
||||
{
|
||||
if (this->update_compatible_internal(active_printer, active_print, select_other_if_incompatible) == (size_t)-1)
|
||||
// Find some other compatible preset, or the "-- default --" preset.
|
||||
this->select_preset(this->first_compatible_idx(prefered_condition));
|
||||
}
|
||||
void update_compatible(const Preset &active_printer, const Preset *active_print, bool select_other_if_incompatible)
|
||||
void update_compatible(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, bool select_other_if_incompatible)
|
||||
{ this->update_compatible(active_printer, active_print, select_other_if_incompatible, [](const std::string&){return true;}); }
|
||||
|
||||
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
|
||||
|
|
@ -472,7 +491,7 @@ private:
|
|||
std::deque<Preset>::const_iterator find_preset_internal(const std::string &name) const
|
||||
{ return const_cast<PresetCollection*>(this)->find_preset_internal(name); }
|
||||
|
||||
size_t update_compatible_internal(const Preset &active_printer, const Preset *active_print, bool unselect_if_incompatible);
|
||||
size_t update_compatible_internal(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, bool unselect_if_incompatible);
|
||||
|
||||
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false);
|
||||
|
||||
|
|
|
|||
|
|
@ -329,58 +329,64 @@ void PresetBundle::load_installed_printers(const AppConfig &config)
|
|||
}
|
||||
}
|
||||
|
||||
const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias) const
|
||||
{
|
||||
// there are not aliases for Printers profiles
|
||||
if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID)
|
||||
return alias;
|
||||
|
||||
const PresetCollection& presets = preset_type == Preset::TYPE_PRINT ? prints :
|
||||
preset_type == Preset::TYPE_SLA_PRINT ? sla_prints :
|
||||
preset_type == Preset::TYPE_FILAMENT ? filaments :
|
||||
sla_materials;
|
||||
|
||||
return presets.get_preset_name_by_alias(alias);
|
||||
}
|
||||
|
||||
void PresetBundle::load_installed_filaments(AppConfig &config)
|
||||
{
|
||||
if (! config.has_section(AppConfig::SECTION_FILAMENTS)) {
|
||||
std::unordered_set<const Preset*> comp_filaments;
|
||||
|
||||
for (const Preset &printer : printers) {
|
||||
if (! printer.is_visible || printer.printer_technology() != ptFFF) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const Preset &filament : filaments) {
|
||||
if (filament.is_compatible_with_printer(printer)) {
|
||||
comp_filaments.insert(&filament);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &filament: comp_filaments) {
|
||||
// Compatibility with the PrusaSlicer 2.1.1 and older, where the filament profiles were not installable yet.
|
||||
// Find all filament profiles, which are compatible with installed printers, and act as if these filament profiles
|
||||
// were installed.
|
||||
std::unordered_set<const Preset*> compatible_filaments;
|
||||
for (const Preset &printer : printers)
|
||||
if (printer.is_visible && printer.printer_technology() == ptFFF) {
|
||||
const PresetWithVendorProfile printer_with_vendor_profile = printers.get_preset_with_vendor_profile(printer);
|
||||
for (const Preset &filament : filaments)
|
||||
if (is_compatible_with_printer(filaments.get_preset_with_vendor_profile(filament), printer_with_vendor_profile))
|
||||
compatible_filaments.insert(&filament);
|
||||
}
|
||||
// and mark these filaments as installed, therefore this code will not be executed at the next start of the application.
|
||||
for (const auto &filament: compatible_filaments)
|
||||
config.set(AppConfig::SECTION_FILAMENTS, filament->name, "1");
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &preset : filaments) {
|
||||
for (auto &preset : filaments)
|
||||
preset.set_visible_from_appconfig(config);
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundle::load_installed_sla_materials(AppConfig &config)
|
||||
{
|
||||
if (! config.has_section(AppConfig::SECTION_MATERIALS)) {
|
||||
std::unordered_set<const Preset*> comp_sla_materials;
|
||||
|
||||
for (const Preset &printer : printers) {
|
||||
if (! printer.is_visible || printer.printer_technology() != ptSLA) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const Preset &material : sla_materials) {
|
||||
if (material.is_compatible_with_printer(printer)) {
|
||||
comp_sla_materials.insert(&material);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &material: comp_sla_materials) {
|
||||
// Compatibility with the PrusaSlicer 2.1.1 and older, where the SLA material profiles were not installable yet.
|
||||
// Find all SLA material profiles, which are compatible with installed printers, and act as if these SLA material profiles
|
||||
// were installed.
|
||||
for (const Preset &printer : printers)
|
||||
if (printer.is_visible && printer.printer_technology() == ptSLA) {
|
||||
const PresetWithVendorProfile printer_with_vendor_profile = printers.get_preset_with_vendor_profile(printer);
|
||||
for (const Preset &material : sla_materials)
|
||||
if (is_compatible_with_printer(sla_materials.get_preset_with_vendor_profile(material), printer_with_vendor_profile))
|
||||
comp_sla_materials.insert(&material);
|
||||
}
|
||||
// and mark these SLA materials as installed, therefore this code will not be executed at the next start of the application.
|
||||
for (const auto &material: comp_sla_materials)
|
||||
config.set(AppConfig::SECTION_MATERIALS, material->name, "1");
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &preset : sla_materials) {
|
||||
for (auto &preset : sla_materials)
|
||||
preset.set_visible_from_appconfig(config);
|
||||
}
|
||||
}
|
||||
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
|
|
@ -1122,6 +1128,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
PresetCollection *presets = nullptr;
|
||||
std::vector<std::string> *loaded = nullptr;
|
||||
std::string preset_name;
|
||||
std::string alias_name;
|
||||
if (boost::starts_with(section.first, "print:")) {
|
||||
presets = &this->prints;
|
||||
loaded = &loaded_prints;
|
||||
|
|
@ -1130,6 +1137,12 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
presets = &this->filaments;
|
||||
loaded = &loaded_filaments;
|
||||
preset_name = section.first.substr(9);
|
||||
|
||||
for (const auto& item : section.second)
|
||||
if (boost::starts_with(item.first, "alias")) {
|
||||
alias_name = item.second.data();
|
||||
break;
|
||||
}
|
||||
} else if (boost::starts_with(section.first, "sla_print:")) {
|
||||
presets = &this->sla_prints;
|
||||
loaded = &loaded_sla_prints;
|
||||
|
|
@ -1138,6 +1151,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
presets = &this->sla_materials;
|
||||
loaded = &loaded_sla_materials;
|
||||
preset_name = section.first.substr(13);
|
||||
|
||||
int end_pos = preset_name.find_first_of("0.");
|
||||
alias_name = preset_name.substr(0, end_pos-1);
|
||||
} else if (boost::starts_with(section.first, "printer:")) {
|
||||
presets = &this->printers;
|
||||
loaded = &loaded_printers;
|
||||
|
|
@ -1283,6 +1299,14 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
loaded.is_system = true;
|
||||
loaded.vendor = vendor_profile;
|
||||
}
|
||||
|
||||
// next step of an preset name aliasing
|
||||
int end_pos = preset_name.find_first_of("@");
|
||||
if (end_pos != std::string::npos)
|
||||
alias_name = preset_name.substr(0, end_pos - 1);
|
||||
|
||||
loaded.alias = alias_name.empty() ? preset_name : alias_name;
|
||||
|
||||
++ presets_loaded;
|
||||
}
|
||||
}
|
||||
|
|
@ -1353,23 +1377,24 @@ void PresetBundle::update_multi_material_filament_presets()
|
|||
|
||||
void PresetBundle::update_compatible(bool select_other_if_incompatible)
|
||||
{
|
||||
const Preset &printer_preset = this->printers.get_edited_preset();
|
||||
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);
|
||||
|
||||
switch (printer_preset.printer_technology()) {
|
||||
case ptFFF:
|
||||
{
|
||||
assert(printer_preset.config.has("default_print_profile"));
|
||||
assert(printer_preset.config.has("default_filament_profile"));
|
||||
const Preset &print_preset = this->prints.get_edited_preset();
|
||||
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, nullptr, select_other_if_incompatible) :
|
||||
this->prints.update_compatible(printer_preset, nullptr, select_other_if_incompatible,
|
||||
this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_if_incompatible) :
|
||||
this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_if_incompatible,
|
||||
[&prefered_print_profile](const std::string& profile_name) { return profile_name == prefered_print_profile; });
|
||||
const PresetWithVendorProfile print_preset_with_vendor_profile = this->prints.get_edited_preset_with_vendor_profile();
|
||||
prefered_filament_profiles.empty() ?
|
||||
this->filaments.update_compatible(printer_preset, &print_preset, select_other_if_incompatible) :
|
||||
this->filaments.update_compatible(printer_preset, &print_preset, select_other_if_incompatible,
|
||||
this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_if_incompatible) :
|
||||
this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_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(); });
|
||||
if (select_other_if_incompatible) {
|
||||
|
|
@ -1401,16 +1426,16 @@ void PresetBundle::update_compatible(bool select_other_if_incompatible)
|
|||
{
|
||||
assert(printer_preset.config.has("default_sla_print_profile"));
|
||||
assert(printer_preset.config.has("default_sla_material_profile"));
|
||||
const Preset &sla_print_preset = this->sla_prints.get_edited_preset();
|
||||
const std::string &prefered_sla_print_profile = 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();
|
||||
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, nullptr, select_other_if_incompatible) :
|
||||
this->sla_prints.update_compatible(printer_preset, nullptr, select_other_if_incompatible,
|
||||
this->sla_prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_if_incompatible) :
|
||||
this->sla_prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_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, &sla_print_preset, select_other_if_incompatible) :
|
||||
this->sla_materials.update_compatible(printer_preset, &sla_print_preset, select_other_if_incompatible,
|
||||
this->sla_materials.update_compatible(printer_preset_with_vendor_profile, &sla_print_preset_with_vendor_profile, select_other_if_incompatible) :
|
||||
this->sla_materials.update_compatible(printer_preset_with_vendor_profile, &sla_print_preset_with_vendor_profile, select_other_if_incompatible,
|
||||
[&prefered_sla_material_profile](const std::string& profile_name){ return profile_name == prefered_sla_material_profile; });
|
||||
break;
|
||||
}
|
||||
|
|
@ -1538,7 +1563,8 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr
|
|||
// Fill in the list from scratch.
|
||||
ui->Freeze();
|
||||
ui->Clear();
|
||||
size_t selected_preset_item = 0;
|
||||
size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected
|
||||
|
||||
const Preset *selected_preset = this->filaments.find_preset(this->filament_presets[idx_extruder]);
|
||||
// Show wide icons if the currently selected preset is not compatible with the current printer,
|
||||
// and draw a red flag in front of the selected preset.
|
||||
|
|
@ -1568,6 +1594,8 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr
|
|||
// set a bitmap height to m_bitmapLock->GetHeight()
|
||||
const int icon_height = m_bitmapLock->GetHeight();//2 * icon_unit; //16 * scale_f + 0.5f;
|
||||
|
||||
wxString tooltip = "";
|
||||
|
||||
for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++i) {
|
||||
const Preset &preset = this->filaments.preset(i);
|
||||
bool selected = this->filament_presets[idx_extruder] == preset.name;
|
||||
|
|
@ -1608,18 +1636,25 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr
|
|||
bitmap = m_bitmapCache->insert(bitmap_key, bmps);
|
||||
}
|
||||
|
||||
if (preset.is_default || preset.is_system) {
|
||||
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()),
|
||||
const std::string name = preset.alias.empty() ? preset.name : preset.alias;
|
||||
if (preset.is_default || preset.is_system) {
|
||||
ui->Append(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()),
|
||||
(bitmap == 0) ? wxNullBitmap : *bitmap);
|
||||
if (selected)
|
||||
if (selected ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX ) {
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
tooltip = wxString::FromUTF8(preset.name.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()),
|
||||
nonsys_presets.emplace(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()),
|
||||
(bitmap == 0) ? &wxNullBitmap : bitmap);
|
||||
if (selected)
|
||||
selected_str = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str());
|
||||
if (selected) {
|
||||
selected_str = wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str());
|
||||
tooltip = wxString::FromUTF8(preset.name.c_str());
|
||||
}
|
||||
}
|
||||
if (preset.is_default)
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap));
|
||||
|
|
@ -1630,15 +1665,25 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr
|
|||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap));
|
||||
for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
|
||||
ui->Append(it->first, *it->second);
|
||||
if (it->first == selected_str)
|
||||
if (it->first == selected_str ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX) {
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove filaments")), wxNullBitmap), GUI::PresetComboBox::LABEL_ITEM_WIZARD_FILAMENTS);
|
||||
|
||||
/* But, if selected_preset_item is still equal to INT_MAX, it means that
|
||||
* there is no presets added to the list.
|
||||
* So, select last combobox item ("Add/Remove filaments")
|
||||
*/
|
||||
if (selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
|
||||
ui->SetSelection(selected_preset_item);
|
||||
ui->SetToolTip(ui->GetString(selected_preset_item));
|
||||
ui->SetToolTip(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip);
|
||||
ui->check_selection();
|
||||
ui->Thaw();
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,8 @@ public:
|
|||
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
|
||||
void load_installed_printers(const AppConfig &config);
|
||||
|
||||
const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias) const;
|
||||
|
||||
static const char *PRUSA_BUNDLE;
|
||||
private:
|
||||
std::string load_system_presets();
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ std::string PresetHints::cooling_description(const Preset &preset)
|
|||
% slowdown_below_layer_time % max_fan_speed % slowdown_below_layer_time % min_print_speed).str();
|
||||
|
||||
if (fan_below_layer_time > slowdown_below_layer_time) {
|
||||
out += (boost::format(_utf8(L("\nIf estimated layer time is greater, but still below ~%1%s, "
|
||||
out += "\n" + (boost::format(_utf8(L("If estimated layer time is greater, but still below ~%1%s, "
|
||||
"fan will run at a proportionally decreasing speed between %2%%% and %3%%%.")))
|
||||
% fan_below_layer_time % max_fan_speed % min_fan_speed).str();
|
||||
}
|
||||
out += _utf8(L("\nDuring the other layers, fan")) + " ";
|
||||
out += "\n" + _utf8(L("During the other layers, fan")) + " ";
|
||||
} else {
|
||||
out = _utf8(L("Fan")) + " ";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -998,6 +998,11 @@ void Tab::update_preset_description_line()
|
|||
default: break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
description_line += "\n\n\t" + _(L("full profile name")) + ": \n\t\t" + parent->name;
|
||||
description_line += "\n\t" + _(L("symbolic profile name")) + ": \n\t\t" + parent->alias;
|
||||
}
|
||||
}
|
||||
|
||||
m_parent_preset_description_line->SetText(description_line, false);
|
||||
|
|
@ -2750,7 +2755,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current)
|
|||
PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
PresetCollection &dependent = (printer_technology == ptFFF) ? m_preset_bundle->filaments : m_preset_bundle->sla_materials;
|
||||
bool old_preset_dirty = dependent.current_is_dirty();
|
||||
bool new_preset_compatible = dependent.get_edited_preset().is_compatible_with_print(*m_presets->find_preset(preset_name, true));
|
||||
bool new_preset_compatible = is_compatible_with_print(dependent.get_edited_preset_with_vendor_profile(), m_presets->get_preset_with_vendor_profile(*m_presets->find_preset(preset_name, true)));
|
||||
if (! canceled)
|
||||
canceled = old_preset_dirty && ! new_preset_compatible && ! may_discard_current_dirty_preset(&dependent, preset_name);
|
||||
if (! canceled) {
|
||||
|
|
@ -2768,6 +2773,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current)
|
|||
// With the introduction of the SLA printer types, we need to support switching between
|
||||
// the FFF and SLA printers.
|
||||
const Preset &new_printer_preset = *m_presets->find_preset(preset_name, true);
|
||||
const PresetWithVendorProfile new_printer_preset_with_vendor_profile = m_presets->get_preset_with_vendor_profile(new_printer_preset);
|
||||
PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology();
|
||||
PrinterTechnology new_printer_technology = new_printer_preset.printer_technology();
|
||||
if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !may_switch_to_SLA_preset())
|
||||
|
|
@ -2788,7 +2794,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current)
|
|||
};
|
||||
for (PresetUpdate &pu : updates) {
|
||||
pu.old_preset_dirty = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty();
|
||||
pu.new_preset_compatible = (new_printer_technology == pu.technology) && pu.presets->get_edited_preset().is_compatible_with_printer(new_printer_preset);
|
||||
pu.new_preset_compatible = (new_printer_technology == pu.technology) && is_compatible_with_printer(pu.presets->get_edited_preset_with_vendor_profile(), new_printer_preset_with_vendor_profile);
|
||||
if (!canceled)
|
||||
canceled = pu.old_preset_dirty && !pu.new_preset_compatible && !may_discard_current_dirty_preset(pu.presets, preset_name);
|
||||
}
|
||||
|
|
@ -2924,7 +2930,13 @@ void Tab::OnTreeSelChange(wxTreeEvent& event)
|
|||
#ifdef __linux__
|
||||
std::unique_ptr<wxWindowUpdateLocker> no_updates(new wxWindowUpdateLocker(this));
|
||||
#else
|
||||
// wxWindowUpdateLocker noUpdates(this);
|
||||
/* On Windows we use DoubleBuffering during rendering,
|
||||
* so on Window is no needed to call a Freeze/Thaw functions.
|
||||
* But under OSX (builds compiled with MacOSX10.14.sdk) wxStaticBitmap rendering is broken without Freeze/Thaw call.
|
||||
*/
|
||||
#ifdef __WXOSX__
|
||||
wxWindowUpdateLocker noUpdates(this);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (m_pages.empty())
|
||||
|
|
@ -2985,7 +2997,8 @@ void Tab::save_preset(std::string name /*= ""*/)
|
|||
if (name.empty()) {
|
||||
const Preset &preset = m_presets->get_selected_preset();
|
||||
auto default_name = preset.is_default ? "Untitled" :
|
||||
preset.is_system ? (boost::format(_utf8(L("%1% - Copy"))) % preset.name).str() :
|
||||
// preset.is_system ? (boost::format(_utf8(L("%1% - Copy"))) % preset.name).str() :
|
||||
preset.is_system ? (boost::format(_CTX_utf8(L_CONTEXT("%1% - Copy", "PresetName"), "PresetName")) % preset.name).str() :
|
||||
preset.name;
|
||||
|
||||
bool have_extention = boost::iends_with(default_name, ".ini");
|
||||
|
|
@ -3020,7 +3033,7 @@ void Tab::save_preset(std::string name /*= ""*/)
|
|||
show_error(this, _(L("Cannot overwrite an external profile.")));
|
||||
return;
|
||||
}
|
||||
if (existing && name != preset.name)
|
||||
if (existing/* && name != preset.name*/)
|
||||
{
|
||||
wxString msg_text = GUI::from_u8((boost::format(_utf8(L("Preset with name \"%1%\" already exist."))) % name).str());
|
||||
msg_text += "\n" + _(L("Replace?"));
|
||||
|
|
|
|||
|
|
@ -156,8 +156,8 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, w
|
|||
"This probably happened as a result of running an older %s after using a newer one.\n\n"
|
||||
|
||||
"You may either exit %s and try again with a newer version, or you may re-run the initial configuration. "
|
||||
"Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s.\n"
|
||||
)), SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME));
|
||||
"Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s.")) + "\n",
|
||||
SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME));
|
||||
text->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
|
||||
content_sizer->Add(text);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <wx/statline.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/numformatter.h>
|
||||
#include <wx/colordlg.h>
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
|
|
@ -22,9 +23,11 @@
|
|||
#include "I18N.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "ExtruderSequenceDialog.hpp"
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
|
||||
using Slic3r::GUI::from_u8;
|
||||
using Slic3r::GUI::into_u8;
|
||||
|
||||
wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent);
|
||||
wxDEFINE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent);
|
||||
|
|
@ -404,6 +407,23 @@ int em_unit(wxWindow* win)
|
|||
return Slic3r::GUI::wxGetApp().em_unit();
|
||||
}
|
||||
|
||||
static float get_svg_scale_factor(wxWindow *win)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
// Note: win->GetContentScaleFactor() is not used anymore here because it tends to
|
||||
// return bogus results quite often (such as 1.0 on Retina or even 0.0).
|
||||
// We're using the max scaling factor across all screens because it's very likely to be good enough.
|
||||
|
||||
static float max_scaling_factor = NAN;
|
||||
if (std::isnan(max_scaling_factor)) {
|
||||
max_scaling_factor = Slic3r::GUI::mac_max_scaling_factor();
|
||||
}
|
||||
return win != nullptr ? max_scaling_factor : 1.0f;
|
||||
#else
|
||||
return 1.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
// If an icon has horizontal orientation (width > height) call this function with is_horizontal = true
|
||||
wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in,
|
||||
const int px_cnt/* = 16*/, const bool is_horizontal /* = false*/, const bool grayscale/* = false*/)
|
||||
|
|
@ -449,7 +469,7 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in,
|
|||
|
||||
|
||||
Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr;
|
||||
/*static*/ std::vector<wxBitmap*> get_extruder_color_icons()
|
||||
std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon/* = false*/)
|
||||
{
|
||||
// Create the bitmap with color bars.
|
||||
std::vector<wxBitmap*> bmps;
|
||||
|
|
@ -465,16 +485,18 @@ Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr;
|
|||
* and scale them in respect to em_unit value
|
||||
*/
|
||||
const double em = Slic3r::GUI::wxGetApp().em_unit();
|
||||
const int icon_width = lround(3.2 * em);
|
||||
const int icon_width = lround((thin_icon ? 1 : 3.2) * em);
|
||||
const int icon_height = lround(1.6 * em);
|
||||
|
||||
for (const std::string& color : colors)
|
||||
{
|
||||
wxBitmap* bitmap = m_bitmap_cache->find(color);
|
||||
std::string bitmap_key = color + "-h" + std::to_string(icon_height) + "-w" + std::to_string(icon_width);
|
||||
|
||||
wxBitmap* bitmap = m_bitmap_cache->find(bitmap_key);
|
||||
if (bitmap == nullptr) {
|
||||
// Paint the color icon.
|
||||
Slic3r::PresetBundle::parse_color(color, rgb);
|
||||
bitmap = m_bitmap_cache->insert(color, m_bitmap_cache->mksolid(icon_width, icon_height, rgb));
|
||||
bitmap = m_bitmap_cache->insert(bitmap_key, m_bitmap_cache->mksolid(icon_width, icon_height, rgb));
|
||||
}
|
||||
bmps.emplace_back(bitmap);
|
||||
}
|
||||
|
|
@ -483,16 +505,62 @@ Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr;
|
|||
}
|
||||
|
||||
|
||||
static wxBitmap get_extruder_color_icon(size_t extruder_idx)
|
||||
static wxBitmap get_extruder_color_icon(size_t extruder_idx, bool thin_icon = false)
|
||||
{
|
||||
// Create the bitmap with color bars.
|
||||
std::vector<wxBitmap*> bmps = get_extruder_color_icons();
|
||||
std::vector<wxBitmap*> bmps = get_extruder_color_icons(thin_icon);
|
||||
if (bmps.empty())
|
||||
return wxNullBitmap;
|
||||
|
||||
return *bmps[extruder_idx >= bmps.size() ? 0 : extruder_idx];
|
||||
}
|
||||
|
||||
void apply_extruder_selector(wxBitmapComboBox** ctrl,
|
||||
wxWindow* parent,
|
||||
const std::string& first_item/* = ""*/,
|
||||
wxPoint pos/* = wxDefaultPosition*/,
|
||||
wxSize size/* = wxDefaultSize*/,
|
||||
bool use_thin_icon/* = false*/)
|
||||
{
|
||||
std::vector<wxBitmap*> icons = get_extruder_color_icons(use_thin_icon);
|
||||
|
||||
if (!*ctrl)
|
||||
*ctrl = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, pos, size,
|
||||
0, nullptr, wxCB_READONLY);
|
||||
else
|
||||
{
|
||||
(*ctrl)->SetPosition(pos);
|
||||
(*ctrl)->SetMinSize(size);
|
||||
(*ctrl)->SetSize(size);
|
||||
(*ctrl)->Clear();
|
||||
}
|
||||
if (first_item.empty())
|
||||
(*ctrl)->Hide(); // to avoid unwanted rendering before layout (ExtruderSequenceDialog)
|
||||
|
||||
if (icons.empty() && !first_item.empty()) {
|
||||
(*ctrl)->Append(_(first_item), wxNullBitmap);
|
||||
return;
|
||||
}
|
||||
|
||||
// For ObjectList we use short extruder name (just a number)
|
||||
const bool use_full_item_name = dynamic_cast<Slic3r::GUI::ObjectList*>(parent) == nullptr;
|
||||
|
||||
int i = 0;
|
||||
wxString str = _(L("Extruder"));
|
||||
for (wxBitmap* bmp : icons) {
|
||||
if (i == 0) {
|
||||
if (!first_item.empty())
|
||||
(*ctrl)->Append(_(first_item), *bmp);
|
||||
++i;
|
||||
}
|
||||
|
||||
(*ctrl)->Append(use_full_item_name ? wxString::Format("%s %d", str, i) : std::to_string(i), *bmp);
|
||||
++i;
|
||||
}
|
||||
(*ctrl)->SetSelection(0);
|
||||
}
|
||||
|
||||
|
||||
// *****************************************************************************
|
||||
// ----------------------------------------------------------------------------
|
||||
// ObjectDataViewModelNode
|
||||
|
|
@ -2230,26 +2298,31 @@ DoubleSlider::DoubleSlider( wxWindow *parent,
|
|||
if (!is_osx)
|
||||
SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
|
||||
|
||||
m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "up_half_circle.png", 16, true));
|
||||
m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "down_half_circle.png", 16, true));
|
||||
m_thumb_size = m_bmp_thumb_lower.bmp().GetSize();
|
||||
const float scale_factor = get_svg_scale_factor(this);
|
||||
|
||||
m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add_on.png");
|
||||
m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_off.png");
|
||||
m_bmp_del_tick_on = ScalableBitmap(this, "colorchange_delete_on.png");
|
||||
m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_delete_off.png");
|
||||
m_tick_icon_dim = m_bmp_add_tick_on.bmp().GetSize().x;
|
||||
m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "thumb_up"));
|
||||
m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "thumb_down"));
|
||||
m_thumb_size = m_bmp_thumb_lower.bmp().GetSize()*(1.0/scale_factor);
|
||||
|
||||
m_bmp_one_layer_lock_on = ScalableBitmap(this, "one_layer_lock_on.png");
|
||||
m_bmp_one_layer_lock_off = ScalableBitmap(this, "one_layer_lock_off.png");
|
||||
m_bmp_one_layer_unlock_on = ScalableBitmap(this, "one_layer_unlock_on.png");
|
||||
m_bmp_one_layer_unlock_off = ScalableBitmap(this, "one_layer_unlock_off.png");
|
||||
m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x;
|
||||
m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add");
|
||||
m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_f");
|
||||
m_bmp_del_tick_on = ScalableBitmap(this, "colorchange_del");
|
||||
m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_del_f");
|
||||
m_tick_icon_dim = int((float)m_bmp_add_tick_on.bmp().GetSize().x / scale_factor);
|
||||
|
||||
m_bmp_one_layer_lock_on = ScalableBitmap(this, "lock_closed");
|
||||
m_bmp_one_layer_lock_off = ScalableBitmap(this, "lock_closed_f");
|
||||
m_bmp_one_layer_unlock_on = ScalableBitmap(this, "lock_open");
|
||||
m_bmp_one_layer_unlock_off = ScalableBitmap(this, "lock_open_f");
|
||||
m_lock_icon_dim = int((float)m_bmp_one_layer_lock_on.bmp().GetSize().x / scale_factor);
|
||||
|
||||
m_bmp_revert = ScalableBitmap(this, "undo");
|
||||
m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x;
|
||||
m_revert_icon_dim = int((float)m_bmp_revert.bmp().GetSize().x / scale_factor);
|
||||
m_bmp_cog = ScalableBitmap(this, "cog");
|
||||
m_cog_icon_dim = int((float)m_bmp_cog.bmp().GetSize().x / scale_factor);
|
||||
|
||||
m_selection = ssUndef;
|
||||
m_pause_print_msg = _utf8(L("Place bearings in slots and resume"));
|
||||
|
||||
// slider events
|
||||
Bind(wxEVT_PAINT, &DoubleSlider::OnPaint, this);
|
||||
|
|
@ -2268,7 +2341,7 @@ DoubleSlider::DoubleSlider( wxWindow *parent,
|
|||
// control's view variables
|
||||
SLIDER_MARGIN = 4 + Slic3r::GUI::wxGetApp().em_unit();
|
||||
|
||||
DARK_ORANGE_PEN = wxPen(wxColour(253, 84, 2));
|
||||
DARK_ORANGE_PEN = wxPen(wxColour(237, 107, 33));
|
||||
ORANGE_PEN = wxPen(wxColour(253, 126, 66));
|
||||
LIGHT_ORANGE_PEN = wxPen(wxColour(254, 177, 139));
|
||||
|
||||
|
|
@ -2306,6 +2379,8 @@ void DoubleSlider::msw_rescale()
|
|||
|
||||
m_bmp_revert.msw_rescale();
|
||||
m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x;
|
||||
m_bmp_cog.msw_rescale();
|
||||
m_cog_icon_dim = m_bmp_cog.bmp().GetSize().x;
|
||||
|
||||
SLIDER_MARGIN = 4 + Slic3r::GUI::wxGetApp().em_unit();
|
||||
|
||||
|
|
@ -2453,41 +2528,45 @@ double DoubleSlider::get_double_value(const SelectedSlider& selection)
|
|||
return m_values[selection == ssLower ? m_lower_value : m_higher_value];
|
||||
}
|
||||
|
||||
std::vector<double> DoubleSlider::GetTicksValues() const
|
||||
using t_custom_code = Slic3r::Model::CustomGCode;
|
||||
std::vector<t_custom_code> DoubleSlider::GetTicksValues() const
|
||||
{
|
||||
std::vector<double> values;
|
||||
std::vector<t_custom_code> values;
|
||||
|
||||
const int val_size = m_values.size();
|
||||
if (!m_values.empty())
|
||||
for (int tick : m_ticks) {
|
||||
if (tick > val_size)
|
||||
for (const TICK_CODE& tick : m_ticks_) {
|
||||
if (tick.tick > val_size)
|
||||
break;
|
||||
values.push_back(m_values[tick]);
|
||||
values.push_back(t_custom_code(m_values[tick.tick], tick.gcode, tick.extruder, tick.color));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
void DoubleSlider::SetTicksValues(const std::vector<double>& heights)
|
||||
void DoubleSlider::SetTicksValues(const std::vector<t_custom_code>& heights)
|
||||
{
|
||||
if (m_values.empty())
|
||||
return;
|
||||
|
||||
const bool was_empty = m_ticks.empty();
|
||||
const bool was_empty = m_ticks_.empty();
|
||||
|
||||
m_ticks.clear();
|
||||
m_ticks_.clear();
|
||||
for (auto h : heights) {
|
||||
auto it = std::lower_bound(m_values.begin(), m_values.end(), h - epsilon());
|
||||
auto it = std::lower_bound(m_values.begin(), m_values.end(), h.height - epsilon());
|
||||
|
||||
if (it == m_values.end())
|
||||
continue;
|
||||
|
||||
m_ticks.insert(it-m_values.begin());
|
||||
m_ticks_.insert(TICK_CODE(it-m_values.begin(), h.gcode, h.extruder, h.color));
|
||||
}
|
||||
|
||||
if (!was_empty && m_ticks.empty())
|
||||
if (!was_empty && m_ticks_.empty())
|
||||
// Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one
|
||||
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
|
||||
|
||||
Refresh();
|
||||
Update();
|
||||
}
|
||||
|
||||
void DoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos)
|
||||
|
|
@ -2538,17 +2617,20 @@ void DoubleSlider::render()
|
|||
// //higher slider:
|
||||
// draw_thumb(dc, higher_pos, ssHigher);
|
||||
|
||||
// draw both sliders
|
||||
draw_thumbs(dc, lower_pos, higher_pos);
|
||||
|
||||
//draw color print ticks
|
||||
draw_ticks(dc);
|
||||
|
||||
// draw both sliders
|
||||
draw_thumbs(dc, lower_pos, higher_pos);
|
||||
|
||||
//draw lock/unlock
|
||||
draw_one_layer_icon(dc);
|
||||
|
||||
//draw revert bitmap (if it's shown)
|
||||
draw_revert_icon(dc);
|
||||
|
||||
//draw cog bitmap (if it's shown)
|
||||
draw_cog_icon(dc);
|
||||
}
|
||||
|
||||
void DoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end)
|
||||
|
|
@ -2560,7 +2642,7 @@ void DoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoin
|
|||
return;
|
||||
|
||||
wxBitmap* icon = m_is_action_icon_focesed ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp();
|
||||
if (m_ticks.find(tick) != m_ticks.end())
|
||||
if (m_ticks_.find(tick) != m_ticks_.end())
|
||||
icon = m_is_action_icon_focesed ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp();
|
||||
|
||||
wxCoord x_draw, y_draw;
|
||||
|
|
@ -2634,7 +2716,7 @@ void DoubleSlider::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedS
|
|||
}
|
||||
else {
|
||||
x_draw = pos.x - int(0.5*m_thumb_size.x);
|
||||
y_draw = pos.y+1;
|
||||
y_draw = pos.y - int(0.5*m_thumb_size.y);
|
||||
}
|
||||
}
|
||||
else{
|
||||
|
|
@ -2644,7 +2726,7 @@ void DoubleSlider::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedS
|
|||
}
|
||||
else {
|
||||
x_draw = pos.x - int(0.5*m_thumb_size.x);
|
||||
y_draw = pos.y - m_thumb_size.y;
|
||||
y_draw = pos.y - int(0.5*m_thumb_size.y);
|
||||
}
|
||||
}
|
||||
dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower.bmp() : m_bmp_thumb_higher.bmp(), x_draw, y_draw);
|
||||
|
|
@ -2703,14 +2785,26 @@ void DoubleSlider::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)
|
||||
for (auto tick : m_ticks_)
|
||||
{
|
||||
const wxCoord pos = get_position_from_value(tick);
|
||||
const wxCoord pos = get_position_from_value(tick.tick);
|
||||
|
||||
is_horizontal() ? dc.DrawLine(pos, mid-14, pos, mid-9) :
|
||||
dc.DrawLine(mid - 14, pos/* - 1*/, mid - 9, pos/* - 1*/);
|
||||
is_horizontal() ? dc.DrawLine(pos, mid+14, pos, mid+9) :
|
||||
dc.DrawLine(mid + 14, pos/* - 1*/, mid + 9, pos/* - 1*/);
|
||||
|
||||
// Draw icon for "Pause print" or "Custom Gcode"
|
||||
if (tick.gcode != Slic3r::ColorChangeCode && tick.gcode != Slic3r::ExtruderChangeCode)
|
||||
{
|
||||
wxBitmap icon = create_scaled_bitmap(this, tick.gcode == Slic3r::PausePrintCode ? "pause_print" : "edit_gcode");
|
||||
|
||||
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;
|
||||
is_horizontal() ? y_draw = mid + 22 : x_draw = mid + m_thumb_size.x + 3;
|
||||
|
||||
dc.DrawBitmap(icon, x_draw, y_draw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2722,47 +2816,42 @@ void DoubleSlider::draw_colored_band(wxDC& dc)
|
|||
int height, width;
|
||||
get_size(&width, &height);
|
||||
|
||||
wxRect main_band = m_rect_lower_thumb;
|
||||
if (is_horizontal()) {
|
||||
main_band.SetLeft(SLIDER_MARGIN);
|
||||
main_band.SetRight(width - SLIDER_MARGIN + 1);
|
||||
}
|
||||
else {
|
||||
const int cut = 2;
|
||||
main_band.x += cut;
|
||||
main_band.width -= 2*cut;
|
||||
main_band.SetTop(SLIDER_MARGIN);
|
||||
main_band.SetBottom(height - SLIDER_MARGIN + 1);
|
||||
}
|
||||
const wxCoord mid = is_horizontal() ? 0.5 * height : 0.5 * width;
|
||||
|
||||
if (m_ticks.empty()) {
|
||||
dc.SetPen(GetParent()->GetBackgroundColour());
|
||||
dc.SetBrush(GetParent()->GetBackgroundColour());
|
||||
dc.DrawRectangle(main_band);
|
||||
return;
|
||||
}
|
||||
wxRect main_band = is_horizontal() ?
|
||||
wxRect(SLIDER_MARGIN, lround(mid - 0.375 * m_thumb_size.y),
|
||||
width - 2 * SLIDER_MARGIN + 1, lround(0.75 * m_thumb_size.y)) :
|
||||
wxRect(lround(mid - 0.375 * m_thumb_size.x), SLIDER_MARGIN,
|
||||
lround(0.75 * m_thumb_size.x), height - 2 * SLIDER_MARGIN + 1);
|
||||
|
||||
const std::vector<std::string>& colors = Slic3r::GCodePreviewData::ColorPrintColors();
|
||||
const size_t colors_cnt = colors.size();
|
||||
|
||||
wxColour clr(colors[0]);
|
||||
dc.SetPen(clr);
|
||||
dc.SetBrush(clr);
|
||||
dc.DrawRectangle(main_band);
|
||||
|
||||
size_t i = 1;
|
||||
for (auto tick : m_ticks)
|
||||
{
|
||||
if (i == colors_cnt)
|
||||
i = 0;
|
||||
const wxCoord pos = get_position_from_value(tick);
|
||||
is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) :
|
||||
main_band.SetBottom(pos-1);
|
||||
|
||||
clr = wxColour(colors[i]);
|
||||
auto draw_band = [](wxDC& dc, const wxColour& clr, const wxRect& band_rc) {
|
||||
dc.SetPen(clr);
|
||||
dc.SetBrush(clr);
|
||||
dc.DrawRectangle(main_band);
|
||||
dc.DrawRectangle(band_rc);
|
||||
};
|
||||
|
||||
const std::vector<std::string>& colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
int colors_cnt = colors.size();
|
||||
|
||||
const wxColour bg_clr = GetParent()->GetBackgroundColour();
|
||||
|
||||
wxColour clr = m_state == msSingleExtruder ? wxColour(colors[0]) : bg_clr;
|
||||
draw_band(dc, clr, main_band);
|
||||
|
||||
size_t i = 1;
|
||||
for (auto tick : m_ticks_)
|
||||
{
|
||||
if ( (m_state == msSingleExtruder && tick.gcode != Slic3r::ColorChangeCode) ||
|
||||
(m_state == msMultiExtruder && tick.gcode != Slic3r::ExtruderChangeCode) )
|
||||
continue;
|
||||
|
||||
const wxCoord pos = get_position_from_value(tick.tick);
|
||||
is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) :
|
||||
main_band.SetBottom(pos - 1);
|
||||
|
||||
clr = (m_state == msMultiExtruder && tick.color.empty()) ? bg_clr :
|
||||
m_state == msMultiExtruder ? wxColour(colors[std::min<int>(colors_cnt - 1, tick.extruder-1)]) : wxColour(tick.color);
|
||||
draw_band(dc, clr, main_band);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
|
@ -2788,7 +2877,7 @@ void DoubleSlider::draw_one_layer_icon(wxDC& dc)
|
|||
|
||||
void DoubleSlider::draw_revert_icon(wxDC& dc)
|
||||
{
|
||||
if (m_ticks.empty() || !m_is_enabled_tick_manipulation)
|
||||
if (m_ticks_.empty() || !m_is_enabled_tick_manipulation)
|
||||
return;
|
||||
|
||||
int width, height;
|
||||
|
|
@ -2804,9 +2893,27 @@ void DoubleSlider::draw_revert_icon(wxDC& dc)
|
|||
m_rect_revert_icon = wxRect(x_draw, y_draw, m_revert_icon_dim, m_revert_icon_dim);
|
||||
}
|
||||
|
||||
void DoubleSlider::draw_cog_icon(wxDC& dc)
|
||||
{
|
||||
if (m_state != msMultiExtruder)
|
||||
return;
|
||||
|
||||
int width, height;
|
||||
get_size(&width, &height);
|
||||
|
||||
wxCoord x_draw, y_draw;
|
||||
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;
|
||||
|
||||
dc.DrawBitmap(m_bmp_cog.bmp(), x_draw, y_draw);
|
||||
|
||||
//update rect of the lock/unlock icon
|
||||
m_rect_cog_icon = wxRect(x_draw, y_draw, m_cog_icon_dim, m_cog_icon_dim);
|
||||
}
|
||||
|
||||
void DoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection)
|
||||
{
|
||||
const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y);
|
||||
const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, int(m_thumb_size.y*0.5));
|
||||
if (selection == ssLower)
|
||||
m_rect_lower_thumb = rect;
|
||||
else
|
||||
|
|
@ -2840,16 +2947,16 @@ bool DoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect)
|
|||
|
||||
int DoubleSlider::is_point_near_tick(const wxPoint& pt)
|
||||
{
|
||||
for (auto tick : m_ticks) {
|
||||
const wxCoord pos = get_position_from_value(tick);
|
||||
for (auto tick : m_ticks_) {
|
||||
const wxCoord pos = get_position_from_value(tick.tick);
|
||||
|
||||
if (is_horizontal()) {
|
||||
if (pos - 4 <= pt.x && pt.x <= pos + 4)
|
||||
return tick;
|
||||
return tick.tick;
|
||||
}
|
||||
else {
|
||||
if (pos - 4 <= pt.y && pt.y <= pos + 4)
|
||||
return tick;
|
||||
return tick.tick;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
|
@ -2901,9 +3008,13 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event)
|
|||
m_selection == ssLower ? correct_lower_value() : correct_higher_value();
|
||||
if (!m_selection) m_selection = ssHigher;
|
||||
|
||||
m_ticks.clear();
|
||||
m_ticks_.clear();
|
||||
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
|
||||
}
|
||||
else if (is_point_in_rect(pos, m_rect_cog_icon) && m_state == msMultiExtruder) {
|
||||
// show dialog for set extruder sequence
|
||||
m_edit_extruder_sequence = true;
|
||||
}
|
||||
else
|
||||
detect_selected_slider(pos);
|
||||
|
||||
|
|
@ -2916,7 +3027,8 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event)
|
|||
get_value_from_position(pos.x, pos.y);
|
||||
if (mouse_val >= 0)
|
||||
{
|
||||
if (abs(mouse_val - m_lower_value) < abs(mouse_val - m_higher_value)) {
|
||||
// if (abs(mouse_val - m_lower_value) < abs(mouse_val - m_higher_value)) {
|
||||
if ( mouse_val <= m_lower_value ) {
|
||||
SetLowerValue(mouse_val);
|
||||
correct_lower_value();
|
||||
m_selection = ssLower;
|
||||
|
|
@ -2956,6 +3068,38 @@ void DoubleSlider::correct_higher_value()
|
|||
m_lower_value = m_higher_value;
|
||||
}
|
||||
|
||||
wxString DoubleSlider::get_tooltip(IconFocus icon_focus)
|
||||
{
|
||||
wxString tooltip(wxEmptyString);
|
||||
if (m_is_one_layer_icon_focesed)
|
||||
tooltip = _(L("One layer mode"));
|
||||
|
||||
if (icon_focus == ifRevert)
|
||||
tooltip = _(L("Discard all custom changes"));
|
||||
if (icon_focus == ifCog)
|
||||
tooltip = _(L("Set extruder sequence for whole print"));
|
||||
else if (m_is_action_icon_focesed)
|
||||
{
|
||||
const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
|
||||
const auto tick_code_it = m_ticks_.find(tick);
|
||||
tooltip = tick_code_it == m_ticks_.end() ? (m_state == msSingleExtruder ?
|
||||
_(L("For add color change use left mouse button click")) :
|
||||
_(L("For add change extruder use left mouse button click"))) + "\n" +
|
||||
_(L("For add another code use right mouse button click")) :
|
||||
tick_code_it->gcode == Slic3r::ColorChangeCode ? ( m_state == msSingleExtruder ?
|
||||
_(L("For Delete color change use left mouse button click\n"
|
||||
"For Edit color use right mouse button click")) :
|
||||
from_u8((boost::format(_utf8(L("Delete color change for Extruder %1%"))) % tick_code_it->extruder).str()) ):
|
||||
// tick_code_it->gcode == Slic3r::PausePrintCode ? _(L("Delete pause")) :
|
||||
tick_code_it->gcode == Slic3r::ExtruderChangeCode ?
|
||||
from_u8((boost::format(_utf8(L("Delete extruder change to \"%1%\""))) % tick_code_it->extruder).str()) :
|
||||
from_u8((boost::format(_utf8(L("For Delete \"%1%\" code use left mouse button click\n"
|
||||
"For Edit \"%1%\" code use right mouse button click"))) % tick_code_it->gcode ).str());
|
||||
}
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
void DoubleSlider::OnMotion(wxMouseEvent& event)
|
||||
{
|
||||
bool action = false;
|
||||
|
|
@ -2964,11 +3108,14 @@ void DoubleSlider::OnMotion(wxMouseEvent& event)
|
|||
const wxPoint pos = event.GetLogicalPosition(dc);
|
||||
|
||||
m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon);
|
||||
bool is_revert_icon_focused = false;
|
||||
IconFocus icon_focus = ifNone;
|
||||
|
||||
if (!m_is_left_down && !m_is_one_layer) {
|
||||
m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action);
|
||||
is_revert_icon_focused = !m_ticks.empty() && is_point_in_rect(pos, m_rect_revert_icon);
|
||||
if (!m_ticks_.empty() && is_point_in_rect(pos, m_rect_revert_icon))
|
||||
icon_focus = ifRevert;
|
||||
else if (is_point_in_rect(pos, m_rect_cog_icon))
|
||||
icon_focus = ifCog;
|
||||
}
|
||||
else if (m_is_left_down || m_is_right_down) {
|
||||
if (m_selection == ssLower) {
|
||||
|
|
@ -2989,10 +3136,7 @@ void DoubleSlider::OnMotion(wxMouseEvent& event)
|
|||
event.Skip();
|
||||
|
||||
// Set tooltips with information for each icon
|
||||
const wxString tooltip = m_is_one_layer_icon_focesed ? _(L("One layer mode")) :
|
||||
m_is_action_icon_focesed ? _(L("Add/Del color change")) :
|
||||
is_revert_icon_focused ? _(L("Discard all color changes")) : "";
|
||||
this->SetToolTip(tooltip);
|
||||
this->SetToolTip(get_tooltip(icon_focus));
|
||||
|
||||
if (action)
|
||||
{
|
||||
|
|
@ -3009,6 +3153,43 @@ void DoubleSlider::OnLeftUp(wxMouseEvent& event)
|
|||
return;
|
||||
this->ReleaseMouse();
|
||||
m_is_left_down = false;
|
||||
|
||||
if (m_show_context_menu)
|
||||
{
|
||||
if (m_state == msMultiExtruder)
|
||||
{
|
||||
wxMenu menu;
|
||||
const int extruders_cnt = Slic3r::GUI::wxGetApp().extruders_edited_cnt();
|
||||
if (extruders_cnt > 1)
|
||||
{
|
||||
const int initial_extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value);
|
||||
|
||||
wxMenu* change_extruder_menu = new wxMenu();
|
||||
|
||||
for (int i = 0; i <= extruders_cnt; i++) {
|
||||
const wxString item_name = i == 0 ? _(L("Default")) : wxString::Format(_(L("Extruder %d")), i);
|
||||
|
||||
append_menu_radio_item(change_extruder_menu, wxID_ANY, item_name, "",
|
||||
[this, i](wxCommandEvent&) { change_extruder(i); }, &menu)->Check(i == initial_extruder);
|
||||
}
|
||||
|
||||
wxMenuItem* change_extruder_menu_item = menu.AppendSubMenu(change_extruder_menu, _(L("Change extruder")), _(L("Use another extruder")));
|
||||
change_extruder_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "change_extruder"));
|
||||
}
|
||||
|
||||
Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu);
|
||||
}
|
||||
else
|
||||
add_code(Slic3r::ColorChangeCode);
|
||||
|
||||
m_show_context_menu = false;
|
||||
}
|
||||
|
||||
if (m_edit_extruder_sequence) {
|
||||
edit_extruder_sequence();
|
||||
m_edit_extruder_sequence = false;
|
||||
}
|
||||
|
||||
Refresh();
|
||||
Update();
|
||||
event.Skip();
|
||||
|
|
@ -3059,21 +3240,36 @@ void DoubleSlider::action_tick(const TicksAction action)
|
|||
|
||||
const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
|
||||
|
||||
if (action == taOnIcon) {
|
||||
if (!m_ticks.insert(tick).second)
|
||||
m_ticks.erase(tick);
|
||||
const auto it = m_ticks_.find(tick);
|
||||
|
||||
if (it != m_ticks_.end()) // erase this tick
|
||||
{
|
||||
if (action == taAdd)
|
||||
return;
|
||||
m_ticks_.erase(TICK_CODE(tick));
|
||||
|
||||
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
|
||||
Refresh();
|
||||
Update();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
const auto it = m_ticks.find(tick);
|
||||
if (it == m_ticks.end() && action == taAdd)
|
||||
m_ticks.insert(tick);
|
||||
else if (it != m_ticks.end() && action == taDel)
|
||||
m_ticks.erase(tick);
|
||||
|
||||
if (action == taDel)
|
||||
return;
|
||||
if (action == taAdd)
|
||||
{
|
||||
// OnChar() is called immediately after OnKeyDown(), which can cause call of add_code() twice.
|
||||
// To avoid this case we should suppress second add_code() call.
|
||||
if (m_suppress_add_code)
|
||||
return;
|
||||
m_suppress_add_code = true;
|
||||
if (m_state != msMultiExtruder)
|
||||
add_code(Slic3r::ColorChangeCode);
|
||||
m_suppress_add_code = false;
|
||||
return;
|
||||
}
|
||||
|
||||
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
|
||||
Refresh();
|
||||
Update();
|
||||
m_show_context_menu = true;
|
||||
}
|
||||
|
||||
void DoubleSlider::OnWheel(wxMouseEvent& event)
|
||||
|
|
@ -3148,6 +3344,27 @@ void DoubleSlider::OnRightDown(wxMouseEvent& event)
|
|||
this->CaptureMouse();
|
||||
|
||||
const wxClientDC dc(this);
|
||||
|
||||
wxPoint pos = event.GetLogicalPosition(dc);
|
||||
if (is_point_in_rect(pos, m_rect_tick_action) && m_is_enabled_tick_manipulation)
|
||||
{
|
||||
const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
|
||||
// if on this Z doesn't exist tick
|
||||
auto it = m_ticks_.find(tick);
|
||||
if (it == m_ticks_.end())
|
||||
{
|
||||
// show context menu on OnRightUp()
|
||||
m_show_context_menu = true;
|
||||
return;
|
||||
}
|
||||
if (it->gcode != Slic3r::ExtruderChangeCode)
|
||||
{
|
||||
// show "Edit" and "Delete" menu on OnRightUp()
|
||||
m_show_edit_menu = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
detect_selected_slider(event.GetLogicalPosition(dc));
|
||||
if (!m_selection)
|
||||
return;
|
||||
|
|
@ -3157,13 +3374,29 @@ void DoubleSlider::OnRightDown(wxMouseEvent& event)
|
|||
else
|
||||
m_lower_value = m_higher_value;
|
||||
|
||||
m_is_right_down = m_is_one_layer = true;
|
||||
// set slider to "one layer" mode
|
||||
m_is_right_down = m_is_one_layer = true;
|
||||
|
||||
Refresh();
|
||||
Update();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
int DoubleSlider::get_extruder_for_tick(int tick)
|
||||
{
|
||||
if (m_ticks_.empty())
|
||||
return 0;
|
||||
|
||||
auto it = m_ticks_.lower_bound(tick);
|
||||
while (it != m_ticks_.begin()) {
|
||||
--it;
|
||||
if(it->gcode == Slic3r::ExtruderChangeCode)
|
||||
return it->extruder;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DoubleSlider::OnRightUp(wxMouseEvent& event)
|
||||
{
|
||||
if (!HasCapture())
|
||||
|
|
@ -3171,11 +3404,296 @@ void DoubleSlider::OnRightUp(wxMouseEvent& event)
|
|||
this->ReleaseMouse();
|
||||
m_is_right_down = m_is_one_layer = false;
|
||||
|
||||
if (m_show_context_menu) {
|
||||
wxMenu menu;
|
||||
|
||||
if (m_state == msMultiExtruder)
|
||||
{
|
||||
const int extruders_cnt = Slic3r::GUI::wxGetApp().extruders_edited_cnt();
|
||||
if (extruders_cnt > 1)
|
||||
{
|
||||
const int initial_extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value);
|
||||
|
||||
wxMenu* change_extruder_menu = new wxMenu();
|
||||
wxMenu* add_color_change_menu = new wxMenu();
|
||||
|
||||
for (int i = 0; i <= extruders_cnt; i++) {
|
||||
const wxString item_name = i == 0 ? _(L("Default")) : wxString::Format(_(L("Extruder %d")), i);
|
||||
|
||||
append_menu_radio_item(change_extruder_menu, wxID_ANY, item_name, "",
|
||||
[this, i](wxCommandEvent&) { change_extruder(i); }, &menu)->Check(i == initial_extruder);
|
||||
|
||||
if (i==0) // don't use M600 for default extruder, if multimaterial print is selected
|
||||
continue;
|
||||
append_menu_item(add_color_change_menu, wxID_ANY, item_name, "",
|
||||
[this, i](wxCommandEvent&) { add_code(Slic3r::ColorChangeCode, i); }, "", &menu);
|
||||
}
|
||||
|
||||
wxMenuItem* change_extruder_menu_item = menu.AppendSubMenu(change_extruder_menu, _(L("Change extruder")), _(L("Use another extruder")));
|
||||
change_extruder_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "change_extruder"));
|
||||
|
||||
const wxString menu_name = from_u8((boost::format(_utf8(L("Add color change (%1%) for:"))) % Slic3r::ColorChangeCode).str());
|
||||
wxMenuItem* add_color_change_menu_item = menu.AppendSubMenu(add_color_change_menu, menu_name, "");
|
||||
add_color_change_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "colorchange_add_m"));
|
||||
}
|
||||
}
|
||||
else
|
||||
append_menu_item(&menu, wxID_ANY, _(L("Add color change")) + " (M600)", "",
|
||||
[this](wxCommandEvent&) { add_code(Slic3r::ColorChangeCode); }, "colorchange_add_m", &menu);
|
||||
|
||||
append_menu_item(&menu, wxID_ANY, _(L("Add pause print")) + " (M601)", "",
|
||||
[this](wxCommandEvent&) { add_code(Slic3r::PausePrintCode); }, "pause_print", &menu);
|
||||
|
||||
append_menu_item(&menu, wxID_ANY, _(L("Add custom G-code")), "",
|
||||
[this](wxCommandEvent&) { add_code(""); }, "edit_gcode", &menu);
|
||||
|
||||
Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu);
|
||||
|
||||
m_show_context_menu = false;
|
||||
}
|
||||
else if (m_show_edit_menu) {
|
||||
wxMenu menu;
|
||||
|
||||
std::set<TICK_CODE>::iterator it = m_ticks_.find(m_selection == ssLower ? m_lower_value : m_higher_value);
|
||||
const bool is_color_change = it->gcode == Slic3r::ColorChangeCode;
|
||||
|
||||
append_menu_item(&menu, wxID_ANY, it->gcode == Slic3r::ColorChangeCode ? _(L("Edit color")) :
|
||||
it->gcode == Slic3r::PausePrintCode ? _(L("Edit pause print message")) :
|
||||
_(L("Edit custom G-code")), "",
|
||||
[this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu);
|
||||
|
||||
append_menu_item(&menu, wxID_ANY, it->gcode == Slic3r::ColorChangeCode ? _(L("Delete color change")) :
|
||||
it->gcode == Slic3r::PausePrintCode ? _(L("Delete pause print")) :
|
||||
_(L("Delete custom G-code")), "",
|
||||
[this](wxCommandEvent&) { action_tick(taDel); }, "colorchange_del_f", &menu);
|
||||
|
||||
Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu);
|
||||
|
||||
m_show_edit_menu = false;
|
||||
}
|
||||
|
||||
Refresh();
|
||||
Update();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
static std::string get_new_color(const std::string& color)
|
||||
{
|
||||
wxColour clr(color);
|
||||
if (!clr.IsOk())
|
||||
clr = wxColour(0, 0, 0); // Don't set alfa to transparence
|
||||
|
||||
auto data = new wxColourData();
|
||||
data->SetChooseFull(1);
|
||||
data->SetColour(clr);
|
||||
|
||||
wxColourDialog dialog(nullptr, data);
|
||||
dialog.CenterOnParent();
|
||||
if (dialog.ShowModal() == wxID_OK)
|
||||
return dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
|
||||
return "";
|
||||
}
|
||||
|
||||
static std::string get_custom_code(const std::string& code_in, double height)
|
||||
{
|
||||
wxString msg_text = from_u8(_utf8(L("Enter custom G-code used on current layer"))) + ":";
|
||||
wxString msg_header = from_u8((boost::format(_utf8(L("Custom Gcode on current layer (%1% mm)."))) % height).str());
|
||||
|
||||
// get custom gcode
|
||||
wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in,
|
||||
wxTextEntryDialogStyle | wxTE_MULTILINE);
|
||||
if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty())
|
||||
return "";
|
||||
|
||||
return dlg.GetValue().ToStdString();
|
||||
}
|
||||
|
||||
static std::string get_pause_print_msg(const std::string& msg_in, double height)
|
||||
{
|
||||
wxString msg_text = from_u8(_utf8(L("Enter short message shown on Printer display during pause print"))) + ":";
|
||||
wxString msg_header = from_u8((boost::format(_utf8(L("Message for pause print on current layer (%1% mm)."))) % height).str());
|
||||
|
||||
// get custom gcode
|
||||
wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in),
|
||||
wxTextEntryDialogStyle);
|
||||
if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty())
|
||||
return "";
|
||||
|
||||
return into_u8(dlg.GetValue());
|
||||
}
|
||||
|
||||
void DoubleSlider::add_code(std::string code, int selected_extruder/* = -1*/)
|
||||
{
|
||||
const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
|
||||
// if on this Z doesn't exist tick
|
||||
auto it = m_ticks_.find(tick);
|
||||
if (it == m_ticks_.end())
|
||||
{
|
||||
std::string color = "";
|
||||
if (code == Slic3r::ColorChangeCode)
|
||||
{
|
||||
std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
|
||||
if (m_state == msSingleExtruder && !m_ticks_.empty()) {
|
||||
auto before_tick_it = std::lower_bound(m_ticks_.begin(), m_ticks_.end(), tick);
|
||||
while (before_tick_it != m_ticks_.begin()) {
|
||||
--before_tick_it;
|
||||
if (before_tick_it->gcode == Slic3r::ColorChangeCode) {
|
||||
color = before_tick_it->color;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (color.empty())
|
||||
color = colors[0];
|
||||
}
|
||||
else
|
||||
color = colors[selected_extruder > 0 ? selected_extruder-1 : 0];
|
||||
|
||||
color = get_new_color(color);
|
||||
if (color.empty())
|
||||
return;
|
||||
}
|
||||
else if (code == Slic3r::PausePrintCode)
|
||||
{
|
||||
/* PausePrintCode doesn't need a color, so
|
||||
* this field is used for save a short message shown on Printer display
|
||||
* */
|
||||
color = get_pause_print_msg(m_pause_print_msg, m_values[tick]);
|
||||
if (color.empty())
|
||||
return;
|
||||
m_pause_print_msg = color;
|
||||
}
|
||||
else if (code.empty())
|
||||
{
|
||||
code = get_custom_code(m_custom_gcode, m_values[tick]);
|
||||
if (code.empty())
|
||||
return;
|
||||
m_custom_gcode = code;
|
||||
}
|
||||
|
||||
int extruder = 1;
|
||||
if (m_state == msMultiExtruder) {
|
||||
if (code == Slic3r::ColorChangeCode && selected_extruder >= 0)
|
||||
extruder = selected_extruder;
|
||||
else
|
||||
extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value);
|
||||
}
|
||||
|
||||
m_ticks_.insert(TICK_CODE(tick, code, extruder, color));
|
||||
|
||||
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
|
||||
Refresh();
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleSlider::edit_tick()
|
||||
{
|
||||
const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
|
||||
// if on this Z exists tick
|
||||
std::set<TICK_CODE>::iterator it = m_ticks_.find(tick);
|
||||
if (it != m_ticks_.end())
|
||||
{
|
||||
std::string edited_value;
|
||||
if (it->gcode == Slic3r::ColorChangeCode)
|
||||
edited_value = get_new_color(it->color);
|
||||
else if (it->gcode == Slic3r::PausePrintCode)
|
||||
edited_value = get_pause_print_msg(it->color, m_values[it->tick]);
|
||||
else
|
||||
edited_value = get_custom_code(it->gcode, m_values[it->tick]);
|
||||
|
||||
if (edited_value.empty())
|
||||
return;
|
||||
|
||||
TICK_CODE changed_tick = *it;
|
||||
if (it->gcode == Slic3r::ColorChangeCode || it->gcode == Slic3r::PausePrintCode) {
|
||||
if (it->color == edited_value)
|
||||
return;
|
||||
changed_tick.color = edited_value;
|
||||
}
|
||||
else {
|
||||
if (it->gcode == edited_value)
|
||||
return;
|
||||
changed_tick.gcode = edited_value;
|
||||
}
|
||||
|
||||
m_ticks_.erase(it);
|
||||
m_ticks_.insert(changed_tick);
|
||||
|
||||
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleSlider::change_extruder(int extruder)
|
||||
{
|
||||
const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
|
||||
|
||||
std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
|
||||
// if on this Y doesn't exist tick
|
||||
if (m_ticks_.find(tick) == m_ticks_.end())
|
||||
{
|
||||
m_ticks_.insert(TICK_CODE(tick, Slic3r::ExtruderChangeCode, extruder, extruder == 0 ? "" : colors[extruder-1]));
|
||||
|
||||
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
|
||||
Refresh();
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleSlider::edit_extruder_sequence()
|
||||
{
|
||||
Slic3r::GUI::ExtruderSequenceDialog dlg(m_extruders_sequence);
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
const ExtrudersSequence& from_dlg_val = dlg.GetValue();
|
||||
if (m_extruders_sequence == from_dlg_val)
|
||||
return;
|
||||
|
||||
m_extruders_sequence = from_dlg_val;
|
||||
|
||||
auto it = m_ticks_.begin();
|
||||
while (it != m_ticks_.end()) {
|
||||
if (it->gcode == Slic3r::ExtruderChangeCode)
|
||||
it = m_ticks_.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
int tick = 0;
|
||||
double value = 0.0;
|
||||
int extruder = 0;
|
||||
const int extr_cnt = m_extruders_sequence.extruders.size();
|
||||
|
||||
std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
|
||||
while (tick <= m_max_value)
|
||||
{
|
||||
int cur_extruder = m_extruders_sequence.extruders[extruder];
|
||||
m_ticks_.insert(TICK_CODE(tick, Slic3r::ExtruderChangeCode, cur_extruder + 1, colors[cur_extruder]));
|
||||
|
||||
extruder++;
|
||||
if (extruder == extr_cnt)
|
||||
extruder = 0;
|
||||
if (m_extruders_sequence.is_mm_intervals)
|
||||
{
|
||||
value += m_extruders_sequence.interval_by_mm;
|
||||
auto it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon());
|
||||
|
||||
if (it == m_values.end())
|
||||
break;
|
||||
|
||||
tick = it - m_values.begin();
|
||||
}
|
||||
else
|
||||
tick += m_extruders_sequence.interval_by_layers;
|
||||
}
|
||||
|
||||
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// LockButton
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <vector>
|
||||
#include <set>
|
||||
#include <functional>
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
enum class ModelVolumeType : int;
|
||||
|
|
@ -48,6 +49,8 @@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string,
|
|||
std::function<void(wxCommandEvent& event)> cb, wxEvtHandler* event_handler);
|
||||
|
||||
class wxDialog;
|
||||
class wxBitmapComboBox;
|
||||
|
||||
void edit_tooltip(wxString& tooltip);
|
||||
void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector<int>& btn_ids);
|
||||
int em_unit(wxWindow* win);
|
||||
|
|
@ -55,7 +58,13 @@ int em_unit(wxWindow* win);
|
|||
wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name,
|
||||
const int px_cnt = 16, const bool is_horizontal = false, const bool grayscale = false);
|
||||
|
||||
std::vector<wxBitmap*> get_extruder_color_icons();
|
||||
std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon = false);
|
||||
void apply_extruder_selector(wxBitmapComboBox** ctrl,
|
||||
wxWindow* parent,
|
||||
const std::string& first_item = "",
|
||||
wxPoint pos = wxDefaultPosition,
|
||||
wxSize size = wxDefaultSize,
|
||||
bool use_thin_icon = false);
|
||||
|
||||
class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
|
||||
{
|
||||
|
|
@ -749,6 +758,11 @@ enum TicksAction{
|
|||
|
||||
class DoubleSlider : public wxControl
|
||||
{
|
||||
enum IconFocus {
|
||||
ifNone,
|
||||
ifRevert,
|
||||
ifCog
|
||||
};
|
||||
public:
|
||||
DoubleSlider(
|
||||
wxWindow *parent,
|
||||
|
|
@ -794,8 +808,8 @@ public:
|
|||
m_values = values;
|
||||
}
|
||||
void ChangeOneLayerLock();
|
||||
std::vector<double> GetTicksValues() const;
|
||||
void SetTicksValues(const std::vector<double>& heights);
|
||||
std::vector<Slic3r::Model::CustomGCode> GetTicksValues() const;
|
||||
void SetTicksValues(const std::vector<Slic3r::Model::CustomGCode> &heights);
|
||||
void EnableTickManipulation(bool enable = true) {
|
||||
m_is_enabled_tick_manipulation = enable;
|
||||
}
|
||||
|
|
@ -803,6 +817,18 @@ public:
|
|||
EnableTickManipulation(false);
|
||||
}
|
||||
|
||||
enum ManipulationState {
|
||||
msSingleExtruder, // single extruder printer preset is selected
|
||||
msMultiExtruder // multiple extruder printer preset is selected, and "Whole print" is selected
|
||||
};
|
||||
void SetManipulationState(ManipulationState state) {
|
||||
m_state = state;
|
||||
}
|
||||
void SetManipulationState(int extruders_cnt) {
|
||||
m_state = extruders_cnt ==1 ? msSingleExtruder : msMultiExtruder;
|
||||
}
|
||||
ManipulationState GetManipulationState() const { return m_state; }
|
||||
|
||||
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; }
|
||||
|
|
@ -820,7 +846,12 @@ public:
|
|||
void OnKeyUp(wxKeyEvent &event);
|
||||
void OnChar(wxKeyEvent &event);
|
||||
void OnRightDown(wxMouseEvent& event);
|
||||
int get_extruder_for_tick(int tick);
|
||||
void OnRightUp(wxMouseEvent& event);
|
||||
void add_code(std::string code, int selected_extruder = -1);
|
||||
void edit_tick();
|
||||
void change_extruder(int extruder);
|
||||
void edit_extruder_sequence();
|
||||
|
||||
protected:
|
||||
|
||||
|
|
@ -834,6 +865,7 @@ protected:
|
|||
void draw_colored_band(wxDC& dc);
|
||||
void draw_one_layer_icon(wxDC& dc);
|
||||
void draw_revert_icon(wxDC& dc);
|
||||
void draw_cog_icon(wxDC &dc);
|
||||
void draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection);
|
||||
void draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection);
|
||||
void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
|
||||
|
|
@ -842,6 +874,7 @@ protected:
|
|||
void detect_selected_slider(const wxPoint& pt);
|
||||
void correct_lower_value();
|
||||
void correct_higher_value();
|
||||
wxString get_tooltip(IconFocus icon_focus);
|
||||
void move_current_thumb(const bool condition);
|
||||
void action_tick(const TicksAction action);
|
||||
void enter_window(wxMouseEvent& event, const bool enter);
|
||||
|
|
@ -876,6 +909,7 @@ private:
|
|||
ScalableBitmap m_bmp_one_layer_unlock_on;
|
||||
ScalableBitmap m_bmp_one_layer_unlock_off;
|
||||
ScalableBitmap m_bmp_revert;
|
||||
ScalableBitmap m_bmp_cog;
|
||||
SelectedSlider m_selection;
|
||||
bool m_is_left_down = false;
|
||||
bool m_is_right_down = false;
|
||||
|
|
@ -884,16 +918,25 @@ private:
|
|||
bool m_is_action_icon_focesed = false;
|
||||
bool m_is_one_layer_icon_focesed = false;
|
||||
bool m_is_enabled_tick_manipulation = true;
|
||||
bool m_show_context_menu = false;
|
||||
bool m_show_edit_menu = false;
|
||||
bool m_edit_extruder_sequence = false;
|
||||
bool m_suppress_add_code = false;
|
||||
ManipulationState m_state = msSingleExtruder;
|
||||
std::string m_custom_gcode = "";
|
||||
std::string m_pause_print_msg;
|
||||
|
||||
wxRect m_rect_lower_thumb;
|
||||
wxRect m_rect_higher_thumb;
|
||||
wxRect m_rect_tick_action;
|
||||
wxRect m_rect_one_layer_icon;
|
||||
wxRect m_rect_revert_icon;
|
||||
wxRect m_rect_cog_icon;
|
||||
wxSize m_thumb_size;
|
||||
int m_tick_icon_dim;
|
||||
int m_lock_icon_dim;
|
||||
int m_revert_icon_dim;
|
||||
int m_cog_icon_dim;
|
||||
long m_style;
|
||||
float m_label_koef = 1.0;
|
||||
|
||||
|
|
@ -912,6 +955,88 @@ private:
|
|||
std::vector<wxPen*> m_segm_pens;
|
||||
std::set<int> m_ticks;
|
||||
std::vector<double> m_values;
|
||||
|
||||
struct TICK_CODE
|
||||
{
|
||||
TICK_CODE(int tick):tick(tick), gcode(Slic3r::ColorChangeCode), extruder(0), color("") {}
|
||||
TICK_CODE(int tick, const std::string& code) :
|
||||
tick(tick), gcode(code), extruder(0) {}
|
||||
TICK_CODE(int tick, int extruder) :
|
||||
tick(tick), gcode(Slic3r::ColorChangeCode), extruder(extruder) {}
|
||||
TICK_CODE(int tick, const std::string& code, int extruder, const std::string& color) :
|
||||
tick(tick), gcode(code), extruder(extruder), color(color) {}
|
||||
|
||||
bool operator<(const TICK_CODE& other) const { return other.tick > this->tick; }
|
||||
bool operator>(const TICK_CODE& other) const { return other.tick < this->tick; }
|
||||
TICK_CODE operator=(const TICK_CODE& other) const {
|
||||
TICK_CODE ret_val(other.tick, other.gcode, other.extruder, other.color);
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
int tick;
|
||||
std::string gcode;
|
||||
int extruder;
|
||||
std::string color;
|
||||
};
|
||||
|
||||
std::set<TICK_CODE> m_ticks_;
|
||||
|
||||
public:
|
||||
struct ExtrudersSequence
|
||||
{
|
||||
bool is_mm_intervals;
|
||||
double interval_by_mm;
|
||||
int interval_by_layers;
|
||||
std::vector<size_t> extruders;
|
||||
|
||||
ExtrudersSequence() :
|
||||
is_mm_intervals(true),
|
||||
interval_by_mm(3.0),
|
||||
interval_by_layers(10),
|
||||
extruders({ 0 }) {}
|
||||
|
||||
ExtrudersSequence(const ExtrudersSequence& other) :
|
||||
is_mm_intervals(other.is_mm_intervals),
|
||||
interval_by_mm(other.interval_by_mm),
|
||||
interval_by_layers(other.interval_by_layers),
|
||||
extruders(other.extruders) {}
|
||||
|
||||
ExtrudersSequence& operator=(const ExtrudersSequence& other) {
|
||||
this->is_mm_intervals = other.is_mm_intervals;
|
||||
this->interval_by_mm = other.interval_by_mm;
|
||||
this->interval_by_layers= other.interval_by_layers;
|
||||
this->extruders = other.extruders;
|
||||
|
||||
return *this;
|
||||
}
|
||||
bool operator==(const ExtrudersSequence& other) const
|
||||
{
|
||||
return (other.is_mm_intervals == this->is_mm_intervals ) &&
|
||||
(other.interval_by_mm == this->interval_by_mm ) &&
|
||||
(other.interval_by_layers == this->interval_by_layers ) &&
|
||||
(other.extruders == this->extruders ) ;
|
||||
}
|
||||
bool operator!=(const ExtrudersSequence& other) const
|
||||
{
|
||||
return (other.is_mm_intervals != this->is_mm_intervals ) &&
|
||||
(other.interval_by_mm != this->interval_by_mm ) &&
|
||||
(other.interval_by_layers != this->interval_by_layers ) &&
|
||||
(other.extruders != this->extruders ) ;
|
||||
}
|
||||
|
||||
void add_extruder(size_t pos)
|
||||
{
|
||||
extruders.insert(extruders.begin() + pos+1, size_t(0));
|
||||
}
|
||||
|
||||
void delete_extruder(size_t pos)
|
||||
{
|
||||
if (extruders.size() == 1)
|
||||
return;// last item can't be deleted
|
||||
extruders.erase(extruders.begin() + pos);
|
||||
}
|
||||
}
|
||||
m_extruders_sequence;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -423,7 +423,7 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
|
|||
wxMessageDialog dlg(nullptr, _(L("Model repaired successfully")), _(L("Model Repair by the Netfabb service")), wxICON_INFORMATION | wxOK_DEFAULT);
|
||||
dlg.ShowModal();
|
||||
} else {
|
||||
wxMessageDialog dlg(nullptr, _(L("Model repair failed: \n")) + _(progress.message), _(L("Model Repair by the Netfabb service")), wxICON_ERROR | wxOK_DEFAULT);
|
||||
wxMessageDialog dlg(nullptr, _(L("Model repair failed:")) + " \n" + _(progress.message), _(L("Model Repair by the Netfabb service")), wxICON_ERROR | wxOK_DEFAULT);
|
||||
dlg.ShowModal();
|
||||
}
|
||||
worker_thread.join();
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ struct Update
|
|||
std::string vendor;
|
||||
std::string changelog_url;
|
||||
|
||||
Update() {}
|
||||
Update(fs::path &&source, fs::path &&target, const Version &version, std::string vendor, std::string changelog_url)
|
||||
: source(std::move(source))
|
||||
, target(std::move(target))
|
||||
|
|
@ -156,7 +157,7 @@ struct PresetUpdater::priv
|
|||
void sync_config(const VendorMap vendors);
|
||||
|
||||
void check_install_indices() const;
|
||||
Updates get_config_updates() const;
|
||||
Updates get_config_updates(const Semver& old_slic3r_version) const;
|
||||
void perform_updates(Updates &&updates, bool snapshot = true) const;
|
||||
};
|
||||
|
||||
|
|
@ -167,7 +168,9 @@ PresetUpdater::priv::priv()
|
|||
, cancel(false)
|
||||
{
|
||||
set_download_prefs(GUI::wxGetApp().app_config);
|
||||
// Install indicies from resources. Only installs those that are either missing or older than in resources.
|
||||
check_install_indices();
|
||||
// Load indices from the cache directory.
|
||||
index_db = Index::load_db();
|
||||
}
|
||||
|
||||
|
|
@ -273,6 +276,7 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors)
|
|||
if (!enabled_config_update) { return; }
|
||||
|
||||
// Donwload vendor preset bundles
|
||||
// Over all indices from the cache directory:
|
||||
for (auto &index : index_db) {
|
||||
if (cancel) { return; }
|
||||
|
||||
|
|
@ -366,30 +370,33 @@ void PresetUpdater::priv::check_install_indices() const
|
|||
}
|
||||
}
|
||||
|
||||
// Generates a list of bundle updates that are to be performed
|
||||
Updates PresetUpdater::priv::get_config_updates() const
|
||||
// Generates a list of bundle updates that are to be performed.
|
||||
// Version of slic3r that was running the last time and which was read out from PrusaSlicer.ini is provided
|
||||
// as a parameter.
|
||||
Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version) const
|
||||
{
|
||||
Updates updates;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Checking for cached configuration updates...";
|
||||
|
||||
// Over all indices from the cache directory:
|
||||
for (const auto idx : index_db) {
|
||||
auto bundle_path = vendor_path / (idx.vendor() + ".ini");
|
||||
auto bundle_path_idx = vendor_path / idx.path().filename();
|
||||
|
||||
if (! fs::exists(bundle_path)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor();
|
||||
BOOST_LOG_TRIVIAL(info) << "Confing bundle not installed for vendor %1%, skipping: " << idx.vendor();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Perform a basic load and check the version
|
||||
// Perform a basic load and check the version of the installed preset bundle.
|
||||
auto vp = VendorProfile::from_ini(bundle_path, false);
|
||||
|
||||
// Getting a recommended version from the latest index, wich may have been downloaded
|
||||
// from the internet, or installed / updated from the installation resources.
|
||||
const auto recommended = idx.recommended();
|
||||
auto recommended = idx.recommended();
|
||||
if (recommended == idx.end()) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % idx.vendor();
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index? Giving up.") % idx.vendor();
|
||||
// XXX: what should be done here?
|
||||
continue;
|
||||
}
|
||||
|
|
@ -404,6 +411,7 @@ Updates PresetUpdater::priv::get_config_updates() const
|
|||
% recommended->config_version.to_string();
|
||||
|
||||
if (! ver_current_found) {
|
||||
// Any published config shall be always found in the latest config index.
|
||||
auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str();
|
||||
BOOST_LOG_TRIVIAL(error) << message;
|
||||
GUI::show_error(nullptr, GUI::from_u8(message));
|
||||
|
|
@ -411,11 +419,90 @@ Updates PresetUpdater::priv::get_config_updates() const
|
|||
}
|
||||
|
||||
if (ver_current_found && !ver_current->is_current_slic3r_supported()) {
|
||||
// "Reconfigure" situation.
|
||||
BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string();
|
||||
updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name);
|
||||
} else if (recommended->config_version > vp.config_version) {
|
||||
// Config bundle update situation
|
||||
continue;
|
||||
}
|
||||
|
||||
if (recommended->config_version < vp.config_version) {
|
||||
BOOST_LOG_TRIVIAL(warning) << (boost::format("Recommended config version for the currently running PrusaSlicer is older than the currently installed config for vendor %1%. This should not happen.") % idx.vendor()).str();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (recommended->config_version == vp.config_version) {
|
||||
// The recommended config bundle is already installed.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Config bundle update situation. The recommended config bundle version for this PrusaSlicer version from the index from the cache is newer
|
||||
// than the version of the currently installed config bundle.
|
||||
|
||||
// The config index inside the cache directory (given by idx.path()) is one of the following:
|
||||
// 1) The last config index downloaded by any previously running PrusaSlicer instance
|
||||
// 2) The last config index installed by any previously running PrusaSlicer instance (older or newer) from its resources.
|
||||
// 3) The last config index installed by the currently running PrusaSlicer instance from its resources.
|
||||
// The config index is always the newest one (given by its newest config bundle referenced), and older config indices shall fully contain
|
||||
// the content of the older config indices.
|
||||
|
||||
// Config bundle inside the cache directory.
|
||||
fs::path path_in_cache = cache_path / (idx.vendor() + ".ini");
|
||||
// Config bundle inside the resources directory.
|
||||
fs::path path_in_rsrc = rsrc_path / (idx.vendor() + ".ini");
|
||||
// Config index inside the resources directory.
|
||||
fs::path path_idx_in_rsrc = rsrc_path / (idx.vendor() + ".idx");
|
||||
|
||||
// Search for a valid config bundle in the cache directory.
|
||||
bool found = false;
|
||||
Update new_update;
|
||||
fs::path bundle_path_idx_to_install;
|
||||
if (fs::exists(path_in_cache)) {
|
||||
try {
|
||||
VendorProfile new_vp = VendorProfile::from_ini(path_in_cache, false);
|
||||
if (new_vp.config_version == recommended->config_version) {
|
||||
// The config bundle from the cache directory matches the recommended version of the index from the cache directory.
|
||||
// This is the newest known recommended config. Use it.
|
||||
new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url);
|
||||
// and install the config index from the cache into vendor's directory.
|
||||
bundle_path_idx_to_install = idx.path();
|
||||
found = true;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Failed to load the config bundle `%1%`: %2%") % path_in_cache.string() % ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the rsrc_idx outside of the next block, as we will reference the "recommended" version by an iterator.
|
||||
Index rsrc_idx;
|
||||
if (! found && fs::exists(path_in_rsrc) && fs::exists(path_idx_in_rsrc)) {
|
||||
// Trying the config bundle from resources (from the installation).
|
||||
// In that case, the recommended version number has to be compared against the recommended version reported by the config index from resources as well,
|
||||
// as the config index in the cache directory may already be newer, recommending a newer config bundle than available in cache or resources.
|
||||
VendorProfile rsrc_vp;
|
||||
try {
|
||||
rsrc_vp = VendorProfile::from_ini(path_in_rsrc, false);
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Cannot load the config bundle at `%1%`: %2%") % path_in_rsrc.string() % ex.what();
|
||||
}
|
||||
if (rsrc_vp.valid()) {
|
||||
try {
|
||||
rsrc_idx.load(path_idx_in_rsrc);
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Cannot load the config index at `%1%`: %2%") % path_idx_in_rsrc.string() % ex.what();
|
||||
}
|
||||
recommended = rsrc_idx.recommended();
|
||||
if (recommended != rsrc_idx.end() && recommended->config_version == rsrc_vp.config_version && recommended->config_version > vp.config_version) {
|
||||
new_update = Update(std::move(path_in_rsrc), std::move(bundle_path), *recommended, vp.name, vp.changelog_url);
|
||||
bundle_path_idx_to_install = path_idx_in_rsrc;
|
||||
found = true;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << (boost::format("The recommended config version for vendor `%1%` in resources does not match the recommended\n"
|
||||
" config version for this version of PrusaSlicer. Corrupted installation?") % idx.vendor()).str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
// Load 'installed' idx, if any.
|
||||
// 'Installed' indices are kept alongside the bundle in the `vendor` subdir
|
||||
// for bookkeeping to remember a cancelled update and not offer it again.
|
||||
|
|
@ -423,15 +510,16 @@ Updates PresetUpdater::priv::get_config_updates() const
|
|||
Index existing_idx;
|
||||
try {
|
||||
existing_idx.load(bundle_path_idx);
|
||||
|
||||
const auto existing_recommended = existing_idx.recommended();
|
||||
// Find a recommended config bundle version for the slic3r version last executed. This makes sure that a config bundle update will not be missed
|
||||
// when upgrading an application. On the other side, the user will be bugged every time he will switch between slic3r versions.
|
||||
const auto existing_recommended = existing_idx.recommended(old_slic3r_version);
|
||||
if (existing_recommended != existing_idx.end() && recommended->config_version == existing_recommended->config_version) {
|
||||
// The user has already seen (and presumably rejected) this update
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Downloaded index for `%1%` is the same as installed one, not offering an update.") % idx.vendor();
|
||||
continue;
|
||||
}
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Could not load installed index at `%1%`: %2%") % bundle_path_idx % err.what();
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Cannot load the installed index at `%1%`: %2%") % bundle_path_idx % err.what();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -445,41 +533,14 @@ Updates PresetUpdater::priv::get_config_updates() const
|
|||
continue;
|
||||
}
|
||||
|
||||
auto path_src = cache_path / (idx.vendor() + ".ini");
|
||||
auto path_in_rsrc = rsrc_path / (idx.vendor() + ".ini");
|
||||
if (! fs::exists(path_src)) {
|
||||
if (! fs::exists(path_in_rsrc)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update, but bundle found in neither cache nor resources")
|
||||
% idx.vendor();
|
||||
continue;
|
||||
} else {
|
||||
path_src = std::move(path_in_rsrc);
|
||||
path_in_rsrc.clear();
|
||||
}
|
||||
}
|
||||
|
||||
auto new_vp = VendorProfile::from_ini(path_src, false);
|
||||
bool found = false;
|
||||
if (new_vp.config_version == recommended->config_version) {
|
||||
updates.updates.emplace_back(std::move(path_src), std::move(bundle_path), *recommended, vp.name, vp.changelog_url);
|
||||
found = true;
|
||||
} else if (! path_in_rsrc.empty() && fs::exists(path_in_rsrc)) {
|
||||
new_vp = VendorProfile::from_ini(path_in_rsrc, false);
|
||||
if (new_vp.config_version == recommended->config_version) {
|
||||
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(bundle_path), *recommended, vp.name, vp.changelog_url);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
// 'Install' the index in the vendor directory. This is used to memoize
|
||||
// offered updates and to not offer the same update again if it was cancelled by the user.
|
||||
copy_file_fix(idx.path(), bundle_path_idx);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources")
|
||||
% idx.vendor()
|
||||
% recommended->config_version.to_string();
|
||||
}
|
||||
updates.updates.emplace_back(std::move(new_update));
|
||||
// 'Install' the index in the vendor directory. This is used to memoize
|
||||
// offered updates and to not offer the same update again if it was cancelled by the user.
|
||||
copy_file_fix(bundle_path_idx_to_install, bundle_path_idx);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources")
|
||||
% idx.vendor()
|
||||
% recommended->config_version.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -607,11 +668,11 @@ void PresetUpdater::slic3r_update_notify()
|
|||
}
|
||||
}
|
||||
|
||||
PresetUpdater::UpdateResult PresetUpdater::config_update() const
|
||||
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3r_version) const
|
||||
{
|
||||
if (! p->enabled_config_update) { return R_NOOP; }
|
||||
|
||||
auto updates = p->get_config_updates();
|
||||
auto updates = p->get_config_updates(old_slic3r_version);
|
||||
if (updates.incompats.size() > 0) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1% bundles incompatible. Asking for action...") % updates.incompats.size();
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ namespace Slic3r {
|
|||
|
||||
class AppConfig;
|
||||
class PresetBundle;
|
||||
class Semver;
|
||||
|
||||
class PresetUpdater
|
||||
{
|
||||
|
|
@ -38,7 +39,9 @@ public:
|
|||
|
||||
// If updating is enabled, check if updates are available in cache, if so, ask about installation.
|
||||
// A false return value implies Slic3r should exit due to incompatibility of configuration.
|
||||
UpdateResult config_update() const;
|
||||
// Providing old slic3r version upgrade profiles on upgrade of an application even in case
|
||||
// that the config index installed from the Internet is equal to the index contained in the installation package.
|
||||
UpdateResult config_update(const Semver &old_slic3r_version) const;
|
||||
|
||||
// "Update" a list of bundles from resources (behaves like an online update).
|
||||
void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue